svamp-cli 0.2.289 → 0.2.290

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/{adminCommands-C7-LgldE.mjs → adminCommands-BVvpkVsG.mjs} +1 -1
  2. package/dist/{agentCommands-B7HQJo3X.mjs → agentCommands-fY4keaFA.mjs} +5 -5
  3. package/dist/{auth-BB_4_6P7.mjs → auth-SR7v-8nf.mjs} +1 -1
  4. package/dist/{cli-BH5LIMPI.mjs → cli-Re14H3q0.mjs} +72 -68
  5. package/dist/cli.mjs +2 -2
  6. package/dist/{commands-Cf9KT-Ob.mjs → commands-BC-T6wDo.mjs} +2 -2
  7. package/dist/{commands-CZSPOqtd.mjs → commands-Bv5EcsN_.mjs} +1 -1
  8. package/dist/{commands-BsiQdwUs.mjs → commands-CBsV6z9K.mjs} +2 -2
  9. package/dist/commands-CIyv2Qt-.mjs +258 -0
  10. package/dist/{commands-BaVcBOKA.mjs → commands-Dc9t_OeY.mjs} +1 -1
  11. package/dist/{commands-DFSD7jBH.mjs → commands-Dd-6WXYJ.mjs} +8 -8
  12. package/dist/{commands-BNxsrqSl.mjs → commands-Dwf-ntY-.mjs} +2 -2
  13. package/dist/{commands-Bb-RzoKB.mjs → commands-zagci6-n.mjs} +1 -1
  14. package/dist/{fleet-2c-iNucb.mjs → fleet-DlRlerEG.mjs} +1 -1
  15. package/dist/{frpc-CuvdlU0g.mjs → frpc-B8zTErxt.mjs} +45 -27
  16. package/dist/{headlessCli-CftEZbxm.mjs → headlessCli-D22LyDyO.mjs} +2 -2
  17. package/dist/index.mjs +1 -1
  18. package/dist/{notifyCommands-1q8COUDd.mjs → notifyCommands-5PueOg8Q.mjs} +1 -1
  19. package/dist/package-CpmmajAP.mjs +64 -0
  20. package/dist/{rpc-C9U0Ho27.mjs → rpc-D3Hg4y67.mjs} +1 -1
  21. package/dist/{rpc-DMpJef5v.mjs → rpc-olai-LcU.mjs} +1 -1
  22. package/dist/{run-CCENV09o.mjs → run-B6V1wuT6.mjs} +1 -1
  23. package/dist/{run-BNeZiySO.mjs → run-BNiSOlBf.mjs} +114 -22
  24. package/dist/{scheduler-CAUwniWA.mjs → scheduler--MbVadb0.mjs} +1 -1
  25. package/dist/{serveCommands-XJVDS4zB.mjs → serveCommands-BONMg_af.mjs} +5 -5
  26. package/dist/{sideband-DB4Uipz9.mjs → sideband-Dnuy9-IL.mjs} +1 -1
  27. package/package.json +46 -46
  28. package/dist/package-DrTlgNWY.mjs +0 -64
