@arker-ai/sdk 0.5.2 → 0.6.2
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/README.md +154 -0
- package/dist/arker-provider-BNIL8NdM.d.ts +7 -0
- package/dist/arker-provider-DwUib5ZW.d.cts +7 -0
- package/dist/chunk-35IEV6BU.js +286 -0
- package/dist/{chunk-YGZOUXII.js → chunk-7BHPVQNG.js} +232 -24
- package/dist/cli.cjs +587 -172
- package/dist/cli.js +355 -148
- package/dist/common-C5zJ-LkS.d.cts +9 -0
- package/dist/common-C5zJ-LkS.d.ts +9 -0
- package/dist/daytona.cjs +1274 -0
- package/dist/daytona.d.cts +37 -0
- package/dist/daytona.d.ts +37 -0
- package/dist/daytona.js +65 -0
- package/dist/e2b.cjs +1288 -0
- package/dist/e2b.d.cts +29 -0
- package/dist/e2b.d.ts +29 -0
- package/dist/e2b.js +75 -0
- package/dist/index.cjs +242 -24
- package/dist/index.d.cts +132 -108
- package/dist/index.d.ts +132 -108
- package/dist/index.js +1 -1
- package/dist/modal.cjs +1356 -0
- package/dist/modal.d.cts +49 -0
- package/dist/modal.d.ts +49 -0
- package/dist/modal.js +130 -0
- package/package.json +30 -4
package/dist/cli.js
CHANGED
|
@@ -3,13 +3,13 @@ import {
|
|
|
3
3
|
ARKER_ORG_ID,
|
|
4
4
|
Arker,
|
|
5
5
|
ArkerError
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-7BHPVQNG.js";
|
|
7
7
|
|
|
8
8
|
// src/cli.ts
|
|
9
9
|
import { readFileSync, existsSync } from "fs";
|
|
10
|
+
import { spawnSync } from "child_process";
|
|
10
11
|
import { homedir } from "os";
|
|
11
12
|
import { join } from "path";
|
|
12
|
-
import * as readline from "readline/promises";
|
|
13
13
|
import { stdin as input, stdout as output } from "process";
|
|
14
14
|
function parseArgs(argv) {
|
|
15
15
|
const positional = [];
|
|
@@ -38,20 +38,25 @@ function parseArgs(argv) {
|
|
|
38
38
|
return { positional, flags };
|
|
39
39
|
}
|
|
40
40
|
function readFileConfig() {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
for (const name of ["config.json", "config"]) {
|
|
42
|
+
const path = join(homedir(), ".arker", name);
|
|
43
|
+
if (!existsSync(path)) continue;
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
46
|
+
} catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
47
49
|
}
|
|
50
|
+
return {};
|
|
48
51
|
}
|
|
49
52
|
function clientFromArgs(args) {
|
|
50
53
|
const file = readFileConfig();
|
|
54
|
+
const explicitBaseUrl = args.flags["base-url"] ?? process.env.ARKER_BASE_URL;
|
|
55
|
+
const explicitRegion = args.flags.region ?? process.env.ARKER_REGION;
|
|
51
56
|
const apiKey = args.flags["api-key"] ?? process.env.ARKER_API_KEY ?? file.apiKey;
|
|
52
|
-
const baseUrl =
|
|
57
|
+
const baseUrl = explicitBaseUrl ?? (explicitRegion ? void 0 : file.baseUrl);
|
|
53
58
|
const controlBaseUrl = args.flags["control-base-url"] ?? process.env.ARKER_CONTROL_BASE_URL;
|
|
54
|
-
const region =
|
|
59
|
+
const region = explicitRegion ?? file.region;
|
|
55
60
|
const provider = args.flags.provider ?? process.env.ARKER_PROVIDER;
|
|
56
61
|
if (!apiKey) {
|
|
57
62
|
die("Missing API key. Set ARKER_API_KEY or pass --api-key.");
|
|
@@ -81,7 +86,8 @@ function fmtVm(vm) {
|
|
|
81
86
|
const region = vm.region ?? "?";
|
|
82
87
|
const name = vm.name ?? "\u2014";
|
|
83
88
|
const state = vm.state ?? "?";
|
|
84
|
-
|
|
89
|
+
const id = vm.vm_id ?? vm.id;
|
|
90
|
+
return `${id} ${provider}-${region} ${state} ${name}`;
|
|
85
91
|
}
|
|
86
92
|
async function cmdVms(args, client) {
|
|
87
93
|
const sub = args.positional[0];
|
|
@@ -126,6 +132,10 @@ async function cmdVms(args, client) {
|
|
|
126
132
|
await cmdRun({ ...args, positional: rest }, client);
|
|
127
133
|
return;
|
|
128
134
|
}
|
|
135
|
+
case "resize": {
|
|
136
|
+
await cmdResize({ ...args, positional: rest }, client);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
129
139
|
default:
|
|
130
140
|
die(`unknown vms subcommand: ${sub}`);
|
|
131
141
|
}
|
|
@@ -144,14 +154,22 @@ async function cmdFork(args, client) {
|
|
|
144
154
|
sourceVmName = refPositional;
|
|
145
155
|
}
|
|
146
156
|
if (!sourceVmId && !sourceVmName) {
|
|
147
|
-
die("usage: arker fork <vm_name> | --source-vm-id <id> | --source-vm-name <name> [--source-org-id <org>]");
|
|
157
|
+
die("usage: arker fork <vm_name> | --source-vm-id <id> | --source-vm-name <name> [--source-org-id <org>]\n [--vcpu N] [--memory-mib N] [--disk-mib N] [--no-disk]");
|
|
148
158
|
}
|
|
159
|
+
const vcpu = numFlag(args, "vcpu");
|
|
160
|
+
const memoryMib = numFlag(args, "memory-mib");
|
|
161
|
+
const diskMib = numFlag(args, "disk-mib");
|
|
162
|
+
const hasResources = vcpu !== void 0 || memoryMib !== void 0 || diskMib !== void 0;
|
|
163
|
+
const resources = hasResources ? { vcpu: vcpu ?? null, memory_mib: memoryMib ?? null, disk_mib: diskMib ?? null } : void 0;
|
|
164
|
+
const disk = boolFlag(args, "no-disk") ? false : void 0;
|
|
149
165
|
const computer = await client.fork({
|
|
150
166
|
sourceVmId,
|
|
151
167
|
sourceVmName,
|
|
152
168
|
sourceOrgId,
|
|
153
169
|
name,
|
|
154
|
-
public: publicFlag
|
|
170
|
+
public: publicFlag,
|
|
171
|
+
...resources ? { resources } : {},
|
|
172
|
+
...disk !== void 0 ? { disk } : {}
|
|
155
173
|
});
|
|
156
174
|
out({ vm_id: computer.id });
|
|
157
175
|
}
|
|
@@ -159,13 +177,24 @@ async function cmdRun(args, client) {
|
|
|
159
177
|
const vmId = args.positional[0] ?? die("usage: arker run <vm_id> <command...>");
|
|
160
178
|
const command = args.positional.slice(1).join(" ");
|
|
161
179
|
if (!command) die("missing command to run");
|
|
180
|
+
const sessionIdx = numFlag(args, "session-idx");
|
|
162
181
|
const result = await client.vm(vmId).run(command, {
|
|
163
182
|
background: boolFlag(args, "background"),
|
|
164
183
|
timeout: numFlag(args, "timeout"),
|
|
165
184
|
acquire: args.flags.acquire,
|
|
166
|
-
release: args.flags.release
|
|
185
|
+
release: args.flags.release,
|
|
186
|
+
session_id: args.flags["session-id"],
|
|
187
|
+
...sessionIdx !== void 0 ? { session_idx: sessionIdx } : {}
|
|
167
188
|
});
|
|
189
|
+
if (args.flags.json) {
|
|
190
|
+
out(runResultForJson(result));
|
|
191
|
+
if (result.type === "completed") process.exitCode = result.exitCode === 0 ? 0 : result.exitCode;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
168
194
|
if (result.type === "completed") {
|
|
195
|
+
if (result.memoryPartial) {
|
|
196
|
+
err(`Memory target partially applied: requested ${formatMib(result.memoryRequestedMib)}, achieved ${formatMib(result.memoryAchievedMib)}.`);
|
|
197
|
+
}
|
|
169
198
|
process.stdout.write(new TextDecoder().decode(result.stdout));
|
|
170
199
|
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
171
200
|
process.exitCode = result.exitCode === 0 ? 0 : result.exitCode;
|
|
@@ -173,6 +202,30 @@ async function cmdRun(args, client) {
|
|
|
173
202
|
}
|
|
174
203
|
out({ run_id: result.runId, state: result.state });
|
|
175
204
|
}
|
|
205
|
+
function formatMib(value) {
|
|
206
|
+
return typeof value === "number" ? `${value} MiB` : "unknown";
|
|
207
|
+
}
|
|
208
|
+
function runResultForJson(result) {
|
|
209
|
+
switch (result.type) {
|
|
210
|
+
case "completed":
|
|
211
|
+
return {
|
|
212
|
+
type: result.type,
|
|
213
|
+
runId: result.runId,
|
|
214
|
+
state: result.state,
|
|
215
|
+
stdout: new TextDecoder().decode(result.stdout),
|
|
216
|
+
stdoutEncoding: result.stdoutEncoding,
|
|
217
|
+
stderr: new TextDecoder().decode(result.stderr),
|
|
218
|
+
stderrEncoding: result.stderrEncoding,
|
|
219
|
+
exitCode: result.exitCode,
|
|
220
|
+
failReason: result.failReason,
|
|
221
|
+
memoryRequestedMib: result.memoryRequestedMib,
|
|
222
|
+
memoryAchievedMib: result.memoryAchievedMib,
|
|
223
|
+
memoryPartial: result.memoryPartial
|
|
224
|
+
};
|
|
225
|
+
case "background":
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
176
229
|
async function cmdRuns(args, client) {
|
|
177
230
|
const sub = args.positional[0];
|
|
178
231
|
const rest = args.positional.slice(1);
|
|
@@ -314,51 +367,24 @@ async function cmdSync(args, client) {
|
|
|
314
367
|
}
|
|
315
368
|
output.write(await client.vm(vm).sync(path));
|
|
316
369
|
}
|
|
317
|
-
async function
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
const res = await client.vm(vm).listTunnels({
|
|
326
|
-
state: args.flags.state,
|
|
327
|
-
cursor: args.flags.cursor,
|
|
328
|
-
limit: numFlag(args, "limit")
|
|
329
|
-
});
|
|
330
|
-
if (args.flags.json) return out(res);
|
|
331
|
-
for (const t of res.tunnels) {
|
|
332
|
-
out(`${t.tunnel_key ?? "-"} ${t.port} ${t.state} ${t.protocol} ${t.url ?? "-"}`);
|
|
333
|
-
}
|
|
334
|
-
if (res.next_cursor) out(`# next_cursor=${res.next_cursor}`);
|
|
335
|
-
return;
|
|
336
|
-
}
|
|
337
|
-
case "create": {
|
|
338
|
-
if (!vm) die("usage: arker tunnels create <vm_id> [--ports 80,8080] [--auth-mode open|authenticated]");
|
|
339
|
-
const tunnel = await client.vm(vm).createTunnel({
|
|
340
|
-
ports: parsePorts(args.flags.ports),
|
|
341
|
-
auth_mode: args.flags["auth-mode"]
|
|
342
|
-
});
|
|
343
|
-
return out(tunnel);
|
|
344
|
-
}
|
|
345
|
-
case "get": {
|
|
346
|
-
if (!vm) die("usage: arker tunnels get <vm_id> <key>");
|
|
347
|
-
const key = rest[1] ?? die("missing key");
|
|
348
|
-
out(await client.vm(vm).getTunnel(key));
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
case "rm":
|
|
352
|
-
case "delete": {
|
|
353
|
-
if (!vm) die("usage: arker tunnels rm <vm_id> <key>");
|
|
354
|
-
const key = rest[1] ?? die("missing key");
|
|
355
|
-
const r = await client.vm(vm).deleteTunnel(key);
|
|
356
|
-
out(r.deleted ? `deleted tunnel ${key}` : "delete failed");
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
359
|
-
default:
|
|
360
|
-
die(`usage: arker tunnels <ls|create|get|rm> ...`);
|
|
370
|
+
async function cmdResize(args, client) {
|
|
371
|
+
const vm = args.positional[0];
|
|
372
|
+
if (!vm) die("usage: arker resize <vm_id> [--memory-mib N] [--vcpu N] [--disk-mib N]");
|
|
373
|
+
const memoryMib = numFlag(args, "memory-mib");
|
|
374
|
+
const vcpu = numFlag(args, "vcpu");
|
|
375
|
+
const diskMib = numFlag(args, "disk-mib");
|
|
376
|
+
if (memoryMib === void 0 && vcpu === void 0 && diskMib === void 0) {
|
|
377
|
+
die("resize: pass at least one of --memory-mib, --vcpu, --disk-mib");
|
|
361
378
|
}
|
|
379
|
+
const updated = await client.vm(vm).resize({
|
|
380
|
+
resources: {
|
|
381
|
+
vcpu: vcpu ?? null,
|
|
382
|
+
memory_mib: memoryMib ?? null,
|
|
383
|
+
disk_mib: diskMib ?? null
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
if (args.flags.json) return out(updated);
|
|
387
|
+
out(fmtVm(updated));
|
|
362
388
|
}
|
|
363
389
|
async function cmdFilesystems(args, client) {
|
|
364
390
|
const sub = args.positional[0];
|
|
@@ -404,6 +430,10 @@ async function cmdFilesystems(args, client) {
|
|
|
404
430
|
async function cmdShell(args, client) {
|
|
405
431
|
let computer;
|
|
406
432
|
const vmIdArg = args.flags["vm-id"] ?? args.positional[0];
|
|
433
|
+
const explicitSessionId = args.flags["session-id"];
|
|
434
|
+
if (!vmIdArg && explicitSessionId) {
|
|
435
|
+
die("usage: arker shell <vm_id> --session-id <session_id>");
|
|
436
|
+
}
|
|
407
437
|
if (vmIdArg) {
|
|
408
438
|
computer = await client.vm(vmIdArg).refresh();
|
|
409
439
|
} else {
|
|
@@ -412,100 +442,242 @@ async function cmdShell(args, client) {
|
|
|
412
442
|
sourceVmName,
|
|
413
443
|
sourceOrgId: ARKER_ORG_ID
|
|
414
444
|
});
|
|
445
|
+
err(`forked ${computer.id}`);
|
|
446
|
+
}
|
|
447
|
+
let sessionId = explicitSessionId;
|
|
448
|
+
if (!sessionId) {
|
|
449
|
+
const session = await computer.createSession({
|
|
450
|
+
cwd: args.flags.cwd
|
|
451
|
+
});
|
|
452
|
+
sessionId = session.session_id ?? session.id;
|
|
453
|
+
if (!sessionId) die("createSession response missing session_id");
|
|
415
454
|
}
|
|
416
|
-
const
|
|
417
|
-
const
|
|
418
|
-
|
|
455
|
+
const persist = args.flags["no-persist"] === true ? false : boolFlag(args, "persist");
|
|
456
|
+
const colsFlag = numFlag(args, "cols");
|
|
457
|
+
const rowsFlag = numFlag(args, "rows");
|
|
458
|
+
const cols = colsFlag ?? output.columns ?? 80;
|
|
459
|
+
const rows = rowsFlag ?? output.rows ?? 24;
|
|
460
|
+
const cancelTtlSecs = numFlag(args, "cancel-ttl");
|
|
461
|
+
const pty = await computer.connectPty({
|
|
462
|
+
sessionId,
|
|
463
|
+
cols,
|
|
464
|
+
rows,
|
|
465
|
+
command: args.flags.command,
|
|
466
|
+
persist,
|
|
467
|
+
cancelTtlSecs
|
|
419
468
|
});
|
|
420
|
-
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
if (
|
|
435
|
-
|
|
436
|
-
exitCode = 1;
|
|
437
|
-
} else {
|
|
438
|
-
exitCode = step.exitCode;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
if (exitAfter || preload === "exit") {
|
|
442
|
-
process.exit(exitCode);
|
|
443
|
-
}
|
|
444
|
-
const rl = readline.createInterface({ input, output, prompt: "> " });
|
|
445
|
-
rl.on("SIGINT", () => {
|
|
446
|
-
if (inFlight) {
|
|
447
|
-
process.stderr.write("^C\n");
|
|
448
|
-
return;
|
|
469
|
+
err(`connected ${computer.id} session ${sessionId}`);
|
|
470
|
+
const exitCode = await bridgePty(pty, {
|
|
471
|
+
fallbackCols: cols,
|
|
472
|
+
fallbackRows: rows,
|
|
473
|
+
autoResize: colsFlag === void 0 && rowsFlag === void 0 && Boolean(output.isTTY)
|
|
474
|
+
});
|
|
475
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
476
|
+
}
|
|
477
|
+
function bridgePty(pty, options) {
|
|
478
|
+
return new Promise((resolve) => {
|
|
479
|
+
let settled = false;
|
|
480
|
+
let rawEnabled = false;
|
|
481
|
+
const wasRaw = Boolean(input.isTTY && input.isRaw);
|
|
482
|
+
const restoreTerminal = () => {
|
|
483
|
+
if (rawEnabled && input.isTTY && typeof input.setRawMode === "function") {
|
|
484
|
+
input.setRawMode(wasRaw);
|
|
449
485
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
486
|
+
rawEnabled = false;
|
|
487
|
+
};
|
|
488
|
+
const finish = (code) => {
|
|
489
|
+
if (settled) return;
|
|
490
|
+
settled = true;
|
|
491
|
+
restoreTerminal();
|
|
492
|
+
input.off("data", onInput);
|
|
493
|
+
input.off("end", onInputEnd);
|
|
494
|
+
process.off("SIGWINCH", onResize);
|
|
495
|
+
process.off("SIGINT", onSigint);
|
|
496
|
+
process.off("SIGTERM", onSigterm);
|
|
497
|
+
process.off("SIGHUP", onSighup);
|
|
498
|
+
process.off("exit", restoreTerminal);
|
|
499
|
+
offData();
|
|
500
|
+
offClose();
|
|
501
|
+
offError();
|
|
502
|
+
resolve(code);
|
|
503
|
+
};
|
|
504
|
+
const onInput = (chunk) => {
|
|
505
|
+
pty.send(chunk);
|
|
506
|
+
};
|
|
507
|
+
const onInputEnd = () => {
|
|
508
|
+
pty.close();
|
|
509
|
+
};
|
|
510
|
+
const onResize = () => {
|
|
511
|
+
pty.resize(output.columns ?? options.fallbackCols, output.rows ?? options.fallbackRows);
|
|
512
|
+
};
|
|
513
|
+
const onSigint = () => {
|
|
514
|
+
pty.send(new Uint8Array([3]));
|
|
515
|
+
};
|
|
516
|
+
const onSigterm = () => {
|
|
517
|
+
pty.close();
|
|
518
|
+
finish(143);
|
|
519
|
+
};
|
|
520
|
+
const onSighup = () => {
|
|
521
|
+
pty.close();
|
|
522
|
+
finish(129);
|
|
523
|
+
};
|
|
524
|
+
const offData = pty.onData((data) => {
|
|
525
|
+
output.write(data);
|
|
453
526
|
});
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
if (
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
if (step.kind === "fatal") {
|
|
466
|
-
err(`shell ended: ${step.message}`);
|
|
467
|
-
exitCode = 1;
|
|
468
|
-
break;
|
|
469
|
-
}
|
|
470
|
-
if (step.kind === "recoverable") {
|
|
471
|
-
err(`error: ${step.message}`);
|
|
527
|
+
const offClose = pty.onClose(() => finish(0));
|
|
528
|
+
const offError = pty.onError((error) => {
|
|
529
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
530
|
+
err(`pty error: ${message}`);
|
|
531
|
+
});
|
|
532
|
+
process.once("exit", restoreTerminal);
|
|
533
|
+
pty.ready.then(() => {
|
|
534
|
+
if (settled) return;
|
|
535
|
+
if (input.isTTY && typeof input.setRawMode === "function") {
|
|
536
|
+
input.setRawMode(true);
|
|
537
|
+
rawEnabled = true;
|
|
472
538
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
539
|
+
input.resume();
|
|
540
|
+
input.on("data", onInput);
|
|
541
|
+
input.on("end", onInputEnd);
|
|
542
|
+
if (options.autoResize) process.on("SIGWINCH", onResize);
|
|
543
|
+
process.on("SIGINT", onSigint);
|
|
544
|
+
process.on("SIGTERM", onSigterm);
|
|
545
|
+
process.on("SIGHUP", onSighup);
|
|
546
|
+
if (options.autoResize) onResize();
|
|
547
|
+
}).catch((error) => {
|
|
548
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
549
|
+
err(`pty failed to open: ${message}`);
|
|
550
|
+
finish(1);
|
|
478
551
|
});
|
|
552
|
+
});
|
|
553
|
+
}
|
|
554
|
+
var SSH_PORT_DEFAULT = 22;
|
|
555
|
+
function defaultIdentityBase() {
|
|
556
|
+
return join(homedir(), ".ssh", "id_ed25519");
|
|
557
|
+
}
|
|
558
|
+
function resolveLocalSshKey(args) {
|
|
559
|
+
const explicit = args.flags.identity;
|
|
560
|
+
const privateKeyPath = explicit ? explicit.replace(/\.pub$/, "") : defaultIdentityBase();
|
|
561
|
+
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
562
|
+
if (!existsSync(publicKeyPath)) {
|
|
563
|
+
if (!boolFlag(args, "generate")) {
|
|
564
|
+
die(
|
|
565
|
+
`no SSH public key at ${publicKeyPath}. Pass --identity <path>, or --generate to create an ed25519 key pair.`
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
err(`generating ed25519 key pair at ${privateKeyPath}`);
|
|
569
|
+
const gen = spawnSync(
|
|
570
|
+
"ssh-keygen",
|
|
571
|
+
["-t", "ed25519", "-N", "", "-f", privateKeyPath, "-C", "arker-cli"],
|
|
572
|
+
{ stdio: "inherit" }
|
|
573
|
+
);
|
|
574
|
+
if (gen.status !== 0) die("ssh-keygen failed to generate a key pair");
|
|
479
575
|
}
|
|
480
|
-
|
|
576
|
+
const publicKey = readFileSync(publicKeyPath, "utf8").trim();
|
|
577
|
+
if (!publicKey) die(`SSH public key at ${publicKeyPath} is empty`);
|
|
578
|
+
return { publicKey, privateKeyPath, publicKeyPath };
|
|
579
|
+
}
|
|
580
|
+
async function registerAccountSshKey(client, publicKey, label) {
|
|
581
|
+
return client._request(
|
|
582
|
+
"POST",
|
|
583
|
+
"/v1/account/ssh-keys",
|
|
584
|
+
{ public_key: publicKey, label: label ?? null },
|
|
585
|
+
client.baseUrl
|
|
586
|
+
);
|
|
481
587
|
}
|
|
482
|
-
|
|
588
|
+
function resolveSshHost(args, client) {
|
|
589
|
+
const explicit = args.flags.host ?? process.env.ARKER_SSH_HOST;
|
|
590
|
+
if (explicit) return explicit;
|
|
591
|
+
if (client.region) return `aws-${client.region}.arker.ai`;
|
|
483
592
|
try {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
488
|
-
const tail = result.stderr.length > 0 ? result.stderr[result.stderr.length - 1] : result.stdout.length > 0 ? result.stdout[result.stdout.length - 1] : void 0;
|
|
489
|
-
if (tail !== void 0 && tail !== 10) process.stdout.write("\n");
|
|
490
|
-
return { kind: "ok", exitCode: result.exitCode };
|
|
491
|
-
}
|
|
492
|
-
out({ run_id: result.runId });
|
|
493
|
-
return { kind: "ok", exitCode: 0 };
|
|
494
|
-
} catch (e) {
|
|
495
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
496
|
-
const kind = classifyShellError(message, computer.id);
|
|
497
|
-
return { kind, message };
|
|
593
|
+
return new URL(client.baseUrl).hostname;
|
|
594
|
+
} catch {
|
|
595
|
+
die("could not determine SSH host; pass --host <hostname>");
|
|
498
596
|
}
|
|
499
597
|
}
|
|
500
|
-
function
|
|
501
|
-
const
|
|
502
|
-
if (
|
|
503
|
-
|
|
598
|
+
async function cmdSsh(args, client) {
|
|
599
|
+
const vmId = args.flags["vm-id"] ?? args.positional[0];
|
|
600
|
+
if (!vmId) {
|
|
601
|
+
die(
|
|
602
|
+
"usage: arker ssh <vm_id> [--identity <path>] [--generate] [--host <h>] [--port <n>] [--connect|-c] [--skip-register]"
|
|
603
|
+
);
|
|
504
604
|
}
|
|
505
|
-
|
|
506
|
-
|
|
605
|
+
const { publicKey, privateKeyPath } = resolveLocalSshKey(args);
|
|
606
|
+
if (!boolFlag(args, "skip-register")) {
|
|
607
|
+
const label = args.flags.label ?? `arker-cli ${homedir().split("/").pop() ?? ""}`.trim();
|
|
608
|
+
try {
|
|
609
|
+
const key = await registerAccountSshKey(client, publicKey, label);
|
|
610
|
+
err(`registered SSH key ${key.fingerprint}${key.label ? ` (${key.label})` : ""}`);
|
|
611
|
+
} catch (e) {
|
|
612
|
+
if (e instanceof ArkerError && e.status === 409) {
|
|
613
|
+
die(`this SSH key is already registered to a different account: ${e.message}`);
|
|
614
|
+
}
|
|
615
|
+
if (e instanceof ArkerError && e.status === 403) {
|
|
616
|
+
die(
|
|
617
|
+
"your API key lacks the developer role required to register SSH keys. Register the key in the console, then re-run with --skip-register."
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
throw e;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
const host = resolveSshHost(args, client);
|
|
624
|
+
const port = numFlag(args, "port") ?? SSH_PORT_DEFAULT;
|
|
625
|
+
const sshArgs = [
|
|
626
|
+
"-i",
|
|
627
|
+
privateKeyPath,
|
|
628
|
+
...port !== 22 ? ["-p", String(port)] : [],
|
|
629
|
+
`${vmId}@${host}`
|
|
630
|
+
];
|
|
631
|
+
const command = `ssh ${sshArgs.join(" ")}`;
|
|
632
|
+
if (boolFlag(args, "connect") || boolFlag(args, "c")) {
|
|
633
|
+
err(`connecting: ${command}`);
|
|
634
|
+
const r = spawnSync("ssh", sshArgs, { stdio: "inherit" });
|
|
635
|
+
process.exit(r.status ?? 1);
|
|
636
|
+
}
|
|
637
|
+
out(command);
|
|
638
|
+
}
|
|
639
|
+
async function cmdSshKeys(args, client) {
|
|
640
|
+
const sub = args.positional[0];
|
|
641
|
+
const rest = args.positional.slice(1);
|
|
642
|
+
switch (sub) {
|
|
643
|
+
case void 0:
|
|
644
|
+
case "ls":
|
|
645
|
+
case "list": {
|
|
646
|
+
const res = await client._request(
|
|
647
|
+
"GET",
|
|
648
|
+
"/v1/account/ssh-keys",
|
|
649
|
+
void 0,
|
|
650
|
+
client.baseUrl
|
|
651
|
+
);
|
|
652
|
+
if (args.flags.json) return out(res);
|
|
653
|
+
for (const k of res.keys) {
|
|
654
|
+
out(`${k.id} ${k.fingerprint} ${k.label ?? "\u2014"}`);
|
|
655
|
+
}
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
case "add": {
|
|
659
|
+
const { publicKey } = resolveLocalSshKey(args);
|
|
660
|
+
const label = args.flags.label;
|
|
661
|
+
const key = await registerAccountSshKey(client, publicKey, label);
|
|
662
|
+
if (args.flags.json) return out(key);
|
|
663
|
+
out(`${key.id} ${key.fingerprint} ${key.label ?? "\u2014"}`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
case "rm":
|
|
667
|
+
case "delete": {
|
|
668
|
+
const id = rest[0] ?? die("usage: arker ssh-keys rm <key_id>");
|
|
669
|
+
const r = await client._request(
|
|
670
|
+
"DELETE",
|
|
671
|
+
`/v1/account/ssh-keys/${encodeURIComponent(id)}`,
|
|
672
|
+
void 0,
|
|
673
|
+
client.baseUrl
|
|
674
|
+
);
|
|
675
|
+
out(r.deleted ? `deleted ${id}` : "delete failed");
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
default:
|
|
679
|
+
die("usage: arker ssh-keys <ls|add|rm> ...");
|
|
507
680
|
}
|
|
508
|
-
return "recoverable";
|
|
509
681
|
}
|
|
510
682
|
function numFlag(args, name) {
|
|
511
683
|
const v = args.flags[name];
|
|
@@ -519,10 +691,6 @@ function boolFlag(args, name) {
|
|
|
519
691
|
if (v === "false" || v === "0") return false;
|
|
520
692
|
return true;
|
|
521
693
|
}
|
|
522
|
-
function parsePorts(value) {
|
|
523
|
-
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
524
|
-
return value.split(",").map((part) => Number(part.trim())).filter((port) => Number.isFinite(port));
|
|
525
|
-
}
|
|
526
694
|
async function readAllStdin() {
|
|
527
695
|
const chunks = [];
|
|
528
696
|
for await (const chunk of input) chunks.push(chunk);
|
|
@@ -543,16 +711,21 @@ function usage() {
|
|
|
543
711
|
" arker fork --source-vm-id <id> fork by global id",
|
|
544
712
|
" arker fork --source-vm-name <n> --source-org-id <org>",
|
|
545
713
|
" fork by name in another org",
|
|
546
|
-
" arker
|
|
547
|
-
"
|
|
714
|
+
" arker fork <vm> [--vcpu N] [--memory-mib N] [--disk-mib N] [--no-disk]",
|
|
715
|
+
" fork with resource/network overrides",
|
|
716
|
+
" arker run <vm> <command> [--session-id <id>] [--session-idx N] run a command",
|
|
717
|
+
" arker resize <vm> [--memory-mib N] [--vcpu N] [--disk-mib N] resize a VM (PATCH)",
|
|
718
|
+
" arker shell [vm_id] native PTY shell (forks ubuntu-full if no vm)",
|
|
719
|
+
" arker ssh <vm_id> register your SSH key + print the ssh command",
|
|
720
|
+
" arker ssh <vm_id> --connect register + drop straight into the ssh session",
|
|
548
721
|
"",
|
|
549
722
|
"Resources:",
|
|
550
723
|
" arker vms <ls|get|rm|fork|run> ...",
|
|
551
724
|
" arker runs <ls|get|rm> <vm_id> ...",
|
|
552
725
|
" arker sessions <ls|get|create|rm> <vm_id> ...",
|
|
553
726
|
" arker syncs <ls|create|rm> <vm_id> ...",
|
|
554
|
-
" arker tunnels <ls|get|rm> <vm_id> ...",
|
|
555
727
|
" arker filesystems <ls|create|get|rm> ... (alias: fs)",
|
|
728
|
+
" arker ssh-keys <ls|add|rm> ... manage account SSH keys",
|
|
556
729
|
"",
|
|
557
730
|
"Flags:",
|
|
558
731
|
" --api-key <key> (or env ARKER_API_KEY)",
|
|
@@ -562,6 +735,35 @@ function usage() {
|
|
|
562
735
|
" --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
|
|
563
736
|
" --json emit JSON instead of tabular output",
|
|
564
737
|
"",
|
|
738
|
+
"Fork flags:",
|
|
739
|
+
" --vcpu <n> vCPU count for the new VM (capped by source max_vcpus)",
|
|
740
|
+
" --memory-mib <n> memory (MiB) for the new VM",
|
|
741
|
+
" --disk-mib <n> disk size (MiB) for the new VM",
|
|
742
|
+
" --no-disk fork a memory-backed (nodisk) VM",
|
|
743
|
+
"",
|
|
744
|
+
"Run flags:",
|
|
745
|
+
" --session-id <ulid> run in a specific existing session",
|
|
746
|
+
" --session-idx <n> run in the session at this index (default 0)",
|
|
747
|
+
" --background return a run id instead of blocking",
|
|
748
|
+
" --timeout <secs> per-run timeout",
|
|
749
|
+
" --acquire <list> warm resources before the run (cpu,memory,disk)",
|
|
750
|
+
" --release <list> release resources after the run (cpu,memory,disk)",
|
|
751
|
+
"",
|
|
752
|
+
"Shell flags:",
|
|
753
|
+
" --session-id <id> reconnect to an existing PTY session",
|
|
754
|
+
" --command <path> shell executable path (default: /bin/bash)",
|
|
755
|
+
" --cols <n> --rows <n> initial terminal size",
|
|
756
|
+
" --no-persist close the remote PTY process on disconnect",
|
|
757
|
+
"",
|
|
758
|
+
"SSH flags:",
|
|
759
|
+
" --identity <path> local key (private or .pub); default ~/.ssh/id_ed25519",
|
|
760
|
+
" --generate create an ed25519 key pair if none exists",
|
|
761
|
+
" --host <hostname> SSH host (or env ARKER_SSH_HOST; default aws-<region>.arker.ai)",
|
|
762
|
+
" --port <n> SSH port (default 22)",
|
|
763
|
+
" --connect, -c exec ssh instead of just printing the command",
|
|
764
|
+
" --skip-register don't register the key (assume already registered)",
|
|
765
|
+
" --label <text> label for the registered key",
|
|
766
|
+
"",
|
|
565
767
|
`Arker org id: ${ARKER_ORG_ID}`
|
|
566
768
|
].join("\n")
|
|
567
769
|
);
|
|
@@ -592,6 +794,11 @@ async function main() {
|
|
|
592
794
|
return await cmdSyncs(args, client);
|
|
593
795
|
case "shell":
|
|
594
796
|
return await cmdShell(args, client);
|
|
797
|
+
case "ssh":
|
|
798
|
+
return await cmdSsh(args, client);
|
|
799
|
+
case "ssh-keys":
|
|
800
|
+
case "ssh_keys":
|
|
801
|
+
return await cmdSshKeys(args, client);
|
|
595
802
|
// Resources.
|
|
596
803
|
case "vms":
|
|
597
804
|
return await cmdVms(args, client);
|
|
@@ -599,8 +806,8 @@ async function main() {
|
|
|
599
806
|
return await cmdRuns(args, client);
|
|
600
807
|
case "sessions":
|
|
601
808
|
return await cmdSessions(args, client);
|
|
602
|
-
case "
|
|
603
|
-
return await
|
|
809
|
+
case "resize":
|
|
810
|
+
return await cmdResize(args, client);
|
|
604
811
|
case "filesystems":
|
|
605
812
|
case "fs":
|
|
606
813
|
return await cmdFilesystems(args, client);
|