@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/dist/cli.js CHANGED
@@ -3,13 +3,13 @@ import {
3
3
  ARKER_ORG_ID,
4
4
  Arker,
5
5
  ArkerError
6
- } from "./chunk-YGZOUXII.js";
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 path = join(homedir(), ".arker", "config.json");
42
- if (!existsSync(path)) return {};
43
- try {
44
- return JSON.parse(readFileSync(path, "utf8"));
45
- } catch {
46
- return {};
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 = args.flags["base-url"] ?? process.env.ARKER_BASE_URL ?? file.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 = args.flags.region ?? process.env.ARKER_REGION ?? file.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
- return `${vm.vm_id ?? vm.id} ${provider}-${region} ${state} ${name}`;
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 cmdTunnels(args, client) {
318
- const sub = args.positional[0];
319
- const rest = args.positional.slice(1);
320
- const vm = rest[0];
321
- switch (sub) {
322
- case "ls":
323
- case "list": {
324
- if (!vm) die("usage: arker tunnels ls <vm_id>");
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 header = computer;
417
- const session = await computer.createSession({
418
- cwd: args.flags.cwd
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
- const sessionId = session.session_id;
421
- const timeout = numFlag(args, "timeout");
422
- const preload = args.positional.slice(vmIdArg ? 1 : 0).join(" ").trim();
423
- const exitAfter = args.flags.exit === true;
424
- output.write(JSON.stringify(header, null, 2) + "\n");
425
- let inFlight = false;
426
- let exitCode = 0;
427
- try {
428
- if (preload && preload !== "exit") {
429
- const step = await runShellLine(computer, sessionId, preload, timeout);
430
- if (step.kind === "fatal") {
431
- err(`shell ended: ${step.message}`);
432
- return;
433
- }
434
- if (step.kind === "recoverable") {
435
- err(`error: ${step.message}`);
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
- process.stdout.write("^C\n");
451
- rl.write(null, { ctrl: true, name: "u" });
452
- rl.prompt();
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
- rl.prompt();
455
- for await (const line of rl) {
456
- const cmd = line.trim();
457
- if (!cmd) {
458
- rl.prompt();
459
- continue;
460
- }
461
- if (cmd === "exit" || cmd === "quit") break;
462
- inFlight = true;
463
- const step = await runShellLine(computer, sessionId, cmd, timeout);
464
- inFlight = false;
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
- rl.prompt();
474
- }
475
- rl.close();
476
- } finally {
477
- await computer.deleteSession(sessionId).catch(() => {
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
- if (exitCode !== 0) process.exit(exitCode);
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
- async function runShellLine(computer, sessionId, cmd, timeout) {
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
- const result = await computer.run(cmd, { session_id: sessionId, timeout });
485
- if (result.type === "completed") {
486
- if (result.stdout.length) process.stdout.write(new TextDecoder().decode(result.stdout));
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 classifyShellError(message, vmId) {
501
- const msg = message.toLowerCase();
502
- if (msg.includes("unauthor") || msg.includes("forbidden") || msg.includes("invalid api key")) {
503
- return "fatal";
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
- if ((msg.includes("not_found") || msg.includes("not found")) && msg.includes(vmId.toLowerCase())) {
506
- return "fatal";
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 run <vm> <command> run a command",
547
- " arker shell [vm_id] interactive shell (forks arkuntu if no vm)",
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 "tunnels":
603
- return await cmdTunnels(args, client);
809
+ case "resize":
810
+ return await cmdResize(args, client);
604
811
  case "filesystems":
605
812
  case "fs":
606
813
  return await cmdFilesystems(args, client);
@@ -0,0 +1,9 @@
1
+ import { SandboxInterface } from 'computesdk';
2
+
3
+ type UniversalSandbox = SandboxInterface;
4
+ interface CreateOptions {
5
+ timeout?: number;
6
+ timeoutMs?: number;
7
+ }
8
+
9
+ export type { CreateOptions as C, UniversalSandbox as U };
@@ -0,0 +1,9 @@
1
+ import { SandboxInterface } from 'computesdk';
2
+
3
+ type UniversalSandbox = SandboxInterface;
4
+ interface CreateOptions {
5
+ timeout?: number;
6
+ timeoutMs?: number;
7
+ }
8
+
9
+ export type { CreateOptions as C, UniversalSandbox as U };