@arker-ai/sdk 0.5.1 → 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-PI3H3TGC.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,19 +177,54 @@ 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;
172
201
  return;
173
202
  }
174
- out({ run_id: result.runId, tunnels: result.tunnels });
203
+ out({ run_id: result.runId, state: result.state });
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
+ }
175
228
  }
176
229
  async function cmdRuns(args, client) {
177
230
  const sub = args.positional[0];
@@ -314,43 +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.port} ${t.state} ${t.protocol} ${t.url ?? "-"}`);
333
- }
334
- if (res.next_cursor) out(`# next_cursor=${res.next_cursor}`);
335
- return;
336
- }
337
- case "get": {
338
- if (!vm) die("usage: arker tunnels get <vm_id> <port>");
339
- const port = Number(rest[1] ?? die("missing port"));
340
- out(await client.vm(vm).getTunnel(port));
341
- return;
342
- }
343
- case "rm":
344
- case "delete": {
345
- if (!vm) die("usage: arker tunnels rm <vm_id> <port>");
346
- const port = Number(rest[1] ?? die("missing port"));
347
- const r = await client.vm(vm).deleteTunnel(port);
348
- out(r.deleted ? `deleted tunnel ${port}` : "delete failed");
349
- return;
350
- }
351
- default:
352
- die(`usage: arker tunnels <ls|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");
353
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));
354
388
  }
355
389
  async function cmdFilesystems(args, client) {
356
390
  const sub = args.positional[0];
@@ -396,6 +430,10 @@ async function cmdFilesystems(args, client) {
396
430
  async function cmdShell(args, client) {
397
431
  let computer;
398
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
+ }
399
437
  if (vmIdArg) {
400
438
  computer = await client.vm(vmIdArg).refresh();
401
439
  } else {
@@ -404,100 +442,242 @@ async function cmdShell(args, client) {
404
442
  sourceVmName,
405
443
  sourceOrgId: ARKER_ORG_ID
406
444
  });
445
+ err(`forked ${computer.id}`);
407
446
  }
408
- const header = computer;
409
- const session = await computer.createSession({
410
- cwd: args.flags.cwd
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");
454
+ }
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
411
468
  });
412
- const sessionId = session.session_id;
413
- const timeout = numFlag(args, "timeout");
414
- const preload = args.positional.slice(vmIdArg ? 1 : 0).join(" ").trim();
415
- const exitAfter = args.flags.exit === true;
416
- output.write(JSON.stringify(header, null, 2) + "\n");
417
- let inFlight = false;
418
- let exitCode = 0;
419
- try {
420
- if (preload && preload !== "exit") {
421
- const step = await runShellLine(computer, sessionId, preload, timeout);
422
- if (step.kind === "fatal") {
423
- err(`shell ended: ${step.message}`);
424
- return;
425
- }
426
- if (step.kind === "recoverable") {
427
- err(`error: ${step.message}`);
428
- exitCode = 1;
429
- } else {
430
- exitCode = step.exitCode;
431
- }
432
- }
433
- if (exitAfter || preload === "exit") {
434
- process.exit(exitCode);
435
- }
436
- const rl = readline.createInterface({ input, output, prompt: "> " });
437
- rl.on("SIGINT", () => {
438
- if (inFlight) {
439
- process.stderr.write("^C\n");
440
- 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);
441
485
  }
442
- process.stdout.write("^C\n");
443
- rl.write(null, { ctrl: true, name: "u" });
444
- 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);
445
526
  });
446
- rl.prompt();
447
- for await (const line of rl) {
448
- const cmd = line.trim();
449
- if (!cmd) {
450
- rl.prompt();
451
- continue;
452
- }
453
- if (cmd === "exit" || cmd === "quit") break;
454
- inFlight = true;
455
- const step = await runShellLine(computer, sessionId, cmd, timeout);
456
- inFlight = false;
457
- if (step.kind === "fatal") {
458
- err(`shell ended: ${step.message}`);
459
- exitCode = 1;
460
- break;
461
- }
462
- if (step.kind === "recoverable") {
463
- 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;
464
538
  }
465
- rl.prompt();
466
- }
467
- rl.close();
468
- } finally {
469
- 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);
470
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");
471
575
  }
472
- 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
+ );
473
587
  }
474
- 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`;
475
592
  try {
476
- const result = await computer.run(cmd, { session_id: sessionId, timeout });
477
- if (result.type === "completed") {
478
- if (result.stdout.length) process.stdout.write(new TextDecoder().decode(result.stdout));
479
- if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
480
- const tail = result.stderr.length > 0 ? result.stderr[result.stderr.length - 1] : result.stdout.length > 0 ? result.stdout[result.stdout.length - 1] : void 0;
481
- if (tail !== void 0 && tail !== 10) process.stdout.write("\n");
482
- return { kind: "ok", exitCode: result.exitCode };
483
- }
484
- out({ run_id: result.runId });
485
- return { kind: "ok", exitCode: 0 };
486
- } catch (e) {
487
- const message = e instanceof Error ? e.message : String(e);
488
- const kind = classifyShellError(message, computer.id);
489
- return { kind, message };
593
+ return new URL(client.baseUrl).hostname;
594
+ } catch {
595
+ die("could not determine SSH host; pass --host <hostname>");
490
596
  }
491
597
  }
