svamp-cli 0.2.174 → 0.2.176

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.
Files changed (26) hide show
  1. package/dist/{agentCommands-XAGMtjRQ.mjs → agentCommands-kchQlyIS.mjs} +5 -5
  2. package/dist/{auth-fEMoSUUD.mjs → auth-oH2upjHc.mjs} +1 -1
  3. package/dist/cli.mjs +61 -61
  4. package/dist/{commands-7wZGSXe5.mjs → commands-9LT5p45A.mjs} +2 -2
  5. package/dist/{commands-BVyi7762.mjs → commands-CXtkDntj.mjs} +2 -2
  6. package/dist/{commands-Di_HYoeZ.mjs → commands-CbrJ16x7.mjs} +5 -5
  7. package/dist/{commands-CPHMhYnp.mjs → commands-Cu6nPOsD.mjs} +1 -1
  8. package/dist/{commands-3mxZi6dQ.mjs → commands-DBi7fxGX.mjs} +2 -2
  9. package/dist/{commands-NdHufk_2.mjs → commands-DGkX7Kcr.mjs} +1 -1
  10. package/dist/{commands-B3Uk_kN9.mjs → commands-DHbv5qMP.mjs} +22 -84
  11. package/dist/{commands-BaGfFhwa.mjs → commands-USG2Z4XP.mjs} +1 -1
  12. package/dist/{fleet-B2GyUSYJ.mjs → fleet-BcS8xNkk.mjs} +1 -1
  13. package/dist/{frpc-BZmO_6PK.mjs → frpc-Bm7BrLan.mjs} +1 -1
  14. package/dist/{headlessCli-Yeuekh99.mjs → headlessCli-B4CVNI7S.mjs} +2 -2
  15. package/dist/index.mjs +1 -1
  16. package/dist/{package-CmQGdoJz.mjs → package-DndVS0ln.mjs} +2 -2
  17. package/dist/rpc-BbaS0cLh.mjs +151 -0
  18. package/dist/rpc-DVNDMfl8.mjs +80 -0
  19. package/dist/{run-BROwnG-V.mjs → run-BmpyfykC.mjs} +52 -6
  20. package/dist/{run-BE4jYZdk.mjs → run-Cr1yt_tm.mjs} +1 -1
  21. package/dist/scheduler-BusuolPD.mjs +93 -0
  22. package/dist/{serveCommands-C1Lfx-NW.mjs → serveCommands-Crb8ENkv.mjs} +5 -5
  23. package/dist/{serveManager-BdjeAjgf.mjs → serveManager-DQByExEO.mjs} +2 -2
  24. package/dist/{sideband-CQDEJ2q2.mjs → sideband-B4L2zR2U.mjs} +1 -1
  25. package/dist/store-BPL__e4D.mjs +125 -0
  26. package/package.json +2 -2
