svamp-cli 0.2.188 → 0.2.190
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/package.json +1 -1
- package/dist/agentCommands-D82YAIjn.mjs +0 -379
- package/dist/auth-CRgqElsS.mjs +0 -83
- package/dist/caddy-CuTbE3NY.mjs +0 -322
- package/dist/cli.mjs +0 -2303
- package/dist/commands-2vgPBzxG.mjs +0 -255
- package/dist/commands-B7oQTg-f.mjs +0 -375
- package/dist/commands-BpIsPzLL.mjs +0 -196
- package/dist/commands-CkxVt5V0.mjs +0 -2721
- package/dist/commands-DB3LqPei.mjs +0 -428
- package/dist/commands-DIPPrpHk.mjs +0 -544
- package/dist/commands-DubAkp0i.mjs +0 -610
- package/dist/fleet-PKdEHCXZ.mjs +0 -356
- package/dist/frpc-CDj2C0fs.mjs +0 -681
- package/dist/headlessCli-BGGF_UPQ.mjs +0 -333
- package/dist/httpServer-Cf54pQ77.mjs +0 -238
- package/dist/index.mjs +0 -22
- package/dist/package-B6C96K9S.mjs +0 -63
- package/dist/pinnedClaudeCode-HydRNEt7.mjs +0 -58
- package/dist/rpc-B0_q0Ore.mjs +0 -87
- package/dist/rpc-DuAe8TKC.mjs +0 -151
- package/dist/run-DZLwKdGH.mjs +0 -15324
- package/dist/run-FgS-4E-C.mjs +0 -1086
- package/dist/sandboxDetect-DNTcbgWD.mjs +0 -12
- package/dist/scheduler-CVODqtZb.mjs +0 -96
- package/dist/serveCommands-C8QvTpse.mjs +0 -310
- package/dist/serveManager-CU5NrVV-.mjs +0 -781
- package/dist/serviceManager-hlOVxkhW.mjs +0 -78
- package/dist/sideband-B7uDd_pR.mjs +0 -80
- package/dist/store-DEZ8e-uE.mjs +0 -148
- package/dist/supervisorLock-DmfzJx7B.mjs +0 -159
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import * as path from 'path';
|
|
2
|
-
|
|
3
|
-
function getFlag(args, flag) {
|
|
4
|
-
const idx = args.indexOf(flag);
|
|
5
|
-
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
|
|
6
|
-
}
|
|
7
|
-
function getAllFlags(args, flag) {
|
|
8
|
-
const values = [];
|
|
9
|
-
for (let i = 0; i < args.length; i++) {
|
|
10
|
-
if (args[i] === flag && i + 1 < args.length) {
|
|
11
|
-
values.push(args[i + 1]);
|
|
12
|
-
i++;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
15
|
-
return values;
|
|
16
|
-
}
|
|
17
|
-
function hasFlag(args, ...flags) {
|
|
18
|
-
return flags.some((f) => args.includes(f));
|
|
19
|
-
}
|
|
20
|
-
function positionalArgs(args) {
|
|
21
|
-
const result = [];
|
|
22
|
-
for (let i = 0; i < args.length; i++) {
|
|
23
|
-
if (args[i].startsWith("--")) {
|
|
24
|
-
if (i + 1 < args.length && !args[i + 1].startsWith("--")) i++;
|
|
25
|
-
continue;
|
|
26
|
-
}
|
|
27
|
-
result.push(args[i]);
|
|
28
|
-
}
|
|
29
|
-
return result;
|
|
30
|
-
}
|
|
31
|
-
function parsePorts(args) {
|
|
32
|
-
const portStrs = getAllFlags(args, "--port");
|
|
33
|
-
if (portStrs.length === 0) return [];
|
|
34
|
-
const ports = [];
|
|
35
|
-
for (const s of portStrs) {
|
|
36
|
-
const p = parseInt(s, 10);
|
|
37
|
-
if (isNaN(p) || p < 1 || p > 65535) {
|
|
38
|
-
console.error(`Error: invalid port '${s}' \u2014 must be 1-65535`);
|
|
39
|
-
process.exit(1);
|
|
40
|
-
}
|
|
41
|
-
ports.push(p);
|
|
42
|
-
}
|
|
43
|
-
return ports;
|
|
44
|
-
}
|
|
45
|
-
async function serviceExpose(args) {
|
|
46
|
-
const positional = positionalArgs(args);
|
|
47
|
-
const name = positional[0];
|
|
48
|
-
const ports = parsePorts(args);
|
|
49
|
-
const group = getFlag(args, "--group");
|
|
50
|
-
const groupKey = getFlag(args, "--group-key");
|
|
51
|
-
const healthCheck = getFlag(args, "--health-check");
|
|
52
|
-
const healthPath = getFlag(args, "--health-path");
|
|
53
|
-
const healthInterval = getFlag(args, "--health-interval");
|
|
54
|
-
const foreground = hasFlag(args, "--foreground");
|
|
55
|
-
if (!name || ports.length === 0) {
|
|
56
|
-
console.error("Usage: svamp service expose <name> --port <port> [--port <port2>] [options]");
|
|
57
|
-
console.error(" --foreground run frpc in this shell (legacy, dies with shell)");
|
|
58
|
-
process.exit(1);
|
|
59
|
-
}
|
|
60
|
-
if (foreground) {
|
|
61
|
-
const { runFrpcTunnel } = await import('./frpc-CDj2C0fs.mjs');
|
|
62
|
-
await runFrpcTunnel(name, ports, void 0, {
|
|
63
|
-
group,
|
|
64
|
-
groupKey,
|
|
65
|
-
healthCheckType: healthCheck,
|
|
66
|
-
healthCheckPath: healthPath,
|
|
67
|
-
healthCheckInterval: healthInterval ? parseInt(healthInterval, 10) : void 0
|
|
68
|
-
});
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
const { connectAndGetMachine } = await import('./commands-CkxVt5V0.mjs');
|
|
72
|
-
const { server, machine } = await connectAndGetMachine();
|
|
73
|
-
try {
|
|
74
|
-
const status = await machine.tunnelStart({
|
|
75
|
-
name,
|
|
76
|
-
ports,
|
|
77
|
-
group: group || void 0,
|
|
78
|
-
groupKey: groupKey || void 0,
|
|
79
|
-
healthCheckType: healthCheck,
|
|
80
|
-
healthCheckPath: healthPath || void 0,
|
|
81
|
-
healthCheckInterval: healthInterval ? parseInt(healthInterval, 10) : void 0
|
|
82
|
-
});
|
|
83
|
-
console.log(`Tunnel '${name}' registered with daemon \u2014 supervised + restart-persistent.`);
|
|
84
|
-
if (status && status.urls) {
|
|
85
|
-
for (const [port, url] of Object.entries(status.urls)) {
|
|
86
|
-
console.log(` port ${port}: ${url}`);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
console.log(`To stop: svamp service delete ${name} (or: svamp tunnel stop ${name})`);
|
|
90
|
-
} catch (err) {
|
|
91
|
-
if (/already running/i.test(err?.message || "")) {
|
|
92
|
-
console.error(`Tunnel '${name}' is already running. Use \`svamp service delete ${name}\` first.`);
|
|
93
|
-
} else {
|
|
94
|
-
console.error(`Failed to register tunnel: ${err?.message || err}`);
|
|
95
|
-
console.error(`Falling back to foreground mode? Add --foreground to run in this shell.`);
|
|
96
|
-
}
|
|
97
|
-
process.exit(1);
|
|
98
|
-
} finally {
|
|
99
|
-
await server.disconnect();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
async function serviceServe(args) {
|
|
103
|
-
const positional = positionalArgs(args);
|
|
104
|
-
const name = positional[0];
|
|
105
|
-
const directory = positional[1] || ".";
|
|
106
|
-
const noListing = hasFlag(args, "--no-listing");
|
|
107
|
-
if (!name) {
|
|
108
|
-
console.error("Usage: svamp service serve <name> [directory] [--no-listing]");
|
|
109
|
-
console.error(" directory defaults to current directory");
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
try {
|
|
113
|
-
const resolvedDir = path.resolve(directory);
|
|
114
|
-
console.log(`Serving ${resolvedDir}`);
|
|
115
|
-
const caddyPort = 18080;
|
|
116
|
-
const { CaddyManager } = await import('./caddy-CuTbE3NY.mjs');
|
|
117
|
-
const caddy = new CaddyManager({ listenPort: caddyPort });
|
|
118
|
-
await caddy.addMount(name, resolvedDir, !noListing);
|
|
119
|
-
await caddy.start();
|
|
120
|
-
const cleanup = () => {
|
|
121
|
-
caddy.stop();
|
|
122
|
-
process.exit(0);
|
|
123
|
-
};
|
|
124
|
-
process.on("SIGINT", cleanup);
|
|
125
|
-
process.on("SIGTERM", cleanup);
|
|
126
|
-
const { runFrpcTunnel } = await import('./frpc-CDj2C0fs.mjs');
|
|
127
|
-
await runFrpcTunnel(name, [caddyPort]);
|
|
128
|
-
} catch (err) {
|
|
129
|
-
console.error(`Error serving directory: ${err.message}`);
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
async function serviceList(_args) {
|
|
134
|
-
try {
|
|
135
|
-
const { connectAndGetMachine } = await import('./commands-CkxVt5V0.mjs');
|
|
136
|
-
const { server, machine } = await connectAndGetMachine();
|
|
137
|
-
try {
|
|
138
|
-
const tunnels = await machine.tunnelList({});
|
|
139
|
-
if (!tunnels || tunnels.length === 0) {
|
|
140
|
-
console.log("No daemon-managed tunnels configured.");
|
|
141
|
-
console.log("Standalone foreground tunnels (from `svamp service expose`) are not listed here \u2014 check `pgrep -af frpc`.");
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const label = (t) => typeof t.state === "string" ? t.state : t.connected ? "connected" : "disconnected";
|
|
145
|
-
const icon = { connected: "\u2713", reconnecting: "\u21BB", failed: "\u2717", disconnected: "\u2717" };
|
|
146
|
-
console.log("Daemon-managed tunnels:");
|
|
147
|
-
for (const t of tunnels) {
|
|
148
|
-
const st = label(t);
|
|
149
|
-
const ports = Array.isArray(t.ports) && t.ports.length ? ` :${t.ports.join(",")}` : "";
|
|
150
|
-
let extra = "";
|
|
151
|
-
if (st !== "connected") {
|
|
152
|
-
const bits = [];
|
|
153
|
-
if (t.restartAttempts) bits.push(`${t.restartAttempts} restarts`);
|
|
154
|
-
if (t.probe && !t.probe.ok && t.probe.stalenessMs) bits.push(`probe stale ${Math.round(t.probe.stalenessMs / 1e3)}s`);
|
|
155
|
-
if (bits.length) extra = ` (${bits.join(", ")})`;
|
|
156
|
-
}
|
|
157
|
-
console.log(` ${icon[st] ?? "\u2022"} ${t.name}${ports} \u2014 ${st}${extra}`);
|
|
158
|
-
}
|
|
159
|
-
} finally {
|
|
160
|
-
await server.disconnect();
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
console.error(`Error listing tunnels: ${err.message}`);
|
|
164
|
-
process.exit(1);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function serviceDelete(args) {
|
|
168
|
-
const positional = positionalArgs(args);
|
|
169
|
-
const name = positional[0];
|
|
170
|
-
if (!name) {
|
|
171
|
-
console.error("Usage: svamp service delete <name>");
|
|
172
|
-
process.exit(1);
|
|
173
|
-
}
|
|
174
|
-
try {
|
|
175
|
-
const { connectAndGetMachine } = await import('./commands-CkxVt5V0.mjs');
|
|
176
|
-
const { server, machine } = await connectAndGetMachine();
|
|
177
|
-
try {
|
|
178
|
-
await machine.tunnelStop({ name });
|
|
179
|
-
console.log(`Stopped tunnel '${name}'.`);
|
|
180
|
-
} catch (err) {
|
|
181
|
-
if (/not found/i.test(err.message)) {
|
|
182
|
-
console.error(`No daemon-managed tunnel named '${name}'.`);
|
|
183
|
-
console.error("If this was a standalone foreground tunnel (from `svamp service expose`), the daemon can't stop it.");
|
|
184
|
-
console.error(`Kill the frpc process directly: pkill -f 'frpc.*${name}'`);
|
|
185
|
-
} else {
|
|
186
|
-
console.error(`Error stopping tunnel: ${err.message}`);
|
|
187
|
-
}
|
|
188
|
-
process.exit(1);
|
|
189
|
-
} finally {
|
|
190
|
-
await server.disconnect();
|
|
191
|
-
}
|
|
192
|
-
} catch (err) {
|
|
193
|
-
console.error(`Error: ${err.message}`);
|
|
194
|
-
process.exit(1);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async function handleServiceCommand() {
|
|
198
|
-
const args = process.argv.slice(2);
|
|
199
|
-
const serviceArgs = args.slice(1);
|
|
200
|
-
const sub = serviceArgs[0];
|
|
201
|
-
if (!sub || sub === "--help" || sub === "-h") {
|
|
202
|
-
printServiceHelp();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const commandArgs = serviceArgs.slice(1);
|
|
206
|
-
if (sub === "expose" || sub === "tunnel") {
|
|
207
|
-
await serviceExpose(commandArgs);
|
|
208
|
-
} else if (sub === "serve") {
|
|
209
|
-
await serviceServe(commandArgs);
|
|
210
|
-
} else if (sub === "list" || sub === "ls") {
|
|
211
|
-
await serviceList();
|
|
212
|
-
} else if (sub === "delete" || sub === "rm") {
|
|
213
|
-
await serviceDelete(commandArgs);
|
|
214
|
-
} else {
|
|
215
|
-
console.error(`Unknown service command: ${sub}`);
|
|
216
|
-
printServiceHelp();
|
|
217
|
-
process.exit(1);
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
function printServiceHelp() {
|
|
221
|
-
console.log(`
|
|
222
|
-
svamp service \u2014 Expose local services via frpc tunnels
|
|
223
|
-
|
|
224
|
-
Usage:
|
|
225
|
-
svamp service expose <name> --port <port> [options] Expose local ports via tunnel
|
|
226
|
-
svamp service serve <name> [directory] [--no-listing] Serve static files via tunnel
|
|
227
|
-
svamp service list List active tunnels (daemon)
|
|
228
|
-
svamp service delete <name> Stop a tunnel (daemon)
|
|
229
|
-
|
|
230
|
-
Options:
|
|
231
|
-
--port <port> Port to expose (repeatable for multi-port)
|
|
232
|
-
--group <name> Service group \u2014 multiple machines share the same URL
|
|
233
|
-
--group-key <secret> Shared key for the service group
|
|
234
|
-
--health-check <tcp|http> Enable health checks on local backend
|
|
235
|
-
--health-path <path> HTTP health check path (e.g., /health)
|
|
236
|
-
--health-interval <sec> Health check interval (default: 10s)
|
|
237
|
-
|
|
238
|
-
Service Groups:
|
|
239
|
-
Run the same command on different machines with --group to create a
|
|
240
|
-
load-balanced service. frps round-robins traffic across all group members.
|
|
241
|
-
|
|
242
|
-
Machine A: svamp service expose my-api --port 8000 --group my-api --group-key s3cret
|
|
243
|
-
Machine B: svamp service expose my-api --port 8000 --group my-api --group-key s3cret
|
|
244
|
-
\u2192 Both serve at the same URL, frps load-balances between them
|
|
245
|
-
|
|
246
|
-
Examples:
|
|
247
|
-
svamp service expose my-api --port 8000
|
|
248
|
-
svamp service expose my-api --port 8000 --health-check http --health-path /health
|
|
249
|
-
svamp service expose my-api --port 8000 --port 3000
|
|
250
|
-
svamp service expose my-api --port 8000 --group my-api --group-key s3cret
|
|
251
|
-
svamp service serve my-site ./dist
|
|
252
|
-
`.trim());
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
export { handleServiceCommand, serviceDelete, serviceExpose, serviceList, serviceServe };
|
|
@@ -1,375 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'node:child_process';
|
|
2
|
-
import { m as resolveProjectRoot, t as searchIssues, q as listIssues, o as addComment, u as updateIssue, n as getIssue, w as summarize, p as addIssue } from './run-DZLwKdGH.mjs';
|
|
3
|
-
import 'os';
|
|
4
|
-
import 'fs/promises';
|
|
5
|
-
import 'fs';
|
|
6
|
-
import 'path';
|
|
7
|
-
import 'url';
|
|
8
|
-
import 'child_process';
|
|
9
|
-
import 'crypto';
|
|
10
|
-
import 'node:crypto';
|
|
11
|
-
import 'node:fs';
|
|
12
|
-
import 'util';
|
|
13
|
-
import 'node:path';
|
|
14
|
-
import 'node:events';
|
|
15
|
-
import 'node:os';
|
|
16
|
-
import '@agentclientprotocol/sdk';
|
|
17
|
-
import '@modelcontextprotocol/sdk/client/index.js';
|
|
18
|
-
import '@modelcontextprotocol/sdk/client/stdio.js';
|
|
19
|
-
import '@modelcontextprotocol/sdk/types.js';
|
|
20
|
-
import 'zod';
|
|
21
|
-
import 'node:fs/promises';
|
|
22
|
-
import 'node:util';
|
|
23
|
-
|
|
24
|
-
const STATUS_GLYPH = {
|
|
25
|
-
ready: "\u25C9",
|
|
26
|
-
in_progress: "\u25D0",
|
|
27
|
-
archived: "\u25A3"
|
|
28
|
-
};
|
|
29
|
-
function flag(args, name) {
|
|
30
|
-
const i = args.indexOf(name);
|
|
31
|
-
return i !== -1 && i + 1 < args.length ? args[i + 1] : void 0;
|
|
32
|
-
}
|
|
33
|
-
function has(args, name) {
|
|
34
|
-
return args.includes(name);
|
|
35
|
-
}
|
|
36
|
-
function positional(args) {
|
|
37
|
-
const out = [];
|
|
38
|
-
for (let i = 0; i < args.length; i++) {
|
|
39
|
-
const a = args[i];
|
|
40
|
-
if (a.startsWith("--")) {
|
|
41
|
-
if (a !== "--json" && a !== "--ready") i++;
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
out.push(a);
|
|
45
|
-
}
|
|
46
|
-
return out;
|
|
47
|
-
}
|
|
48
|
-
function fmtIssue(i) {
|
|
49
|
-
const tri = i.triaged === false ? " \u25B3triage" : "";
|
|
50
|
-
const disp = i.disposition === "crew" ? " {crew}" : "";
|
|
51
|
-
const label = i.labels.length ? ` [${i.labels.join(", ")}]` : "";
|
|
52
|
-
const v = i.verify ? ` (verify:${i.verify.type})` : "";
|
|
53
|
-
const br = i.branch ? ` {branch:${i.branch}}` : "";
|
|
54
|
-
return `#${i.id} ${STATUS_GLYPH[i.status]} ${i.title}${tri}${disp}${label}${v}${br}`;
|
|
55
|
-
}
|
|
56
|
-
function buildVerify(args) {
|
|
57
|
-
const cmd = flag(args, "--verify-cmd");
|
|
58
|
-
if (cmd) return { type: "command", text: cmd };
|
|
59
|
-
const agent = flag(args, "--verify");
|
|
60
|
-
if (agent) return { type: "agent", text: agent };
|
|
61
|
-
if (has(args, "--manual")) return { type: "manual" };
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
async function issueCommand(args) {
|
|
65
|
-
const sub = args[0];
|
|
66
|
-
const rest = args.slice(1);
|
|
67
|
-
const root = resolveProjectRoot();
|
|
68
|
-
const json = has(args, "--json");
|
|
69
|
-
const sessionId = process.env.SVAMP_SESSION_ID || null;
|
|
70
|
-
const out = (text) => console.log(text);
|
|
71
|
-
const ok = (obj, text) => {
|
|
72
|
-
if (json) console.log(JSON.stringify(obj));
|
|
73
|
-
else out(text);
|
|
74
|
-
};
|
|
75
|
-
switch (sub) {
|
|
76
|
-
case "add": {
|
|
77
|
-
let title = positional(rest)[0];
|
|
78
|
-
let body = flag(rest, "--body");
|
|
79
|
-
let original;
|
|
80
|
-
if (!title) {
|
|
81
|
-
const src = (body || "").trim();
|
|
82
|
-
if (!src) {
|
|
83
|
-
console.error('usage: svamp issue add "<title or text>" [--ready] [--label x] [--verify-cmd "npm test"] [--scope project|session] [--body "\u2026"]');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const nl = src.indexOf("\n");
|
|
87
|
-
title = (nl === -1 ? src : src.slice(0, nl)).trim().slice(0, 200);
|
|
88
|
-
original = src;
|
|
89
|
-
body = void 0;
|
|
90
|
-
}
|
|
91
|
-
const labels = rest.filter((a, idx) => rest[idx - 1] === "--label");
|
|
92
|
-
const owner = flag(rest, "--session") || sessionId;
|
|
93
|
-
const scope = flag(rest, "--scope") || (owner ? "session" : "project");
|
|
94
|
-
const issue = addIssue(root, {
|
|
95
|
-
title,
|
|
96
|
-
// New issues are actionable immediately (#0049) — there is no backlog to promote from.
|
|
97
|
-
// `--ready` is accepted for back-compat but is now the only behavior.
|
|
98
|
-
status: "ready",
|
|
99
|
-
scope,
|
|
100
|
-
labels,
|
|
101
|
-
verify: buildVerify(rest),
|
|
102
|
-
session: scope === "session" ? owner : null,
|
|
103
|
-
original,
|
|
104
|
-
body
|
|
105
|
-
});
|
|
106
|
-
ok(issue, `Created #${issue.id} (${issue.status}): ${issue.title}`);
|
|
107
|
-
break;
|
|
108
|
-
}
|
|
109
|
-
case "list":
|
|
110
|
-
case "ls": {
|
|
111
|
-
const status = flag(rest, "--status");
|
|
112
|
-
const opts = { includeArchived: status === "all" || status === "archived" || has(rest, "--all") };
|
|
113
|
-
if (status && status !== "all") opts.status = status;
|
|
114
|
-
if (flag(rest, "--label")) opts.label = flag(rest, "--label");
|
|
115
|
-
if (flag(rest, "--scope")) opts.scope = flag(rest, "--scope");
|
|
116
|
-
const mineList = flag(rest, "--session") || (has(rest, "--mine") ? sessionId : null);
|
|
117
|
-
let items = listIssues(root, opts);
|
|
118
|
-
if (mineList) items = items.filter((i) => i.scope === "project" || i.session === mineList);
|
|
119
|
-
if (json) {
|
|
120
|
-
console.log(JSON.stringify(items.map(({ body, original, ...r }) => r)));
|
|
121
|
-
break;
|
|
122
|
-
}
|
|
123
|
-
if (!items.length) {
|
|
124
|
-
out("No issues.");
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
127
|
-
for (const i of items) out(fmtIssue(i));
|
|
128
|
-
const s = summarize(listIssues(root, { includeArchived: true }));
|
|
129
|
-
out(`
|
|
130
|
-
${s.ready} ready \xB7 ${s.in_progress} in-progress \xB7 ${s.archived} archived`);
|
|
131
|
-
break;
|
|
132
|
-
}
|
|
133
|
-
case "show": {
|
|
134
|
-
const id = positional(rest)[0];
|
|
135
|
-
const issue = id ? getIssue(root, id) : null;
|
|
136
|
-
if (!issue) {
|
|
137
|
-
console.error(`Issue not found: ${id}`);
|
|
138
|
-
process.exit(1);
|
|
139
|
-
}
|
|
140
|
-
if (json) {
|
|
141
|
-
console.log(JSON.stringify(issue));
|
|
142
|
-
break;
|
|
143
|
-
}
|
|
144
|
-
out(fmtIssue(issue));
|
|
145
|
-
out(`status: ${issue.status} \xB7 scope: ${issue.scope} \xB7 created: ${issue.created}${issue.closed ? ` \xB7 closed: ${issue.closed}` : ""}`);
|
|
146
|
-
if (issue.original) out(`
|
|
147
|
-
Original request:
|
|
148
|
-
${issue.original}`);
|
|
149
|
-
if (issue.body) out(`
|
|
150
|
-
Description:
|
|
151
|
-
${issue.body}`);
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case "work": {
|
|
155
|
-
const id = positional(rest)[0];
|
|
156
|
-
if (!id) {
|
|
157
|
-
console.error("usage: svamp issue work <id> [--branch <name>] [--session <id>]");
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
const branch = flag(rest, "--branch");
|
|
161
|
-
const worker = flag(rest, "--session") || sessionId;
|
|
162
|
-
const cur = getIssue(root, id);
|
|
163
|
-
const claim = worker && cur && cur.scope === "project" && !cur.session ? { session: worker } : {};
|
|
164
|
-
const updated = updateIssue(root, id, { status: "in_progress", ...branch ? { branch } : {}, ...claim });
|
|
165
|
-
if (!updated) {
|
|
166
|
-
console.error(`Issue not found: ${id}`);
|
|
167
|
-
process.exit(1);
|
|
168
|
-
}
|
|
169
|
-
ok(updated, `#${updated.id} \u2192 in_progress${updated.branch ? ` on ${updated.branch}` : ""}${claim.session ? ` (claimed by ${claim.session})` : ""}`);
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
case "ready":
|
|
173
|
-
case "start":
|
|
174
|
-
case "close":
|
|
175
|
-
case "done":
|
|
176
|
-
case "reopen":
|
|
177
|
-
case "archive":
|
|
178
|
-
case "backlog": {
|
|
179
|
-
const id = positional(rest)[0];
|
|
180
|
-
if (!id) {
|
|
181
|
-
console.error(`usage: svamp issue ${sub} <id>`);
|
|
182
|
-
process.exit(1);
|
|
183
|
-
}
|
|
184
|
-
const map = {
|
|
185
|
-
ready: "ready",
|
|
186
|
-
start: "in_progress",
|
|
187
|
-
close: "archived",
|
|
188
|
-
done: "archived",
|
|
189
|
-
reopen: "ready",
|
|
190
|
-
archive: "archived",
|
|
191
|
-
backlog: "ready"
|
|
192
|
-
};
|
|
193
|
-
if (sub === "close" || sub === "done") {
|
|
194
|
-
const current = getIssue(root, id);
|
|
195
|
-
const vc = current?.verify?.type === "command" ? current.verify.text?.trim() : "";
|
|
196
|
-
if (vc) {
|
|
197
|
-
if (has(rest, "--force")) {
|
|
198
|
-
console.error(`\u26A0 #${id}: closing WITHOUT running verify-cmd (--force): \`${vc}\``);
|
|
199
|
-
} else {
|
|
200
|
-
try {
|
|
201
|
-
execSync(vc, { cwd: root, stdio: "pipe", timeout: 6e5, maxBuffer: 64 * 1024 * 1024 });
|
|
202
|
-
} catch (e) {
|
|
203
|
-
const tail = (String(e?.stdout || "") + String(e?.stderr || "")).split("\n").slice(-15).join("\n").trim();
|
|
204
|
-
console.error(`Refusing to close #${id}: its verify-cmd failed \u2014 the fix is not verified.
|
|
205
|
-
$ ${vc}
|
|
206
|
-
${tail}
|
|
207
|
-
(Fix the issue, or pass --force to override.)`);
|
|
208
|
-
process.exit(1);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
const updated = updateIssue(root, id, { status: map[sub] });
|
|
214
|
-
if (!updated) {
|
|
215
|
-
console.error(`Issue not found: ${id}`);
|
|
216
|
-
process.exit(1);
|
|
217
|
-
}
|
|
218
|
-
ok(updated, `#${updated.id} \u2192 ${updated.status}: ${updated.title}`);
|
|
219
|
-
break;
|
|
220
|
-
}
|
|
221
|
-
case "edit": {
|
|
222
|
-
const id = positional(rest)[0];
|
|
223
|
-
if (!id) {
|
|
224
|
-
console.error("usage: svamp issue edit <id> [--title \u2026] [--label x] [--verify-cmd \u2026] [--scope \u2026] [--body \u2026]");
|
|
225
|
-
process.exit(1);
|
|
226
|
-
}
|
|
227
|
-
const patch = {};
|
|
228
|
-
if (flag(rest, "--title")) patch.title = flag(rest, "--title");
|
|
229
|
-
const labels = rest.filter((a, idx) => rest[idx - 1] === "--label");
|
|
230
|
-
if (labels.length) patch.labels = labels;
|
|
231
|
-
const v = buildVerify(rest);
|
|
232
|
-
if (v || has(rest, "--no-verify")) patch.verify = has(rest, "--no-verify") ? null : v;
|
|
233
|
-
if (flag(rest, "--scope")) patch.scope = flag(rest, "--scope");
|
|
234
|
-
if (has(rest, "--unclaim")) patch.session = null;
|
|
235
|
-
else if (flag(rest, "--session")) patch.session = flag(rest, "--session");
|
|
236
|
-
if (flag(rest, "--body")) patch.body = flag(rest, "--body");
|
|
237
|
-
if (flag(rest, "--status")) patch.status = flag(rest, "--status");
|
|
238
|
-
if (has(rest, "--crew")) patch.disposition = "crew";
|
|
239
|
-
else if (has(rest, "--inline")) patch.disposition = "inline";
|
|
240
|
-
if (has(rest, "--triaged")) patch.triaged = true;
|
|
241
|
-
const updated = updateIssue(root, id, patch);
|
|
242
|
-
if (!updated) {
|
|
243
|
-
console.error(`Issue not found: ${id}`);
|
|
244
|
-
process.exit(1);
|
|
245
|
-
}
|
|
246
|
-
ok(updated, `Updated #${updated.id}: ${updated.title}`);
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
case "triage": {
|
|
250
|
-
const id = positional(rest)[0];
|
|
251
|
-
if (!id) {
|
|
252
|
-
console.error('usage: svamp issue triage <id> [--title "\u2026"] [--label x \u2026] [--crew|--inline] [--verify-cmd "cmd" | --verify "criterion" | --manual] [--body "rewritten"] [--backlog]');
|
|
253
|
-
process.exit(1);
|
|
254
|
-
}
|
|
255
|
-
const patch = { triaged: true };
|
|
256
|
-
if (flag(rest, "--title")) patch.title = flag(rest, "--title");
|
|
257
|
-
const tlabels = rest.filter((a, idx) => rest[idx - 1] === "--label");
|
|
258
|
-
if (tlabels.length) patch.labels = tlabels;
|
|
259
|
-
if (has(rest, "--crew")) patch.disposition = "crew";
|
|
260
|
-
else if (has(rest, "--inline")) patch.disposition = "inline";
|
|
261
|
-
const tv = buildVerify(rest);
|
|
262
|
-
if (tv) patch.verify = tv;
|
|
263
|
-
if (flag(rest, "--body")) patch.body = flag(rest, "--body");
|
|
264
|
-
patch.status = "ready";
|
|
265
|
-
const updated = updateIssue(root, id, patch);
|
|
266
|
-
if (!updated) {
|
|
267
|
-
console.error(`Issue not found: ${id}`);
|
|
268
|
-
process.exit(1);
|
|
269
|
-
}
|
|
270
|
-
ok(updated, `Triaged #${updated.id}: ${updated.title} [${updated.disposition}${updated.verify ? ", verify:" + updated.verify.type : ""}] \u2192 ${updated.status}`);
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
case "comment": {
|
|
274
|
-
const id = positional(rest)[0];
|
|
275
|
-
const text = positional(rest)[1];
|
|
276
|
-
if (!id || !text) {
|
|
277
|
-
console.error('usage: svamp issue comment <id> "<text>"');
|
|
278
|
-
process.exit(1);
|
|
279
|
-
}
|
|
280
|
-
const updated = addComment(root, id, text);
|
|
281
|
-
if (!updated) {
|
|
282
|
-
console.error(`Issue not found: ${id}`);
|
|
283
|
-
process.exit(1);
|
|
284
|
-
}
|
|
285
|
-
ok(updated, `Commented on #${updated.id}`);
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
case "pending": {
|
|
289
|
-
const mine = flag(rest, "--session") || (has(rest, "--mine") ? sessionId : null);
|
|
290
|
-
const inScope = (i) => !mine || i.scope === "project" || i.session === mine;
|
|
291
|
-
const pending = listIssues(root).filter(inScope).filter((i) => i.status === "ready" || i.status === "in_progress");
|
|
292
|
-
if (json) console.log(JSON.stringify(pending));
|
|
293
|
-
else out(pending.length ? `${pending.length} pending: ${pending.map((i) => "#" + i.id).join(" ")}` : "No pending issues.");
|
|
294
|
-
process.exit(pending.length ? 1 : 0);
|
|
295
|
-
break;
|
|
296
|
-
}
|
|
297
|
-
case "search": {
|
|
298
|
-
const q = positional(rest)[0] || "";
|
|
299
|
-
const items = searchIssues(root, q);
|
|
300
|
-
if (json) {
|
|
301
|
-
console.log(JSON.stringify(items.map(({ body, original, ...r }) => r)));
|
|
302
|
-
break;
|
|
303
|
-
}
|
|
304
|
-
if (!items.length) {
|
|
305
|
-
out(`No issues match "${q}".`);
|
|
306
|
-
break;
|
|
307
|
-
}
|
|
308
|
-
for (const i of items) out(fmtIssue(i));
|
|
309
|
-
break;
|
|
310
|
-
}
|
|
311
|
-
case "guide": {
|
|
312
|
-
const topic = positional(rest)[0];
|
|
313
|
-
if (topic === "triage") {
|
|
314
|
-
out([
|
|
315
|
-
"TRIAGE an issue \u2014 turn a raw user post into a fully-specified, actionable issue:",
|
|
316
|
-
" 1. Read it: svamp issue show <id> (the user's original text is preserved separately)",
|
|
317
|
-
" 2. Triage it:",
|
|
318
|
-
' svamp issue triage <id> --title "<concise title>" [--label <tag>]... \\',
|
|
319
|
-
` [--inline | --crew] [--verify-cmd "<pass/fail cmd>" | --verify "<how we know it's done>"] \\`,
|
|
320
|
-
' [--body "<expanded description>"]',
|
|
321
|
-
" - inline = work it in this session; crew = isolated worktree child.",
|
|
322
|
-
" - verify-cmd = a real pass/fail command (oracle); --verify = an agent/human criterion.",
|
|
323
|
-
" 3. SPLIT if the post bundles several independent asks: `svamp issue add` a new issue for",
|
|
324
|
-
" each extra piece (carry over the relevant context) and triage each; keep the original as the primary.",
|
|
325
|
-
" 4. If it implies running on a schedule/event, also `svamp workflow add` it.",
|
|
326
|
-
" Then reply with a one-line summary."
|
|
327
|
-
].join("\n"));
|
|
328
|
-
} else if (topic === "work") {
|
|
329
|
-
out([
|
|
330
|
-
"WORK the ready issues in THIS session's backlog (your own + shared project items; leave other",
|
|
331
|
-
"sessions' private items alone). List: svamp issue list --status ready --session <id>.",
|
|
332
|
-
" TRIAGE FIRST any \u25B3triage (untriaged) issue \u2014 see `svamp issue guide triage`.",
|
|
333
|
-
" For each issue:",
|
|
334
|
-
" 1. On pickup: svamp issue work <id> (status \u2192 in_progress; live status reflects the work).",
|
|
335
|
-
' 2. As you go: leave substantive `svamp issue comment <id> "\u2026"` notes \u2014 decisions, takeaways, blockers.',
|
|
336
|
-
" 3. Before closing: VERIFY with evidence \u2014 run its verify-cmd; for verify:agent cite the files/commit",
|
|
337
|
-
" that resolve it in a summary comment. Post the summary, THEN `svamp issue close <id>`.",
|
|
338
|
-
" NEVER close an issue that isn't genuinely resolved.",
|
|
339
|
-
" CONVERGE TO MAIN: if an issue used an isolated branch/worktree, merge it back to `main` (and remove",
|
|
340
|
-
" the worktree) BEFORE closing \u2014 never leave unmerged branches.",
|
|
341
|
-
" Keep going until `svamp issue pending --session <id>` is empty."
|
|
342
|
-
].join("\n"));
|
|
343
|
-
} else {
|
|
344
|
-
out("usage: svamp issue guide <triage|work>");
|
|
345
|
-
}
|
|
346
|
-
break;
|
|
347
|
-
}
|
|
348
|
-
case "help":
|
|
349
|
-
case void 0:
|
|
350
|
-
case "--help":
|
|
351
|
-
case "-h":
|
|
352
|
-
out([
|
|
353
|
-
"svamp issue \u2014 the session backlog (folder of .svamp/issues/<id>.md)",
|
|
354
|
-
"",
|
|
355
|
-
' add ["<title>"] [--ready] [--label x] [--verify-cmd "cmd"] [--scope project|session] [--body "\u2026"]',
|
|
356
|
-
" # title optional \u2014 if omitted, the first line of --body becomes the title",
|
|
357
|
-
" list [--status ready|in_progress|archived|all] [--label x] [--scope \u2026] [--session <id>] [--json]",
|
|
358
|
-
" show <id> [--json]",
|
|
359
|
-
" ready <id> | close <id> (\u2192 archived) | reopen <id> | archive <id> # lifecycle: ready \u2192 in_progress \u2192 archived",
|
|
360
|
-
" work <id> [--branch <name>] # mark in-progress (optionally on a branch; merge to main before close)",
|
|
361
|
-
" triage <id> [--title \u2026] [--label x] [--inline|--crew] [--verify-cmd \u2026|--verify \u2026] [--body \u2026]",
|
|
362
|
-
" edit <id> [--title \u2026] [--label x] [--verify-cmd \u2026] [--no-verify] [--status \u2026] [--body \u2026]",
|
|
363
|
-
' comment <id> "<text>" # append a follow-up to the issue',
|
|
364
|
-
" pending [--session <id>] # exit 0 if no actionable (ready/in_progress) issues \u2014 the loop oracle",
|
|
365
|
-
' search "<query>" [--json]',
|
|
366
|
-
" guide <triage|work> # the triage / work-loop procedure (so triggers can stay short)"
|
|
367
|
-
].join("\n"));
|
|
368
|
-
break;
|
|
369
|
-
default:
|
|
370
|
-
console.error(`Unknown: svamp issue ${sub}. Try: svamp issue help`);
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
export { issueCommand };
|