promigence 0.1.0-beta.12

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 (97) hide show
  1. package/NOTICE +14 -0
  2. package/README.md +88 -0
  3. package/dist/account.js +850 -0
  4. package/dist/agent.js +374 -0
  5. package/dist/argv.js +159 -0
  6. package/dist/bin.js +332 -0
  7. package/dist/bundle.js +133 -0
  8. package/dist/capacity.js +69 -0
  9. package/dist/commands.d.ts +110 -0
  10. package/dist/commands.js +2675 -0
  11. package/dist/connectreq.js +125 -0
  12. package/dist/credfile.js +188 -0
  13. package/dist/credits.js +197 -0
  14. package/dist/devcontainer.d.ts +99 -0
  15. package/dist/devcontainer.js +744 -0
  16. package/dist/device.js +129 -0
  17. package/dist/dirsnap.js +378 -0
  18. package/dist/errfix.js +262 -0
  19. package/dist/generated.d.ts +35 -0
  20. package/dist/generated.js +307 -0
  21. package/dist/github.js +307 -0
  22. package/dist/gitlab.js +210 -0
  23. package/dist/guide.d.ts +19 -0
  24. package/dist/guide.js +366 -0
  25. package/dist/handlers.js +511 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.js +7 -0
  28. package/dist/init.d.ts +40 -0
  29. package/dist/init.js +352 -0
  30. package/dist/install.d.ts +23 -0
  31. package/dist/install.js +127 -0
  32. package/dist/io.d.ts +37 -0
  33. package/dist/io.js +318 -0
  34. package/dist/live.js +469 -0
  35. package/dist/login.js +1063 -0
  36. package/dist/mcp.js +889 -0
  37. package/dist/money.js +5 -0
  38. package/dist/orgs.js +475 -0
  39. package/dist/pricing.d.ts +52 -0
  40. package/dist/pricing.js +181 -0
  41. package/dist/report.js +132 -0
  42. package/dist/routes.js +135 -0
  43. package/dist/runners-github.js +257 -0
  44. package/dist/runners-gitlab.js +125 -0
  45. package/dist/runners-spec.js +137 -0
  46. package/dist/runners.js +1458 -0
  47. package/dist/sdk/blake3.d.ts +10 -0
  48. package/dist/sdk/blake3.js +111 -0
  49. package/dist/sdk/bundle.d.ts +96 -0
  50. package/dist/sdk/bundle.js +227 -0
  51. package/dist/sdk/ca-guard.js +166 -0
  52. package/dist/sdk/caller.d.ts +73 -0
  53. package/dist/sdk/caller.js +47 -0
  54. package/dist/sdk/client.d.ts +3015 -0
  55. package/dist/sdk/client.js +2583 -0
  56. package/dist/sdk/command-errors.d.ts +41 -0
  57. package/dist/sdk/command-errors.js +36 -0
  58. package/dist/sdk/context.d.ts +119 -0
  59. package/dist/sdk/context.js +588 -0
  60. package/dist/sdk/core.d.ts +285 -0
  61. package/dist/sdk/core.js +266 -0
  62. package/dist/sdk/credstore.d.ts +90 -0
  63. package/dist/sdk/credstore.js +206 -0
  64. package/dist/sdk/dispatcher.js +86 -0
  65. package/dist/sdk/github.d.ts +158 -0
  66. package/dist/sdk/github.js +78 -0
  67. package/dist/sdk/gitlab.d.ts +104 -0
  68. package/dist/sdk/gitlab.js +20 -0
  69. package/dist/sdk/handles.d.ts +82 -0
  70. package/dist/sdk/handles.js +135 -0
  71. package/dist/sdk/hint.js +7 -0
  72. package/dist/sdk/http2.js +1921 -0
  73. package/dist/sdk/index.d.ts +34 -0
  74. package/dist/sdk/index.js +15 -0
  75. package/dist/sdk/keyed.gen.js +7 -0
  76. package/dist/sdk/models.gen.d.ts +6162 -0
  77. package/dist/sdk/models.gen.js +1 -0
  78. package/dist/sdk/proxy.js +249 -0
  79. package/dist/sdk/pty.d.ts +171 -0
  80. package/dist/sdk/pty.js +496 -0
  81. package/dist/sdk/sandbox.d.ts +537 -0
  82. package/dist/sdk/sandbox.js +725 -0
  83. package/dist/sdk/ssh.d.ts +25 -0
  84. package/dist/sdk/ssh.js +114 -0
  85. package/dist/sdk/trace.js +80 -0
  86. package/dist/sdk/transport-worker.js +466 -0
  87. package/dist/sdk/warm.js +40 -0
  88. package/dist/ssh.js +267 -0
  89. package/dist/surface.d.ts +95 -0
  90. package/dist/surface.js +1452 -0
  91. package/dist/templates.js +53 -0
  92. package/dist/term.js +50 -0
  93. package/dist/timing.js +71 -0
  94. package/dist/untar.js +324 -0
  95. package/dist/uploadguard.js +75 -0
  96. package/llms.txt +209 -0
  97. package/package.json +52 -0