@@ -0,0 +1,93 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { m as resolveProjectRoot, v as cronMatches } from './run-BmpyfykC.mjs';
3
+ import { l as listWorkflows, b as workflowCrons, w as workflowSteps } from './store-BPL__e4D.mjs';
4
+ import 'os';
5
+ import 'fs/promises';
6
+ import 'fs';
7
+ import 'path';
8
+ import 'url';
9
+ import 'child_process';
10
+ import 'crypto';
11
+ import 'node: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
+ import 'yaml';
25
+
26
+ function defaultRunStep(root, command, log) {
27
+ try {
28
+ const child = spawn("sh", ["-c", command], { cwd: root, detached: true, stdio: "ignore" });
29
+ child.on("error", (e) => log?.(`[workflow] step spawn error in ${root}: ${e.message}`));
30
+ child.unref();
31
+ } catch (e) {
32
+ log?.(`[workflow] step spawn threw in ${root}: ${e?.message || e}`);
33
+ }
34
+ }
35
+ function cronMatchesSafe(expr, date) {
36
+ try {
37
+ return cronMatches(expr, date);
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+ class WorkflowScheduler {
43
+ constructor(deps) {
44
+ this.deps = deps;
45
+ }
46
+ // Wall-clock minute we last processed, so multiple sub-minute ticks fire a workflow at most once
47
+ // per minute (cron granularity is one minute).
48
+ lastMinuteKey = null;
49
+ static minuteKey(d) {
50
+ return `${d.getFullYear()}-${d.getMonth()}-${d.getDate()}-${d.getHours()}-${d.getMinutes()}`;
51
+ }
52
+ /** Fire all scheduled workflows whose cron matches `now`. Returns the names fired (for tests/logs). */
53
+ tick(now) {
54
+ const key = WorkflowScheduler.minuteKey(now);
55
+ if (key === this.lastMinuteKey) return [];
56
+ this.lastMinuteKey = key;
57
+ const fired = [];
58
+ const seen = /* @__PURE__ */ new Set();
59
+ for (const dir of this.deps.projectRoots()) {
60
+ let root;
61
+ try {
62
+ root = resolveProjectRoot(dir);
63
+ } catch {
64
+ continue;
65
+ }
66
+ if (seen.has(root)) continue;
67
+ seen.add(root);
68
+ let workflows;
69
+ try {
70
+ workflows = listWorkflows(root);
71
+ } catch {
72
+ continue;
73
+ }
74
+ for (const wf of workflows) {
75
+ const crons = workflowCrons(wf);
76
+ if (!crons.length) continue;
77
+ if (crons.some((c) => cronMatchesSafe(c, now))) {
78
+ this.fire(root, wf);
79
+ fired.push(wf.name);
80
+ }
81
+ }
82
+ }
83
+ return fired;
84
+ }
85
+ fire(root, wf) {
86
+ const steps = workflowSteps(wf);
87
+ this.deps.log?.(`[workflow] schedule fired "${wf.name}" (${steps.length} step${steps.length === 1 ? "" : "s"}) in ${root}`);
88
+ const run = this.deps.runStep || ((r, c) => defaultRunStep(r, c, this.deps.log));
89
+ for (const step of steps) run(root, step.run);
90
+ }
91
+ }
92
+
93
+ export { WorkflowScheduler, cronMatchesSafe };
@@ -54,7 +54,7 @@ async function handleServeCommand() {
54
54
  }
55
55
  }
56
56
  async function serveAdd(args, machineId) {
57
- const { connectAndGetMachine } = await import('./commands-NdHufk_2.mjs');
57
+ const { connectAndGetMachine } = await import('./commands-DGkX7Kcr.mjs');
58
58
  const pos = positionalArgs(args);
59
59
  const name = pos[0];
60
60
  if (!name) {
@@ -93,7 +93,7 @@ async function serveAdd(args, machineId) {
93
93
  }
94
94
  }
95
95
  async function serveApply(args, machineId) {
96
- const { connectAndGetMachine } = await import('./commands-NdHufk_2.mjs');
96
+ const { connectAndGetMachine } = await import('./commands-DGkX7Kcr.mjs');
97
97
  const fs = await import('fs');
98
98
  const yaml = await import('yaml');
99
99
  const file = positionalArgs(args)[0];
@@ -182,7 +182,7 @@ async function serveApply(args, machineId) {
182
182
  }
183
183
  }
184
184
  async function serveRemove(args, machineId) {
185
- const { connectAndGetMachine } = await import('./commands-NdHufk_2.mjs');
185
+ const { connectAndGetMachine } = await import('./commands-DGkX7Kcr.mjs');
186
186
  const pos = positionalArgs(args);
187
187
  const name = pos[0];
188
188
  if (!name) {
@@ -202,7 +202,7 @@ async function serveRemove(args, machineId) {
202
202
  }
203
203
  }
204
204
  async function serveList(args, machineId) {
205
- const { connectAndGetMachine } = await import('./commands-NdHufk_2.mjs');
205
+ const { connectAndGetMachine } = await import('./commands-DGkX7Kcr.mjs');
206
206
  const all = hasFlag(args, "--all", "-a");
207
207
  const json = hasFlag(args, "--json");
208
208
  const sessionId = getFlag(args, "--session");
@@ -235,7 +235,7 @@ async function serveList(args, machineId) {
235
235
  }
236
236
  }
237
237
  async function serveInfo(machineId) {
238
- const { connectAndGetMachine } = await import('./commands-NdHufk_2.mjs');
238
+ const { connectAndGetMachine } = await import('./commands-DGkX7Kcr.mjs');
239
239
  const { machine, server } = await connectAndGetMachine(machineId);
240
240
  try {
241
241
  const info = await machine.serveInfo();
@@ -4,7 +4,7 @@ import * as fs from 'fs';
4
4
  import * as http from 'http';
5
5
  import * as net from 'net';
6
6
  import * as path from 'path';
7
- import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-BROwnG-V.mjs';
7
+ import { k as getHyphaServerUrl, S as ServeAuth, l as hasCookieToken } from './run-BmpyfykC.mjs';
8
8
  import 'os';
9
9
  import 'fs/promises';
10
10
  import 'url';
@@ -733,7 +733,7 @@ class ServeManager {
733
733
  const mount = this.mounts.get(mountName);
734
734
  const subdomainOverride = mount?.access === "link" && mount.linkToken ? /* @__PURE__ */ new Map([[this.port, buildLinkSubdomain(subdomainSafe, mount.linkToken)]]) : void 0;
735
735
  try {
736
- const { FrpcTunnel } = await import('./frpc-BZmO_6PK.mjs');
736
+ const { FrpcTunnel } = await import('./frpc-Bm7BrLan.mjs');
737
737
  let tunnel;
738
738
  tunnel = new FrpcTunnel({
739
739
  name: tunnelName,
@@ -1,4 +1,4 @@
1
- import { I as READ_ONLY_TOOLS, J as loadMachineContext, K as buildMachineInstructions, L as machineToolsForRole, M as buildMachineTools } from './run-BROwnG-V.mjs';
1
+ import { z as READ_ONLY_TOOLS, A as loadMachineContext, B as buildMachineInstructions, C as machineToolsForRole, D as buildMachineTools } from './run-BmpyfykC.mjs';
2
2
  import 'node:child_process';
3
3
  import 'os';
4
4
  import 'fs/promises';
@@ -0,0 +1,125 @@
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 workflowSteps(wf) {
6
+ return Object.values(wf.jobs || {}).flatMap((j) => j?.steps || []);
7
+ }
8
+ function workflowCrons(wf) {
9
+ return (wf.on?.schedule || []).map((s) => s?.cron).filter((c) => !!c);
10
+ }
11
+ function workflowsDir(projectRoot) {
12
+ return join(projectRoot, ".svamp", "workflows");
13
+ }
14
+ function workflowPath(projectRoot, name) {
15
+ return join(workflowsDir(projectRoot), `${name}.yaml`);
16
+ }
17
+ function normalizeOn(on) {
18
+ if (!on || typeof on !== "object") {
19
+ return void 0;
20
+ }
21
+ const out = {};
22
+ if (Array.isArray(on.schedule)) {
23
+ const sched = on.schedule.map((s) => ({ cron: String(s?.cron ?? s ?? "") })).filter((s) => s.cron);
24
+ if (sched.length) out.schedule = sched;
25
+ } else if (typeof on.schedule === "string" && on.schedule.trim()) {
26
+ out.schedule = [{ cron: on.schedule.trim() }];
27
+ }
28
+ if ("workflow_dispatch" in on || on.dispatch === true) out.workflow_dispatch = true;
29
+ if (typeof on.channel === "string" && on.channel) out.channel = on.channel;
30
+ if (Array.isArray(on.issue) && on.issue.length) out.issue = on.issue.map(String);
31
+ return Object.keys(out).length ? out : void 0;
32
+ }
33
+ function normalizeJobs(jobs) {
34
+ if (Array.isArray(jobs)) {
35
+ const steps = jobs.map((j) => ({ run: String(j?.run ?? "") })).filter((s) => s.run);
36
+ return steps.length ? { run: { steps } } : {};
37
+ }
38
+ if (jobs && typeof jobs === "object") {
39
+ const out = {};
40
+ for (const [id, job] of Object.entries(jobs)) {
41
+ const rawSteps = Array.isArray(job?.steps) ? job.steps : job?.run ? [{ run: job.run }] : [];
42
+ const steps = rawSteps.map((s) => ({ run: String(s?.run ?? ""), ...s?.name ? { name: String(s.name) } : {} })).filter((s) => s.run);
43
+ if (steps.length) out[String(id)] = { steps };
44
+ }
45
+ return out;
46
+ }
47
+ return {};
48
+ }
49
+ function parseWorkflow(content) {
50
+ try {
51
+ const o = parse(content);
52
+ if (!o || typeof o !== "object" || !o.name) return null;
53
+ return {
54
+ name: String(o.name),
55
+ on: normalizeOn(o.on),
56
+ jobs: normalizeJobs(o.jobs),
57
+ session: o.session ?? null
58
+ };
59
+ } catch {
60
+ return null;
61
+ }
62
+ }
63
+ function serializeWorkflow(wf) {
64
+ const clean = { name: wf.name };
65
+ if (wf.session) clean.session = wf.session;
66
+ const on = {};
67
+ if (wf.on?.schedule?.length) on.schedule = wf.on.schedule.map((s) => ({ cron: s.cron }));
68
+ if (wf.on?.workflow_dispatch) on.workflow_dispatch = {};
69
+ if (wf.on?.channel) on.channel = wf.on.channel;
70
+ if (wf.on?.issue?.length) on.issue = wf.on.issue;
71
+ if (Object.keys(on).length) clean.on = on;
72
+ const jobs = {};
73
+ for (const [id, job] of Object.entries(wf.jobs || {})) {
74
+ jobs[id] = { steps: (job.steps || []).map((s) => s.name ? { name: s.name, run: s.run } : { run: s.run }) };
75
+ }
76
+ clean.jobs = Object.keys(jobs).length ? jobs : { run: { steps: [] } };
77
+ return stringify(clean);
78
+ }
79
+ function listWorkflows(projectRoot) {
80
+ const dir = workflowsDir(projectRoot);
81
+ if (!existsSync(dir)) return [];
82
+ const out = [];
83
+ for (const name of readdirSync(dir)) {
84
+ if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
85
+ try {
86
+ const wf = parseWorkflow(readFileSync(join(dir, name), "utf-8"));
87
+ if (wf) out.push(wf);
88
+ } catch {
89
+ }
90
+ }
91
+ return out.sort((a, b) => a.name.localeCompare(b.name));
92
+ }
93
+ function getWorkflow(projectRoot, name) {
94
+ const p = workflowPath(projectRoot, name);
95
+ if (!existsSync(p)) return null;
96
+ try {
97
+ return parseWorkflow(readFileSync(p, "utf-8"));
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+ function rawWorkflow(projectRoot, name) {
103
+ const p = workflowPath(projectRoot, name);
104
+ return existsSync(p) ? readFileSync(p, "utf-8") : null;
105
+ }
106
+ function saveWorkflow(projectRoot, wf) {
107
+ const dir = workflowsDir(projectRoot);
108
+ mkdirSync(dir, { recursive: true });
109
+ const path = workflowPath(projectRoot, wf.name);
110
+ const tmp = `${path}.tmp-${process.pid}`;
111
+ writeFileSync(tmp, serializeWorkflow(wf));
112
+ renameSync(tmp, path);
113
+ }
114
+ function removeWorkflow(projectRoot, name) {
115
+ const p = workflowPath(projectRoot, name);
116
+ if (!existsSync(p)) return false;
117
+ try {
118
+ unlinkSync(p);
119
+ return true;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+
125
+ export { rawWorkflow as a, workflowCrons as b, getWorkflow as g, listWorkflows as l, removeWorkflow as r, saveWorkflow as s, workflowSteps as w };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svamp-cli",
3
- "version": "0.2.174",
3
+ "version": "0.2.176",
4
4
  "description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
5
5
  "author": "Amun AI AB",
6
6
  "license": "SEE LICENSE IN LICENSE",
@@ -20,7 +20,7 @@
20
20
  "scripts": {
21
21
  "build": "rm -rf dist bin/skills && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/loop bin/skills/loop && cp -r ../../skills/crew bin/skills/crew && tsc --noEmit && pkgroll",
22
22
  "typecheck": "tsc --noEmit",
23
- "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs",
23
+ "test": "npx tsx test/test-context-window.mjs && npx tsx test/test-ratelimit-retry.mjs && npx tsx test/test-instance-config.mjs && npx tsx test/test-authorize.mjs && npx tsx test/test-normalize-allowed-user.mjs && npx tsx test/test-share-url.mjs && npx tsx test/test-update-sharing-normalization.mjs && npx tsx test/test-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-loop-activation.mjs && npx tsx test/test-message-helpers.mjs && npx tsx test/test-agent-config.mjs && npx tsx test/test-wrap-command.mjs && npx tsx test/test-credential-staging.mjs && npx tsx test/test-claude-auth.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-auto-topic.mjs && npx tsx test/test-project-info.mjs && npx tsx test/test-agent-types.mjs && npx tsx test/test-transport.mjs && npx tsx test/test-session-update-handlers.mjs && npx tsx test/test-session-scanner.mjs && npx tsx test/test-hypha-client.mjs && npx tsx test/test-hook-settings.mjs && npx tsx test/test-session-service-logic.mjs && npx tsx test/test-daemon-persistence.mjs && npx tsx test/test-detect-isolation.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-backend.mjs && npx tsx test/test-acp-backend.mjs && npx tsx test/test-acp-bridge.mjs && npx tsx test/test-hook-server.mjs && npx tsx test/test-session-commands.mjs && npx tsx test/test-interactive-console.mjs && npx tsx test/test-session-messages.mjs && npx tsx test/test-session-send-query.mjs && npx tsx test/test-skills.mjs && npx tsx test/test-agent-grouping.mjs && npx tsx test/test-machine-list-directory.mjs && npx tsx test/test-service-commands.mjs && npx tsx test/test-supervisor.mjs && npx tsx test/test-supervisor-lock.mjs && node test/test-supervisor-restart.mjs && npx tsx test/test-clear-detection.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-serve-link-subdomain.mjs && npx tsx test/test-short-id.mjs && npx tsx test/test-transcript-edit.mjs && npx tsx test/test-edit-history.mjs && npx tsx test/test-friendly-name.mjs && npx tsx test/test-session-rpc-dispatch.mjs && npx tsx test/test-sandbox-cli.mjs && npx tsx test/test-serve-manager.mjs && npx tsx test/test-serve-stability.mjs && npx tsx test/test-frpc-e2e.mjs --unit-only && npx tsx test/test-frpc-status.mjs && node test/pinnedClaudeCode.test.mjs && node test/fleet.test.mjs && npx tsx test/test-routine.mjs && npx tsx test/test-routine-rpc.mjs && npx tsx test/test-session-file.mjs && npx tsx test/test-channel-rpc.mjs && npx tsx test/test-wise-agent.mjs && npx tsx test/test-channel-agent.mjs && npx tsx test/test-channels-service.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-shared-session-identity.mjs && npx tsx test/test-wise-agent-auth.mjs && npx tsx test/test-channel-http.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs",
24
24
  "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
25
25
  "dev": "tsx src/cli.ts",
26
26
  "dev:daemon": "tsx src/cli.ts daemon start-sync",