492
- function classifyShellError(message, vmId) {
493
- const msg = message.toLowerCase();
494
- if (msg.includes("unauthor") || msg.includes("forbidden") || msg.includes("invalid api key")) {
495
- 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
+ );
496
604
  }
497
- if ((msg.includes("not_found") || msg.includes("not found")) && msg.includes(vmId.toLowerCase())) {
498
- 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> ...");
499
680
  }
500
- return "recoverable";
501
681
  }
502
682
  function numFlag(args, name) {
503
683
  const v = args.flags[name];
@@ -531,16 +711,21 @@ function usage() {
531
711
  " arker fork --source-vm-id <id> fork by global id",
532
712
  " arker fork --source-vm-name <n> --source-org-id <org>",
533
713
  " fork by name in another org",
534
- " arker run <vm> <command> run a command",
535
- " 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",
536
721
  "",
537
722
  "Resources:",
538
723
  " arker vms <ls|get|rm|fork|run> ...",
539
724
  " arker runs <ls|get|rm> <vm_id> ...",
540
725
  " arker sessions <ls|get|create|rm> <vm_id> ...",
541
726
  " arker syncs <ls|create|rm> <vm_id> ...",
542
- " arker tunnels <ls|get|rm> <vm_id> ...",
543
727
  " arker filesystems <ls|create|get|rm> ... (alias: fs)",
728
+ " arker ssh-keys <ls|add|rm> ... manage account SSH keys",
544
729
  "",
545
730
  "Flags:",
546
731
  " --api-key <key> (or env ARKER_API_KEY)",
@@ -550,6 +735,35 @@ function usage() {
550
735
  " --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
551
736
  " --json emit JSON instead of tabular output",
552
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
+ "",
553
767
  `Arker org id: ${ARKER_ORG_ID}`
554
768
  ].join("\n")
555
769
  );
@@ -580,6 +794,11 @@ async function main() {
580
794
  return await cmdSyncs(args, client);
581
795
  case "shell":
582
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);
583
802
  // Resources.
584
803
  case "vms":
585
804
  return await cmdVms(args, client);
@@ -587,8 +806,8 @@ async function main() {
587
806
  return await cmdRuns(args, client);
588
807
  case "sessions":
589
808
  return await cmdSessions(args, client);
590
- case "tunnels":
591
- return await cmdTunnels(args, client);
809
+ case "resize":
810
+ return await cmdResize(args, client);
592
811
  case "filesystems":
593
812
  case "fs":
594
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 };