svamp-cli 0.2.175 → 0.2.177
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/skills/loop/SKILL.md +1 -1
- package/dist/{agentCommands-C4upUtMc.mjs → agentCommands-DWvXxX8p.mjs} +5 -5
- package/dist/{auth-BKN98KO4.mjs → auth-Dxka4cE6.mjs} +1 -1
- package/dist/cli.mjs +60 -64
- package/dist/{commands-D2lD-S4d.mjs → commands--dKRqMv8.mjs} +5 -5
- package/dist/{commands-BssfhM-2.mjs → commands-B4u6CUWH.mjs} +1 -1
- package/dist/{commands-CSK-jSnk.mjs → commands-Bf-TovMw.mjs} +1 -1
- package/dist/{commands-DICHik9Y.mjs → commands-BwBxmMxJ.mjs} +19 -13
- package/dist/{commands-BF_Wy2B4.mjs → commands-CwdYZQ-a.mjs} +1 -1
- package/dist/{commands-BbqjhR4z.mjs → commands-DVKFo1pt.mjs} +2 -2
- package/dist/{commands-D03sBXV4.mjs → commands-iXMwyExn.mjs} +2 -2
- package/dist/{fleet-qUmhLn41.mjs → fleet-plROHfB8.mjs} +1 -1
- package/dist/{frpc-BDZgTsi8.mjs → frpc-yc5-4EUe.mjs} +1 -1
- package/dist/{headlessCli-BG-PcYjM.mjs → headlessCli-D7vLrl7K.mjs} +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{package-CMB-hfB0.mjs → package-Do-iBoUv.mjs} +2 -2
- package/dist/{rpc-CQTahguB.mjs → rpc-D1gr74eF.mjs} +11 -9
- package/dist/{rpc-gCEvLUNu.mjs → rpc-DUZqKPK4.mjs} +1 -1
- package/dist/{run-Buov6wz-.mjs → run-21-bMHCc.mjs} +37 -402
- package/dist/{run-DGs1hcvd.mjs → run-C__yViCA.mjs} +1 -1
- package/dist/scheduler-DweevMw_.mjs +93 -0
- package/dist/{serveCommands-C6l8yA9_.mjs → serveCommands-BZmiAjxi.mjs} +5 -5
- package/dist/{serveManager-B_Bj1AF4.mjs → serveManager-AbWv-Akt.mjs} +2 -2
- package/dist/{sideband-BYiyQgZR.mjs → sideband-CzzHtakf.mjs} +1 -1
- package/dist/store-BPL__e4D.mjs +125 -0
- package/package.json +2 -2
- package/bin/skills/loop/bin/routine-cli.mjs +0 -121
- package/bin/skills/loop/bin/routine-runner.mjs +0 -125
- package/bin/skills/loop/bin/routine-store.mjs +0 -49
- package/bin/skills/loop/routines.process.yaml +0 -20
- package/bin/skills/loop/test/test-routine-engine.mjs +0 -122
- package/dist/commands-CkygtYMZ.mjs +0 -223
- package/dist/store-D2HNe-Kv.mjs +0 -74
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Deterministic tests for routine-store + routine-runner (mock delivery, fake clock).
|
|
3
|
-
import { mkdtempSync, rmSync } from 'node:fs';
|
|
4
|
-
import { tmpdir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { RoutineStore } from '../bin/routine-store.mjs';
|
|
7
|
-
import { RoutineRunner } from '../bin/routine-runner.mjs';
|
|
8
|
-
|
|
9
|
-
let pass = 0, fail = 0;
|
|
10
|
-
function ok(c, m) { if (c) { pass++; console.log(` ✓ ${m}`); } else { fail++; console.log(` ✗ ${m}`); } }
|
|
11
|
-
const D = (y, mo, d, h, mi) => new Date(y, mo - 1, d, h, mi, 0, 0);
|
|
12
|
-
|
|
13
|
-
const dirs = [];
|
|
14
|
-
function freshStore() { const dir = mkdtempSync(join(tmpdir(), 'routines-')); dirs.push(dir); return new RoutineStore(dir); }
|
|
15
|
-
|
|
16
|
-
try {
|
|
17
|
-
console.log('store');
|
|
18
|
-
{ const s = freshStore();
|
|
19
|
-
const r = s.save({ session_id: 's1', name: 'hook', trigger: { type: 'webhook' }, action: { kind: 'message', template: 'hi ${body.x}' } });
|
|
20
|
-
ok(r.id?.startsWith('rt_'), 'save assigns id');
|
|
21
|
-
ok(typeof r.trigger.key === 'string' && r.trigger.key.length > 10, 'webhook routine gets a capability key');
|
|
22
|
-
ok(s.get(r.id).name === 'hook', 'get round-trips');
|
|
23
|
-
ok(s.list().length === 1, 'list returns saved routine');
|
|
24
|
-
s.setEnabled(r.id, false); ok(s.get(r.id).enabled === false, 'setEnabled persists');
|
|
25
|
-
ok(s.remove(r.id) === true && s.list().length === 0, 'remove deletes'); }
|
|
26
|
-
|
|
27
|
-
console.log('resolveAction (templating)');
|
|
28
|
-
{ const s = freshStore();
|
|
29
|
-
const runner = new RoutineRunner({ store: s, deliver: async () => {}, now: () => D(2026, 6, 10, 9, 0) });
|
|
30
|
-
const msg = s.save({ session_id: 's', name: 'm', trigger: { type: 'manual' }, action: { kind: 'message', template: 'PR ${body.pr}' } });
|
|
31
|
-
ok(runner.resolveAction(msg, { body: { pr: 7 } }).text === 'PR 7', 'message action templated');
|
|
32
|
-
const lp = s.save({ session_id: 's', name: 'l', trigger: { type: 'webhook' }, action: { kind: 'loop', task_template: 'fix ${body.issue}' } });
|
|
33
|
-
const r = runner.resolveAction(lp, { body: { issue: 'X' } });
|
|
34
|
-
ok(r.kind === 'loop' && r.task === 'fix X', 'loop action task templated'); }
|
|
35
|
-
|
|
36
|
-
console.log('schedule tick (fire once per minute)');
|
|
37
|
-
{ const s = freshStore();
|
|
38
|
-
let clock = D(2026, 6, 10, 9, 5);
|
|
39
|
-
const calls = [];
|
|
40
|
-
const runner = new RoutineRunner({ store: s, deliver: async (c) => calls.push(c), now: () => clock });
|
|
41
|
-
s.save({ session_id: 's', name: 'poll', trigger: { type: 'schedule', cron: '*/5 * * * *' }, action: { kind: 'message', template: 'tick' } });
|
|
42
|
-
await runner.tick(D(2026, 6, 10, 9, 3)); ok(calls.length === 0, 'no fire at non-matching minute 9:03');
|
|
43
|
-
await runner.tick(D(2026, 6, 10, 9, 5)); ok(calls.length === 1, 'fires at matching minute 9:05');
|
|
44
|
-
await runner.tick(D(2026, 6, 10, 9, 5)); ok(calls.length === 1, 'does not double-fire same minute');
|
|
45
|
-
await runner.tick(D(2026, 6, 10, 9, 10)); ok(calls.length === 2, 'fires again next matching minute');
|
|
46
|
-
ok(s.list()[0].last_runs.length === 2, 'run history recorded'); }
|
|
47
|
-
|
|
48
|
-
console.log('overlap policy (concurrent in-flight guard)');
|
|
49
|
-
{ const s = freshStore();
|
|
50
|
-
const calls = []; let release; const gate = new Promise((r) => { release = r; });
|
|
51
|
-
const runner = new RoutineRunner({ store: s, deliver: async () => { calls.push(1); await gate; } });
|
|
52
|
-
const r = s.save({ session_id: 's', name: 'l', overlap: 'skip', trigger: { type: 'manual' }, action: { kind: 'loop', task_template: 'go' } });
|
|
53
|
-
const p1 = runner.fire(s.get(r.id), {}, 'manual'); // enters deliver, holds active
|
|
54
|
-
await new Promise((res) => setTimeout(res, 10));
|
|
55
|
-
const res2 = await runner.fire(s.get(r.id), {}, 'manual'); // concurrent -> skipped
|
|
56
|
-
ok(res2.skipped === 'busy' && calls.length === 1, 'overlap=skip skips a concurrent fire');
|
|
57
|
-
release(); await p1; // first delivery completes -> active cleared
|
|
58
|
-
const res3 = await runner.fire(s.get(r.id), {}, 'manual');
|
|
59
|
-
ok(res3.fired && calls.length === 2, 'fires again once the in-flight delivery completes (no permanent skip)'); }
|
|
60
|
-
|
|
61
|
-
console.log('webhook dispatch + key/method checks');
|
|
62
|
-
{ const s = freshStore();
|
|
63
|
-
const calls = [];
|
|
64
|
-
const runner = new RoutineRunner({ store: s, deliver: async (c) => calls.push(c) });
|
|
65
|
-
const r = s.save({ session_id: 's', name: 'wh', trigger: { type: 'webhook', methods: ['POST'] }, action: { kind: 'message', template: 'got ${body.v}' } });
|
|
66
|
-
const key = s.get(r.id).trigger.key;
|
|
67
|
-
ok((await runner.webhook(r.id, { key: 'wrong', method: 'POST' })).status === 401, 'bad key -> 401');
|
|
68
|
-
ok((await runner.webhook(r.id, { key, method: 'GET' })).status === 405, 'disallowed method -> 405');
|
|
69
|
-
const good = await runner.webhook(r.id, { key, method: 'POST', body: { v: 42 } });
|
|
70
|
-
ok(good.status === 200 && good.resolved.text === 'got 42', 'good webhook delivers templated message');
|
|
71
|
-
s.setEnabled(r.id, false);
|
|
72
|
-
ok((await runner.webhook(r.id, { key, method: 'POST' })).status === 409, 'disabled -> 409'); }
|
|
73
|
-
|
|
74
|
-
console.log('catch-up missed schedule');
|
|
75
|
-
{ const s = freshStore();
|
|
76
|
-
const calls = [];
|
|
77
|
-
const now = D(2026, 6, 10, 9, 30);
|
|
78
|
-
const runner = new RoutineRunner({ store: s, deliver: async (c) => calls.push(c), now: () => now });
|
|
79
|
-
s.save({ session_id: 's', name: 'daily', trigger: { type: 'schedule', cron: '0 9 * * *', missed: 'catchup', deadline_sec: 7200 }, action: { kind: 'message', template: 'good morning' } });
|
|
80
|
-
await runner.catchUp(D(2026, 6, 10, 8, 0), now); ok(calls.length === 1, 'catch-up fires the missed 09:00 run');
|
|
81
|
-
await runner.catchUp(D(2026, 6, 10, 8, 0), now); ok(calls.length === 1, 'catch-up does not re-fire once recorded'); }
|
|
82
|
-
|
|
83
|
-
console.log('daily cap');
|
|
84
|
-
{ const s = freshStore(); const calls = []; const now = D(2026, 6, 10, 9, 0);
|
|
85
|
-
const runner = new RoutineRunner({ store: s, deliver: async (c) => calls.push(c), now: () => now });
|
|
86
|
-
const r = s.save({ session_id: 's', name: 'capped', daily_cap: 2, trigger: { type: 'manual' }, action: { kind: 'message', template: 'x' } });
|
|
87
|
-
await runner.fire(s.get(r.id), {}, 'manual');
|
|
88
|
-
await runner.fire(s.get(r.id), {}, 'manual');
|
|
89
|
-
const third = await runner.fire(s.get(r.id), {}, 'manual');
|
|
90
|
-
ok(calls.length === 2, 'daily_cap=2 delivers exactly twice');
|
|
91
|
-
ok(third.skipped === 'daily_cap', 'third fire skipped by daily cap'); }
|
|
92
|
-
|
|
93
|
-
console.log('fire-time public+loop guard (#8)');
|
|
94
|
-
{ const s = freshStore(); const calls = [];
|
|
95
|
-
const runner = new RoutineRunner({ store: s, deliver: async () => calls.push(1) });
|
|
96
|
-
// hand-built (bypasses save validation, simulating a legacy/pre-guard stored routine)
|
|
97
|
-
const res = await runner.fire({ id: 'x', enabled: true, trigger: { type: 'webhook', public: true }, action: { kind: 'loop', task_template: 't' } }, {}, 'webhook');
|
|
98
|
-
ok(res.skipped?.includes('forbidden') && calls.length === 0, 'public webhook + loop action refused at fire time'); }
|
|
99
|
-
|
|
100
|
-
console.log('daily_cap under concurrency (#9 reserve-before-await)');
|
|
101
|
-
{ const s = freshStore(); const calls = []; let release; const gate = new Promise((r) => { release = r; });
|
|
102
|
-
const runner = new RoutineRunner({ store: s, deliver: async () => { calls.push(1); await gate; } });
|
|
103
|
-
const r = s.save({ session_id: 's', name: 'cap', daily_cap: 2, trigger: { type: 'manual' }, action: { kind: 'message', template: 'x' } });
|
|
104
|
-
const fires = Promise.all([0, 1, 2, 3, 4].map(() => runner.fire(s.get(r.id), {}, 'manual')));
|
|
105
|
-
await new Promise((res) => setTimeout(res, 20));
|
|
106
|
-
release(); await fires;
|
|
107
|
-
ok(calls.length === 2, `5 concurrent fires respect daily_cap=2 (delivered ${calls.length})`); }
|
|
108
|
-
|
|
109
|
-
console.log('catch-up honors timezone (#10)');
|
|
110
|
-
{ const s = freshStore();
|
|
111
|
-
const now = new Date('2026-06-10T13:30:00Z'); // 09:30 EDT
|
|
112
|
-
const calls = [];
|
|
113
|
-
const runner = new RoutineRunner({ store: s, deliver: async () => calls.push(1), now: () => now });
|
|
114
|
-
s.save({ session_id: 's', name: 'tzd', trigger: { type: 'schedule', cron: '0 9 * * *', tz: 'America/New_York', missed: 'catchup', deadline_sec: 7200 }, action: { kind: 'message', template: 'gm' } });
|
|
115
|
-
await runner.catchUp(new Date('2026-06-10T12:00:00Z'), now);
|
|
116
|
-
ok(calls.length === 1, 'catch-up fires the missed 09:00-America/New_York run (tz-aware)'); }
|
|
117
|
-
|
|
118
|
-
console.log(`\n${fail === 0 ? '✅' : '❌'} ${pass} passed, ${fail} failed`);
|
|
119
|
-
process.exit(fail === 0 ? 0 : 1);
|
|
120
|
-
} finally {
|
|
121
|
-
for (const d of dirs) { try { rmSync(d, { recursive: true, force: true }); } catch {} }
|
|
122
|
-
}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { createServer } from 'node:http';
|
|
4
|
-
import { R as RoutineStore, w as RoutineRunner } from './run-Buov6wz-.mjs';
|
|
5
|
-
import 'os';
|
|
6
|
-
import 'fs/promises';
|
|
7
|
-
import 'fs';
|
|
8
|
-
import 'path';
|
|
9
|
-
import 'url';
|
|
10
|
-
import 'child_process';
|
|
11
|
-
import 'crypto';
|
|
12
|
-
import 'node:fs';
|
|
13
|
-
import 'util';
|
|
14
|
-
import 'node:path';
|
|
15
|
-
import 'node:events';
|
|
16
|
-
import 'node:os';
|
|
17
|
-
import '@agentclientprotocol/sdk';
|
|
18
|
-
import '@modelcontextprotocol/sdk/client/index.js';
|
|
19
|
-
import '@modelcontextprotocol/sdk/client/stdio.js';
|
|
20
|
-
import '@modelcontextprotocol/sdk/types.js';
|
|
21
|
-
import 'zod';
|
|
22
|
-
import 'node:fs/promises';
|
|
23
|
-
import 'node:util';
|
|
24
|
-
|
|
25
|
-
function parseArgs(argv) {
|
|
26
|
-
const a = { _: [] };
|
|
27
|
-
for (let i = 0; i < argv.length; i++) {
|
|
28
|
-
const t = argv[i];
|
|
29
|
-
if (t.startsWith("--")) a[t.slice(2)] = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[++i] : true;
|
|
30
|
-
else a._.push(t);
|
|
31
|
-
}
|
|
32
|
-
return a;
|
|
33
|
-
}
|
|
34
|
-
function isSessionLive(sid) {
|
|
35
|
-
try {
|
|
36
|
-
const out = execFileSync("svamp", ["session", "list", "--json"], { stdio: ["ignore", "pipe", "ignore"], timeout: 15e3 }).toString();
|
|
37
|
-
const list = JSON.parse(out);
|
|
38
|
-
return Array.isArray(list) && list.some((s) => s?.sessionId === sid);
|
|
39
|
-
} catch {
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
async function cliDeliver({ routine, resolved }) {
|
|
44
|
-
if (resolved.kind === "verify") {
|
|
45
|
-
const dir = routine.dir;
|
|
46
|
-
if (!dir) {
|
|
47
|
-
console.error(`[trigger] skip ${routine.id || routine.name}: verify action has no dir`);
|
|
48
|
-
return { skipped: "verify: no dir" };
|
|
49
|
-
}
|
|
50
|
-
const target = routine.action.verify?.target_session || routine.session_id;
|
|
51
|
-
const reportTo = routine.action.verify?.report_to || routine.session_id;
|
|
52
|
-
if (!isSessionLive(reportTo)) {
|
|
53
|
-
console.error(`[trigger] skip ${routine.id || routine.name}: report session ${reportTo} not live (inbox is in-memory)`);
|
|
54
|
-
return { skipped: "verify: report session not live" };
|
|
55
|
-
}
|
|
56
|
-
let res;
|
|
57
|
-
try {
|
|
58
|
-
const out = execFileSync("svamp", ["issue", "pending"], { cwd: dir, encoding: "utf8" });
|
|
59
|
-
res = { verdict: "approved", guidance: out.trim() || "No pending issues.", criteria: "svamp issue pending" };
|
|
60
|
-
} catch (e) {
|
|
61
|
-
res = { verdict: "rework", guidance: (String(e?.stdout || "") + String(e?.stderr || "")).trim() || "Pending issues remain.", criteria: "svamp issue pending" };
|
|
62
|
-
}
|
|
63
|
-
const message = {
|
|
64
|
-
messageId: randomUUID(),
|
|
65
|
-
from: `watchdog:${routine.name || routine.id}`,
|
|
66
|
-
fromSession: target,
|
|
67
|
-
to: reportTo,
|
|
68
|
-
subject: `watchdog: ${res.verdict} \u2014 ${routine.name || routine.id}`,
|
|
69
|
-
body: `<supervision-verdict source="watchdog" session="${target}" verdict="${res.verdict}" judge="oracle" round="0">
|
|
70
|
-
${res.guidance}
|
|
71
|
-
Criteria: ${res.criteria || "(none)"}
|
|
72
|
-
</supervision-verdict>`,
|
|
73
|
-
timestamp: Date.now(),
|
|
74
|
-
read: false,
|
|
75
|
-
urgency: "normal",
|
|
76
|
-
hopCount: 1
|
|
77
|
-
};
|
|
78
|
-
const { connectAndGetMachine } = await import('./commands-BssfhM-2.mjs');
|
|
79
|
-
const { server, machine } = await connectAndGetMachine();
|
|
80
|
-
try {
|
|
81
|
-
await machine.sessionRPC(reportTo, "sendInboxMessage", { message });
|
|
82
|
-
} finally {
|
|
83
|
-
await server.disconnect();
|
|
84
|
-
}
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
if (routine.bind === "stateless") {
|
|
88
|
-
const dir = routine.dir;
|
|
89
|
-
if (!dir) {
|
|
90
|
-
console.error(`[routine] skip ${routine.id || routine.name}: stateless routine has no dir`);
|
|
91
|
-
return { skipped: "stateless: no dir" };
|
|
92
|
-
}
|
|
93
|
-
execFileSync("svamp", ["session", "query", dir, resolved.text || resolved.task || "run"], { stdio: "pipe" });
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
const sid = routine.session_id;
|
|
97
|
-
if (!isSessionLive(sid)) {
|
|
98
|
-
console.error(`[routine] skip ${routine.id || routine.name}: session ${sid} is stopped/archived`);
|
|
99
|
-
return { skipped: "session stopped/archived" };
|
|
100
|
-
}
|
|
101
|
-
if (resolved.kind === "message") {
|
|
102
|
-
execFileSync("svamp", ["session", "send", sid, resolved.text || ""], { stdio: "pipe" });
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
execFileSync("svamp", [
|
|
106
|
-
"session",
|
|
107
|
-
"send",
|
|
108
|
-
sid,
|
|
109
|
-
`Begin the loop (LOOP MODE). Task: ${resolved.task}. Iterate until the gate lets you stop.`
|
|
110
|
-
], { stdio: "pipe" });
|
|
111
|
-
}
|
|
112
|
-
function buildRoutineFromArgs(a) {
|
|
113
|
-
const r = { session_id: a.session, name: a.name || "routine", overlap: a.overlap || "queue" };
|
|
114
|
-
if (a.schedule) r.trigger = { type: "schedule", cron: a.schedule, missed: a.missed || "skip", tz: a.tz };
|
|
115
|
-
else if (a.webhook || a.api) r.trigger = { type: a.api ? "api" : "webhook", methods: a.get ? ["GET"] : ["GET", "POST"], public: !!a.public };
|
|
116
|
-
else r.trigger = { type: "manual" };
|
|
117
|
-
if (a.verify) r.action = { kind: "verify", verify: { target_session: a["target-session"], report_to: a["report-to"] } };
|
|
118
|
-
else if (a.loop) r.action = { kind: "loop", task_template: a.task || "", loop: { dir: a.dir, oracle: a.oracle, evaluator: a.evaluator || "off" } };
|
|
119
|
-
else r.action = { kind: "message", template: a.message || a.task || "run" };
|
|
120
|
-
if (a.dir) r.dir = a.dir;
|
|
121
|
-
if (a.stateless) r.bind = "stateless";
|
|
122
|
-
return r;
|
|
123
|
-
}
|
|
124
|
-
async function routineCommand(args) {
|
|
125
|
-
const a = parseArgs(args);
|
|
126
|
-
const cmd = a._[0];
|
|
127
|
-
const store = new RoutineStore();
|
|
128
|
-
const runner = new RoutineRunner({ store, deliver: cliDeliver, log: console.error });
|
|
129
|
-
switch (cmd) {
|
|
130
|
-
case "add": {
|
|
131
|
-
if (!a.session) {
|
|
132
|
-
console.error("--session <id> required");
|
|
133
|
-
process.exit(1);
|
|
134
|
-
}
|
|
135
|
-
const r = store.save(buildRoutineFromArgs(a));
|
|
136
|
-
console.log(`\u2705 added trigger ${r.id} (${r.name}) source=${r.trigger.type} action=${r.action.kind} session=${r.session_id}`);
|
|
137
|
-
if (r.trigger.key) console.log(` webhook key: ${r.trigger.key}`);
|
|
138
|
-
break;
|
|
139
|
-
}
|
|
140
|
-
case "list": {
|
|
141
|
-
const rs = store.list(typeof a.session === "string" ? a.session : void 0);
|
|
142
|
-
if (a.json) {
|
|
143
|
-
console.log(JSON.stringify(rs, null, 2));
|
|
144
|
-
break;
|
|
145
|
-
}
|
|
146
|
-
if (!rs.length) {
|
|
147
|
-
console.log("(no triggers)");
|
|
148
|
-
break;
|
|
149
|
-
}
|
|
150
|
-
for (const r of rs) console.log(`${r.enabled ? "\u25CF" : "\u25CB"} ${r.id} ${r.name} [${r.trigger.type}${r.trigger.cron ? " " + r.trigger.cron : ""}] -> ${r.action.kind} ${r.bind === "stateless" ? `stateless(${r.dir || "?"})` : `session=${r.session_id}`}`);
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
case "remove":
|
|
154
|
-
console.log(store.remove(a._[1]) ? `removed ${a._[1]}` : "not found");
|
|
155
|
-
break;
|
|
156
|
-
case "enable":
|
|
157
|
-
store.setEnabled(a._[1], true);
|
|
158
|
-
console.log(`enabled ${a._[1]}`);
|
|
159
|
-
break;
|
|
160
|
-
case "disable":
|
|
161
|
-
store.setEnabled(a._[1], false);
|
|
162
|
-
console.log(`disabled ${a._[1]}`);
|
|
163
|
-
break;
|
|
164
|
-
case "run-now":
|
|
165
|
-
console.log(JSON.stringify(await runner.runNow(a._[1], {}), null, 2));
|
|
166
|
-
break;
|
|
167
|
-
case "serve":
|
|
168
|
-
await serve(runner, Number(a.port) || 8722);
|
|
169
|
-
break;
|
|
170
|
-
default:
|
|
171
|
-
console.log(`usage: svamp trigger <add|list|remove|enable|disable|run-now|serve> (alias: svamp routine)
|
|
172
|
-
add --session <id> --name <n> [--schedule "*/5 * * * *" [--missed catchup]] [--webhook|--api [--get] [--public]] [--stateless --dir <path>] (--message "..." | --loop --dir <path> --task "..." [--oracle "cmd"])
|
|
173
|
-
list [--session <id>] [--json]
|
|
174
|
-
serve [--port 8722]`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
async function serve(runner, port) {
|
|
178
|
-
await runner.catchUp(new Date(Date.now() - 24 * 3600 * 1e3), /* @__PURE__ */ new Date());
|
|
179
|
-
const timer = setInterval(() => {
|
|
180
|
-
runner.tick(/* @__PURE__ */ new Date()).catch((e) => console.error("tick error", e));
|
|
181
|
-
}, 2e4);
|
|
182
|
-
const server = createServer((req, res) => {
|
|
183
|
-
const u = new URL(req.url || "/", `http://localhost:${port}`);
|
|
184
|
-
if (u.pathname === "/" || u.pathname === "/health") {
|
|
185
|
-
res.writeHead(200).end("routines ok");
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
const m = u.pathname.match(/^\/routine\/([\w.-]+)$/);
|
|
189
|
-
if (!m) {
|
|
190
|
-
res.writeHead(404).end("not found");
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
const id = m[1];
|
|
194
|
-
const key = u.searchParams.get("key");
|
|
195
|
-
const query = Object.fromEntries(u.searchParams.entries());
|
|
196
|
-
let chunks = "";
|
|
197
|
-
req.on("data", (c) => {
|
|
198
|
-
chunks += c;
|
|
199
|
-
if (chunks.length > 1e6) req.destroy();
|
|
200
|
-
});
|
|
201
|
-
req.on("end", async () => {
|
|
202
|
-
let body = {};
|
|
203
|
-
try {
|
|
204
|
-
body = chunks ? JSON.parse(chunks) : {};
|
|
205
|
-
} catch {
|
|
206
|
-
}
|
|
207
|
-
const result = await runner.webhook(id, { key, method: req.method, body, query });
|
|
208
|
-
res.writeHead(result.status || 200, { "content-type": "application/json" }).end(JSON.stringify(result));
|
|
209
|
-
});
|
|
210
|
-
});
|
|
211
|
-
server.listen(port, () => {
|
|
212
|
-
console.log(`\u{1FA9D} trigger server on http://localhost:${port}`);
|
|
213
|
-
console.log(` webhook URL: http://localhost:${port}/routine/<id>?key=<key>`);
|
|
214
|
-
console.log(` expose: svamp service expose triggers --port ${port}`);
|
|
215
|
-
});
|
|
216
|
-
process.on("SIGINT", () => {
|
|
217
|
-
clearInterval(timer);
|
|
218
|
-
server.close();
|
|
219
|
-
process.exit(0);
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export { routineCommand };
|
package/dist/store-D2HNe-Kv.mjs
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { existsSync, unlinkSync, readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { parse, stringify } from 'yaml';
|
|
4
|
-
|
|
5
|
-
function workflowsDir(projectRoot) {
|
|
6
|
-
return join(projectRoot, ".svamp", "workflows");
|
|
7
|
-
}
|
|
8
|
-
function workflowPath(projectRoot, name) {
|
|
9
|
-
return join(workflowsDir(projectRoot), `${name}.yaml`);
|
|
10
|
-
}
|
|
11
|
-
function parseWorkflow(content) {
|
|
12
|
-
try {
|
|
13
|
-
const o = parse(content);
|
|
14
|
-
if (!o || typeof o !== "object" || !o.name) return null;
|
|
15
|
-
const jobs = Array.isArray(o.jobs) ? o.jobs.map((j) => ({ run: String(j?.run ?? "") })).filter((j) => j.run) : [];
|
|
16
|
-
return { name: String(o.name), on: o.on && typeof o.on === "object" ? o.on : void 0, jobs, session: o.session ?? null };
|
|
17
|
-
} catch {
|
|
18
|
-
return null;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
function serializeWorkflow(wf) {
|
|
22
|
-
const clean = { name: wf.name };
|
|
23
|
-
if (wf.session) clean.session = wf.session;
|
|
24
|
-
if (wf.on && Object.keys(wf.on).length) clean.on = wf.on;
|
|
25
|
-
clean.jobs = wf.jobs.map((j) => ({ run: j.run }));
|
|
26
|
-
return stringify(clean);
|
|
27
|
-
}
|
|
28
|
-
function listWorkflows(projectRoot) {
|
|
29
|
-
const dir = workflowsDir(projectRoot);
|
|
30
|
-
if (!existsSync(dir)) return [];
|
|
31
|
-
const out = [];
|
|
32
|
-
for (const name of readdirSync(dir)) {
|
|
33
|
-
if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
|
|
34
|
-
try {
|
|
35
|
-
const wf = parseWorkflow(readFileSync(join(dir, name), "utf-8"));
|
|
36
|
-
if (wf) out.push(wf);
|
|
37
|
-
} catch {
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
|
-
}
|
|
42
|
-
function getWorkflow(projectRoot, name) {
|
|
43
|
-
const p = workflowPath(projectRoot, name);
|
|
44
|
-
if (!existsSync(p)) return null;
|
|
45
|
-
try {
|
|
46
|
-
return parseWorkflow(readFileSync(p, "utf-8"));
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function rawWorkflow(projectRoot, name) {
|
|
52
|
-
const p = workflowPath(projectRoot, name);
|
|
53
|
-
return existsSync(p) ? readFileSync(p, "utf-8") : null;
|
|
54
|
-
}
|
|
55
|
-
function saveWorkflow(projectRoot, wf) {
|
|
56
|
-
const dir = workflowsDir(projectRoot);
|
|
57
|
-
mkdirSync(dir, { recursive: true });
|
|
58
|
-
const path = workflowPath(projectRoot, wf.name);
|
|
59
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
60
|
-
writeFileSync(tmp, serializeWorkflow(wf));
|
|
61
|
-
renameSync(tmp, path);
|
|
62
|
-
}
|
|
63
|
-
function removeWorkflow(projectRoot, name) {
|
|
64
|
-
const p = workflowPath(projectRoot, name);
|
|
65
|
-
if (!existsSync(p)) return false;
|
|
66
|
-
try {
|
|
67
|
-
unlinkSync(p);
|
|
68
|
-
return true;
|
|
69
|
-
} catch {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export { rawWorkflow as a, getWorkflow as g, listWorkflows as l, removeWorkflow as r, saveWorkflow as s };
|