chore: trim distribution clutter and add lint/test tooling

Keep only example workflows in default/, move site-specific YAMLs to gitignored local/, remove the duplicate get-current-time plugin install, document personal plugins, clarify PM2 ecosystem usage, add shared Vitest and web ESLint, and extract OpsStatusCard for ops cards.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-22 06:30:37 +07:00
co-authored by Cursor
parent db884808f2
commit f9f21052d9
34 changed files with 2057 additions and 551 deletions
+2 -1
View File
@@ -1,7 +1,8 @@
/**
* Dev ecosystem for `pnpm dev:pm2`.
* Local multi-process Ops UI ecosystem (`pnpm dev:pm2`).
* Prefer starting children via control.js (desired state) rather than this file.
* Kept as a reference / fallback: `pm2 start packages/server/ecosystem.dev.cjs`
* For production monolith, use root ecosystem.config.cjs instead.
*/
const path = require("path");
@@ -1,29 +0,0 @@
name: Joplin nightly daily log
description: |
POST joplin-auto /api/logs/run at 04:00 (sync + yearly/monthly/today), then ntfy.
Secret joplin_setup_token (SETUP_API_TOKEN). Variable ntfy_channel.
scripts:
- script: plugin/joplin-api
config:
url: http://10.8.0.6:3040/api/logs/run
method: POST
token: $SECRET_joplin_setup_token
timeoutMs: 600000
- set:
expression: |
{
"title": data.ok ? "Bullet journal" : "Bullet journal failed",
"message": data.ok
? "The bullet journal for " & data.httpResponse.date & " has been created."
: data.message
}
- script: ntfy.js
config:
url: $VAR_ntfy_channel
triggers:
- type: cron
schedule: "0 4 * * *"
- type: HTTP
method: POST
path: /dev-joplin-daily
enabled: false
@@ -1,17 +0,0 @@
name: Joplin get note
description: |
GET a Joplin note by id from joplin-api. Input (data): id (32-char hex).
Secret joplin_api_token (JOPLIN_API_TOKEN / API_KEYS). Returns the full note.
scripts:
- script: plugin/joplin-api
config:
url: http://10.8.0.6:3030/notes
method: GET
token: $SECRET_joplin_api_token
timeoutMs: 60000
triggers:
- type: workflow
- type: HTTP
method: POST
path: /dev-joplin-get-note
enabled: false
@@ -1,27 +0,0 @@
name: Joplin nightly sync
description: |
POST joplin-auto /api/sync at 03:00, then ntfy success or failure.
Secret joplin_setup_token (SETUP_API_TOKEN). Variable ntfy_channel.
scripts:
- script: plugin/joplin-api
config:
url: http://10.8.0.6:3040/api/sync
method: POST
token: $SECRET_joplin_setup_token
timeoutMs: 600000
- set:
expression: |
{
"title": data.ok ? "Joplin sync ok" : "Joplin sync failed",
"message": data.message
}
- script: ntfy.js
config:
url: $VAR_ntfy_channel
triggers:
- type: cron
schedule: "0 3 * * *"
- type: HTTP
method: POST
path: /dev-joplin-sync
enabled: false
@@ -1,17 +0,0 @@
name: dev-zte-sms
description: |
Send an SMS via a ZTE modem web UI.
Input (data): to, message
Password is the named secret zte_modem_password ($SECRET_).
scripts:
- script: plugin/send-sms
config:
url: http://192.168.5.1/reqproc/proc_post
password: $SECRET_sms_secret
triggers:
- type: workflow
- type: HTTP
method: POST
path: /dev-zte-sms
auth:
- 0f78d6d7-bd44-45d7-a826-f51c027b767f
@@ -1,12 +0,0 @@
name: fetch-devto
scripts:
- script: fetch-html.js
config:
url: https://dev.to/t/productivity/top/week
selector: "#substories h2"
outputVar: titles
jsonata: "$[].text"
triggers:
- type: HTTP
method: POST
path: /fetch-dev-to
@@ -1,50 +0,0 @@
name: Jadwal Solat Jakarta ntfy
scripts:
- id: fetch
script: fetch-http.js
config:
url: https://kemenag.go.id/api/prayer-times/1301
method: GET
headers:
Content-Type: application/json
Accept: application/json
- id: transform
script: jsonata.js
config:
expression: |-
{
"title": data.httpResponse.data.date,
"message": "Imsak:" & data.httpResponse.data.imsak & "\n" &
"Subuh:" & data.httpResponse.data.subuh & "\n" &
"Dzuhur:" & data.httpResponse.data.dzuhur & "\n" &
"Ashar:" & data.httpResponse.data.ashar & "\n" &
"Maghrib:" & data.httpResponse.data.maghrib & "\n" &
"Isya:" & data.httpResponse.data.isya
}
needs:
- fetch
- id: ntfy
script: ntfy.js
config:
url: $VAR_ntfy_channel
fingerprint: true
needs:
- transform
- id: slack
script: ntfy.js
config:
url: $VAR_ntfy_channel2
fingerprint: fingerprint:ntfy2
needs:
- transform
- script: slack-webhook.js
config:
webhookUrlSecret: slack_deploy_webhook
fingerprint: true
fingerprintMaxAge: 1h
text: $INPUT_message
needs:
- transform
triggers:
- type: cron
schedule: 0 5 * * *
@@ -1,20 +1,5 @@
scripts:
- time-to-ntfy-example.yaml
- test.yaml
- cron-example.yaml
- fetch-devto.yaml
- comic-monkeyuser-to-ntfy.yaml
- time-and-comic-to-ntfy.yaml
- rss-selfhst-to-ntfy.yaml
- send-gmail.yaml
- test-send-gmail.yaml
- track.yaml
- rss-devto-to-ntfy.yaml
- test-minio.yaml
- dev-joplin-sync.yaml
- dev-joplin-daily.yaml
- dev-joplin-get-note.yaml
- web-dave.yaml
- jadwal-sholat-jakart.yaml
- detect-example-changes.yaml
- test-sftp.yaml
- time-to-ntfy-example.yaml
- comic-monkeyuser-to-ntfy.yaml
@@ -1,27 +0,0 @@
name: RSS - selfh.st first item to ntfy (copy)
scripts:
- script: fetch-rss-feed.js
config:
url: https://selfh.st/rss/
outputVar: item
jsonata: items[0]
- script: fingerprint.js
config:
key: selfhst-latest
jsonata: "data.item.guid ? data.item.guid : data.item.link"
- script: jsonata.js
config:
expression: |
{
"title": data.item.title,
"message": data.item.contentSnippet & "\n" & data.item.link,
"attach": data.item.mediaContent.url ? data.item.mediaContent.url : (data.item.mediaContent.$ ? data.item.mediaContent.$.url : undefined)
}
- script: ntfy.js
config:
url: https://n.0dev.web.id/system
triggers:
- type: HTTP
method: POST
path: /selfhst-rss-rss-devto-to-ntfy
enabled: false
@@ -1,26 +0,0 @@
name: RSS - selfh.st first item to ntfy
scripts:
- script: fetch-rss-feed.js
config:
url: https://selfh.st/rss/
outputVar: item
jsonata: items[0]
- script: fingerprint.js
config:
key: selfhst-latest
jsonata: "data.item.guid ? data.item.guid : data.item.link"
- script: jsonata.js
config:
expression: |
{
"title": data.item.title,
"message": data.item.contentSnippet & "\n" & data.item.link,
"attach": data.item.mediaContent.url ? data.item.mediaContent.url : (data.item.mediaContent.$ ? data.item.mediaContent.$.url : undefined)
}
- script: ntfy.js
config:
url: https://n.0dev.web.id/system
triggers:
- type: HTTP
method: POST
path: /selfhst-rss
@@ -1,40 +0,0 @@
name: send-gmail
description: |
Send email via Gmail SMTP. Configure user/from and the named secret,
then call from other workflows with trigger-workflow.js (name: send-gmail).
Input (data):
to optional recipient(s); defaults to your Gmail below
subject required
text plain body (aliases: body, message)
html optional HTML body
from, cc, bcc, replyTo, priority, headers optional
scripts:
- script: jsonata.js
config:
expression: |
{
"to": $exists(data.to) ? data.to : "nasyarobby@gmail.com",
"from": data.from,
"subject": data.subject,
"text": $exists(data.text) ? data.text : ($exists(data.body) ? data.body : data.message),
"html": data.html,
"cc": data.cc,
"bcc": data.bcc,
"replyTo": data.replyTo,
"priority": data.priority,
"headers": data.headers
}
- script: send-email.js
config:
service: Gmail
user: elevent16th@gmail.com
from: elevent16th@gmail.com
passwordSecret: gmail_app_password
triggers:
- type: workflow
- type: HTTP
method: POST
path: /send-gmail
auth:
- 0f78d6d7-bd44-45d7-a826-f51c027b767f
@@ -1,20 +0,0 @@
name: Test MinIO
scripts:
- script: fetch-binary.js
config:
outputVar: file
url: https://nsrb:error403@dav.0dev.web.id/ntfy/IyM9784UdG4S
filename: test.png
- script: s3.js
config:
action: write
endpoint: http://localhost:9000
bucket: default
forcePathStyle: true
accessKeyIdSecret: minio_user
secretAccessKeySecret: minio_pass
key: file.png
triggers:
- type: HTTP
method: POST
path: /new
@@ -1,18 +0,0 @@
name: test-send-gmail
description: |
Kick send-gmail with sample data. Edit the payload below, then Run
(or POST /u/default/test-send-gmail).
scripts:
- script: trigger-workflow.js
config:
name: send-gmail
expression: |
{
"subject": "JerapahFlow test",
"text": "Hello from test-send-gmail",
"html": "<p>Hello from <strong>test-send-gmail</strong></p>"
}
triggers:
- type: HTTP
method: POST
path: /test-send-gmail
@@ -1,22 +0,0 @@
name: SFTP Test
scripts:
- script: remote-fs.js
config:
protocol: sftp
action: list
host: localhost
path: /Users/nsrb/
username: nsrb
port: 22
password: JKLjkl
- script: jsonata.js
config:
expression: '{"message": $join(data.entries.name, "\n")}'
- script: ntfy.js
config:
url: $VAR_ntfy_channel
fingerprint: "false"
triggers:
- type: HTTP
method: POST
path: /new
@@ -1,12 +0,0 @@
name: jsonata
scripts:
- plugin/get-current-time
- script: jsonata.js
config:
expression: '{"message": data.datetime & " " & data.processId}'
- ntfy.js
triggers:
- type: HTTP
method: POST
path: /mt
@@ -1,37 +0,0 @@
name: time and comic to ntfy
description: >
Fan-in from two scripts (current time + monkeyuser comic), then send to ntfy.
scripts:
- id: time
script: plugin/get-current-time
- id: comic
script: fetch-html.js
config:
url: "https://www.monkeyuser.com/"
outputVar: "httpResponse"
selector: ".comic img"
jsonata: |
{"url": "https://www.monkeyuser.com" & [attributes.src][0], "title": [attributes.title][0]}
- id: compose
script: jsonata.js
needs: [time, comic]
config:
expression: |
{
"title": data.comic.httpResponse.title,
"message": data.time.datetime & " " & data.comic.httpResponse.title,
"attach": data.comic.httpResponse.url
}
- id: notify
script: ntfy.js
needs: [compose]
config:
url: https://ntfy.sh/jerapah-flow
triggers:
- type: HTTP
method: POST
path: /time-and-comic
@@ -1,31 +0,0 @@
name: Track Gojek Trip
scripts:
- script: fetch-http.js
config:
url: https://api.gojekapi.com/live/track/491c3132c2cc7043
method: GET
headers:
Content-Type: application/json
Accept: application/json
Origin: https://track.gojek.com
- script: fingerprint.js
config:
key: gojek-track-status
jsonata: data.httpResponse.status
- script: jsonata.js
config:
expression: |
{
"title": data.httpResponse.customer_name,
"message": data.httpResponse.customer_name & " : " & data.httpResponse.status
}
- script: ntfy.js
config:
url: https://n.0dev.web.id/system
triggers:
- type: cron
schedule: "* * * * *"
- type: HTTP
method: GET
path: /new
enabled: false
@@ -1,14 +0,0 @@
name: Dab 0dev
scripts:
- script: list-webdav.js
config:
url: https://dav.0dev.web.id/books
path: /
includeDirectories: true
recursive: false
username: nsrb
passwordSecret: dav_0dev_password
triggers:
- type: HTTP
method: POST
path: /new