package/dist/live.js ADDED
@@ -0,0 +1,469 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { ptyCommand, shellQuote, toEntryInfo, } from "./sdk/index.js";
3
+ import { bool, client, list, nexts, positional, required, str, } from "./commands.js";
4
+ import { parseRange } from "./handlers.js";
5
+ import { emit, exitWith, noCommandFix, okEnvelope, parseCommandTimeout, parseMaxOutput, usageError, write } from "./io.js";
6
+ import { fieldForTerminal, jsonLine, sh, shellArg } from "./term.js";
7
+ const FOLLOW_WAIT_MS = 1_000;
8
+ const FOLLOW_POLL_MS = 250;
9
+ const pause = (ms) => new Promise((r) => { setTimeout(r, ms); });
10
+ const sandboxFlag = (ctx) => required(ctx, "--sandbox", `Pass --sandbox sbx_<id> (from \`promigence run report <run_id>\`), e.g. promigence ${ctx.cmd.name} … --sandbox sbx_01.`);
11
+ export const RECONNECTED_LINE = "the service restarted; reconnected to a new shell";
12
+ const sandboxShell = async (ctx) => {
13
+ const sid = positional(ctx, "a sandbox id", "promigence sandbox shell sbx_01");
14
+ const tail = ctx.cmdTail.length ? ctx.cmdTail.map(shellQuote).join(" ") : undefined;
15
+ const flagCmd = str(ctx, "--cmd");
16
+ if (tail !== undefined && flagCmd !== undefined) {
17
+ throw usageError("sandbox shell takes --cmd or a command after --, not both", "Pick one: promigence sandbox shell sbx_01 -- htop.");
18
+ }
19
+ const cwd = str(ctx, "--cwd");
20
+ const envs = {};
21
+ for (const kv of list(ctx, "--env")) {
22
+ const eq = kv.indexOf("=");
23
+ if (eq <= 0)
24
+ throw usageError(`--env '${kv}' is not K=V`, "Pass --env NAME=value (repeatable).");
25
+ envs[kv.slice(0, eq)] = kv.slice(eq + 1);
26
+ }
27
+ const secrets = [];
28
+ for (const spec of list(ctx, "--secret")) {
29
+ const eq = spec.indexOf("=");
30
+ if (eq === 0)
31
+ throw usageError(`--secret '${spec}' has no name`, "Pass --secret NAME or --secret NAME=VAR.");
32
+ secrets.push(eq < 0 ? spec : { name: spec.slice(0, eq), as: spec.slice(eq + 1) });
33
+ }
34
+ const inner = tail ?? flagCmd;
35
+ const cmd = ptyCommand({
36
+ ...(inner === undefined ? {} : { cmd: tail !== undefined ? `exec ${inner}` : inner }),
37
+ ...(cwd === undefined ? {} : { cwd }),
38
+ envs,
39
+ });
40
+ const stdin = process.stdin;
41
+ const stdout = process.stdout;
42
+ const tty = Boolean(stdin.isTTY && stdout.isTTY);
43
+ const size = () => ({ cols: stdout.columns || 80, rows: stdout.rows || 24 });
44
+ const session = await client(ctx).sandboxes.pty(sid, {
45
+ ...size(),
46
+ ...(cmd === undefined ? {} : { cmd }),
47
+ ...(secrets.length ? { secrets } : {}),
48
+ onData: (d) => { stdout.write(d); },
49
+ reconnect: inner === undefined,
50
+ onReconnected: ({ resumed }) => {
51
+ if (resumed)
52
+ return;
53
+ const line = RECONNECTED_LINE;
54
+ void write(process.stderr, process.stderr.isTTY ? `\r\n\x1b[2m${line}\x1b[0m\r\n` : `promigence sandbox shell: ${line}\n`);
55
+ },
56
+ });
57
+ const restore = () => {
58
+ if (tty && stdin.isRaw)
59
+ stdin.setRawMode(false);
60
+ stdin.pause();
61
+ };
62
+ if (tty)
63
+ stdin.setRawMode(true);
64
+ stdin.on("data", (d) => session.write(d));
65
+ stdin.on("end", () => session.eof());
66
+ stdin.resume();
67
+ const onResize = () => session.resize(size());
68
+ if (tty)
69
+ stdout.on("resize", onResize);
70
+ for (const sig of ["SIGTERM", "SIGHUP"]) {
71
+ process.on(sig, () => { restore(); session.disconnect(); });
72
+ }
73
+ const exit = await session.wait();
74
+ restore();
75
+ stdout.off("resize", onResize);
76
+ if (exit.exitCode === null && exit.signal === null) {
77
+ const why = exit.error && exit.error.code !== "closed"
78
+ ? `the terminal failed: ${exit.error.code}: ${exit.error.message}`
79
+ : "the connection closed without an exit record";
80
+ await write(process.stderr, `\r\npromigence sandbox shell: ${why}\r\n`);
81
+ return exitWith(125);
82
+ }
83
+ return exitWith(exit.exitCode ?? 128 + (exit.signal ?? 0));
84
+ };
85
+ const portExpose = async (ctx) => {
86
+ const raw = positional(ctx, "a port", "promigence port expose 3000 --sandbox sbx_01");
87
+ const port = Number(raw);
88
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
89
+ throw usageError(`'${raw}' is not a port`, "Pass the port your process listens on inside the sandbox, 1..65535.");
90
+ }
91
+ const sid = sandboxFlag(ctx);
92
+ const p = await client(ctx).sandboxes.ports.expose(sid, port);
93
+ const next = nexts([sh `promigence port list --sandbox ${sid}`, "every URL this sandbox has"], [sh `promigence port revoke ${port} --sandbox ${sid}`, "take the URL down (anyone with it can reach the port)"]);
94
+ if (ctx.mode === "text") {
95
+ await write(process.stdout, `${p.url}\n`);
96
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {}, next), [`port ${port} of ${sid} is public at the URL above until revoked`], 0, ctx.pointer, "stderr");
97
+ }
98
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...p }, next), [], 0, ctx.pointer);
99
+ };
100
+ const portList = async (ctx) => {
101
+ const sid = sandboxFlag(ctx);
102
+ const ports = await client(ctx).sandboxes.ports.list(sid);
103
+ const next = ports.length ? nexts([sh `promigence port revoke PORT --sandbox ${sid}`, "stop publishing one: put its port from this list in place of PORT"]) : [];
104
+ const lines = ports.length
105
+ ? ports.map((p) => ` ${String(p.port).padEnd(6)} ${p.url}`)
106
+ : [` no published ports on ${sid}`];
107
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, ports }, next), [`ports of ${sid}:`, ...lines], 0, ctx.pointer);
108
+ };
109
+ const portRevoke = async (ctx) => {
110
+ const which = positional(ctx, "a token or a port", "promigence port revoke 3000 --sandbox sbx_01");
111
+ const sid = sandboxFlag(ctx);
112
+ const r = await client(ctx).sandboxes.ports.revoke(sid, which);
113
+ const next = nexts([sh `promigence port list --sandbox ${sid}`, "what is still published"]);
114
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, revoked: r.revoked }, next), [`${sid}: revoked ${r.revoked.length} URL${r.revoked.length === 1 ? "" : "s"}`], 0, ctx.pointer);
115
+ };
116
+ export async function execBackground(ctx) {
117
+ const sid = str(ctx, "--sandbox");
118
+ if (sid === undefined) {
119
+ throw usageError("exec --background needs --sandbox SID", "Background execs run in one sandbox: promigence exec --sandbox sbx_01 --background -- npm run dev.");
120
+ }
121
+ if (str(ctx, "--run") !== undefined)
122
+ throw usageError("exec --background takes --sandbox, not --run", "Start it in one sandbox.");
123
+ if (ctx.cmdTail.length === 0) {
124
+ throw usageError("no command: everything after `--` is the command to run", noCommandFix("e.g. promigence exec --sandbox sbx_01 --background -- npm run dev."));
125
+ }
126
+ const maxOutput = str(ctx, "--max-output");
127
+ const timeout = str(ctx, "--timeout");
128
+ const secrets = list(ctx, "--secret");
129
+ const stdin = bool(ctx, "--stdin");
130
+ const rec = await client(ctx).sandboxes.execBackground(sid, {
131
+ argv: ctx.cmdTail,
132
+ ...(secrets.length ? { secrets } : {}),
133
+ ...(stdin ? { stdin: true } : {}),
134
+ ...(maxOutput === undefined ? {} : { maxOutputBytes: parseMaxOutput(maxOutput) }),
135
+ ...(timeout === undefined ? {} : { timeoutMs: parseCommandTimeout(timeout, "--timeout") }),
136
+ });
137
+ const stdinHint = stdin
138
+ ? [[sh `promigence exec stdin ${rec.exec_id} --sandbox ${sid}`, "write to its stdin, then close it"]]
139
+ : [];
140
+ const next = nexts(...stdinHint, [sh `promigence exec output ${rec.exec_id} --sandbox ${sid} --follow`, "stream its output until it exits"], [sh `promigence exec kill ${rec.exec_id} --sandbox ${sid}`, "stop it"], [sh `promigence exec kill ${rec.exec_id} --sandbox ${sid} --forget`, FORGET_WHY]);
141
+ if (ctx.mode === "text") {
142
+ await write(process.stdout, `${rec.exec_id}\n`);
143
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {}, next), [`started in the background on ${sid}: ${ctx.cmdTail.join(" ")}`, ` next: ${next[0].cmd}`], 0, false);
144
+ }
145
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, exec_id: rec.exec_id, state: rec.state, argv: ctx.cmdTail, background: true }, next), [], 0, false);
146
+ }
147
+ export const FORGET_WHY = "once it has finished: until its output is read to the end or it is forgotten, it holds off an idle pause for a minute";
148
+ function exitOf(r) {
149
+ if (!r.exit)
150
+ return undefined;
151
+ return r.exit.exit_code ?? (r.exit.signal ? 128 + r.exit.signal : undefined);
152
+ }
153
+ const execPs = async (ctx) => {
154
+ const sid = sandboxFlag(ctx);
155
+ const execs = await client(ctx).sandboxes.execs.list(sid);
156
+ const running = execs.find((e) => e.state === "running");
157
+ const next = nexts(...(execs.length ? [[sh `promigence exec output ${execs[execs.length - 1].exec_id} --sandbox ${sid}`, "read one's output"]] : []), ...(running ? [[sh `promigence exec kill EXEC_ID --sandbox ${sid}`, "stop one: put its id from this list in place of EXEC_ID"]] : []));
158
+ const lines = execs.length
159
+ ? execs.map((e) => {
160
+ const code = exitOf(e);
161
+ return ` ${e.exec_id.padEnd(28)} ${(e.state + (code === undefined ? "" : ` ${code}`)).padEnd(10)} ${e.argv}`;
162
+ })
163
+ : [` no background execs on ${sid}`];
164
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, execs }, next), [`background execs on ${sid}:`, ...lines], 0, ctx.pointer);
165
+ };
166
+ export function signalOk(sig) {
167
+ if (/^\d+$/.test(sig))
168
+ return Number(sig) >= 1 && Number(sig) <= 64;
169
+ const name = sig.toUpperCase().replace(/^SIG/, "");
170
+ return ["HUP", "INT", "QUIT", "KILL", "USR1", "USR2", "TERM", "CONT", "STOP"].includes(name);
171
+ }
172
+ const execKill = async (ctx) => {
173
+ const eid = positional(ctx, "an exec id", "promigence exec kill x_4f1c2a9e --sandbox sbx_01");
174
+ const sid = sandboxFlag(ctx);
175
+ const c = client(ctx);
176
+ const sig = str(ctx, "--signal") ?? "SIGKILL";
177
+ if (!signalOk(sig)) {
178
+ throw usageError(`--signal ${sig} is not a signal`, "Pass a number 1-64 or a name: HUP, INT, QUIT, KILL, USR1, USR2, TERM, CONT, STOP (with or without SIG).");
179
+ }
180
+ const r = await c.sandboxes.execs.signal(sid, eid, /^\d+$/.test(sig) ? Number(sig) : sig);
181
+ let forgotten = false;
182
+ if (bool(ctx, "--forget")) {
183
+ await c.sandboxes.execs.forget(sid, eid);
184
+ forgotten = true;
185
+ }
186
+ const next = nexts([sh `promigence exec ps --sandbox ${sid}`, "what is still running"]);
187
+ const what = r.delivered ? `signal ${r.signal} delivered` : "had already exited";
188
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, exec_id: eid, signal: r.signal, delivered: r.delivered, forgotten }, next), [`${eid}: ${what}${forgotten ? "; handle and output dropped" : ""}`], 0, ctx.pointer);
189
+ };
190
+ export async function backgroundOutput(ctx, execId) {
191
+ const sid = str(ctx, "--sandbox");
192
+ if (sid === undefined) {
193
+ throw usageError(`${execId} is a background exec; its output lives on the sandbox that runs it`, `Name it: ${sh `promigence exec output ${execId} --sandbox sbx_<id>`}.`);
194
+ }
195
+ const c = client(ctx);
196
+ if (bool(ctx, "--follow")) {
197
+ const off = { stdout: 0, stderr: 0 };
198
+ const dec = { stdout: new TextDecoder(), stderr: new TextDecoder() };
199
+ let rec = await c.sandboxes.execs.get(sid, execId);
200
+ for (;;) {
201
+ const ended = rec.state !== "running";
202
+ let more = false;
203
+ for (const s of ["stdout", "stderr"]) {
204
+ const r = await c.sandboxes.execs.read(sid, execId, s, { offset: off[s] });
205
+ rec = r.record;
206
+ if (r.data.length) {
207
+ more = true;
208
+ off[s] += r.data.length;
209
+ const text = dec[s].decode(r.data, { stream: true });
210
+ if (ctx.mode === "json")
211
+ await write(process.stdout, jsonLine(process.stdout, { stream: s, data: text }));
212
+ else
213
+ await write(s === "stdout" ? process.stdout : process.stderr, text);
214
+ }
215
+ }
216
+ if (ended && !more)
217
+ break;
218
+ if (!more) {
219
+ rec = await c.sandboxes.execs.get(sid, execId, { waitMs: FOLLOW_WAIT_MS });
220
+ if (rec.state === "running" && !((rec.waited_ms ?? 0) >= FOLLOW_WAIT_MS))
221
+ await pause(FOLLOW_POLL_MS);
222
+ }
223
+ }
224
+ const code = exitOf(rec) ?? (rec.state === "failed" ? 125 : 0);
225
+ const forget = nexts([sh `promigence exec kill ${execId} --sandbox ${sid} --forget`, FORGET_WHY]);
226
+ if (ctx.mode === "json") {
227
+ await write(process.stdout, jsonLine(process.stdout, okEnvelope(ctx.cmd.name, { sandbox_id: sid, exec_id: execId, state: rec.state, exit: rec.exit ?? null, ...(rec.error ? { error: rec.error } : {}) }, forget)));
228
+ }
229
+ return exitWith(code);
230
+ }
231
+ const stream = bool(ctx, "--stderr") ? "stderr" : "stdout";
232
+ const range = str(ctx, "--range");
233
+ const rg = range === undefined ? {} : parseRange(range);
234
+ const from = rg.from ?? 0;
235
+ const len = rg.to === undefined ? undefined : rg.to - from + 1;
236
+ const parts = [];
237
+ let at = from;
238
+ let rec;
239
+ for (;;) {
240
+ const want = len === undefined ? undefined : len - (at - from);
241
+ if (want !== undefined && want <= 0)
242
+ break;
243
+ const r = await c.sandboxes.execs.read(sid, execId, stream, { offset: at, ...(want === undefined ? {} : { len: want }) });
244
+ rec = r.record;
245
+ if (r.data.length === 0)
246
+ break;
247
+ parts.push(r.data);
248
+ at += r.data.length;
249
+ }
250
+ const bytes = Buffer.concat(parts);
251
+ if (ctx.mode === "text") {
252
+ await new Promise((resolve) => { process.stdout.write(bytes, () => resolve()); });
253
+ return exitWith(0);
254
+ }
255
+ const next = rec !== undefined && rec.state !== "running"
256
+ ? nexts([sh `promigence exec kill ${execId} --sandbox ${sid} --forget`, FORGET_WHY])
257
+ : nexts([sh `promigence exec output ${execId} --sandbox ${sid} --follow`, "stream it until it exits"], [sh `promigence exec kill ${execId} --sandbox ${sid}`, "stop it"]);
258
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, {
259
+ exec_id: execId, sandbox_id: sid, background: true, state: rec?.state, stream,
260
+ [stream]: bytes.toString("utf8"), bytes: bytes.length,
261
+ total_bytes: stream === "stdout" ? rec?.stdout_total : rec?.stderr_total,
262
+ ...(rec?.exit ? { exit: rec.exit } : {}),
263
+ range: { from, to: from + bytes.length - 1 },
264
+ }, next), [], 0, false);
265
+ }
266
+ function readStdin() {
267
+ return new Promise((resolve, reject) => {
268
+ const parts = [];
269
+ process.stdin.on("data", (d) => parts.push(d));
270
+ process.stdin.on("end", () => resolve(Buffer.concat(parts)));
271
+ process.stdin.on("error", reject);
272
+ process.stdin.resume();
273
+ });
274
+ }
275
+ const execStdin = async (ctx) => {
276
+ const eid = positional(ctx, "an exec id", "promigence exec stdin x_4f1c2a9e --sandbox sbx_01 --data 'y\\n'");
277
+ const sid = sandboxFlag(ctx);
278
+ const data = str(ctx, "--data");
279
+ const file = str(ctx, "--file");
280
+ if (data !== undefined && file !== undefined) {
281
+ throw usageError("exec stdin takes --data or --file, not both", "Pick one, or pipe the bytes in and pass neither.");
282
+ }
283
+ let bytes;
284
+ let eof;
285
+ if (data !== undefined) {
286
+ bytes = new TextEncoder().encode(data);
287
+ eof = bool(ctx, "--eof");
288
+ }
289
+ else if (file !== undefined) {
290
+ try {
291
+ bytes = new Uint8Array(readFileSync(file));
292
+ }
293
+ catch (e) {
294
+ throw usageError(`cannot read ${file}: ${e.message}`, "Give a readable local path; its bytes are written to the exec's stdin as they are.");
295
+ }
296
+ eof = bool(ctx, "--eof");
297
+ }
298
+ else {
299
+ bytes = new Uint8Array(await readStdin());
300
+ eof = !bool(ctx, "--no-eof");
301
+ }
302
+ if (bool(ctx, "--no-eof"))
303
+ eof = false;
304
+ const r = await client(ctx).sandboxes.execs.stdin(sid, eid, { data: bytes, ...(eof ? { eof: true } : {}) });
305
+ const next = nexts([sh `promigence exec output ${eid} --sandbox ${sid} --follow`, "read what it wrote back"], [sh `promigence exec kill ${eid} --sandbox ${sid}`, "stop it"]);
306
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { sandbox_id: sid, exec_id: eid, written: r.written, eof: r.eof }, next), [`${eid}: wrote ${r.written} byte${r.written === 1 ? "" : "s"}${r.eof ? ", stdin closed" : "; stdin left open (pass --eof to close it)"}`], 0, ctx.pointer);
307
+ };
308
+ function absArg(ctx, index, fallback) {
309
+ const p = ctx.args[index] ?? fallback;
310
+ if (p === undefined) {
311
+ throw usageError(`${ctx.cmd.name} needs a path`, `Example: promigence ${ctx.cmd.name} /tmp --sandbox sbx_01.`);
312
+ }
313
+ if (!p.startsWith("/")) {
314
+ throw usageError(`'${p}' must be absolute inside the sandbox`, `Paths inside a sandbox are absolute: promigence ${ctx.cmd.name} /tmp … .`);
315
+ }
316
+ return p;
317
+ }
318
+ function modeFlag(ctx) {
319
+ const raw = str(ctx, "--mode");
320
+ if (raw === undefined)
321
+ return undefined;
322
+ if (!/^[0-7]{3,4}$/.test(raw)) {
323
+ throw usageError(`--mode: '${raw}' is not an octal mode`, "Pass three or four octal digits, e.g. --mode 755.");
324
+ }
325
+ return raw;
326
+ }
327
+ function uintFlag(ctx, name) {
328
+ const raw = str(ctx, name);
329
+ if (raw === undefined)
330
+ return undefined;
331
+ const n = Number(raw);
332
+ if (!Number.isInteger(n) || n < 0)
333
+ throw usageError(`${name}: '${raw}' is not a non-negative integer`, `Pass ${name} N with N >= 0.`);
334
+ return n;
335
+ }
336
+ function fileLine(f) {
337
+ return `${f.path}${f.is_dir ? "/" : ""} mode ${f.mode}${f.size ? ` · ${f.size} bytes` : ""}`;
338
+ }
339
+ const filesLs = async (ctx) => {
340
+ const path = absArg(ctx, 0, "/");
341
+ const sid = sandboxFlag(ctx);
342
+ const depth = uintFlag(ctx, "--depth");
343
+ const limit = uintFlag(ctx, "--limit");
344
+ const r = await client(ctx).sandboxes.files.list(sid, path, {
345
+ ...(bool(ctx, "--recursive") ? { recursive: true } : {}),
346
+ ...(depth === undefined ? {} : { depth }),
347
+ ...(limit === undefined ? {} : { limit }),
348
+ ...(bool(ctx, "--no-hidden") ? { hidden: false } : {}),
349
+ });
350
+ const file = r.entries.find((e) => !e.is_dir && !e.is_symlink);
351
+ const next = file
352
+ ? nexts([sh `promigence files get ${file.path} ${`./${file.name.split("/").pop()}`} --sandbox ${sid}`, "copy one out"])
353
+ : [];
354
+ const lines = r.entries.map((e) => {
355
+ const info = toEntryInfo(e);
356
+ const kind = e.is_symlink ? "l" : e.is_dir ? "d" : "-";
357
+ const when = `${info.modifiedTime.toISOString().slice(0, 16).replace("T", " ")}Z`;
358
+ const base = r.path === "/" ? "/" : `${r.path.replace(/\/+$/, "")}/`;
359
+ const shown = bool(ctx, "--recursive") && typeof e.path === "string" && e.path.startsWith(base) ? e.path.slice(base.length) : e.name;
360
+ return `${kind}${info.permissions} ${String(e.size).padStart(10)} ${when} ${shown}${e.is_dir ? "/" : ""}`;
361
+ });
362
+ if (ctx.mode === "text") {
363
+ await write(process.stdout, lines.length ? lines.map((l) => fieldForTerminal(process.stdout, l)).join("\n") + "\n" : "");
364
+ if (r.truncated)
365
+ await write(process.stderr, `promigence files ls: truncated at ${r.entries.length} entries\n`);
366
+ return exitWith(0);
367
+ }
368
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), [], 0, ctx.pointer);
369
+ };
370
+ const filesSearch = async (ctx) => {
371
+ const path = absArg(ctx, 0, "/");
372
+ const sid = sandboxFlag(ctx);
373
+ const pattern = str(ctx, "--name");
374
+ const contains = str(ctx, "--contains");
375
+ if (pattern === undefined && contains === undefined) {
376
+ throw usageError("files search needs --name, --contains, or both", "Example: promigence files search /tmp --name '*.py' --sandbox sbx_01, or --contains TODO.");
377
+ }
378
+ const limit = uintFlag(ctx, "--limit");
379
+ const r = await client(ctx).sandboxes.files.search(sid, path, {
380
+ ...(pattern === undefined ? {} : { pattern }),
381
+ ...(contains === undefined ? {} : { contains }),
382
+ ...(bool(ctx, "--ignore-case") ? { ignoreCase: true } : {}),
383
+ ...(limit === undefined ? {} : { limit }),
384
+ ...(bool(ctx, "--no-hidden") ? { hidden: false } : {}),
385
+ });
386
+ const hit = r.matches[0];
387
+ const next = hit ? nexts([sh `promigence files get ${hit.path} ${`./${hit.path.split("/").pop()}`} --sandbox ${sid}`, "copy one out"]) : [];
388
+ const lines = r.matches.map((m) => (m.line ? `${m.path}:${m.line}: ${m.text ?? ""}` : m.path));
389
+ if (ctx.mode === "text") {
390
+ await write(process.stdout, lines.length ? lines.map((l) => fieldForTerminal(process.stdout, l)).join("\n") + "\n" : "");
391
+ if (r.truncated)
392
+ await write(process.stderr, `promigence files search: truncated at ${r.matches.length} matches\n`);
393
+ return exitWith(0);
394
+ }
395
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...r }, next), [], 0, ctx.pointer);
396
+ };
397
+ const filesMkdir = async (ctx) => {
398
+ const path = absArg(ctx, 0);
399
+ const sid = sandboxFlag(ctx);
400
+ const mode = modeFlag(ctx);
401
+ const f = await client(ctx).sandboxes.files.mkdir(sid, path, {
402
+ ...(mode === undefined ? {} : { mode }),
403
+ ...(bool(ctx, "--parents") ? { parents: true } : {}),
404
+ });
405
+ const next = nexts([sh `promigence files ls ${path} --sandbox ${sid}`, "what is in it"]);
406
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...f }, next), [fileLine(f)], 0, ctx.pointer);
407
+ };
408
+ function insidePair(ctx) {
409
+ const sid = sandboxFlag(ctx);
410
+ const [from, to] = [ctx.args[0], ctx.args[1]];
411
+ if (!from || !to) {
412
+ throw usageError(`${ctx.cmd.name} needs SRC and DST`, `Both are inside the sandbox: promigence ${ctx.cmd.name} /tmp/a /tmp/b --sandbox ${shellArg(sid)}.`);
413
+ }
414
+ for (const p of [from, to]) {
415
+ if (!p.startsWith("/"))
416
+ throw usageError(`'${p}' must be absolute inside the sandbox`, "Example: /tmp/out.json.");
417
+ }
418
+ return { sid, from, to };
419
+ }
420
+ const filesMv = async (ctx) => {
421
+ const { sid, from, to } = insidePair(ctx);
422
+ const f = await client(ctx).sandboxes.files.move(sid, from, to, {
423
+ ...(bool(ctx, "--overwrite") ? { overwrite: true } : {}),
424
+ });
425
+ const next = nexts([sh `promigence files ls ${to} --sandbox ${sid}`, "see it where it landed"]);
426
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...f, from }, next), [`${from} -> ${fileLine(f)}`], 0, ctx.pointer);
427
+ };
428
+ const filesCp = async (ctx) => {
429
+ const { sid, from, to } = insidePair(ctx);
430
+ const f = await client(ctx).sandboxes.files.copy(sid, from, to, {
431
+ ...(bool(ctx, "--recursive") ? { recursive: true } : {}),
432
+ ...(bool(ctx, "--overwrite") ? { overwrite: true } : {}),
433
+ });
434
+ const next = nexts([sh `promigence files ls ${to} --sandbox ${sid}`, "see the copy"]);
435
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...f, from }, next), [`${from} -> ${fileLine(f)}`], 0, ctx.pointer);
436
+ };
437
+ const filesChmod = async (ctx) => {
438
+ const path = absArg(ctx, 0);
439
+ const sid = sandboxFlag(ctx);
440
+ const mode = modeFlag(ctx);
441
+ const uid = uintFlag(ctx, "--uid");
442
+ const gid = uintFlag(ctx, "--gid");
443
+ if (mode === undefined && uid === undefined && gid === undefined) {
444
+ throw usageError("files chmod needs --mode, --uid or --gid", "A chmod that changes nothing is a round trip that answers nothing: promigence files chmod /tmp/run.sh --mode 755 --sandbox sbx_01.");
445
+ }
446
+ const f = await client(ctx).sandboxes.files.chmod(sid, path, {
447
+ ...(mode === undefined ? {} : { mode }),
448
+ ...(uid === undefined ? {} : { uid }),
449
+ ...(gid === undefined ? {} : { gid }),
450
+ ...(bool(ctx, "--recursive") ? { recursive: true } : {}),
451
+ });
452
+ const next = nexts([sh `promigence files ls ${path} --sandbox ${sid}`, "confirm it"]);
453
+ return emit(ctx.mode, okEnvelope(ctx.cmd.name, { ...f }, next), [fileLine(f)], 0, ctx.pointer);
454
+ };
455
+ export const LIVE_HANDLERS = {
456
+ "sandbox shell": sandboxShell,
457
+ "port expose": portExpose,
458
+ "port list": portList,
459
+ "port revoke": portRevoke,
460
+ "exec ps": execPs,
461
+ "exec kill": execKill,
462
+ "exec stdin": execStdin,
463
+ "files ls": filesLs,
464
+ "files search": filesSearch,
465
+ "files mkdir": filesMkdir,
466
+ "files mv": filesMv,
467
+ "files cp": filesCp,
468
+ "files chmod": filesChmod,
469
+ };