svamp-cli 0.1.6 → 0.1.7
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 +112 -17
- package/dist/commands-C8QbpD3d.mjs +476 -0
- package/dist/commands-D8bwJSm-.mjs +487 -0
- package/dist/commands-DNF79jRn.mjs +440 -0
- package/dist/commands-p36abIvL.mjs +485 -0
- package/dist/commands-zTpXV_OI.mjs +452 -0
- package/dist/index.mjs +1 -1
- package/dist/package-DD4JHOe_.mjs +57 -0
- package/dist/package-DsYI3JjY.mjs +57 -0
- package/dist/run-C9o6o85x.mjs +3617 -0
- package/dist/run-CZTsU12c.mjs +3629 -0
- package/dist/run-DLreYPnX.mjs +3628 -0
- package/dist/run-aevHxpEl.mjs +3630 -0
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-
|
|
1
|
+
import { s as startDaemon, b as stopDaemon, d as daemonStatus } from './run-aevHxpEl.mjs';
|
|
2
2
|
import 'os';
|
|
3
3
|
import 'fs/promises';
|
|
4
4
|
import 'fs';
|
|
@@ -74,10 +74,13 @@ async function main() {
|
|
|
74
74
|
}
|
|
75
75
|
} else if (subcommand === "agent") {
|
|
76
76
|
await handleAgentCommand();
|
|
77
|
+
} else if (subcommand === "session") {
|
|
78
|
+
await handleSessionCommand();
|
|
77
79
|
} else if (subcommand === "--help" || subcommand === "-h" || !subcommand) {
|
|
78
80
|
printHelp();
|
|
79
81
|
} else if (subcommand === "--version" || subcommand === "-v") {
|
|
80
|
-
|
|
82
|
+
const pkg = await import('./package-DD4JHOe_.mjs').catch(() => ({ default: { version: "unknown" } }));
|
|
83
|
+
console.log(`svamp version: ${pkg.default.version}`);
|
|
81
84
|
} else {
|
|
82
85
|
console.error(`Unknown command: ${subcommand}`);
|
|
83
86
|
printHelp();
|
|
@@ -91,7 +94,7 @@ async function handleAgentCommand() {
|
|
|
91
94
|
return;
|
|
92
95
|
}
|
|
93
96
|
if (agentArgs[0] === "list") {
|
|
94
|
-
const { KNOWN_ACP_AGENTS } = await import('./run-
|
|
97
|
+
const { KNOWN_ACP_AGENTS } = await import('./run-aevHxpEl.mjs').then(function (n) { return n.f; });
|
|
95
98
|
console.log("Known ACP agents:");
|
|
96
99
|
for (const [name, config2] of Object.entries(KNOWN_ACP_AGENTS)) {
|
|
97
100
|
console.log(` ${name.padEnd(12)} ${config2.command} ${config2.args.join(" ")}`);
|
|
@@ -100,10 +103,10 @@ async function handleAgentCommand() {
|
|
|
100
103
|
console.log('Use "svamp agent -- <command> [args]" for a custom ACP agent.');
|
|
101
104
|
return;
|
|
102
105
|
}
|
|
103
|
-
const { resolveAcpAgentConfig } = await import('./run-
|
|
104
|
-
const { AcpBackend } = await import('./run-
|
|
105
|
-
const { GeminiTransport } = await import('./run-
|
|
106
|
-
const { DefaultTransport } = await import('./run-
|
|
106
|
+
const { resolveAcpAgentConfig } = await import('./run-aevHxpEl.mjs').then(function (n) { return n.f; });
|
|
107
|
+
const { AcpBackend } = await import('./run-aevHxpEl.mjs').then(function (n) { return n.e; });
|
|
108
|
+
const { GeminiTransport } = await import('./run-aevHxpEl.mjs').then(function (n) { return n.G; });
|
|
109
|
+
const { DefaultTransport } = await import('./run-aevHxpEl.mjs').then(function (n) { return n.D; });
|
|
107
110
|
let cwd = process.cwd();
|
|
108
111
|
const filteredArgs = [];
|
|
109
112
|
for (let i = 0; i < agentArgs.length; i++) {
|
|
@@ -187,7 +190,8 @@ Agent stopped: ${msg.detail || ""}`);
|
|
|
187
190
|
console.log(`Session started: ${result.sessionId}`);
|
|
188
191
|
process.stdout.write("> ");
|
|
189
192
|
} catch (err) {
|
|
190
|
-
|
|
193
|
+
const errMsg = err?.message || (typeof err === "object" ? JSON.stringify(err) : String(err));
|
|
194
|
+
console.error(`Failed to start ${config.agentName}: ${errMsg}`);
|
|
191
195
|
process.exit(1);
|
|
192
196
|
}
|
|
193
197
|
const readline = await import('readline');
|
|
@@ -224,6 +228,62 @@ Agent stopped: ${msg.detail || ""}`);
|
|
|
224
228
|
process.exit(0);
|
|
225
229
|
});
|
|
226
230
|
}
|
|
231
|
+
async function handleSessionCommand() {
|
|
232
|
+
const sessionArgs = args.slice(1);
|
|
233
|
+
const sessionSubcommand = sessionArgs[0];
|
|
234
|
+
if (!sessionSubcommand || sessionSubcommand === "--help" || sessionSubcommand === "-h") {
|
|
235
|
+
printSessionHelp();
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const { sessionList, sessionSpawn, sessionStop, sessionInfo, sessionMessages, sessionAttach } = await import('./commands-p36abIvL.mjs');
|
|
239
|
+
if (sessionSubcommand === "list" || sessionSubcommand === "ls") {
|
|
240
|
+
await sessionList();
|
|
241
|
+
} else if (sessionSubcommand === "spawn") {
|
|
242
|
+
const agent = sessionArgs[1] || "claude";
|
|
243
|
+
let dir = process.cwd();
|
|
244
|
+
for (let i = 1; i < sessionArgs.length; i++) {
|
|
245
|
+
if ((sessionArgs[i] === "-d" || sessionArgs[i] === "--directory") && i + 1 < sessionArgs.length) {
|
|
246
|
+
dir = sessionArgs[i + 1];
|
|
247
|
+
i++;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
await sessionSpawn(agent, dir);
|
|
251
|
+
} else if (sessionSubcommand === "stop") {
|
|
252
|
+
if (!sessionArgs[1]) {
|
|
253
|
+
console.error("Usage: svamp session stop <session-id>");
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
await sessionStop(sessionArgs[1]);
|
|
257
|
+
} else if (sessionSubcommand === "info") {
|
|
258
|
+
if (!sessionArgs[1]) {
|
|
259
|
+
console.error("Usage: svamp session info <session-id>");
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
await sessionInfo(sessionArgs[1]);
|
|
263
|
+
} else if (sessionSubcommand === "messages" || sessionSubcommand === "msgs") {
|
|
264
|
+
if (!sessionArgs[1]) {
|
|
265
|
+
console.error("Usage: svamp session messages <session-id> [--last N]");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
let last;
|
|
269
|
+
const lastIdx = sessionArgs.indexOf("--last");
|
|
270
|
+
if (lastIdx >= 0 && sessionArgs[lastIdx + 1]) {
|
|
271
|
+
last = parseInt(sessionArgs[lastIdx + 1], 10);
|
|
272
|
+
}
|
|
273
|
+
await sessionMessages(sessionArgs[1], last);
|
|
274
|
+
} else if (sessionSubcommand === "attach") {
|
|
275
|
+
if (!sessionArgs[1]) {
|
|
276
|
+
console.error("Usage: svamp session attach <session-id>");
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
await sessionAttach(sessionArgs[1]);
|
|
280
|
+
} else {
|
|
281
|
+
console.error(`Unknown session command: ${sessionSubcommand}`);
|
|
282
|
+
printSessionHelp();
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
process.exit(0);
|
|
286
|
+
}
|
|
227
287
|
async function loginToHypha() {
|
|
228
288
|
const serverUrl = args[1] || process.env.HYPHA_SERVER_URL;
|
|
229
289
|
if (!serverUrl) {
|
|
@@ -313,15 +373,19 @@ function printHelp() {
|
|
|
313
373
|
svamp \u2014 Svamp CLI with Hypha transport
|
|
314
374
|
|
|
315
375
|
Usage:
|
|
316
|
-
svamp login [url]
|
|
317
|
-
svamp daemon start
|
|
318
|
-
svamp daemon stop
|
|
319
|
-
svamp daemon status
|
|
320
|
-
svamp
|
|
321
|
-
svamp
|
|
322
|
-
svamp
|
|
323
|
-
svamp --
|
|
324
|
-
svamp
|
|
376
|
+
svamp login [url] Login to Hypha (opens browser, stores token)
|
|
377
|
+
svamp daemon start Start the daemon (detached)
|
|
378
|
+
svamp daemon stop Stop the daemon
|
|
379
|
+
svamp daemon status Show daemon status
|
|
380
|
+
svamp session list List active daemon sessions
|
|
381
|
+
svamp session spawn Spawn a new session on the daemon
|
|
382
|
+
svamp session attach <id> Attach to a session (interactive)
|
|
383
|
+
svamp session --help Show all session commands
|
|
384
|
+
svamp agent list List known ACP agents
|
|
385
|
+
svamp agent <name> Start local ACP agent session
|
|
386
|
+
svamp agent -- <cmd> Start custom ACP agent
|
|
387
|
+
svamp --version Show version
|
|
388
|
+
svamp --help Show this help
|
|
325
389
|
|
|
326
390
|
Environment variables:
|
|
327
391
|
HYPHA_SERVER_URL Hypha server URL (required for daemon)
|
|
@@ -341,6 +405,37 @@ Usage:
|
|
|
341
405
|
svamp daemon status Show daemon status
|
|
342
406
|
`);
|
|
343
407
|
}
|
|
408
|
+
function printSessionHelp() {
|
|
409
|
+
console.log(`
|
|
410
|
+
svamp session \u2014 Manage daemon sessions (Claude, Gemini, OpenCode)
|
|
411
|
+
|
|
412
|
+
Usage:
|
|
413
|
+
svamp session list List active sessions
|
|
414
|
+
svamp session spawn <agent> [-d <path>] Spawn a new session
|
|
415
|
+
svamp session stop <id> Stop a session
|
|
416
|
+
svamp session info <id> Show session metadata
|
|
417
|
+
svamp session messages <id> [--last N] Dump recent messages
|
|
418
|
+
svamp session attach <id> Attach to session (interactive)
|
|
419
|
+
|
|
420
|
+
Agents: claude (default), gemini, opencode
|
|
421
|
+
|
|
422
|
+
Session IDs can be abbreviated (prefix match, like Docker).
|
|
423
|
+
|
|
424
|
+
Attach commands:
|
|
425
|
+
/quit, /detach Detach (session keeps running)
|
|
426
|
+
/abort, /cancel Cancel current agent turn
|
|
427
|
+
/kill Stop the session
|
|
428
|
+
/info Show session status
|
|
429
|
+
|
|
430
|
+
Examples:
|
|
431
|
+
svamp session list
|
|
432
|
+
svamp session spawn claude -d ~/projects/myapp
|
|
433
|
+
svamp session spawn gemini
|
|
434
|
+
svamp session info abc12345
|
|
435
|
+
svamp session messages abc12345 --last 10
|
|
436
|
+
svamp session attach abc12345
|
|
437
|
+
`);
|
|
438
|
+
}
|
|
344
439
|
function printAgentHelp() {
|
|
345
440
|
console.log(`
|
|
346
441
|
svamp agent \u2014 Interactive ACP agent sessions
|
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { c as connectToHypha } from './run-aevHxpEl.mjs';
|
|
5
|
+
import 'os';
|
|
6
|
+
import 'fs/promises';
|
|
7
|
+
import 'fs';
|
|
8
|
+
import 'path';
|
|
9
|
+
import 'url';
|
|
10
|
+
import 'child_process';
|
|
11
|
+
import 'crypto';
|
|
12
|
+
import 'node:crypto';
|
|
13
|
+
import '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
|
+
import 'node:http';
|
|
15
|
+
import '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
16
|
+
import 'zod';
|
|
17
|
+
import 'node:child_process';
|
|
18
|
+
import '@agentclientprotocol/sdk';
|
|
19
|
+
|
|
20
|
+
const SVAMP_HOME = process.env.SVAMP_HOME || join(os.homedir(), ".svamp");
|
|
21
|
+
const DAEMON_STATE_FILE = join(SVAMP_HOME, "daemon.state.json");
|
|
22
|
+
const ENV_FILE = join(SVAMP_HOME, ".env");
|
|
23
|
+
function loadDotEnv() {
|
|
24
|
+
if (!existsSync(ENV_FILE)) return;
|
|
25
|
+
const lines = readFileSync(ENV_FILE, "utf-8").split("\n");
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
29
|
+
const eqIdx = trimmed.indexOf("=");
|
|
30
|
+
if (eqIdx === -1) continue;
|
|
31
|
+
const key = trimmed.slice(0, eqIdx).trim();
|
|
32
|
+
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, "");
|
|
33
|
+
if (!process.env[key]) {
|
|
34
|
+
process.env[key] = value;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function readDaemonState() {
|
|
39
|
+
if (!existsSync(DAEMON_STATE_FILE)) return null;
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(readFileSync(DAEMON_STATE_FILE, "utf-8"));
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function isDaemonAlive(state) {
|
|
47
|
+
try {
|
|
48
|
+
process.kill(state.pid, 0);
|
|
49
|
+
return true;
|
|
50
|
+
} catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function connectAndGetMachine() {
|
|
55
|
+
loadDotEnv();
|
|
56
|
+
const state = readDaemonState();
|
|
57
|
+
if (!state || !isDaemonAlive(state)) {
|
|
58
|
+
console.error('Daemon is not running. Start it with "svamp daemon start".');
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
const serverUrl = process.env.HYPHA_SERVER_URL || state.hyphaServerUrl;
|
|
62
|
+
const token = process.env.HYPHA_TOKEN;
|
|
63
|
+
if (!serverUrl) {
|
|
64
|
+
console.error('No Hypha server URL. Run "svamp login <url>" first.');
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
|
67
|
+
const origLog = console.log;
|
|
68
|
+
const origWarn = console.warn;
|
|
69
|
+
const origInfo = console.info;
|
|
70
|
+
const stdoutWrite = process.stdout.write.bind(process.stdout);
|
|
71
|
+
console.log = () => {
|
|
72
|
+
};
|
|
73
|
+
console.warn = () => {
|
|
74
|
+
};
|
|
75
|
+
console.info = () => {
|
|
76
|
+
};
|
|
77
|
+
process.stdout.write = (chunk, ...args) => {
|
|
78
|
+
if (typeof chunk === "string" && (chunk.includes("WebSocket") || chunk.includes("Connection established") || chunk.includes("registering service") || chunk.includes("Subscribing") || chunk.includes("subscribed") || chunk.includes("Cleaning up") || chunk.includes("disconnected"))) {
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return stdoutWrite(chunk, ...args);
|
|
82
|
+
};
|
|
83
|
+
const restoreConsole = () => {
|
|
84
|
+
console.log = origLog;
|
|
85
|
+
console.warn = origWarn;
|
|
86
|
+
console.info = origInfo;
|
|
87
|
+
process.stdout.write = stdoutWrite;
|
|
88
|
+
};
|
|
89
|
+
let server;
|
|
90
|
+
try {
|
|
91
|
+
server = await connectToHypha({
|
|
92
|
+
serverUrl,
|
|
93
|
+
token,
|
|
94
|
+
name: "svamp-session-cli"
|
|
95
|
+
});
|
|
96
|
+
} catch (err) {
|
|
97
|
+
restoreConsole();
|
|
98
|
+
console.error(`Failed to connect to Hypha: ${err.message}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
let machine;
|
|
102
|
+
if (state.machineId) {
|
|
103
|
+
try {
|
|
104
|
+
machine = await server.getService(`svamp-machine-${state.machineId}`);
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (!machine) {
|
|
109
|
+
try {
|
|
110
|
+
const services = await server.listServices();
|
|
111
|
+
const machineServices = services.filter((svc) => {
|
|
112
|
+
const sid = svc.id || svc.name || "";
|
|
113
|
+
return sid.includes("svamp-machine-");
|
|
114
|
+
});
|
|
115
|
+
if (machineServices.length === 0) {
|
|
116
|
+
restoreConsole();
|
|
117
|
+
console.error("No machine service found. Is the daemon registered on Hypha?");
|
|
118
|
+
await server.disconnect();
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const svcId = machineServices[0].id || machineServices[0].name;
|
|
122
|
+
machine = await server.getService(svcId);
|
|
123
|
+
} catch (err) {
|
|
124
|
+
restoreConsole();
|
|
125
|
+
console.error(`Failed to discover machine service: ${err.message}`);
|
|
126
|
+
await server.disconnect();
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
restoreConsole();
|
|
131
|
+
return { server, machine };
|
|
132
|
+
}
|
|
133
|
+
function resolveSessionId(sessions, partial) {
|
|
134
|
+
const exact = sessions.find((s) => s.sessionId === partial);
|
|
135
|
+
if (exact) return exact;
|
|
136
|
+
const matches = sessions.filter((s) => s.sessionId.startsWith(partial));
|
|
137
|
+
if (matches.length === 1) return matches[0];
|
|
138
|
+
if (matches.length === 0) {
|
|
139
|
+
console.error(`No session found matching: ${partial}`);
|
|
140
|
+
console.error('Run "svamp session list" to see active sessions.');
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
console.error(`Ambiguous session ID "${partial}". Matches:`);
|
|
144
|
+
for (const s of matches) {
|
|
145
|
+
console.error(` ${s.sessionId}`);
|
|
146
|
+
}
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
function truncate(str, max) {
|
|
150
|
+
if (str.length <= max) return str;
|
|
151
|
+
return "..." + str.slice(str.length - max + 3);
|
|
152
|
+
}
|
|
153
|
+
function renderMessage(msg) {
|
|
154
|
+
const content = msg.content;
|
|
155
|
+
if (!content) return;
|
|
156
|
+
const role = content.role;
|
|
157
|
+
if (role === "user") {
|
|
158
|
+
const data = content.content;
|
|
159
|
+
let text;
|
|
160
|
+
if (typeof data === "string") {
|
|
161
|
+
try {
|
|
162
|
+
const parsed = JSON.parse(data);
|
|
163
|
+
text = parsed?.text || parsed?.content?.text || data;
|
|
164
|
+
} catch {
|
|
165
|
+
text = data;
|
|
166
|
+
}
|
|
167
|
+
} else if (data?.text) {
|
|
168
|
+
text = data.text;
|
|
169
|
+
} else if (data?.type === "text") {
|
|
170
|
+
text = data.text || "";
|
|
171
|
+
} else {
|
|
172
|
+
text = typeof data === "object" ? JSON.stringify(data) : String(data || "");
|
|
173
|
+
}
|
|
174
|
+
console.log(`\x1B[36m[user]\x1B[0m ${text}`);
|
|
175
|
+
} else if (role === "agent" || role === "assistant") {
|
|
176
|
+
const data = content.content?.data || content.content;
|
|
177
|
+
if (!data) return;
|
|
178
|
+
if (data.type === "assistant" && Array.isArray(data.content)) {
|
|
179
|
+
for (const block of data.content) {
|
|
180
|
+
if (block.type === "text" && block.text) {
|
|
181
|
+
process.stdout.write(block.text);
|
|
182
|
+
if (!block.text.endsWith("\n")) process.stdout.write("\n");
|
|
183
|
+
} else if (block.type === "tool_use") {
|
|
184
|
+
const argsStr = JSON.stringify(block.input || {}).slice(0, 120);
|
|
185
|
+
console.log(`\x1B[33m[tool]\x1B[0m ${block.name}(${argsStr})`);
|
|
186
|
+
} else if (block.type === "tool_result") {
|
|
187
|
+
const resultStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content || "");
|
|
188
|
+
console.log(`\x1B[90m[result]\x1B[0m ${resultStr.slice(0, 200)}${resultStr.length > 200 ? "..." : ""}`);
|
|
189
|
+
} else if (block.type === "thinking") {
|
|
190
|
+
const text = block.thinking || block.text || "";
|
|
191
|
+
if (text) console.log(`\x1B[90m[thinking] ${text.slice(0, 200)}\x1B[0m`);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
} else if (data.type === "result") {
|
|
195
|
+
if (data.result) console.log(`\x1B[32m[done]\x1B[0m ${data.result}`);
|
|
196
|
+
} else if (data.type === "output") {
|
|
197
|
+
const inner = data.data;
|
|
198
|
+
if (inner?.type === "assistant" && Array.isArray(inner.content)) {
|
|
199
|
+
for (const block of inner.content) {
|
|
200
|
+
if (block.type === "text" && block.text) {
|
|
201
|
+
process.stdout.write(block.text);
|
|
202
|
+
if (!block.text.endsWith("\n")) process.stdout.write("\n");
|
|
203
|
+
} else if (block.type === "tool_use") {
|
|
204
|
+
const argsStr = JSON.stringify(block.input || {}).slice(0, 120);
|
|
205
|
+
console.log(`\x1B[33m[tool]\x1B[0m ${block.name}(${argsStr})`);
|
|
206
|
+
} else if (block.type === "tool_result") {
|
|
207
|
+
const resultStr = typeof block.content === "string" ? block.content : JSON.stringify(block.content || "");
|
|
208
|
+
console.log(`\x1B[90m[result]\x1B[0m ${resultStr.slice(0, 200)}${resultStr.length > 200 ? "..." : ""}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else if (inner?.type === "result") {
|
|
212
|
+
if (inner.result) console.log(`\x1B[32m[done]\x1B[0m ${inner.result}`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} else if (role === "session") {
|
|
216
|
+
const data = content.content?.data;
|
|
217
|
+
if (data?.type === "system" && data?.subtype === "init") {
|
|
218
|
+
console.log(`\x1B[90m[session init]\x1B[0m`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async function sessionList() {
|
|
223
|
+
const { server, machine } = await connectAndGetMachine();
|
|
224
|
+
try {
|
|
225
|
+
const sessions = await machine.listSessions();
|
|
226
|
+
if (sessions.length === 0) {
|
|
227
|
+
console.log("No active sessions.");
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
const enriched = [];
|
|
231
|
+
for (const s of sessions) {
|
|
232
|
+
let flavor = "claude";
|
|
233
|
+
let name = "";
|
|
234
|
+
if (s.metadata) {
|
|
235
|
+
flavor = s.metadata.flavor || "claude";
|
|
236
|
+
name = s.metadata.name || "";
|
|
237
|
+
}
|
|
238
|
+
if (!name && s.active) {
|
|
239
|
+
try {
|
|
240
|
+
const svc = await server.getService(`svamp-session-${s.sessionId}`);
|
|
241
|
+
const { metadata } = await svc.getMetadata();
|
|
242
|
+
flavor = metadata?.flavor || flavor;
|
|
243
|
+
name = metadata?.name || "";
|
|
244
|
+
} catch {
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
enriched.push({ ...s, flavor, name });
|
|
248
|
+
}
|
|
249
|
+
const header = `${"ID".padEnd(10)} ${"AGENT".padEnd(10)} ${"STATUS".padEnd(9)} ${"NAME".padEnd(25)} ${"DIRECTORY".padEnd(35)}`;
|
|
250
|
+
console.log(header);
|
|
251
|
+
console.log("-".repeat(header.length));
|
|
252
|
+
for (const s of enriched) {
|
|
253
|
+
const id = s.sessionId.slice(0, 8);
|
|
254
|
+
const agent = (s.flavor || "claude").padEnd(10);
|
|
255
|
+
const status = s.active ? "\x1B[32mactive\x1B[0m " : "\x1B[90minactive\x1B[0m";
|
|
256
|
+
const name = truncate(s.name || "-", 25).padEnd(25);
|
|
257
|
+
const dir = truncate(s.directory || "-", 33).padEnd(35);
|
|
258
|
+
console.log(`${id.padEnd(10)} ${agent} ${status} ${name} ${dir}`);
|
|
259
|
+
}
|
|
260
|
+
} finally {
|
|
261
|
+
await server.disconnect();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
async function sessionSpawn(agent, directory) {
|
|
265
|
+
const { server, machine } = await connectAndGetMachine();
|
|
266
|
+
try {
|
|
267
|
+
console.log(`Spawning ${agent} session in ${directory}...`);
|
|
268
|
+
const result = await machine.spawnSession({
|
|
269
|
+
directory,
|
|
270
|
+
agent
|
|
271
|
+
});
|
|
272
|
+
if (result.type === "success") {
|
|
273
|
+
console.log(`Session started: ${result.sessionId}`);
|
|
274
|
+
if (result.message) console.log(` ${result.message}`);
|
|
275
|
+
} else if (result.type === "requestToApproveDirectoryCreation") {
|
|
276
|
+
console.error(`Directory ${result.directory} does not exist. Create it first or use an existing directory.`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
} else {
|
|
279
|
+
console.error(`Failed: ${result.errorMessage || "Unknown error"}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
} finally {
|
|
283
|
+
await server.disconnect();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
async function sessionStop(sessionId) {
|
|
287
|
+
const { server, machine } = await connectAndGetMachine();
|
|
288
|
+
try {
|
|
289
|
+
const sessions = await machine.listSessions();
|
|
290
|
+
const match = resolveSessionId(sessions, sessionId);
|
|
291
|
+
const success = await machine.stopSession(match.sessionId);
|
|
292
|
+
if (success) {
|
|
293
|
+
console.log(`Session ${match.sessionId.slice(0, 8)} stopped.`);
|
|
294
|
+
} else {
|
|
295
|
+
console.error("Failed to stop session (not found on daemon).");
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
} finally {
|
|
299
|
+
await server.disconnect();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function sessionInfo(sessionId) {
|
|
303
|
+
const { server, machine } = await connectAndGetMachine();
|
|
304
|
+
try {
|
|
305
|
+
const sessions = await machine.listSessions();
|
|
306
|
+
const match = resolveSessionId(sessions, sessionId);
|
|
307
|
+
const fullId = match.sessionId;
|
|
308
|
+
let metadata = {};
|
|
309
|
+
let activity = {};
|
|
310
|
+
try {
|
|
311
|
+
const svc = await server.getService(`svamp-session-${fullId}`);
|
|
312
|
+
const metaResult = await svc.getMetadata();
|
|
313
|
+
metadata = metaResult.metadata || {};
|
|
314
|
+
activity = await svc.getActivityState();
|
|
315
|
+
} catch {
|
|
316
|
+
}
|
|
317
|
+
console.log(`Session: ${fullId}`);
|
|
318
|
+
console.log(`Agent: ${metadata.flavor || "claude"}`);
|
|
319
|
+
console.log(`Name: ${metadata.name || "(unnamed)"}`);
|
|
320
|
+
console.log(`Directory: ${metadata.path || match.directory || "-"}`);
|
|
321
|
+
console.log(`Host: ${metadata.host || "-"}`);
|
|
322
|
+
console.log(`State: ${metadata.lifecycleState || "unknown"}`);
|
|
323
|
+
console.log(`Active: ${activity.active ?? "-"}`);
|
|
324
|
+
console.log(`Thinking: ${activity.thinking ?? "-"}`);
|
|
325
|
+
console.log(`Started by: ${metadata.startedBy || match.startedBy || "-"}`);
|
|
326
|
+
if (metadata.summary?.text) {
|
|
327
|
+
console.log(`Summary: ${metadata.summary.text}`);
|
|
328
|
+
}
|
|
329
|
+
if (metadata.claudeSessionId) {
|
|
330
|
+
console.log(`Claude ID: ${metadata.claudeSessionId}`);
|
|
331
|
+
}
|
|
332
|
+
if (metadata.sessionLink?.url) {
|
|
333
|
+
console.log(`Link: ${metadata.sessionLink.url}`);
|
|
334
|
+
}
|
|
335
|
+
} finally {
|
|
336
|
+
await server.disconnect();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
async function sessionMessages(sessionId, last) {
|
|
340
|
+
const { server, machine } = await connectAndGetMachine();
|
|
341
|
+
try {
|
|
342
|
+
const sessions = await machine.listSessions();
|
|
343
|
+
const match = resolveSessionId(sessions, sessionId);
|
|
344
|
+
const fullId = match.sessionId;
|
|
345
|
+
const svc = await server.getService(`svamp-session-${fullId}`);
|
|
346
|
+
const { messages } = await svc.getMessages(0, 1e3);
|
|
347
|
+
const toShow = last ? messages.slice(-last) : messages;
|
|
348
|
+
if (toShow.length === 0) {
|
|
349
|
+
console.log("No messages yet.");
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
for (const msg of toShow) {
|
|
353
|
+
renderMessage(msg);
|
|
354
|
+
}
|
|
355
|
+
} finally {
|
|
356
|
+
await server.disconnect();
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
async function sessionAttach(sessionId) {
|
|
360
|
+
const { server, machine } = await connectAndGetMachine();
|
|
361
|
+
const sessions = await machine.listSessions();
|
|
362
|
+
const match = resolveSessionId(sessions, sessionId);
|
|
363
|
+
const fullId = match.sessionId;
|
|
364
|
+
let svc;
|
|
365
|
+
try {
|
|
366
|
+
svc = await server.getService(`svamp-session-${fullId}`);
|
|
367
|
+
} catch (err) {
|
|
368
|
+
console.error(`Could not find session service: ${err.message}`);
|
|
369
|
+
await server.disconnect();
|
|
370
|
+
process.exit(1);
|
|
371
|
+
}
|
|
372
|
+
const { metadata } = await svc.getMetadata();
|
|
373
|
+
const flavor = metadata?.flavor || "claude";
|
|
374
|
+
const name = metadata?.name || fullId.slice(0, 8);
|
|
375
|
+
console.log(`Attached to ${flavor} session "${name}". Commands: /quit /abort /kill
|
|
376
|
+
`);
|
|
377
|
+
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
378
|
+
let replayDone = false;
|
|
379
|
+
await svc.registerListener({
|
|
380
|
+
onUpdate: (update) => {
|
|
381
|
+
if (update.type === "new-message") {
|
|
382
|
+
const msg = update.message;
|
|
383
|
+
if (!msg?.id) return;
|
|
384
|
+
if (seenMessageIds.has(msg.id)) return;
|
|
385
|
+
seenMessageIds.add(msg.id);
|
|
386
|
+
if (!replayDone) return;
|
|
387
|
+
renderMessage(msg);
|
|
388
|
+
} else if (update.type === "activity") {
|
|
389
|
+
if (!replayDone) return;
|
|
390
|
+
if (update.thinking) {
|
|
391
|
+
process.stdout.write("\x1B[90m[thinking...]\x1B[0m\r");
|
|
392
|
+
} else if (!update.active) {
|
|
393
|
+
process.stdout.write("\n> ");
|
|
394
|
+
}
|
|
395
|
+
} else if (update.type === "update-session") ;
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
399
|
+
replayDone = true;
|
|
400
|
+
console.log(`\x1B[90m(${seenMessageIds.size} messages in history)\x1B[0m`);
|
|
401
|
+
process.stdout.write("> ");
|
|
402
|
+
const readline = await import('readline');
|
|
403
|
+
const rl = readline.createInterface({
|
|
404
|
+
input: process.stdin,
|
|
405
|
+
output: process.stdout,
|
|
406
|
+
terminal: true
|
|
407
|
+
});
|
|
408
|
+
rl.on("line", async (line) => {
|
|
409
|
+
const trimmed = line.trim();
|
|
410
|
+
if (!trimmed) {
|
|
411
|
+
process.stdout.write("> ");
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (trimmed === "/quit" || trimmed === "/detach") {
|
|
415
|
+
console.log("Detaching (session continues running)...");
|
|
416
|
+
rl.close();
|
|
417
|
+
await server.disconnect();
|
|
418
|
+
process.exit(0);
|
|
419
|
+
}
|
|
420
|
+
if (trimmed === "/abort" || trimmed === "/cancel") {
|
|
421
|
+
try {
|
|
422
|
+
await svc.abort();
|
|
423
|
+
console.log("Abort sent.");
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error(`Abort failed: ${err.message}`);
|
|
426
|
+
}
|
|
427
|
+
process.stdout.write("> ");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (trimmed === "/kill") {
|
|
431
|
+
try {
|
|
432
|
+
await svc.killSession();
|
|
433
|
+
console.log("Session killed.");
|
|
434
|
+
} catch (err) {
|
|
435
|
+
console.error(`Kill failed: ${err.message}`);
|
|
436
|
+
}
|
|
437
|
+
rl.close();
|
|
438
|
+
await server.disconnect();
|
|
439
|
+
process.exit(0);
|
|
440
|
+
}
|
|
441
|
+
if (trimmed === "/info") {
|
|
442
|
+
try {
|
|
443
|
+
const { metadata: m } = await svc.getMetadata();
|
|
444
|
+
const act = await svc.getActivityState();
|
|
445
|
+
console.log(` Agent: ${m?.flavor || "claude"}, State: ${m?.lifecycleState || "?"}, Active: ${act?.active}, Thinking: ${act?.thinking}`);
|
|
446
|
+
} catch (err) {
|
|
447
|
+
console.error(`Info failed: ${err.message}`);
|
|
448
|
+
}
|
|
449
|
+
process.stdout.write("> ");
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
try {
|
|
453
|
+
await svc.sendMessage(
|
|
454
|
+
JSON.stringify({
|
|
455
|
+
role: "user",
|
|
456
|
+
content: { type: "text", text: trimmed }
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
} catch (err) {
|
|
460
|
+
console.error(`Send failed: ${err.message}`);
|
|
461
|
+
process.stdout.write("> ");
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
rl.on("close", async () => {
|
|
465
|
+
await server.disconnect();
|
|
466
|
+
process.exit(0);
|
|
467
|
+
});
|
|
468
|
+
process.on("SIGINT", async () => {
|
|
469
|
+
console.log("\nDetaching (session continues running)...");
|
|
470
|
+
rl.close();
|
|
471
|
+
await server.disconnect();
|
|
472
|
+
process.exit(0);
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
export { sessionAttach, sessionInfo, sessionList, sessionMessages, sessionSpawn, sessionStop };
|