svamp-cli 0.2.151 → 0.2.152
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/dist/cli.mjs
CHANGED
|
@@ -375,9 +375,13 @@ async function main() {
|
|
|
375
375
|
}), machineId);
|
|
376
376
|
process.exit(0);
|
|
377
377
|
} else if (subcommand === "issue" || subcommand === "issues") {
|
|
378
|
-
const { issueCommand } = await import('./commands
|
|
378
|
+
const { issueCommand } = await import('./commands-GYM5Mj8d.mjs');
|
|
379
379
|
await issueCommand(args.slice(1));
|
|
380
380
|
process.exit(0);
|
|
381
|
+
} else if (subcommand === "workflow" || subcommand === "workflows") {
|
|
382
|
+
const { workflowCommand } = await import('./commands-C5NCV1-2.mjs');
|
|
383
|
+
await workflowCommand(args.slice(1));
|
|
384
|
+
process.exit(0);
|
|
381
385
|
} else if (subcommand === "trigger" || subcommand === "triggers" || subcommand === "routine" || subcommand === "routines") {
|
|
382
386
|
const { routineCommand } = await import('./commands-DRFQ4tEC.mjs');
|
|
383
387
|
await routineCommand(args.slice(1));
|
|
@@ -394,7 +398,7 @@ async function main() {
|
|
|
394
398
|
} else if (!subcommand || subcommand === "start") {
|
|
395
399
|
await handleInteractiveCommand();
|
|
396
400
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
397
|
-
const pkg = await import('./package-
|
|
401
|
+
const pkg = await import('./package-9anLa1tI.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
398
402
|
console.log(`svamp version: ${pkg.default.version}`);
|
|
399
403
|
} else {
|
|
400
404
|
console.error(`Unknown command: ${subcommand}`);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { r as resolveProjectRoot } from './store-ChN9bgel.mjs';
|
|
3
|
+
import { existsSync, unlinkSync, readFileSync, readdirSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { parse, stringify } from 'yaml';
|
|
6
|
+
|
|
7
|
+
function workflowsDir(projectRoot) {
|
|
8
|
+
return join(projectRoot, ".svamp", "workflows");
|
|
9
|
+
}
|
|
10
|
+
function workflowPath(projectRoot, name) {
|
|
11
|
+
return join(workflowsDir(projectRoot), `${name}.yaml`);
|
|
12
|
+
}
|
|
13
|
+
function parseWorkflow(content) {
|
|
14
|
+
try {
|
|
15
|
+
const o = parse(content);
|
|
16
|
+
if (!o || typeof o !== "object" || !o.name) return null;
|
|
17
|
+
const jobs = Array.isArray(o.jobs) ? o.jobs.map((j) => ({ run: String(j?.run ?? "") })).filter((j) => j.run) : [];
|
|
18
|
+
return { name: String(o.name), on: o.on && typeof o.on === "object" ? o.on : void 0, jobs };
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function serializeWorkflow(wf) {
|
|
24
|
+
const clean = { name: wf.name };
|
|
25
|
+
if (wf.on && Object.keys(wf.on).length) clean.on = wf.on;
|
|
26
|
+
clean.jobs = wf.jobs.map((j) => ({ run: j.run }));
|
|
27
|
+
return stringify(clean);
|
|
28
|
+
}
|
|
29
|
+
function listWorkflows(projectRoot) {
|
|
30
|
+
const dir = workflowsDir(projectRoot);
|
|
31
|
+
if (!existsSync(dir)) return [];
|
|
32
|
+
const out = [];
|
|
33
|
+
for (const name of readdirSync(dir)) {
|
|
34
|
+
if (!name.endsWith(".yaml") && !name.endsWith(".yml")) continue;
|
|
35
|
+
try {
|
|
36
|
+
const wf = parseWorkflow(readFileSync(join(dir, name), "utf-8"));
|
|
37
|
+
if (wf) out.push(wf);
|
|
38
|
+
} catch {
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
42
|
+
}
|
|
43
|
+
function getWorkflow(projectRoot, name) {
|
|
44
|
+
const p = workflowPath(projectRoot, name);
|
|
45
|
+
if (!existsSync(p)) return null;
|
|
46
|
+
try {
|
|
47
|
+
return parseWorkflow(readFileSync(p, "utf-8"));
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function rawWorkflow(projectRoot, name) {
|
|
53
|
+
const p = workflowPath(projectRoot, name);
|
|
54
|
+
return existsSync(p) ? readFileSync(p, "utf-8") : null;
|
|
55
|
+
}
|
|
56
|
+
function saveWorkflow(projectRoot, wf) {
|
|
57
|
+
const dir = workflowsDir(projectRoot);
|
|
58
|
+
mkdirSync(dir, { recursive: true });
|
|
59
|
+
const path = workflowPath(projectRoot, wf.name);
|
|
60
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
61
|
+
writeFileSync(tmp, serializeWorkflow(wf));
|
|
62
|
+
renameSync(tmp, path);
|
|
63
|
+
}
|
|
64
|
+
function removeWorkflow(projectRoot, name) {
|
|
65
|
+
const p = workflowPath(projectRoot, name);
|
|
66
|
+
if (!existsSync(p)) return false;
|
|
67
|
+
try {
|
|
68
|
+
unlinkSync(p);
|
|
69
|
+
return true;
|
|
70
|
+
} catch {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function flag(args, name) {
|
|
76
|
+
const i = args.indexOf(name);
|
|
77
|
+
return i !== -1 && i + 1 < args.length ? args[i + 1] : void 0;
|
|
78
|
+
}
|
|
79
|
+
function allFlags(args, name) {
|
|
80
|
+
return args.filter((_a, idx) => args[idx - 1] === name);
|
|
81
|
+
}
|
|
82
|
+
function positional(args) {
|
|
83
|
+
const out = [];
|
|
84
|
+
for (let i = 0; i < args.length; i++) {
|
|
85
|
+
const a = args[i];
|
|
86
|
+
if (a.startsWith("--")) {
|
|
87
|
+
if (a !== "--json") i++;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
out.push(a);
|
|
91
|
+
}
|
|
92
|
+
return out;
|
|
93
|
+
}
|
|
94
|
+
function describeOn(on) {
|
|
95
|
+
if (!on) return "manual";
|
|
96
|
+
const parts = [];
|
|
97
|
+
if (on.schedule) parts.push(`schedule(${on.schedule})`);
|
|
98
|
+
if (on.dispatch) parts.push("dispatch");
|
|
99
|
+
if (on.channel) parts.push(`channel(${on.channel})`);
|
|
100
|
+
if (on.issue?.length) parts.push(`issue(${on.issue.join("/")})`);
|
|
101
|
+
return parts.length ? parts.join(" ") : "manual";
|
|
102
|
+
}
|
|
103
|
+
async function workflowCommand(args) {
|
|
104
|
+
const sub = args[0];
|
|
105
|
+
const rest = args.slice(1);
|
|
106
|
+
const root = resolveProjectRoot();
|
|
107
|
+
const json = args.includes("--json");
|
|
108
|
+
switch (sub) {
|
|
109
|
+
case "add": {
|
|
110
|
+
const name = positional(rest)[0];
|
|
111
|
+
const runs = allFlags(rest, "--run");
|
|
112
|
+
if (!name || !runs.length) {
|
|
113
|
+
console.error('usage: svamp workflow add <name> --run "<cmd>" [--run "<cmd2>"] [--on schedule|dispatch|channel|issue] [--cron "* * * * *"] [--channel <name>] [--issue ready,closed,labeled]');
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
const onKinds = allFlags(rest, "--on");
|
|
117
|
+
const on = {};
|
|
118
|
+
if (onKinds.includes("schedule") || flag(rest, "--cron")) on.schedule = flag(rest, "--cron") || "0 * * * *";
|
|
119
|
+
if (onKinds.includes("dispatch")) on.dispatch = true;
|
|
120
|
+
if (onKinds.includes("channel") || flag(rest, "--channel")) on.channel = flag(rest, "--channel") || "default";
|
|
121
|
+
if (onKinds.includes("issue") || flag(rest, "--issue")) on.issue = (flag(rest, "--issue") || "ready").split(",").map((s) => s.trim()).filter(Boolean);
|
|
122
|
+
const wf = { name, on: Object.keys(on).length ? on : void 0, jobs: runs.map((r) => ({ run: r })) };
|
|
123
|
+
saveWorkflow(root, wf);
|
|
124
|
+
if (json) console.log(JSON.stringify(wf));
|
|
125
|
+
else console.log(`Saved workflow "${name}" (on: ${describeOn(wf.on)}, ${wf.jobs.length} step${wf.jobs.length === 1 ? "" : "s"}).`);
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
case "list":
|
|
129
|
+
case "ls": {
|
|
130
|
+
const all = listWorkflows(root);
|
|
131
|
+
if (json) {
|
|
132
|
+
console.log(JSON.stringify(all));
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
if (!all.length) {
|
|
136
|
+
console.log("No workflows.");
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
for (const wf of all) console.log(`${wf.name} [${describeOn(wf.on)}] ${wf.jobs.length} step${wf.jobs.length === 1 ? "" : "s"}`);
|
|
140
|
+
break;
|
|
141
|
+
}
|
|
142
|
+
case "show": {
|
|
143
|
+
const name = positional(rest)[0];
|
|
144
|
+
const raw = name ? rawWorkflow(root, name) : null;
|
|
145
|
+
if (!raw) {
|
|
146
|
+
console.error(`Workflow not found: ${name}`);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
if (json) console.log(JSON.stringify(getWorkflow(root, name)));
|
|
150
|
+
else process.stdout.write(raw);
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case "remove":
|
|
154
|
+
case "rm": {
|
|
155
|
+
const name = positional(rest)[0];
|
|
156
|
+
if (!name) {
|
|
157
|
+
console.error("usage: svamp workflow remove <name>");
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
console.log(removeWorkflow(root, name) ? `Removed workflow "${name}".` : `Workflow not found: ${name}`);
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
case "run": {
|
|
164
|
+
const name = positional(rest)[0];
|
|
165
|
+
const wf = name ? getWorkflow(root, name) : null;
|
|
166
|
+
if (!wf) {
|
|
167
|
+
console.error(`Workflow not found: ${name}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
console.log(`\u25B6 running workflow "${wf.name}" (${wf.jobs.length} step${wf.jobs.length === 1 ? "" : "s"})`);
|
|
171
|
+
for (let i = 0; i < wf.jobs.length; i++) {
|
|
172
|
+
const step = wf.jobs[i];
|
|
173
|
+
console.log(` [${i + 1}/${wf.jobs.length}] $ ${step.run}`);
|
|
174
|
+
const r = spawnSync("sh", ["-c", step.run], { cwd: root, stdio: "inherit" });
|
|
175
|
+
if (r.status !== 0) {
|
|
176
|
+
console.error(` \u2717 step ${i + 1} failed (exit ${r.status ?? "signal"}). Stopping.`);
|
|
177
|
+
process.exit(r.status ?? 1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
console.log(`\u2713 workflow "${wf.name}" completed.`);
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
case "help":
|
|
184
|
+
case void 0:
|
|
185
|
+
case "--help":
|
|
186
|
+
case "-h":
|
|
187
|
+
console.log([
|
|
188
|
+
"svamp workflow \u2014 GitHub-Actions-style automation as .svamp/workflows/<name>.yaml",
|
|
189
|
+
"",
|
|
190
|
+
' add <name> --run "<cmd>" [--run "<cmd2>"] [--on schedule|dispatch|channel|issue]',
|
|
191
|
+
' [--cron "0 9 * * 1-5"] [--channel <name>] [--issue ready,closed,labeled]',
|
|
192
|
+
" list | show <name> | remove <name>",
|
|
193
|
+
" run <name> # execute the workflow's run-only steps now",
|
|
194
|
+
"",
|
|
195
|
+
"Each step is a shell command \u2014 every verb is a `svamp` CLI call, e.g.",
|
|
196
|
+
' svamp workflow add nightly --on schedule --cron "0 2 * * *" \\',
|
|
197
|
+
' --run "svamp session send . \\"Run the nightly checks and fix failures\\""'
|
|
198
|
+
].join("\n"));
|
|
199
|
+
break;
|
|
200
|
+
default:
|
|
201
|
+
console.error(`Unknown: svamp workflow ${sub}. Try: svamp workflow help`);
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export { workflowCommand };
|
|
@@ -1,183 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "branch", "session", "created", "closed"];
|
|
6
|
-
function resolveProjectRoot(start = process.cwd()) {
|
|
7
|
-
let dir = start;
|
|
8
|
-
for (let i = 0; i < 40; i++) {
|
|
9
|
-
if (existsSync(join(dir, ".git")) || existsSync(join(dir, ".svamp"))) return dir;
|
|
10
|
-
const parent = dirname(dir);
|
|
11
|
-
if (parent === dir) break;
|
|
12
|
-
dir = parent;
|
|
13
|
-
}
|
|
14
|
-
return start;
|
|
15
|
-
}
|
|
16
|
-
function issuesDir(projectRoot) {
|
|
17
|
-
return join(projectRoot, ".svamp", "issues");
|
|
18
|
-
}
|
|
19
|
-
function archiveDir(projectRoot) {
|
|
20
|
-
return join(issuesDir(projectRoot), "archive");
|
|
21
|
-
}
|
|
22
|
-
function issuePath(projectRoot, id, archived = false) {
|
|
23
|
-
return join(archived ? archiveDir(projectRoot) : issuesDir(projectRoot), `${id}.md`);
|
|
24
|
-
}
|
|
25
|
-
function serializeIssue(issue) {
|
|
26
|
-
const lines = ["---"];
|
|
27
|
-
for (const k of FIELD_ORDER) {
|
|
28
|
-
const v = issue[k];
|
|
29
|
-
if (v === void 0) continue;
|
|
30
|
-
lines.push(`${k}: ${JSON.stringify(v)}`);
|
|
31
|
-
}
|
|
32
|
-
lines.push("---", "");
|
|
33
|
-
return lines.join("\n") + (issue.body ? issue.body.replace(/\s+$/, "") + "\n" : "");
|
|
34
|
-
}
|
|
35
|
-
function parseIssue(content) {
|
|
36
|
-
const m = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
37
|
-
if (!m) return null;
|
|
38
|
-
const fm = {};
|
|
39
|
-
for (const line of m[1].split("\n")) {
|
|
40
|
-
const idx = line.indexOf(": ");
|
|
41
|
-
if (idx === -1) continue;
|
|
42
|
-
const key = line.slice(0, idx).trim();
|
|
43
|
-
const raw = line.slice(idx + 2).trim();
|
|
44
|
-
try {
|
|
45
|
-
fm[key] = JSON.parse(raw);
|
|
46
|
-
} catch {
|
|
47
|
-
fm[key] = raw;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (!fm.id) return null;
|
|
51
|
-
return {
|
|
52
|
-
id: String(fm.id),
|
|
53
|
-
title: String(fm.title ?? ""),
|
|
54
|
-
status: fm.status ?? "backlog",
|
|
55
|
-
scope: fm.scope ?? "session",
|
|
56
|
-
labels: Array.isArray(fm.labels) ? fm.labels.map(String) : [],
|
|
57
|
-
verify: fm.verify ?? null,
|
|
58
|
-
branch: fm.branch ?? null,
|
|
59
|
-
session: fm.session ?? null,
|
|
60
|
-
created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
61
|
-
closed: fm.closed ?? null,
|
|
62
|
-
body: (m[2] || "").trim() || void 0
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
function readDir(dir) {
|
|
66
|
-
if (!existsSync(dir)) return [];
|
|
67
|
-
const out = [];
|
|
68
|
-
for (const name of readdirSync(dir)) {
|
|
69
|
-
if (!name.endsWith(".md")) continue;
|
|
70
|
-
try {
|
|
71
|
-
const issue = parseIssue(readFileSync(join(dir, name), "utf-8"));
|
|
72
|
-
if (issue) out.push(issue);
|
|
73
|
-
} catch {
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return out;
|
|
77
|
-
}
|
|
78
|
-
function listIssues(projectRoot, opts = {}) {
|
|
79
|
-
let items = readDir(issuesDir(projectRoot));
|
|
80
|
-
if (opts.includeArchived) items = items.concat(readDir(archiveDir(projectRoot)));
|
|
81
|
-
if (opts.status) items = items.filter((i) => i.status === opts.status);
|
|
82
|
-
if (opts.label) items = items.filter((i) => i.labels.includes(opts.label));
|
|
83
|
-
if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
|
|
84
|
-
return items.sort((a, b) => Number(a.id) - Number(b.id));
|
|
85
|
-
}
|
|
86
|
-
function getIssue(projectRoot, id) {
|
|
87
|
-
const padded = /^\d+$/.test(id) ? id.padStart(4, "0") : id;
|
|
88
|
-
for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
|
|
89
|
-
if (existsSync(p)) {
|
|
90
|
-
try {
|
|
91
|
-
return parseIssue(readFileSync(p, "utf-8"));
|
|
92
|
-
} catch {
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
function atomicWrite(path, content) {
|
|
100
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
-
const tmp = `${path}.tmp-${process.pid}`;
|
|
102
|
-
writeFileSync(tmp, content);
|
|
103
|
-
renameSync(tmp, path);
|
|
104
|
-
}
|
|
105
|
-
function nextId(projectRoot) {
|
|
106
|
-
const all = listIssues(projectRoot, { includeArchived: true });
|
|
107
|
-
const max = all.reduce((acc, i) => Math.max(acc, Number(i.id) || 0), 0);
|
|
108
|
-
return String(max + 1).padStart(4, "0");
|
|
109
|
-
}
|
|
110
|
-
function addIssue(projectRoot, fields) {
|
|
111
|
-
const issue = {
|
|
112
|
-
id: nextId(projectRoot),
|
|
113
|
-
title: fields.title.trim(),
|
|
114
|
-
status: fields.status ?? "backlog",
|
|
115
|
-
scope: fields.scope ?? "session",
|
|
116
|
-
labels: fields.labels ?? [],
|
|
117
|
-
verify: fields.verify ?? null,
|
|
118
|
-
branch: null,
|
|
119
|
-
session: fields.session ?? null,
|
|
120
|
-
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121
|
-
closed: null,
|
|
122
|
-
body: fields.body
|
|
123
|
-
};
|
|
124
|
-
atomicWrite(issuePath(projectRoot, issue.id), serializeIssue(issue));
|
|
125
|
-
return issue;
|
|
126
|
-
}
|
|
127
|
-
function updateIssue(projectRoot, id, patch) {
|
|
128
|
-
const cur = getIssue(projectRoot, id);
|
|
129
|
-
if (!cur) return null;
|
|
130
|
-
const wasArchived = cur.status === "archived";
|
|
131
|
-
const next = { ...cur, ...patch, id: cur.id };
|
|
132
|
-
const nowArchived = next.status === "archived";
|
|
133
|
-
if ((next.status === "done" || nowArchived) && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
|
|
134
|
-
if (next.status !== "done" && !nowArchived) next.closed = null;
|
|
135
|
-
atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
|
|
136
|
-
if (wasArchived !== nowArchived) {
|
|
137
|
-
const oldPath = issuePath(projectRoot, cur.id, wasArchived);
|
|
138
|
-
try {
|
|
139
|
-
if (existsSync(oldPath)) unlinkSync(oldPath);
|
|
140
|
-
} catch {
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return next;
|
|
144
|
-
}
|
|
145
|
-
function addComment(projectRoot, id, text) {
|
|
146
|
-
const cur = getIssue(projectRoot, id);
|
|
147
|
-
if (!cur || !text.trim()) return cur;
|
|
148
|
-
const entry = `
|
|
149
|
-
|
|
150
|
-
---
|
|
151
|
-
**Follow-up \xB7 ${(/* @__PURE__ */ new Date()).toISOString()}**
|
|
152
|
-
|
|
153
|
-
${text.trim()}`;
|
|
154
|
-
const body = (cur.body ? cur.body.replace(/\s+$/, "") : "") + entry;
|
|
155
|
-
return updateIssue(projectRoot, id, { body });
|
|
156
|
-
}
|
|
157
|
-
function searchIssues(projectRoot, query) {
|
|
158
|
-
const q = query.trim();
|
|
159
|
-
if (!q) return [];
|
|
160
|
-
try {
|
|
161
|
-
const out = execFileSync("rg", ["-l", "-i", "--no-messages", q, issuesDir(projectRoot)], { encoding: "utf-8" });
|
|
162
|
-
const files = out.split("\n").filter(Boolean);
|
|
163
|
-
const issues = [];
|
|
164
|
-
for (const f of files) {
|
|
165
|
-
try {
|
|
166
|
-
const i = parseIssue(readFileSync(f, "utf-8"));
|
|
167
|
-
if (i) issues.push(i);
|
|
168
|
-
} catch {
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return issues.sort((a, b) => Number(a.id) - Number(b.id));
|
|
172
|
-
} catch {
|
|
173
|
-
const ql = q.toLowerCase();
|
|
174
|
-
return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
function summarize(issues) {
|
|
178
|
-
const by = (s) => issues.filter((i) => i.status === s).length;
|
|
179
|
-
return { total: issues.length, backlog: by("backlog"), ready: by("ready"), in_progress: by("in_progress"), done: by("done"), archived: by("archived") };
|
|
180
|
-
}
|
|
1
|
+
import { r as resolveProjectRoot, s as searchIssues, l as listIssues, a as addComment, u as updateIssue, b as summarize, c as addIssue, g as getIssue } from './store-ChN9bgel.mjs';
|
|
2
|
+
import 'node:fs';
|
|
3
|
+
import 'node:path';
|
|
4
|
+
import 'node:child_process';
|
|
181
5
|
|
|
182
6
|
const STATUS_GLYPH = {
|
|
183
7
|
backlog: "\u25CB",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "svamp-cli";
|
|
2
|
-
var version = "0.2.
|
|
2
|
+
var version = "0.2.152";
|
|
3
3
|
var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
|
|
4
4
|
var author = "Amun AI AB";
|
|
5
5
|
var license = "SEE LICENSE IN LICENSE";
|
|
@@ -19,7 +19,7 @@ var exports$1 = {
|
|
|
19
19
|
var scripts = {
|
|
20
20
|
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",
|
|
21
21
|
typecheck: "tsc --noEmit",
|
|
22
|
-
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-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-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-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-checklist-watchdog.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",
|
|
22
|
+
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-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-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.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-checklist-watchdog.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",
|
|
23
23
|
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
24
24
|
dev: "tsx src/cli.ts",
|
|
25
25
|
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync, readFileSync, unlinkSync, readdirSync, mkdirSync, writeFileSync, renameSync } from 'node:fs';
|
|
2
|
+
import { join, dirname } from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
|
|
5
|
+
const FIELD_ORDER = ["id", "title", "status", "scope", "labels", "verify", "branch", "session", "created", "closed"];
|
|
6
|
+
function resolveProjectRoot(start = process.cwd()) {
|
|
7
|
+
let dir = start;
|
|
8
|
+
for (let i = 0; i < 40; i++) {
|
|
9
|
+
if (existsSync(join(dir, ".git")) || existsSync(join(dir, ".svamp"))) return dir;
|
|
10
|
+
const parent = dirname(dir);
|
|
11
|
+
if (parent === dir) break;
|
|
12
|
+
dir = parent;
|
|
13
|
+
}
|
|
14
|
+
return start;
|
|
15
|
+
}
|
|
16
|
+
function issuesDir(projectRoot) {
|
|
17
|
+
return join(projectRoot, ".svamp", "issues");
|
|
18
|
+
}
|
|
19
|
+
function archiveDir(projectRoot) {
|
|
20
|
+
return join(issuesDir(projectRoot), "archive");
|
|
21
|
+
}
|
|
22
|
+
function issuePath(projectRoot, id, archived = false) {
|
|
23
|
+
return join(archived ? archiveDir(projectRoot) : issuesDir(projectRoot), `${id}.md`);
|
|
24
|
+
}
|
|
25
|
+
function serializeIssue(issue) {
|
|
26
|
+
const lines = ["---"];
|
|
27
|
+
for (const k of FIELD_ORDER) {
|
|
28
|
+
const v = issue[k];
|
|
29
|
+
if (v === void 0) continue;
|
|
30
|
+
lines.push(`${k}: ${JSON.stringify(v)}`);
|
|
31
|
+
}
|
|
32
|
+
lines.push("---", "");
|
|
33
|
+
return lines.join("\n") + (issue.body ? issue.body.replace(/\s+$/, "") + "\n" : "");
|
|
34
|
+
}
|
|
35
|
+
function parseIssue(content) {
|
|
36
|
+
const m = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
37
|
+
if (!m) return null;
|
|
38
|
+
const fm = {};
|
|
39
|
+
for (const line of m[1].split("\n")) {
|
|
40
|
+
const idx = line.indexOf(": ");
|
|
41
|
+
if (idx === -1) continue;
|
|
42
|
+
const key = line.slice(0, idx).trim();
|
|
43
|
+
const raw = line.slice(idx + 2).trim();
|
|
44
|
+
try {
|
|
45
|
+
fm[key] = JSON.parse(raw);
|
|
46
|
+
} catch {
|
|
47
|
+
fm[key] = raw;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (!fm.id) return null;
|
|
51
|
+
return {
|
|
52
|
+
id: String(fm.id),
|
|
53
|
+
title: String(fm.title ?? ""),
|
|
54
|
+
status: fm.status ?? "backlog",
|
|
55
|
+
scope: fm.scope ?? "session",
|
|
56
|
+
labels: Array.isArray(fm.labels) ? fm.labels.map(String) : [],
|
|
57
|
+
verify: fm.verify ?? null,
|
|
58
|
+
branch: fm.branch ?? null,
|
|
59
|
+
session: fm.session ?? null,
|
|
60
|
+
created: String(fm.created ?? (/* @__PURE__ */ new Date()).toISOString()),
|
|
61
|
+
closed: fm.closed ?? null,
|
|
62
|
+
body: (m[2] || "").trim() || void 0
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function readDir(dir) {
|
|
66
|
+
if (!existsSync(dir)) return [];
|
|
67
|
+
const out = [];
|
|
68
|
+
for (const name of readdirSync(dir)) {
|
|
69
|
+
if (!name.endsWith(".md")) continue;
|
|
70
|
+
try {
|
|
71
|
+
const issue = parseIssue(readFileSync(join(dir, name), "utf-8"));
|
|
72
|
+
if (issue) out.push(issue);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function listIssues(projectRoot, opts = {}) {
|
|
79
|
+
let items = readDir(issuesDir(projectRoot));
|
|
80
|
+
if (opts.includeArchived) items = items.concat(readDir(archiveDir(projectRoot)));
|
|
81
|
+
if (opts.status) items = items.filter((i) => i.status === opts.status);
|
|
82
|
+
if (opts.label) items = items.filter((i) => i.labels.includes(opts.label));
|
|
83
|
+
if (opts.scope) items = items.filter((i) => i.scope === opts.scope);
|
|
84
|
+
return items.sort((a, b) => Number(a.id) - Number(b.id));
|
|
85
|
+
}
|
|
86
|
+
function getIssue(projectRoot, id) {
|
|
87
|
+
const padded = /^\d+$/.test(id) ? id.padStart(4, "0") : id;
|
|
88
|
+
for (const p of [issuePath(projectRoot, padded), issuePath(projectRoot, padded, true)]) {
|
|
89
|
+
if (existsSync(p)) {
|
|
90
|
+
try {
|
|
91
|
+
return parseIssue(readFileSync(p, "utf-8"));
|
|
92
|
+
} catch {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
function atomicWrite(path, content) {
|
|
100
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
101
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
102
|
+
writeFileSync(tmp, content);
|
|
103
|
+
renameSync(tmp, path);
|
|
104
|
+
}
|
|
105
|
+
function nextId(projectRoot) {
|
|
106
|
+
const all = listIssues(projectRoot, { includeArchived: true });
|
|
107
|
+
const max = all.reduce((acc, i) => Math.max(acc, Number(i.id) || 0), 0);
|
|
108
|
+
return String(max + 1).padStart(4, "0");
|
|
109
|
+
}
|
|
110
|
+
function addIssue(projectRoot, fields) {
|
|
111
|
+
const issue = {
|
|
112
|
+
id: nextId(projectRoot),
|
|
113
|
+
title: fields.title.trim(),
|
|
114
|
+
status: fields.status ?? "backlog",
|
|
115
|
+
scope: fields.scope ?? "session",
|
|
116
|
+
labels: fields.labels ?? [],
|
|
117
|
+
verify: fields.verify ?? null,
|
|
118
|
+
branch: null,
|
|
119
|
+
session: fields.session ?? null,
|
|
120
|
+
created: (/* @__PURE__ */ new Date()).toISOString(),
|
|
121
|
+
closed: null,
|
|
122
|
+
body: fields.body
|
|
123
|
+
};
|
|
124
|
+
atomicWrite(issuePath(projectRoot, issue.id), serializeIssue(issue));
|
|
125
|
+
return issue;
|
|
126
|
+
}
|
|
127
|
+
function updateIssue(projectRoot, id, patch) {
|
|
128
|
+
const cur = getIssue(projectRoot, id);
|
|
129
|
+
if (!cur) return null;
|
|
130
|
+
const wasArchived = cur.status === "archived";
|
|
131
|
+
const next = { ...cur, ...patch, id: cur.id };
|
|
132
|
+
const nowArchived = next.status === "archived";
|
|
133
|
+
if ((next.status === "done" || nowArchived) && !next.closed) next.closed = (/* @__PURE__ */ new Date()).toISOString();
|
|
134
|
+
if (next.status !== "done" && !nowArchived) next.closed = null;
|
|
135
|
+
atomicWrite(issuePath(projectRoot, next.id, nowArchived), serializeIssue(next));
|
|
136
|
+
if (wasArchived !== nowArchived) {
|
|
137
|
+
const oldPath = issuePath(projectRoot, cur.id, wasArchived);
|
|
138
|
+
try {
|
|
139
|
+
if (existsSync(oldPath)) unlinkSync(oldPath);
|
|
140
|
+
} catch {
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return next;
|
|
144
|
+
}
|
|
145
|
+
function addComment(projectRoot, id, text) {
|
|
146
|
+
const cur = getIssue(projectRoot, id);
|
|
147
|
+
if (!cur || !text.trim()) return cur;
|
|
148
|
+
const entry = `
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
**Follow-up \xB7 ${(/* @__PURE__ */ new Date()).toISOString()}**
|
|
152
|
+
|
|
153
|
+
${text.trim()}`;
|
|
154
|
+
const body = (cur.body ? cur.body.replace(/\s+$/, "") : "") + entry;
|
|
155
|
+
return updateIssue(projectRoot, id, { body });
|
|
156
|
+
}
|
|
157
|
+
function searchIssues(projectRoot, query) {
|
|
158
|
+
const q = query.trim();
|
|
159
|
+
if (!q) return [];
|
|
160
|
+
try {
|
|
161
|
+
const out = execFileSync("rg", ["-l", "-i", "--no-messages", q, issuesDir(projectRoot)], { encoding: "utf-8" });
|
|
162
|
+
const files = out.split("\n").filter(Boolean);
|
|
163
|
+
const issues = [];
|
|
164
|
+
for (const f of files) {
|
|
165
|
+
try {
|
|
166
|
+
const i = parseIssue(readFileSync(f, "utf-8"));
|
|
167
|
+
if (i) issues.push(i);
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return issues.sort((a, b) => Number(a.id) - Number(b.id));
|
|
172
|
+
} catch {
|
|
173
|
+
const ql = q.toLowerCase();
|
|
174
|
+
return listIssues(projectRoot, { includeArchived: true }).filter((i) => i.title.toLowerCase().includes(ql) || (i.body || "").toLowerCase().includes(ql) || i.labels.some((l) => l.toLowerCase().includes(ql)));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function summarize(issues) {
|
|
178
|
+
const by = (s) => issues.filter((i) => i.status === s).length;
|
|
179
|
+
return { total: issues.length, backlog: by("backlog"), ready: by("ready"), in_progress: by("in_progress"), done: by("done"), archived: by("archived") };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export { addComment as a, summarize as b, addIssue as c, getIssue as g, listIssues as l, resolveProjectRoot as r, searchIssues as s, updateIssue as u };
|
package/package.json
CHANGED
|
@@ -1,47 +1,47 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
2
|
+
"name": "svamp-cli",
|
|
3
|
+
"version": "0.2.152",
|
|
4
|
+
"description": "Svamp CLI — AI workspace daemon on Hypha Cloud",
|
|
5
|
+
"author": "Amun AI AB",
|
|
6
|
+
"license": "SEE LICENSE IN LICENSE",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"svamp": "./bin/svamp.mjs"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"bin"
|
|
14
|
+
],
|
|
15
|
+
"main": "./dist/index.mjs",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": "./dist/index.mjs",
|
|
18
|
+
"./cli": "./dist/cli.mjs"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
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
|
+
"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-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-checklist.mjs && npx tsx test/test-checklist-cli.mjs && npx tsx test/test-issue-store.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-checklist-watchdog.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",
|
|
24
|
+
"test:hypha": "node --no-warnings test/test-hypha-service.mjs",
|
|
25
|
+
"dev": "tsx src/cli.ts",
|
|
26
|
+
"dev:daemon": "tsx src/cli.ts daemon start-sync",
|
|
27
|
+
"test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
|
|
28
|
+
"test:frpc": "npx tsx test/test-frpc-e2e.mjs"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@agentclientprotocol/sdk": "^0.14.1",
|
|
32
|
+
"@modelcontextprotocol/sdk": "^1.25.3",
|
|
33
|
+
"hypha-rpc": "0.21.42",
|
|
34
|
+
"node-pty": "1.2.0-beta.11",
|
|
35
|
+
"ws": "^8.18.0",
|
|
36
|
+
"yaml": "^2.8.2",
|
|
37
|
+
"zod": "^3.24.4"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": ">=20",
|
|
41
|
+
"@types/ws": "^8.5.14",
|
|
42
|
+
"pkgroll": "^2.14.2",
|
|
43
|
+
"tsx": "^4.20.6",
|
|
44
|
+
"typescript": "5.9.3"
|
|
45
|
+
},
|
|
46
|
+
"packageManager": "yarn@1.22.22"
|
|
47
47
|
}
|