@@ -0,0 +1,258 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { execSync } from 'node:child_process';
3
+ import { join } from 'node:path';
4
+ import os from 'node:os';
5
+ import { a3 as ChannelStore, a4 as gatewayBase, a5 as genKey } from './run-BNiSOlBf.mjs';
6
+ import 'os';
7
+ import 'fs/promises';
8
+ import 'fs';
9
+ import 'path';
10
+ import 'url';
11
+ import 'child_process';
12
+ import 'crypto';
13
+ import 'node:crypto';
14
+ import 'util';
15
+ import 'http';
16
+ import 'net';
17
+ import 'node:events';
18
+ import '@agentclientprotocol/sdk';
19
+ import 'node:readline';
20
+ import 'node:fs/promises';
21
+ import 'node:util';
22
+ import 'yaml';
23
+
24
+ const DEFAULT_DOWNLOAD_BASE = "https://github.com/amun-ai/hypha-cloud/releases/latest/download";
25
+ function shq(v) {
26
+ return `'${String(v).replace(/'/g, `'\\''`)}'`;
27
+ }
28
+ function psq(v) {
29
+ return `'${String(v).replace(/'/g, `''`)}'`;
30
+ }
31
+ function buildOutpostInstall(params) {
32
+ const base = params.serverBase.replace(/\/$/, "");
33
+ const dl = (params.downloadBase || DEFAULT_DOWNLOAD_BASE).replace(/\/$/, "");
34
+ const session = params.session || params.channel;
35
+ const q = `channel=${encodeURIComponent(params.channel)}&token=${encodeURIComponent(params.token)}&session=${encodeURIComponent(session)}&dl=${encodeURIComponent(dl)}`;
36
+ const unix = `curl -fsSL ${shq(`${base}/outpost/install.sh?${q}`)} | SVAMP_OUTPOST_SERVER=${shq(base)} SVAMP_OUTPOST_CHANNEL=${shq(params.channel)} SVAMP_OUTPOST_TOKEN=${shq(params.token)} SVAMP_OUTPOST_SESSION=${shq(session)} sh`;
37
+ const windows = `$env:SVAMP_OUTPOST_SERVER=${psq(base)}; $env:SVAMP_OUTPOST_CHANNEL=${psq(params.channel)}; $env:SVAMP_OUTPOST_TOKEN=${psq(params.token)}; $env:SVAMP_OUTPOST_SESSION=${psq(session)}; irm ${psq(`${base}/outpost/install.ps1?${q}`)} | iex`;
38
+ const direct = `svamp-outpost --server ${shq(base)} --channel ${shq(params.channel)} --token ${shq(params.token)} --session ${shq(session)}`;
39
+ return { unix, windows, direct };
40
+ }
41
+
42
+ function loadConfig() {
43
+ const p = join(os.homedir(), ".svamp", "outpost.json");
44
+ if (!existsSync(p)) return null;
45
+ try {
46
+ return JSON.parse(readFileSync(p, "utf8"));
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function saveConfig(cfg) {
52
+ writeFileSync(join(os.homedir(), ".svamp", "outpost.json"), JSON.stringify(cfg, null, 2));
53
+ }
54
+ function envVar(name) {
55
+ if (process.env[name]) return process.env[name];
56
+ try {
57
+ for (const line of readFileSync(join(os.homedir(), ".svamp", ".env"), "utf8").split("\n")) {
58
+ const m = line.match(/^\s*([A-Za-z0-9_]+)\s*=\s*(.*)\s*$/);
59
+ if (m && m[1] === name) return m[2].replace(/^["']|["']$/g, "");
60
+ }
61
+ } catch {
62
+ }
63
+ return void 0;
64
+ }
65
+ function machineId() {
66
+ try {
67
+ return JSON.parse(readFileSync(join(os.homedir(), ".svamp", "daemon.state.json"), "utf8")).machineId;
68
+ } catch {
69
+ return void 0;
70
+ }
71
+ }
72
+ function projectDir() {
73
+ try {
74
+ return execSync("git rev-parse --show-toplevel", { encoding: "utf8" }).trim();
75
+ } catch {
76
+ return process.cwd();
77
+ }
78
+ }
79
+ async function outpostUp(args) {
80
+ const session = process.env.SVAMP_SESSION_ID;
81
+ if (!session) {
82
+ console.error("SVAMP_SESSION_ID not set \u2014 run this inside a Svamp session.");
83
+ process.exit(1);
84
+ }
85
+ const workspace = envVar("HYPHA_WORKSPACE");
86
+ const serverUrl = envVar("HYPHA_SERVER_URL") || "https://hypha.aicell.io";
87
+ const mid = machineId();
88
+ if (!workspace || !mid) {
89
+ console.error("Could not resolve workspace/machine (HYPHA_WORKSPACE / daemon.state.json).");
90
+ process.exit(1);
91
+ }
92
+ const channelsServiceId = `${workspace}/${mid}:channels`;
93
+ const gw = gatewayBase(channelsServiceId, serverUrl);
94
+ const channelId = `outpost-${session}`;
95
+ const store = new ChannelStore(projectDir());
96
+ const existing = store.get(channelId);
97
+ let token = existing?.identity?.callers?.find((c) => c.name === "outpost")?.key;
98
+ if (!token) token = genKey();
99
+ const channel = {
100
+ id: channelId,
101
+ name: `Remote outpost \u2014 ${session}`,
102
+ description: "FDE remote machine: a svamp-outpost binary connects here and the agent runs commands on it.",
103
+ enabled: true,
104
+ bind: { session },
105
+ identity: { mode: "per-key", callers: [{ name: "outpost", kind: "agent", key: token }] },
106
+ action: { kind: "message" },
107
+ reply: { mode: "queue" },
108
+ skill: { name: "remote-outpost", description: "Run commands on the connected remote machine." }
109
+ };
110
+ store.save(channel);
111
+ const dl = argVal(args, "--download-base") || envVar("SVAMP_OUTPOST_DL") || "https://static-outpost-bin-08c08c38.svc.hypha.aicell.io";
112
+ saveConfig({ control: "", serverBase: gw, serveBase: dl, token, channel: channelId, session });
113
+ const gen = buildOutpostInstall({ serverBase: gw, channel: channelId, token, session, downloadBase: dl });
114
+ console.log(`\u2713 outpost channel ready for session ${session} (channel "${channelId}", gateway transport)
115
+ `);
116
+ console.log("Give this to the person at the remote machine:\n");
117
+ console.log(" macOS / Linux:\n " + gen.unix + "\n");
118
+ console.log(" Windows (PowerShell):\n " + gen.windows + "\n");
119
+ console.log(" Direct binaries: " + dl);
120
+ console.log('\nThen drive it from this session: svamp outpost exec "<command>" \xB7 svamp outpost status');
121
+ }
122
+ function argVal(args, name) {
123
+ const i = args.indexOf(name);
124
+ return i >= 0 && i + 1 < args.length ? args[i + 1] : void 0;
125
+ }
126
+ async function http(method, url, body) {
127
+ const resp = await fetch(url, {
128
+ method,
129
+ ...body !== void 0 ? { headers: { "content-type": "application/json" }, body: JSON.stringify(body) } : {}
130
+ });
131
+ const text = await resp.text();
132
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}: ${text.slice(0, 200)}`);
133
+ try {
134
+ return JSON.parse(text);
135
+ } catch {
136
+ return text;
137
+ }
138
+ }
139
+ function usage() {
140
+ console.log(`svamp outpost \u2014 drive a connected remote machine (FDE outpost)
141
+
142
+ svamp outpost up Create this session's outpost channel + print the install command
143
+
144
+ svamp outpost status Show connected remotes + counts
145
+ svamp outpost exec "<command>" Run a shell command on the connected remote, print its output
146
+ svamp outpost logs [--json] Show everything the remote(s) have sent back
147
+ svamp outpost install [--os <os>] Print the install one-liner(s) for the current outpost setup
148
+
149
+ Reads the relay control endpoint from ~/.svamp/outpost.json (or pass --control <url>).`);
150
+ }
151
+ async function outpostCommand(args) {
152
+ const sub = args[0];
153
+ const cfg = loadConfig();
154
+ const control = argVal(args, "--control") || cfg?.control;
155
+ if (!sub || sub === "help" || sub === "--help") {
156
+ usage();
157
+ return;
158
+ }
159
+ if (sub === "up") {
160
+ await outpostUp(args.slice(1));
161
+ return;
162
+ }
163
+ if (sub === "install") {
164
+ if (!cfg) {
165
+ console.error("No outpost configured (~/.svamp/outpost.json missing).");
166
+ process.exit(1);
167
+ }
168
+ const gen = buildOutpostInstall({ serverBase: cfg.serverBase, channel: cfg.channel, token: cfg.token, session: cfg.session, downloadBase: cfg.serveBase });
169
+ const only = argVal(args, "--os");
170
+ if (only === "windows") console.log(gen.windows);
171
+ else if (only === "unix" || only === "linux" || only === "darwin" || only === "macos") console.log(gen.unix);
172
+ else {
173
+ console.log("macOS / Linux:\n " + gen.unix + "\n");
174
+ console.log("Windows (PowerShell):\n " + gen.windows + "\n");
175
+ if (cfg.serveBase) console.log("Direct download: " + cfg.serveBase);
176
+ }
177
+ return;
178
+ }
179
+ const gateway = !!cfg?.serverBase && /\/services\/[^/]*:channels/.test(cfg.serverBase);
180
+ if (sub === "exec" && gateway) {
181
+ const cmd = args.slice(1).join(" ").trim();
182
+ if (!cmd) {
183
+ console.error('usage: svamp outpost exec "<command>"');
184
+ process.exit(1);
185
+ }
186
+ const { ChannelOutbox } = await import('./run-BNiSOlBf.mjs').then(function (n) { return n.as; });
187
+ const outbox = new ChannelOutbox(projectDir());
188
+ const id = "x" + Date.now().toString(36);
189
+ outbox.append(cfg.channel, { to: "outpost", body: JSON.stringify({ type: "exec", id, cmd }) });
190
+ console.log(`Sent to the remote (channel ${cfg.channel}). Its output arrives as a message in this session shortly.`);
191
+ return;
192
+ }
193
+ if (sub === "status" && gateway) {
194
+ const ch = new ChannelStore(projectDir()).get(cfg.channel);
195
+ const calls = (ch?.last_calls || []).filter((c) => c.sender === "outpost");
196
+ console.log(calls.length ? `outpost active on ${cfg.channel} \u2014 ${calls.length} recent message(s); last at ${calls[0]?.at}` : `no outpost has connected to ${cfg.channel} yet`);
197
+ return;
198
+ }
199
+ if (sub === "logs" && gateway) {
200
+ console.log(`Gateway mode: the remote's host info + command output arrive as messages in this session's chat (channel ${cfg.channel}).`);
201
+ return;
202
+ }
203
+ if (!control) {
204
+ console.error("No relay control endpoint (~/.svamp/outpost.json missing; run `svamp outpost up`, or pass --control <url>).");
205
+ process.exit(1);
206
+ }
207
+ if (sub === "status") {
208
+ const s = await http("GET", `${control}/state`);
209
+ console.log(`connected remotes: ${s.connectedOutposts} commands sent: ${s.commands} results: ${s.results}`);
210
+ return;
211
+ }
212
+ if (sub === "logs") {
213
+ const r = await http("GET", `${control}/results`);
214
+ if (args.includes("--json")) {
215
+ console.log(JSON.stringify(r, null, 2));
216
+ return;
217
+ }
218
+ for (const m of r) {
219
+ if (m.type === "outpost-hello") console.log(`\u2022 connected: ${m.host?.user}@${m.host?.hostname} (${m.host?.os}/${m.host?.arch}) cwd=${m.host?.cwd}`);
220
+ else if (m.type === "exec-result") console.log(`\u2022 [${m.id}] exit=${m.exitCode}
221
+ ${m.output}`);
222
+ }
223
+ return;
224
+ }
225
+ if (sub === "exec") {
226
+ const cmd = args.slice(1).filter((a) => a !== "--control" && a !== control).join(" ").trim();
227
+ if (!cmd) {
228
+ console.error('usage: svamp outpost exec "<command>"');
229
+ process.exit(1);
230
+ }
231
+ const { enqueued } = await http("POST", `${control}/enqueue?cmd=${encodeURIComponent(cmd)}`, {});
232
+ if (!enqueued?.id) {
233
+ console.error("failed to enqueue");
234
+ process.exit(1);
235
+ }
236
+ const deadline = Date.now() + 6e5;
237
+ for (; ; ) {
238
+ const results = await http("GET", `${control}/results`);
239
+ const hit = results.find((m) => m.type === "exec-result" && m.id === enqueued.id);
240
+ if (hit) {
241
+ process.stdout.write(hit.output || "");
242
+ if (typeof hit.exitCode === "number" && hit.exitCode !== 0) console.error(`
243
+ [exit ${hit.exitCode}]`);
244
+ return;
245
+ }
246
+ if (Date.now() > deadline) {
247
+ console.error("timed out waiting for the remote to respond (is it connected?)");
248
+ process.exit(1);
249
+ }
250
+ await new Promise((r) => setTimeout(r, 500));
251
+ }
252
+ }
253
+ console.error(`unknown outpost command: ${sub}`);
254
+ usage();
255
+ process.exit(1);
256
+ }
257
+
258
+ export { outpostCommand };
@@ -2,7 +2,7 @@ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { execSync, execFileSync } from 'node:child_process';
3
3
  import { basename, resolve, join, isAbsolute } from 'node:path';
4
4
  import os from 'node:os';
5
- import { a3 as formatHandle, a4 as normalizeAllowedUser, a5 as loadSecurityContextConfig, a6 as resolveSecurityContext, a7 as buildSecurityContextFromFlags, a8 as mergeSecurityContexts, c as connectToHypha, a9 as buildSessionShareUrl, aa as computeOutboundHop, i as shortId, ab as registerAwaitingReply, ac as buildMachineShareUrl, ad as parseHandle, ae as handleMatchesMetadata } from './run-BNeZiySO.mjs';
5
+ import { a6 as formatHandle, a7 as normalizeAllowedUser, a8 as loadSecurityContextConfig, a9 as resolveSecurityContext, aa as buildSecurityContextFromFlags, ab as mergeSecurityContexts, c as connectToHypha, ac as buildSessionShareUrl, ad as computeOutboundHop, i as shortId, ae as registerAwaitingReply, af as buildMachineShareUrl, ag as parseHandle, ah as handleMatchesMetadata } from './run-BNiSOlBf.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
8
8
  import 'fs';
@@ -60,7 +60,7 @@ async function serviceExpose(args) {
60
60
  process.exit(1);
61
61
  }
62
62
  if (foreground) {
63
- const { runFrpcTunnel } = await import('./frpc-CuvdlU0g.mjs');
63
+ const { runFrpcTunnel } = await import('./frpc-B8zTErxt.mjs');
64
64
  await runFrpcTunnel(name, ports, void 0, {
65
65
  group,
66
66
  groupKey,
@@ -70,7 +70,7 @@ async function serviceExpose(args) {
70
70
  });
71
71
  return;
72
72
  }
73
- const { connectAndGetMachine } = await import('./commands-BaVcBOKA.mjs');
73
+ const { connectAndGetMachine } = await import('./commands-Dc9t_OeY.mjs');
74
74
  const { server, machine } = await connectAndGetMachine();
75
75
  try {
76
76
  const status = await machine.tunnelStart({
@@ -92,7 +92,7 @@ async function serviceExpose(args) {
92
92
  console.log(` port ${port}: ${url}`);
93
93
  }
94
94
  if (process.env.SVAMP_SESSION_ID) {
95
- const { autoAddSessionLink } = await import('./agentCommands-B7HQJo3X.mjs');
95
+ const { autoAddSessionLink } = await import('./agentCommands-fY4keaFA.mjs');
96
96
  let added = 0;
97
97
  for (const [port, url] of urlEntries) {
98
98
  const label = urlEntries.length > 1 ? `${name}:${port}` : name;
@@ -101,7 +101,7 @@ async function serviceExpose(args) {
101
101
  if (added) console.log(` \u21B3 added to this session's Launch Pad`);
102
102
  }
103
103
  }
104
- console.log(`To stop: svamp service delete ${name} (or: svamp tunnel stop ${name})`);
104
+ console.log(`To stop: svamp service delete ${name}`);
105
105
  } catch (err) {
106
106
  if (/already running/i.test(err?.message || "")) {
107
107
  console.error(`Tunnel '${name}' is already running. Use \`svamp service delete ${name}\` first.`);
@@ -129,7 +129,7 @@ async function serviceServe(args) {
129
129
  console.log(`Serving ${resolvedDir}`);
130
130
  const servePort = 18080;
131
131
  const http = await import('http');
132
- const { serveStaticMount } = await import('./run-BNeZiySO.mjs').then(function (n) { return n.al; });
132
+ const { serveStaticMount } = await import('./run-BNiSOlBf.mjs').then(function (n) { return n.ao; });
133
133
  const server = http.createServer((req, res) => {
134
134
  const u = new URL(req.url || "/", "http://127.0.0.1");
135
135
  let rel = u.pathname;
@@ -140,7 +140,7 @@ async function serviceServe(args) {
140
140
  server.once("error", reject);
141
141
  server.listen(servePort, "127.0.0.1", () => resolve());
142
142
  });
143
- const { runFrpcTunnel } = await import('./frpc-CuvdlU0g.mjs');
143
+ const { runFrpcTunnel } = await import('./frpc-B8zTErxt.mjs');
144
144
  void server;
145
145
  await runFrpcTunnel(name, [servePort]);
146
146
  } catch (err) {
@@ -150,7 +150,7 @@ async function serviceServe(args) {
150
150
  }
151
151
  async function serviceList(_args) {
152
152
  try {
153
- const { connectAndGetMachine } = await import('./commands-BaVcBOKA.mjs');
153
+ const { connectAndGetMachine } = await import('./commands-Dc9t_OeY.mjs');
154
154
  const { server, machine } = await connectAndGetMachine();
155
155
  try {
156
156
  const tunnels = await machine.tunnelList({});
@@ -190,7 +190,7 @@ async function serviceDelete(args) {
190
190
  process.exit(1);
191
191
  }
192
192
  try {
193
- const { connectAndGetMachine } = await import('./commands-BaVcBOKA.mjs');
193
+ const { connectAndGetMachine } = await import('./commands-Dc9t_OeY.mjs');
194
194
  const { server, machine } = await connectAndGetMachine();
195
195
  try {
196
196
  await machine.tunnelStop({ name });
@@ -1,8 +1,8 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { resolve } from 'node:path';
3
- import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-BaVcBOKA.mjs';
3
+ import { connectAndGetMachine, resolveSessionId, createWorktree, connectAndResolveSession } from './commands-Dc9t_OeY.mjs';
4
4
  import { execFileSync } from 'node:child_process';
5
- import { u as updateIssue, m as addComment, n as addIssue, i as shortId } from './run-BNeZiySO.mjs';
5
+ import { u as updateIssue, m as addComment, n as addIssue, i as shortId } from './run-BNiSOlBf.mjs';
6
6
  import 'node:os';
7
7
  import 'os';
8
8
  import 'fs/promises';
@@ -1,5 +1,5 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { j as resolveProjectRoot, I as workflowSteps, z as setWorkflowEnabled, E as isWorkflowEnabled, A as removeWorkflow, x as getWorkflow, D as listWorkflows, B as saveWorkflow, C as rawWorkflow } from './run-BNeZiySO.mjs';
2
+ import { j as resolveProjectRoot, I as workflowSteps, z as setWorkflowEnabled, E as isWorkflowEnabled, A as removeWorkflow, x as getWorkflow, D as listWorkflows, B as saveWorkflow, C as rawWorkflow } from './run-BNiSOlBf.mjs';
3
3
  import 'os';
4
4
  import 'fs/promises';
5
5
  import 'fs';
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import os from 'node:os';
4
- import { c as connectToHypha } from './run-BNeZiySO.mjs';
4
+ import { c as connectToHypha } from './run-BNiSOlBf.mjs';
5
5
  import { PINNED_CLAUDE_CODE_VERSION } from './pinnedClaudeCode-BaMR97BE.mjs';
6
6
  import 'os';
7
7
  import 'fs/promises';
@@ -4,7 +4,7 @@ import { mkdirSync, chmodSync, writeFileSync, unlinkSync, existsSync, readFileSy
4
4
  import { join } from 'path';
5
5
  import { homedir, platform, arch } from 'os';
6
6
  import { randomUUID, createHash } from 'crypto';
7
- import { e as getFrpsSubdomainHost, f as getFrpsServerPort, h as getFrpsServerAddr } from './run-BNeZiySO.mjs';
7
+ import { e as getFrpsSubdomainHost, f as getFrpsServerPort, h as getFrpsServerAddr } from './run-BNiSOlBf.mjs';
8
8
  import 'fs/promises';
9
9
  import 'url';
10
10
  import 'node:crypto';
@@ -278,6 +278,8 @@ class FrpcTunnel {
278
278
  _lastErrorAt = 0;
279
279
  _firstErrorAt = 0;
280
280
  _restartAttempts = 0;
281
+ _restartTimer = null;
282
+ // #0583: pending reconnect timer (cancelled on destroy)
281
283
  // End-to-end probe state (set when options.probeUrl is provided).
282
284
  _probeTimer = null;
283
285
  _lastProbeOkAt = 0;
@@ -309,7 +311,8 @@ class FrpcTunnel {
309
311
  const multiPort = options.ports.length > 1;
310
312
  this.proxies = options.ports.map((port) => {
311
313
  const standaloneHash = createHash("sha256").update(multiPort ? `${options.name}|${port}|${machineFingerprint}` : `${options.name}|${machineFingerprint}`).digest("hex").slice(0, 8);
312
- const subdomain = options.subdomains?.get(port) || (options.group ? `${options.group}-${createHash("sha256").update(options.group).digest("hex").slice(0, 8)}` : `${options.name}-${standaloneHash}`);
314
+ const groupSubdomainKey = multiPort ? `${options.group}|${port}` : `${options.group}`;
315
+ const subdomain = options.subdomains?.get(port) || (options.group ? `${options.group}-${createHash("sha256").update(groupSubdomainKey).digest("hex").slice(0, 8)}` : `${options.name}-${standaloneHash}`);
313
316
  const proxyName = `${options.name}-${port}-${machineHash}`;
314
317
  return {
315
318
  name: proxyName,
@@ -332,6 +335,29 @@ class FrpcTunnel {
332
335
  }
333
336
  this.configPath = join(configDir, `${options.name}.toml`);
334
337
  }
338
+ /**
339
+ * #0583: schedule a backoff reconnect after the child went down (spawn error OR exit).
340
+ * Extracted from the exit handler so BOTH the spawn-'error' and 'exit' paths share it, and
341
+ * so it is unit-testable without a real spawn. No-op once destroyed.
342
+ */
343
+ _reconnectAfterDown(reason) {
344
+ if (this._destroyed) return;
345
+ const STABILITY_WINDOW_MS = 3e4;
346
+ if (this._lastConnectedAt > 0 && Date.now() - this._lastConnectedAt >= STABILITY_WINDOW_MS) {
347
+ this._restartAttempts = 0;
348
+ }
349
+ this._restartAttempts++;
350
+ const delay = Math.min(3e3 * Math.pow(2, this._restartAttempts - 1), 6e4);
351
+ this.log(`frpc down (${reason}), restarting in ${Math.round(delay / 1e3)}s (attempt ${this._restartAttempts})...`);
352
+ this._restartTimer = setTimeout(() => {
353
+ this._restartTimer = null;
354
+ if (!this._destroyed) {
355
+ this.connect().catch((err) => {
356
+ this.options.onError?.(err);
357
+ });
358
+ }
359
+ }, delay);
360
+ }
335
361
  /** Connect to frps by starting the frpc process. */
336
362
  async connect() {
337
363
  if (this._destroyed) return;
@@ -404,37 +430,25 @@ class FrpcTunnel {
404
430
  };
405
431
  child.stdout?.on("data", onOutput);
406
432
  child.stderr?.on("data", onOutput);
407
- child.on("error", (err) => {
408
- this.options.onError?.(err);
409
- if (!resolved) {
410
- resolved = true;
411
- reject(err);
412
- }
413
- });
414
- child.on("exit", (code) => {
433
+ let downHandled = false;
434
+ const onChildDown = (rejectErr) => {
435
+ if (downHandled) return;
436
+ downHandled = true;
415
437
  this._connected = false;
416
438
  this.process = null;
417
439
  this.options.onDisconnect?.();
418
440
  if (!resolved) {
419
441
  resolved = true;
420
- reject(new Error(`frpc exited with code ${code}`));
421
- }
422
- if (!this._destroyed) {
423
- const STABILITY_WINDOW_MS = 3e4;
424
- if (this._lastConnectedAt > 0 && Date.now() - this._lastConnectedAt >= STABILITY_WINDOW_MS) {
425
- this._restartAttempts = 0;
426
- }
427
- this._restartAttempts++;
428
- const delay = Math.min(3e3 * Math.pow(2, this._restartAttempts - 1), 6e4);
429
- this.log(`frpc exited with code ${code}, restarting in ${Math.round(delay / 1e3)}s (attempt ${this._restartAttempts})...`);
430
- setTimeout(() => {
431
- if (!this._destroyed) {
432
- this.connect().catch((err) => {
433
- this.options.onError?.(err);
434
- });
435
- }
436
- }, delay);
442
+ reject(rejectErr);
437
443
  }
444
+ this._reconnectAfterDown(rejectErr.message);
445
+ };
446
+ child.on("error", (err) => {
447
+ this.options.onError?.(err);
448
+ onChildDown(err instanceof Error ? err : new Error(String(err)));
449
+ });
450
+ child.on("exit", (code) => {
451
+ onChildDown(new Error(`frpc exited with code ${code}`));
438
452
  });
439
453
  if (this.options.probeUrl || this.options.probePath) {
440
454
  this.startProbeLoop({
@@ -625,6 +639,10 @@ class FrpcTunnel {
625
639
  this._connected = false;
626
640
  this.stopProbeLoop();
627
641
  this.stopAdminStatusLoop();
642
+ if (this._restartTimer) {
643
+ clearTimeout(this._restartTimer);
644
+ this._restartTimer = null;
645
+ }
628
646
  if (this.process) {
629
647
  this.process.kill("SIGTERM");
630
648
  const p = this.process;
@@ -1,5 +1,5 @@
1
- import { a0 as resolveModel, af as describeMisconfiguration, ag as buildMachineDeps } from './run-BNeZiySO.mjs';
2
- import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-DB4Uipz9.mjs';
1
+ import { a0 as resolveModel, ai as describeMisconfiguration, aj as buildMachineDeps } from './run-BNiSOlBf.mjs';
2
+ import { handleRealtimeEvent, initMachineVoiceSession } from './sideband-Dnuy9-IL.mjs';
3
3
  import { WebSocket } from 'ws';
4
4
  import { execSync, spawn } from 'child_process';
5
5
  import 'os';
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-BNeZiySO.mjs';
1
+ export { c as connectToHypha, a as createSessionStore, d as daemonStatus, g as getHyphaServerUrl, r as registerMachineService, s as startDaemon, b as stopDaemon } from './run-BNiSOlBf.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { c as connectToHypha, S as SharingNotificationSync, J as parseJwtEmail } from './run-BNeZiySO.mjs';
1
+ import { c as connectToHypha, S as SharingNotificationSync, J as parseJwtEmail } from './run-BNiSOlBf.mjs';
2
2
  import { randomUUID } from 'node:crypto';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
  import { join } from 'node:path';
@@ -0,0 +1,64 @@
1
+ var name = "svamp-cli";
2
+ var version = "0.2.290";
3
+ var description = "Svamp CLI — AI workspace daemon on Hypha Cloud";
4
+ var author = "Amun AI AB";
5
+ var license = "SEE LICENSE IN LICENSE";
6
+ var type = "module";
7
+ var bin = {
8
+ svamp: "./bin/svamp.mjs"
9
+ };
10
+ var files = [
11
+ "dist",
12
+ "bin"
13
+ ];
14
+ var main = "./dist/index.mjs";
15
+ var exports$1 = {
16
+ ".": "./dist/index.mjs",
17
+ "./cli": "./dist/cli.mjs"
18
+ };
19
+ var scripts = {
20
+ build: "rm -rf dist bin/skills bin/commands && mkdir -p bin/skills && cp -r ../../skills/artifact bin/skills/artifact && cp -r ../../skills/crew bin/skills/crew && cp -r ../../commands bin/commands && tsc --noEmit && pkgroll",
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-sharing-notify-sync.mjs && npx tsx test/test-staged-homes-sweep.mjs && npx tsx test/test-session-helpers.mjs && npx tsx test/test-cli-routing.mjs && npx tsx test/test-security-context.mjs && npx tsx test/test-isolation-decision.mjs && npx tsx test/test-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-backend-accounts.mjs && npx tsx test/test-codex-force-model.mjs && npx tsx test/test-backend-oauth.mjs && npx tsx test/test-btw-proxy-env.mjs && npx tsx test/test-output-formatters.mjs && npx tsx test/test-inbox-guard.mjs && npx tsx test/test-user-events.mjs && npx tsx test/test-migrate-children.mjs && npx tsx test/test-outpost-install.mjs && npx tsx test/test-inbox-reconcile.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-isolation-disable.mjs && npx tsx test/test-machine-service-logic.mjs && npx tsx test/test-session-access-fallback.mjs && npx tsx test/test-interactive-helpers.mjs && npx tsx test/test-codex-app-server.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-compact-detect.mjs && npx tsx test/test-session-consolidation.mjs && npx tsx test/test-inbox-store.mjs && npx tsx test/test-inbox.mjs && npx tsx test/test-inbox-cross-machine.mjs && npx tsx test/test-issue-store.mjs && npx tsx test/test-issue-close-gate.mjs && npx tsx test/test-loop-verify.mjs && npx tsx test/test-restore-decision.mjs && npx tsx test/test-issue-rpc.mjs && npx tsx test/test-issue-pause.mjs && npx tsx test/test-workflow-store.mjs && npx tsx test/test-workflow-rpc.mjs && npx tsx test/test-workflow-scheduler.mjs && npx tsx test/test-cron.mjs && npx tsx test/test-workflow-runs.mjs && npx tsx test/test-workflow-idle.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-ws-upgrade.mjs && npx tsx test/test-serve-auth.mjs && npx tsx test/test-static-file-server.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-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-hotreload-seam.mjs && npx tsx test/test-hot-reload.mjs && npx tsx test/test-hot-reload-live.mjs && npx tsx test/test-session-core.mjs && npx tsx test/test-channel-async-reply.mjs && npx tsx test/test-outbox-reload.mjs && npx tsx test/test-channel-binding.mjs && npx tsx test/test-channel-identity.mjs && npx tsx test/test-stateless-dispatch-limiter.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-channel-upload.mjs && npx tsx test/test-wise-voice.mjs && npx tsx test/test-wise-headless.mjs && npx tsx test/test-wise-machine.mjs && npx tsx test/test-crew-merge.mjs && npx tsx test/test-crew-verdict-routing.mjs && npx tsx test/test-crew-standalone.mjs && npx tsx test/test-session-links.mjs && npx tsx test/test-issue-loop-pause-guard.mjs && npx tsx test/test-artifact-guard.mjs && npx tsx test/test-artifact-sync-pagination.mjs && npx tsx test/test-graceful-restart.mjs && npx tsx test/test-flush-exit.mjs",
23
+ "test:hypha": "node --no-warnings test/test-hypha-service.mjs",
24
+ dev: "tsx src/cli.ts",
25
+ "dev:daemon": "tsx src/cli.ts daemon start-sync",
26
+ "test:e2e": "node --no-warnings test/e2e-session-tests.mjs",
27
+ "test:frpc": "npx tsx test/test-frpc-e2e.mjs",
28
+ prepublishOnly: "yarn build"
29
+ };
30
+ var 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
+ var 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
+ var packageManager = "yarn@1.22.22";
47
+ var _package = {
48
+ name: name,
49
+ version: version,
50
+ description: description,
51
+ author: author,
52
+ license: license,
53
+ type: type,
54
+ bin: bin,
55
+ files: files,
56
+ main: main,
57
+ exports: exports$1,
58
+ scripts: scripts,
59
+ dependencies: dependencies,
60
+ devDependencies: devDependencies,
61
+ packageManager: packageManager
62
+ };
63
+
64
+ export { author, bin, _package as default, dependencies, description, devDependencies, exports$1 as exports, files, license, main, name, packageManager, scripts, type, version };
@@ -1,4 +1,4 @@
1
- import { j as resolveProjectRoot, v as getRun, w as listRuns, x as getWorkflow, y as runWorkflow, z as setWorkflowEnabled, A as removeWorkflow, B as saveWorkflow, C as rawWorkflow, D as listWorkflows } from './run-BNeZiySO.mjs';
1
+ import { j as resolveProjectRoot, v as getRun, w as listRuns, x as getWorkflow, y as runWorkflow, z as setWorkflowEnabled, A as removeWorkflow, B as saveWorkflow, C as rawWorkflow, D as listWorkflows } from './run-BNiSOlBf.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import { j as resolveProjectRoot, u as updateIssue, k as getIssue, l as resumeIssue, p as pauseIssue, m as addComment, n as addIssue, o as listIssues, q as searchIssues, t as isVisibleTo } from './run-BNeZiySO.mjs';
1
+ import { j as resolveProjectRoot, u as updateIssue, k as getIssue, l as resumeIssue, p as pauseIssue, m as addComment, n as addIssue, o as listIssues, q as searchIssues, t as isVisibleTo } from './run-BNiSOlBf.mjs';
2
2
  import 'os';
3
3
  import 'fs/promises';
4
4
  import 'fs';
@@ -1,4 +1,4 @@
1
- import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { ah as applyClaudeProxyEnv, ai as composeSessionId, aj as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, ak as generateHookSettings } from './run-BNeZiySO.mjs';
1
+ import{createRequire as _pkgrollCR}from"node:module";const require=_pkgrollCR(import.meta.url);import { ak as applyClaudeProxyEnv, al as composeSessionId, am as generateFriendlyName, c as connectToHypha, a as createSessionStore, r as registerMachineService, an as generateHookSettings } from './run-BNiSOlBf.mjs';
2
2
  import os from 'node:os';
3
3
  import { resolve, join } from 'node:path';
4
4
  import { existsSync, readFileSync, watch } from 'node:fs';