@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.cjs
CHANGED
|
@@ -25,9 +25,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
25
25
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_node_fs = require("fs");
|
|
28
|
+
var import_node_child_process = require("child_process");
|
|
28
29
|
var import_node_os = require("os");
|
|
29
30
|
var import_node_path = require("path");
|
|
30
|
-
var readline = __toESM(require("readline/promises"), 1);
|
|
31
31
|
var import_node_process = require("process");
|
|
32
32
|
|
|
33
33
|
// src/index.ts
|
|
@@ -153,6 +153,12 @@ var Arker = class {
|
|
|
153
153
|
);
|
|
154
154
|
}
|
|
155
155
|
const sourceOrgId = src.sourceOrgId ?? (src.sourceVmName !== void 0 && GOLDEN_NAMES.has(src.sourceVmName) ? ARKER_ORG_ID : void 0);
|
|
156
|
+
const legacy = src;
|
|
157
|
+
const resources = src.resources ?? (legacy.vcpu_count != null || legacy.memory_mib != null || legacy.disk_mib != null ? {
|
|
158
|
+
vcpu: legacy.vcpu_count ?? null,
|
|
159
|
+
memory_mib: legacy.memory_mib ?? null,
|
|
160
|
+
disk_mib: legacy.disk_mib ?? null
|
|
161
|
+
} : null);
|
|
156
162
|
const body = {
|
|
157
163
|
source_vm_id: src.sourceVmId ?? null,
|
|
158
164
|
source_vm_name: src.sourceVmName ?? null,
|
|
@@ -160,13 +166,10 @@ var Arker = class {
|
|
|
160
166
|
name: src.name ?? null,
|
|
161
167
|
public: src.public ?? null,
|
|
162
168
|
network: src.network ?? null,
|
|
169
|
+
egress: src.egress ?? null,
|
|
163
170
|
disk: src.disk ?? true,
|
|
164
|
-
vcpu_count: src.vcpu_count ?? null,
|
|
165
|
-
memory_mib: src.memory_mib ?? null,
|
|
166
|
-
max_memory_mib: src.max_memory_mib ?? null,
|
|
167
|
-
disk_mib: src.disk_mib ?? null,
|
|
168
171
|
durable: src.durable ?? null,
|
|
169
|
-
|
|
172
|
+
resources
|
|
170
173
|
};
|
|
171
174
|
const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
|
|
172
175
|
const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
|
|
@@ -313,6 +316,10 @@ var Arker = class {
|
|
|
313
316
|
return retryDelay(this.retry, attempt);
|
|
314
317
|
}
|
|
315
318
|
/** @internal */
|
|
319
|
+
_authHeaders() {
|
|
320
|
+
return { authorization: `Bearer ${this.apiKey}` };
|
|
321
|
+
}
|
|
322
|
+
/** @internal */
|
|
316
323
|
_baseUrlFor(ref) {
|
|
317
324
|
if (isBurstRef(ref) && this.burstBaseUrl) return this.burstBaseUrl;
|
|
318
325
|
return this.baseUrl;
|
|
@@ -347,7 +354,6 @@ var VM = class _VM {
|
|
|
347
354
|
root_source_vm_name;
|
|
348
355
|
worker_id;
|
|
349
356
|
sessions;
|
|
350
|
-
tunnels;
|
|
351
357
|
constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
|
|
352
358
|
this._client = client;
|
|
353
359
|
this.id = vmId;
|
|
@@ -470,8 +476,25 @@ var VM = class _VM {
|
|
|
470
476
|
}
|
|
471
477
|
throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
|
|
472
478
|
}
|
|
479
|
+
/**
|
|
480
|
+
* Update this VM's resource allocation and/or network settings via
|
|
481
|
+
* `PATCH /v1/vms/{id}`. Returns the updated `Vm`.
|
|
482
|
+
*
|
|
483
|
+
* Accepts either a `PatchVmRequest` (`{ resources, network }`) or, for
|
|
484
|
+
* convenience, flat resource fields (`{ vcpu, memory_mib, disk_mib }`)
|
|
485
|
+
* which are folded into `resources`.
|
|
486
|
+
*/
|
|
473
487
|
async resize(request) {
|
|
474
|
-
|
|
488
|
+
const r = request;
|
|
489
|
+
const body = r.resources !== void 0 || r.vcpu === void 0 && r.memory_mib === void 0 && r.disk_mib === void 0 ? { resources: r.resources ?? null, network: r.network ?? null } : {
|
|
490
|
+
resources: {
|
|
491
|
+
vcpu: r.vcpu ?? null,
|
|
492
|
+
memory_mib: r.memory_mib ?? null,
|
|
493
|
+
disk_mib: r.disk_mib ?? null
|
|
494
|
+
},
|
|
495
|
+
network: r.network ?? null
|
|
496
|
+
};
|
|
497
|
+
return this._client._request("PATCH", vmPath(this.id), body, this.baseUrl);
|
|
475
498
|
}
|
|
476
499
|
async delete() {
|
|
477
500
|
return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
|
|
@@ -527,22 +550,30 @@ var VM = class _VM {
|
|
|
527
550
|
async deleteSession(sessionId) {
|
|
528
551
|
return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
|
|
529
552
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
553
|
+
async connectPty(options = {}) {
|
|
554
|
+
const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
|
|
555
|
+
const useTicket = options.useTicket ?? !isNodeRuntime();
|
|
556
|
+
const params = {
|
|
557
|
+
cols: options.cols,
|
|
558
|
+
rows: options.rows,
|
|
559
|
+
command: options.command,
|
|
560
|
+
persist: options.persist,
|
|
561
|
+
cancel_ttl_secs: options.cancelTtlSecs && options.cancelTtlSecs > 0 ? Math.floor(options.cancelTtlSecs) : void 0
|
|
562
|
+
};
|
|
563
|
+
let ticket;
|
|
564
|
+
if (useTicket) {
|
|
565
|
+
const response = await this._client._request(
|
|
566
|
+
"POST",
|
|
567
|
+
`${vmPath(this.id)}/sessions/${pathSegment(sessionId)}/pty-ticket`,
|
|
568
|
+
{},
|
|
569
|
+
this.baseUrl
|
|
570
|
+
);
|
|
571
|
+
ticket = response.ticket;
|
|
572
|
+
}
|
|
573
|
+
const url = buildPtyWebSocketUrl(this.baseUrl, this.id, sessionId, { ...params, ticket });
|
|
574
|
+
const factory = options.webSocketFactory ?? (useTicket ? browserPtyWebSocketFactory : nodePtyWebSocketFactory);
|
|
575
|
+
const socket = await factory(url, useTicket ? {} : { headers: this._client._authHeaders() });
|
|
576
|
+
return new PtyConnectionImpl(sessionId, socket);
|
|
546
577
|
}
|
|
547
578
|
};
|
|
548
579
|
function buildQuery(path, params) {
|
|
@@ -554,6 +585,176 @@ function buildQuery(path, params) {
|
|
|
554
585
|
const qs = usp.toString();
|
|
555
586
|
return qs ? `${path}?${qs}` : path;
|
|
556
587
|
}
|
|
588
|
+
function buildPtyWebSocketUrl(baseUrl, vmId, sessionId, params) {
|
|
589
|
+
const url = new URL(`${normalizeBaseUrl(baseUrl)}${vmPath(vmId)}/sessions/${pathSegment(sessionId)}/pty`);
|
|
590
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
591
|
+
else if (url.protocol === "http:") url.protocol = "ws:";
|
|
592
|
+
else throw new Error(`unsupported PTY WebSocket protocol: ${url.protocol}`);
|
|
593
|
+
for (const [key, value] of Object.entries(params)) {
|
|
594
|
+
if (value === void 0 || value === null) continue;
|
|
595
|
+
url.searchParams.set(key, String(value));
|
|
596
|
+
}
|
|
597
|
+
return url.toString();
|
|
598
|
+
}
|
|
599
|
+
function sessionIdFrom(session) {
|
|
600
|
+
const id = session.session_id ?? session.id;
|
|
601
|
+
if (!id) throw new ArkerError("internal", "createSession response missing session_id", 200);
|
|
602
|
+
return id;
|
|
603
|
+
}
|
|
604
|
+
function isNodeRuntime() {
|
|
605
|
+
return typeof process !== "undefined" && Boolean(process.versions?.node);
|
|
606
|
+
}
|
|
607
|
+
async function nodePtyWebSocketFactory(url, init) {
|
|
608
|
+
const ws = await import("ws");
|
|
609
|
+
return new ws.default(url, { headers: init.headers });
|
|
610
|
+
}
|
|
611
|
+
function browserPtyWebSocketFactory(url) {
|
|
612
|
+
if (typeof globalThis.WebSocket !== "function") {
|
|
613
|
+
throw new Error("WebSocket is not available in this runtime");
|
|
614
|
+
}
|
|
615
|
+
return new globalThis.WebSocket(url);
|
|
616
|
+
}
|
|
617
|
+
var PtyConnectionImpl = class {
|
|
618
|
+
constructor(sessionId, socket) {
|
|
619
|
+
this.sessionId = sessionId;
|
|
620
|
+
this.socket = socket;
|
|
621
|
+
try {
|
|
622
|
+
socket.binaryType = "arraybuffer";
|
|
623
|
+
} catch {
|
|
624
|
+
}
|
|
625
|
+
this.ready = waitForSocketOpen(socket);
|
|
626
|
+
addSocketListener(socket, "message", (event) => {
|
|
627
|
+
const data = messageData(event);
|
|
628
|
+
if (data !== void 0) this.emitData(bytesFromMessageData(data));
|
|
629
|
+
});
|
|
630
|
+
addSocketListener(socket, "close", (event) => this.emitClose(closeEvent(event)));
|
|
631
|
+
addSocketListener(socket, "error", (event) => this.emitError(event));
|
|
632
|
+
}
|
|
633
|
+
sessionId;
|
|
634
|
+
socket;
|
|
635
|
+
ready;
|
|
636
|
+
dataListeners = /* @__PURE__ */ new Set();
|
|
637
|
+
closeListeners = /* @__PURE__ */ new Set();
|
|
638
|
+
errorListeners = /* @__PURE__ */ new Set();
|
|
639
|
+
onData(listener) {
|
|
640
|
+
this.dataListeners.add(listener);
|
|
641
|
+
return () => this.dataListeners.delete(listener);
|
|
642
|
+
}
|
|
643
|
+
onClose(listener) {
|
|
644
|
+
this.closeListeners.add(listener);
|
|
645
|
+
return () => this.closeListeners.delete(listener);
|
|
646
|
+
}
|
|
647
|
+
onError(listener) {
|
|
648
|
+
this.errorListeners.add(listener);
|
|
649
|
+
return () => this.errorListeners.delete(listener);
|
|
650
|
+
}
|
|
651
|
+
send(data) {
|
|
652
|
+
this.socket.send(ptyInputBytes(data));
|
|
653
|
+
}
|
|
654
|
+
resize(cols, rows) {
|
|
655
|
+
this.socket.send(JSON.stringify({ type: "resize", cols: clampPtyDimension(cols), rows: clampPtyDimension(rows) }));
|
|
656
|
+
}
|
|
657
|
+
kill() {
|
|
658
|
+
this.socket.send(JSON.stringify({ type: "kill" }));
|
|
659
|
+
}
|
|
660
|
+
close(code, reason) {
|
|
661
|
+
this.socket.close(code, reason);
|
|
662
|
+
}
|
|
663
|
+
emitData(data) {
|
|
664
|
+
for (const listener of this.dataListeners) listener(data);
|
|
665
|
+
}
|
|
666
|
+
emitClose(event) {
|
|
667
|
+
for (const listener of this.closeListeners) listener(event);
|
|
668
|
+
}
|
|
669
|
+
emitError(error) {
|
|
670
|
+
for (const listener of this.errorListeners) listener(error);
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
function waitForSocketOpen(socket) {
|
|
674
|
+
if (socket.readyState === 1) return Promise.resolve();
|
|
675
|
+
return new Promise((resolve, reject) => {
|
|
676
|
+
let removeOpen;
|
|
677
|
+
let removeError;
|
|
678
|
+
let removeClose;
|
|
679
|
+
const cleanup = () => {
|
|
680
|
+
removeOpen?.();
|
|
681
|
+
removeError?.();
|
|
682
|
+
removeClose?.();
|
|
683
|
+
};
|
|
684
|
+
removeOpen = addSocketListener(socket, "open", () => {
|
|
685
|
+
cleanup();
|
|
686
|
+
resolve();
|
|
687
|
+
});
|
|
688
|
+
removeError = addSocketListener(socket, "error", (event) => {
|
|
689
|
+
cleanup();
|
|
690
|
+
reject(event instanceof Error ? event : new Error("PTY WebSocket failed to open"));
|
|
691
|
+
});
|
|
692
|
+
removeClose = addSocketListener(socket, "close", (event) => {
|
|
693
|
+
cleanup();
|
|
694
|
+
const ev = closeEvent(event);
|
|
695
|
+
reject(new Error(`PTY WebSocket closed before opening${ev.code ? ` (${ev.code})` : ""}`));
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
function addSocketListener(socket, type, listener) {
|
|
700
|
+
if (socket.addEventListener) {
|
|
701
|
+
socket.addEventListener(type, listener);
|
|
702
|
+
return () => socket.removeEventListener?.(type, listener);
|
|
703
|
+
}
|
|
704
|
+
if (socket.on) {
|
|
705
|
+
const nodeListener = (...args) => {
|
|
706
|
+
if (type === "message") listener({ data: args[0] });
|
|
707
|
+
else if (type === "close") listener({ code: args[0], reason: args[1] });
|
|
708
|
+
else listener(args[0]);
|
|
709
|
+
};
|
|
710
|
+
socket.on(type, nodeListener);
|
|
711
|
+
return () => socket.off?.(type, nodeListener);
|
|
712
|
+
}
|
|
713
|
+
return () => {
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
function messageData(event) {
|
|
717
|
+
if (event && typeof event === "object" && "data" in event) {
|
|
718
|
+
return event.data;
|
|
719
|
+
}
|
|
720
|
+
return void 0;
|
|
721
|
+
}
|
|
722
|
+
function closeEvent(event) {
|
|
723
|
+
if (!event || typeof event !== "object") return {};
|
|
724
|
+
const raw = event;
|
|
725
|
+
return {
|
|
726
|
+
code: typeof raw.code === "number" ? raw.code : void 0,
|
|
727
|
+
reason: typeof raw.reason === "string" ? raw.reason : void 0
|
|
728
|
+
};
|
|
729
|
+
}
|
|
730
|
+
function bytesFromMessageData(data) {
|
|
731
|
+
if (typeof data === "string") return new TextEncoder().encode(data);
|
|
732
|
+
if (data instanceof Uint8Array) return data;
|
|
733
|
+
if (data instanceof ArrayBuffer) return new Uint8Array(data);
|
|
734
|
+
if (ArrayBuffer.isView(data)) {
|
|
735
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
736
|
+
}
|
|
737
|
+
if (Array.isArray(data)) {
|
|
738
|
+
const chunks = data.map(bytesFromMessageData);
|
|
739
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
740
|
+
const out2 = new Uint8Array(total);
|
|
741
|
+
let offset = 0;
|
|
742
|
+
for (const chunk of chunks) {
|
|
743
|
+
out2.set(chunk, offset);
|
|
744
|
+
offset += chunk.length;
|
|
745
|
+
}
|
|
746
|
+
return out2;
|
|
747
|
+
}
|
|
748
|
+
return new Uint8Array();
|
|
749
|
+
}
|
|
750
|
+
function ptyInputBytes(data) {
|
|
751
|
+
if (typeof data === "string") return new TextEncoder().encode(data);
|
|
752
|
+
return data;
|
|
753
|
+
}
|
|
754
|
+
function clampPtyDimension(value) {
|
|
755
|
+
if (!Number.isFinite(value)) return 1;
|
|
756
|
+
return Math.max(1, Math.min(1e3, Math.trunc(value)));
|
|
757
|
+
}
|
|
557
758
|
function normalizeBaseUrl(baseUrl) {
|
|
558
759
|
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
559
760
|
if (!trimmed) throw new Error("baseUrl must not be empty");
|
|
@@ -636,7 +837,10 @@ function parseRunResponse(payload) {
|
|
|
636
837
|
stderr: decodeBytes(stderr, stderrEncoding),
|
|
637
838
|
stderrEncoding,
|
|
638
839
|
exitCode: numberField(body.exit_code, "run response.exit_code"),
|
|
639
|
-
failReason: typeof body.fail_reason === "string" ? body.fail_reason : null
|
|
840
|
+
failReason: typeof body.fail_reason === "string" ? body.fail_reason : null,
|
|
841
|
+
memoryRequestedMib: optionalNumberOrNull(body.memory_requested_mib),
|
|
842
|
+
memoryAchievedMib: optionalNumberOrNull(body.memory_achieved_mib),
|
|
843
|
+
memoryPartial: typeof body.memory_partial === "boolean" ? body.memory_partial : void 0
|
|
640
844
|
};
|
|
641
845
|
}
|
|
642
846
|
if (typeof body.run_id === "string") {
|
|
@@ -707,6 +911,10 @@ function numberField(value, context) {
|
|
|
707
911
|
if (typeof value !== "number") throw new ArkerError("internal", `${context} must be a number`, 200);
|
|
708
912
|
return value;
|
|
709
913
|
}
|
|
914
|
+
function optionalNumberOrNull(value) {
|
|
915
|
+
if (value === null || typeof value === "number") return value;
|
|
916
|
+
return void 0;
|
|
917
|
+
}
|
|
710
918
|
function assertWriteComplete(result, context) {
|
|
711
919
|
if (result.complete && result.written) return;
|
|
712
920
|
throw new ArkerError("internal", `${context} did not complete`, 200);
|
|
@@ -782,20 +990,25 @@ function parseArgs(argv) {
|
|
|
782
990
|
return { positional, flags };
|
|
783
991
|
}
|
|
784
992
|
function readFileConfig() {
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
993
|
+
for (const name of ["config.json", "config"]) {
|
|
994
|
+
const path = (0, import_node_path.join)((0, import_node_os.homedir)(), ".arker", name);
|
|
995
|
+
if (!(0, import_node_fs.existsSync)(path)) continue;
|
|
996
|
+
try {
|
|
997
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
998
|
+
} catch {
|
|
999
|
+
return {};
|
|
1000
|
+
}
|
|
791
1001
|
}
|
|
1002
|
+
return {};
|
|
792
1003
|
}
|
|
793
1004
|
function clientFromArgs(args) {
|
|
794
1005
|
const file = readFileConfig();
|
|
1006
|
+
const explicitBaseUrl = args.flags["base-url"] ?? process.env.ARKER_BASE_URL;
|
|
1007
|
+
const explicitRegion = args.flags.region ?? process.env.ARKER_REGION;
|
|
795
1008
|
const apiKey = args.flags["api-key"] ?? process.env.ARKER_API_KEY ?? file.apiKey;
|
|
796
|
-
const baseUrl =
|
|
1009
|
+
const baseUrl = explicitBaseUrl ?? (explicitRegion ? void 0 : file.baseUrl);
|
|
797
1010
|
const controlBaseUrl = args.flags["control-base-url"] ?? process.env.ARKER_CONTROL_BASE_URL;
|
|
798
|
-
const region =
|
|
1011
|
+
const region = explicitRegion ?? file.region;
|
|
799
1012
|
const provider = args.flags.provider ?? process.env.ARKER_PROVIDER;
|
|
800
1013
|
if (!apiKey) {
|
|
801
1014
|
die("Missing API key. Set ARKER_API_KEY or pass --api-key.");
|
|
@@ -825,7 +1038,8 @@ function fmtVm(vm) {
|
|
|
825
1038
|
const region = vm.region ?? "?";
|
|
826
1039
|
const name = vm.name ?? "\u2014";
|
|
827
1040
|
const state = vm.state ?? "?";
|
|
828
|
-
|
|
1041
|
+
const id = vm.vm_id ?? vm.id;
|
|
1042
|
+
return `${id} ${provider}-${region} ${state} ${name}`;
|
|
829
1043
|
}
|
|
830
1044
|
async function cmdVms(args, client) {
|
|
831
1045
|
const sub = args.positional[0];
|
|
@@ -870,6 +1084,10 @@ async function cmdVms(args, client) {
|
|
|
870
1084
|
await cmdRun({ ...args, positional: rest }, client);
|
|
871
1085
|
return;
|
|
872
1086
|
}
|
|
1087
|
+
case "resize": {
|
|
1088
|
+
await cmdResize({ ...args, positional: rest }, client);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
873
1091
|
default:
|
|
874
1092
|
die(`unknown vms subcommand: ${sub}`);
|
|
875
1093
|
}
|
|
@@ -888,14 +1106,22 @@ async function cmdFork(args, client) {
|
|
|
888
1106
|
sourceVmName = refPositional;
|
|
889
1107
|
}
|
|
890
1108
|
if (!sourceVmId && !sourceVmName) {
|
|
891
|
-
die("usage: arker fork <vm_name> | --source-vm-id <id> | --source-vm-name <name> [--source-org-id <org>]");
|
|
892
|
-
}
|
|
1109
|
+
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]");
|
|
1110
|
+
}
|
|
1111
|
+
const vcpu = numFlag(args, "vcpu");
|
|
1112
|
+
const memoryMib = numFlag(args, "memory-mib");
|
|
1113
|
+
const diskMib = numFlag(args, "disk-mib");
|
|
1114
|
+
const hasResources = vcpu !== void 0 || memoryMib !== void 0 || diskMib !== void 0;
|
|
1115
|
+
const resources = hasResources ? { vcpu: vcpu ?? null, memory_mib: memoryMib ?? null, disk_mib: diskMib ?? null } : void 0;
|
|
1116
|
+
const disk = boolFlag(args, "no-disk") ? false : void 0;
|
|
893
1117
|
const computer = await client.fork({
|
|
894
1118
|
sourceVmId,
|
|
895
1119
|
sourceVmName,
|
|
896
1120
|
sourceOrgId,
|
|
897
1121
|
name,
|
|
898
|
-
public: publicFlag
|
|
1122
|
+
public: publicFlag,
|
|
1123
|
+
...resources ? { resources } : {},
|
|
1124
|
+
...disk !== void 0 ? { disk } : {}
|
|
899
1125
|
});
|
|
900
1126
|
out({ vm_id: computer.id });
|
|
901
1127
|
}
|
|
@@ -903,13 +1129,24 @@ async function cmdRun(args, client) {
|
|
|
903
1129
|
const vmId = args.positional[0] ?? die("usage: arker run <vm_id> <command...>");
|
|
904
1130
|
const command = args.positional.slice(1).join(" ");
|
|
905
1131
|
if (!command) die("missing command to run");
|
|
1132
|
+
const sessionIdx = numFlag(args, "session-idx");
|
|
906
1133
|
const result = await client.vm(vmId).run(command, {
|
|
907
1134
|
background: boolFlag(args, "background"),
|
|
908
1135
|
timeout: numFlag(args, "timeout"),
|
|
909
1136
|
acquire: args.flags.acquire,
|
|
910
|
-
release: args.flags.release
|
|
1137
|
+
release: args.flags.release,
|
|
1138
|
+
session_id: args.flags["session-id"],
|
|
1139
|
+
...sessionIdx !== void 0 ? { session_idx: sessionIdx } : {}
|
|
911
1140
|
});
|
|
1141
|
+
if (args.flags.json) {
|
|
1142
|
+
out(runResultForJson(result));
|
|
1143
|
+
if (result.type === "completed") process.exitCode = result.exitCode === 0 ? 0 : result.exitCode;
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
912
1146
|
if (result.type === "completed") {
|
|
1147
|
+
if (result.memoryPartial) {
|
|
1148
|
+
err(`Memory target partially applied: requested ${formatMib(result.memoryRequestedMib)}, achieved ${formatMib(result.memoryAchievedMib)}.`);
|
|
1149
|
+
}
|
|
913
1150
|
process.stdout.write(new TextDecoder().decode(result.stdout));
|
|
914
1151
|
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
915
1152
|
process.exitCode = result.exitCode === 0 ? 0 : result.exitCode;
|
|
@@ -917,6 +1154,30 @@ async function cmdRun(args, client) {
|
|
|
917
1154
|
}
|
|
918
1155
|
out({ run_id: result.runId, state: result.state });
|
|
919
1156
|
}
|
|
1157
|
+
function formatMib(value) {
|
|
1158
|
+
return typeof value === "number" ? `${value} MiB` : "unknown";
|
|
1159
|
+
}
|
|
1160
|
+
function runResultForJson(result) {
|
|
1161
|
+
switch (result.type) {
|
|
1162
|
+
case "completed":
|
|
1163
|
+
return {
|
|
1164
|
+
type: result.type,
|
|
1165
|
+
runId: result.runId,
|
|
1166
|
+
state: result.state,
|
|
1167
|
+
stdout: new TextDecoder().decode(result.stdout),
|
|
1168
|
+
stdoutEncoding: result.stdoutEncoding,
|
|
1169
|
+
stderr: new TextDecoder().decode(result.stderr),
|
|
1170
|
+
stderrEncoding: result.stderrEncoding,
|
|
1171
|
+
exitCode: result.exitCode,
|
|
1172
|
+
failReason: result.failReason,
|
|
1173
|
+
memoryRequestedMib: result.memoryRequestedMib,
|
|
1174
|
+
memoryAchievedMib: result.memoryAchievedMib,
|
|
1175
|
+
memoryPartial: result.memoryPartial
|
|
1176
|
+
};
|
|
1177
|
+
case "background":
|
|
1178
|
+
return result;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
920
1181
|
async function cmdRuns(args, client) {
|
|
921
1182
|
const sub = args.positional[0];
|
|
922
1183
|
const rest = args.positional.slice(1);
|
|
@@ -1058,51 +1319,24 @@ async function cmdSync(args, client) {
|
|
|
1058
1319
|
}
|
|
1059
1320
|
import_node_process.stdout.write(await client.vm(vm).sync(path));
|
|
1060
1321
|
}
|
|
1061
|
-
async function
|
|
1062
|
-
const
|
|
1063
|
-
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
for (const t of res.tunnels) {
|
|
1076
|
-
out(`${t.tunnel_key ?? "-"} ${t.port} ${t.state} ${t.protocol} ${t.url ?? "-"}`);
|
|
1077
|
-
}
|
|
1078
|
-
if (res.next_cursor) out(`# next_cursor=${res.next_cursor}`);
|
|
1079
|
-
return;
|
|
1322
|
+
async function cmdResize(args, client) {
|
|
1323
|
+
const vm = args.positional[0];
|
|
1324
|
+
if (!vm) die("usage: arker resize <vm_id> [--memory-mib N] [--vcpu N] [--disk-mib N]");
|
|
1325
|
+
const memoryMib = numFlag(args, "memory-mib");
|
|
1326
|
+
const vcpu = numFlag(args, "vcpu");
|
|
1327
|
+
const diskMib = numFlag(args, "disk-mib");
|
|
1328
|
+
if (memoryMib === void 0 && vcpu === void 0 && diskMib === void 0) {
|
|
1329
|
+
die("resize: pass at least one of --memory-mib, --vcpu, --disk-mib");
|
|
1330
|
+
}
|
|
1331
|
+
const updated = await client.vm(vm).resize({
|
|
1332
|
+
resources: {
|
|
1333
|
+
vcpu: vcpu ?? null,
|
|
1334
|
+
memory_mib: memoryMib ?? null,
|
|
1335
|
+
disk_mib: diskMib ?? null
|
|
1080
1336
|
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
ports: parsePorts(args.flags.ports),
|
|
1085
|
-
auth_mode: args.flags["auth-mode"]
|
|
1086
|
-
});
|
|
1087
|
-
return out(tunnel);
|
|
1088
|
-
}
|
|
1089
|
-
case "get": {
|
|
1090
|
-
if (!vm) die("usage: arker tunnels get <vm_id> <key>");
|
|
1091
|
-
const key = rest[1] ?? die("missing key");
|
|
1092
|
-
out(await client.vm(vm).getTunnel(key));
|
|
1093
|
-
return;
|
|
1094
|
-
}
|
|
1095
|
-
case "rm":
|
|
1096
|
-
case "delete": {
|
|
1097
|
-
if (!vm) die("usage: arker tunnels rm <vm_id> <key>");
|
|
1098
|
-
const key = rest[1] ?? die("missing key");
|
|
1099
|
-
const r = await client.vm(vm).deleteTunnel(key);
|
|
1100
|
-
out(r.deleted ? `deleted tunnel ${key}` : "delete failed");
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
default:
|
|
1104
|
-
die(`usage: arker tunnels <ls|create|get|rm> ...`);
|
|
1105
|
-
}
|
|
1337
|
+
});
|
|
1338
|
+
if (args.flags.json) return out(updated);
|
|
1339
|
+
out(fmtVm(updated));
|
|
1106
1340
|
}
|
|
1107
1341
|
async function cmdFilesystems(args, client) {
|
|
1108
1342
|
const sub = args.positional[0];
|
|
@@ -1148,6 +1382,10 @@ async function cmdFilesystems(args, client) {
|
|
|
1148
1382
|
async function cmdShell(args, client) {
|
|
1149
1383
|
let computer;
|
|
1150
1384
|
const vmIdArg = args.flags["vm-id"] ?? args.positional[0];
|
|
1385
|
+
const explicitSessionId = args.flags["session-id"];
|
|
1386
|
+
if (!vmIdArg && explicitSessionId) {
|
|
1387
|
+
die("usage: arker shell <vm_id> --session-id <session_id>");
|
|
1388
|
+
}
|
|
1151
1389
|
if (vmIdArg) {
|
|
1152
1390
|
computer = await client.vm(vmIdArg).refresh();
|
|
1153
1391
|
} else {
|
|
@@ -1156,100 +1394,242 @@ async function cmdShell(args, client) {
|
|
|
1156
1394
|
sourceVmName,
|
|
1157
1395
|
sourceOrgId: ARKER_ORG_ID
|
|
1158
1396
|
});
|
|
1397
|
+
err(`forked ${computer.id}`);
|
|
1159
1398
|
}
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1399
|
+
let sessionId = explicitSessionId;
|
|
1400
|
+
if (!sessionId) {
|
|
1401
|
+
const session = await computer.createSession({
|
|
1402
|
+
cwd: args.flags.cwd
|
|
1403
|
+
});
|
|
1404
|
+
sessionId = session.session_id ?? session.id;
|
|
1405
|
+
if (!sessionId) die("createSession response missing session_id");
|
|
1406
|
+
}
|
|
1407
|
+
const persist = args.flags["no-persist"] === true ? false : boolFlag(args, "persist");
|
|
1408
|
+
const colsFlag = numFlag(args, "cols");
|
|
1409
|
+
const rowsFlag = numFlag(args, "rows");
|
|
1410
|
+
const cols = colsFlag ?? import_node_process.stdout.columns ?? 80;
|
|
1411
|
+
const rows = rowsFlag ?? import_node_process.stdout.rows ?? 24;
|
|
1412
|
+
const cancelTtlSecs = numFlag(args, "cancel-ttl");
|
|
1413
|
+
const pty = await computer.connectPty({
|
|
1414
|
+
sessionId,
|
|
1415
|
+
cols,
|
|
1416
|
+
rows,
|
|
1417
|
+
command: args.flags.command,
|
|
1418
|
+
persist,
|
|
1419
|
+
cancelTtlSecs
|
|
1163
1420
|
});
|
|
1164
|
-
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
if (
|
|
1179
|
-
|
|
1180
|
-
exitCode = 1;
|
|
1181
|
-
} else {
|
|
1182
|
-
exitCode = step.exitCode;
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
if (exitAfter || preload === "exit") {
|
|
1186
|
-
process.exit(exitCode);
|
|
1187
|
-
}
|
|
1188
|
-
const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout, prompt: "> " });
|
|
1189
|
-
rl.on("SIGINT", () => {
|
|
1190
|
-
if (inFlight) {
|
|
1191
|
-
process.stderr.write("^C\n");
|
|
1192
|
-
return;
|
|
1421
|
+
err(`connected ${computer.id} session ${sessionId}`);
|
|
1422
|
+
const exitCode = await bridgePty(pty, {
|
|
1423
|
+
fallbackCols: cols,
|
|
1424
|
+
fallbackRows: rows,
|
|
1425
|
+
autoResize: colsFlag === void 0 && rowsFlag === void 0 && Boolean(import_node_process.stdout.isTTY)
|
|
1426
|
+
});
|
|
1427
|
+
if (exitCode !== 0) process.exit(exitCode);
|
|
1428
|
+
}
|
|
1429
|
+
function bridgePty(pty, options) {
|
|
1430
|
+
return new Promise((resolve) => {
|
|
1431
|
+
let settled = false;
|
|
1432
|
+
let rawEnabled = false;
|
|
1433
|
+
const wasRaw = Boolean(import_node_process.stdin.isTTY && import_node_process.stdin.isRaw);
|
|
1434
|
+
const restoreTerminal = () => {
|
|
1435
|
+
if (rawEnabled && import_node_process.stdin.isTTY && typeof import_node_process.stdin.setRawMode === "function") {
|
|
1436
|
+
import_node_process.stdin.setRawMode(wasRaw);
|
|
1193
1437
|
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1438
|
+
rawEnabled = false;
|
|
1439
|
+
};
|
|
1440
|
+
const finish = (code) => {
|
|
1441
|
+
if (settled) return;
|
|
1442
|
+
settled = true;
|
|
1443
|
+
restoreTerminal();
|
|
1444
|
+
import_node_process.stdin.off("data", onInput);
|
|
1445
|
+
import_node_process.stdin.off("end", onInputEnd);
|
|
1446
|
+
process.off("SIGWINCH", onResize);
|
|
1447
|
+
process.off("SIGINT", onSigint);
|
|
1448
|
+
process.off("SIGTERM", onSigterm);
|
|
1449
|
+
process.off("SIGHUP", onSighup);
|
|
1450
|
+
process.off("exit", restoreTerminal);
|
|
1451
|
+
offData();
|
|
1452
|
+
offClose();
|
|
1453
|
+
offError();
|
|
1454
|
+
resolve(code);
|
|
1455
|
+
};
|
|
1456
|
+
const onInput = (chunk) => {
|
|
1457
|
+
pty.send(chunk);
|
|
1458
|
+
};
|
|
1459
|
+
const onInputEnd = () => {
|
|
1460
|
+
pty.close();
|
|
1461
|
+
};
|
|
1462
|
+
const onResize = () => {
|
|
1463
|
+
pty.resize(import_node_process.stdout.columns ?? options.fallbackCols, import_node_process.stdout.rows ?? options.fallbackRows);
|
|
1464
|
+
};
|
|
1465
|
+
const onSigint = () => {
|
|
1466
|
+
pty.send(new Uint8Array([3]));
|
|
1467
|
+
};
|
|
1468
|
+
const onSigterm = () => {
|
|
1469
|
+
pty.close();
|
|
1470
|
+
finish(143);
|
|
1471
|
+
};
|
|
1472
|
+
const onSighup = () => {
|
|
1473
|
+
pty.close();
|
|
1474
|
+
finish(129);
|
|
1475
|
+
};
|
|
1476
|
+
const offData = pty.onData((data) => {
|
|
1477
|
+
import_node_process.stdout.write(data);
|
|
1197
1478
|
});
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
const
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
if (
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
if (step.kind === "fatal") {
|
|
1210
|
-
err(`shell ended: ${step.message}`);
|
|
1211
|
-
exitCode = 1;
|
|
1212
|
-
break;
|
|
1213
|
-
}
|
|
1214
|
-
if (step.kind === "recoverable") {
|
|
1215
|
-
err(`error: ${step.message}`);
|
|
1479
|
+
const offClose = pty.onClose(() => finish(0));
|
|
1480
|
+
const offError = pty.onError((error) => {
|
|
1481
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1482
|
+
err(`pty error: ${message}`);
|
|
1483
|
+
});
|
|
1484
|
+
process.once("exit", restoreTerminal);
|
|
1485
|
+
pty.ready.then(() => {
|
|
1486
|
+
if (settled) return;
|
|
1487
|
+
if (import_node_process.stdin.isTTY && typeof import_node_process.stdin.setRawMode === "function") {
|
|
1488
|
+
import_node_process.stdin.setRawMode(true);
|
|
1489
|
+
rawEnabled = true;
|
|
1216
1490
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1491
|
+
import_node_process.stdin.resume();
|
|
1492
|
+
import_node_process.stdin.on("data", onInput);
|
|
1493
|
+
import_node_process.stdin.on("end", onInputEnd);
|
|
1494
|
+
if (options.autoResize) process.on("SIGWINCH", onResize);
|
|
1495
|
+
process.on("SIGINT", onSigint);
|
|
1496
|
+
process.on("SIGTERM", onSigterm);
|
|
1497
|
+
process.on("SIGHUP", onSighup);
|
|
1498
|
+
if (options.autoResize) onResize();
|
|
1499
|
+
}).catch((error) => {
|
|
1500
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1501
|
+
err(`pty failed to open: ${message}`);
|
|
1502
|
+
finish(1);
|
|
1222
1503
|
});
|
|
1504
|
+
});
|
|
1505
|
+
}
|
|
1506
|
+
var SSH_PORT_DEFAULT = 22;
|
|
1507
|
+
function defaultIdentityBase() {
|
|
1508
|
+
return (0, import_node_path.join)((0, import_node_os.homedir)(), ".ssh", "id_ed25519");
|
|
1509
|
+
}
|
|
1510
|
+
function resolveLocalSshKey(args) {
|
|
1511
|
+
const explicit = args.flags.identity;
|
|
1512
|
+
const privateKeyPath = explicit ? explicit.replace(/\.pub$/, "") : defaultIdentityBase();
|
|
1513
|
+
const publicKeyPath = `${privateKeyPath}.pub`;
|
|
1514
|
+
if (!(0, import_node_fs.existsSync)(publicKeyPath)) {
|
|
1515
|
+
if (!boolFlag(args, "generate")) {
|
|
1516
|
+
die(
|
|
1517
|
+
`no SSH public key at ${publicKeyPath}. Pass --identity <path>, or --generate to create an ed25519 key pair.`
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
err(`generating ed25519 key pair at ${privateKeyPath}`);
|
|
1521
|
+
const gen = (0, import_node_child_process.spawnSync)(
|
|
1522
|
+
"ssh-keygen",
|
|
1523
|
+
["-t", "ed25519", "-N", "", "-f", privateKeyPath, "-C", "arker-cli"],
|
|
1524
|
+
{ stdio: "inherit" }
|
|
1525
|
+
);
|
|
1526
|
+
if (gen.status !== 0) die("ssh-keygen failed to generate a key pair");
|
|
1223
1527
|
}
|
|
1224
|
-
|
|
1528
|
+
const publicKey = (0, import_node_fs.readFileSync)(publicKeyPath, "utf8").trim();
|
|
1529
|
+
if (!publicKey) die(`SSH public key at ${publicKeyPath} is empty`);
|
|
1530
|
+
return { publicKey, privateKeyPath, publicKeyPath };
|
|
1225
1531
|
}
|
|
1226
|
-
async function
|
|
1532
|
+
async function registerAccountSshKey(client, publicKey, label) {
|
|
1533
|
+
return client._request(
|
|
1534
|
+
"POST",
|
|
1535
|
+
"/v1/account/ssh-keys",
|
|
1536
|
+
{ public_key: publicKey, label: label ?? null },
|
|
1537
|
+
client.baseUrl
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
function resolveSshHost(args, client) {
|
|
1541
|
+
const explicit = args.flags.host ?? process.env.ARKER_SSH_HOST;
|
|
1542
|
+
if (explicit) return explicit;
|
|
1543
|
+
if (client.region) return `aws-${client.region}.arker.ai`;
|
|
1227
1544
|
try {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
1232
|
-
const tail = result.stderr.length > 0 ? result.stderr[result.stderr.length - 1] : result.stdout.length > 0 ? result.stdout[result.stdout.length - 1] : void 0;
|
|
1233
|
-
if (tail !== void 0 && tail !== 10) process.stdout.write("\n");
|
|
1234
|
-
return { kind: "ok", exitCode: result.exitCode };
|
|
1235
|
-
}
|
|
1236
|
-
out({ run_id: result.runId });
|
|
1237
|
-
return { kind: "ok", exitCode: 0 };
|
|
1238
|
-
} catch (e) {
|
|
1239
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1240
|
-
const kind = classifyShellError(message, computer.id);
|
|
1241
|
-
return { kind, message };
|
|
1545
|
+
return new URL(client.baseUrl).hostname;
|
|
1546
|
+
} catch {
|
|
1547
|
+
die("could not determine SSH host; pass --host <hostname>");
|
|
1242
1548
|
}
|
|
1243
1549
|
}
|
|
1244
|
-
function
|
|
1245
|
-
const
|
|
1246
|
-
if (
|
|
1247
|
-
|
|
1550
|
+
async function cmdSsh(args, client) {
|
|
1551
|
+
const vmId = args.flags["vm-id"] ?? args.positional[0];
|
|
1552
|
+
if (!vmId) {
|
|
1553
|
+
die(
|
|
1554
|
+
"usage: arker ssh <vm_id> [--identity <path>] [--generate] [--host <h>] [--port <n>] [--connect|-c] [--skip-register]"
|
|
1555
|
+
);
|
|
1248
1556
|
}
|
|
1249
|
-
|
|
1250
|
-
|
|
1557
|
+
const { publicKey, privateKeyPath } = resolveLocalSshKey(args);
|
|
1558
|
+
if (!boolFlag(args, "skip-register")) {
|
|
1559
|
+
const label = args.flags.label ?? `arker-cli ${(0, import_node_os.homedir)().split("/").pop() ?? ""}`.trim();
|
|
1560
|
+
try {
|
|
1561
|
+
const key = await registerAccountSshKey(client, publicKey, label);
|
|
1562
|
+
err(`registered SSH key ${key.fingerprint}${key.label ? ` (${key.label})` : ""}`);
|
|
1563
|
+
} catch (e) {
|
|
1564
|
+
if (e instanceof ArkerError && e.status === 409) {
|
|
1565
|
+
die(`this SSH key is already registered to a different account: ${e.message}`);
|
|
1566
|
+
}
|
|
1567
|
+
if (e instanceof ArkerError && e.status === 403) {
|
|
1568
|
+
die(
|
|
1569
|
+
"your API key lacks the developer role required to register SSH keys. Register the key in the console, then re-run with --skip-register."
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
throw e;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
const host = resolveSshHost(args, client);
|
|
1576
|
+
const port = numFlag(args, "port") ?? SSH_PORT_DEFAULT;
|
|
1577
|
+
const sshArgs = [
|
|
1578
|
+
"-i",
|
|
1579
|
+
privateKeyPath,
|
|
1580
|
+
...port !== 22 ? ["-p", String(port)] : [],
|
|
1581
|
+
`${vmId}@${host}`
|
|
1582
|
+
];
|
|
1583
|
+
const command = `ssh ${sshArgs.join(" ")}`;
|
|
1584
|
+
if (boolFlag(args, "connect") || boolFlag(args, "c")) {
|
|
1585
|
+
err(`connecting: ${command}`);
|
|
1586
|
+
const r = (0, import_node_child_process.spawnSync)("ssh", sshArgs, { stdio: "inherit" });
|
|
1587
|
+
process.exit(r.status ?? 1);
|
|
1588
|
+
}
|
|
1589
|
+
out(command);
|
|
1590
|
+
}
|
|
1591
|
+
async function cmdSshKeys(args, client) {
|
|
1592
|
+
const sub = args.positional[0];
|
|
1593
|
+
const rest = args.positional.slice(1);
|
|
1594
|
+
switch (sub) {
|
|
1595
|
+
case void 0:
|
|
1596
|
+
case "ls":
|
|
1597
|
+
case "list": {
|
|
1598
|
+
const res = await client._request(
|
|
1599
|
+
"GET",
|
|
1600
|
+
"/v1/account/ssh-keys",
|
|
1601
|
+
void 0,
|
|
1602
|
+
client.baseUrl
|
|
1603
|
+
);
|
|
1604
|
+
if (args.flags.json) return out(res);
|
|
1605
|
+
for (const k of res.keys) {
|
|
1606
|
+
out(`${k.id} ${k.fingerprint} ${k.label ?? "\u2014"}`);
|
|
1607
|
+
}
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
case "add": {
|
|
1611
|
+
const { publicKey } = resolveLocalSshKey(args);
|
|
1612
|
+
const label = args.flags.label;
|
|
1613
|
+
const key = await registerAccountSshKey(client, publicKey, label);
|
|
1614
|
+
if (args.flags.json) return out(key);
|
|
1615
|
+
out(`${key.id} ${key.fingerprint} ${key.label ?? "\u2014"}`);
|
|
1616
|
+
return;
|
|
1617
|
+
}
|
|
1618
|
+
case "rm":
|
|
1619
|
+
case "delete": {
|
|
1620
|
+
const id = rest[0] ?? die("usage: arker ssh-keys rm <key_id>");
|
|
1621
|
+
const r = await client._request(
|
|
1622
|
+
"DELETE",
|
|
1623
|
+
`/v1/account/ssh-keys/${encodeURIComponent(id)}`,
|
|
1624
|
+
void 0,
|
|
1625
|
+
client.baseUrl
|
|
1626
|
+
);
|
|
1627
|
+
out(r.deleted ? `deleted ${id}` : "delete failed");
|
|
1628
|
+
return;
|
|
1629
|
+
}
|
|
1630
|
+
default:
|
|
1631
|
+
die("usage: arker ssh-keys <ls|add|rm> ...");
|
|
1251
1632
|
}
|
|
1252
|
-
return "recoverable";
|
|
1253
1633
|
}
|
|
1254
1634
|
function numFlag(args, name) {
|
|
1255
1635
|
const v = args.flags[name];
|
|
@@ -1263,10 +1643,6 @@ function boolFlag(args, name) {
|
|
|
1263
1643
|
if (v === "false" || v === "0") return false;
|
|
1264
1644
|
return true;
|
|
1265
1645
|
}
|
|
1266
|
-
function parsePorts(value) {
|
|
1267
|
-
if (typeof value !== "string" || value.trim() === "") return void 0;
|
|
1268
|
-
return value.split(",").map((part) => Number(part.trim())).filter((port) => Number.isFinite(port));
|
|
1269
|
-
}
|
|
1270
1646
|
async function readAllStdin() {
|
|
1271
1647
|
const chunks = [];
|
|
1272
1648
|
for await (const chunk of import_node_process.stdin) chunks.push(chunk);
|
|
@@ -1287,16 +1663,21 @@ function usage() {
|
|
|
1287
1663
|
" arker fork --source-vm-id <id> fork by global id",
|
|
1288
1664
|
" arker fork --source-vm-name <n> --source-org-id <org>",
|
|
1289
1665
|
" fork by name in another org",
|
|
1290
|
-
" arker
|
|
1291
|
-
"
|
|
1666
|
+
" arker fork <vm> [--vcpu N] [--memory-mib N] [--disk-mib N] [--no-disk]",
|
|
1667
|
+
" fork with resource/network overrides",
|
|
1668
|
+
" arker run <vm> <command> [--session-id <id>] [--session-idx N] run a command",
|
|
1669
|
+
" arker resize <vm> [--memory-mib N] [--vcpu N] [--disk-mib N] resize a VM (PATCH)",
|
|
1670
|
+
" arker shell [vm_id] native PTY shell (forks ubuntu-full if no vm)",
|
|
1671
|
+
" arker ssh <vm_id> register your SSH key + print the ssh command",
|
|
1672
|
+
" arker ssh <vm_id> --connect register + drop straight into the ssh session",
|
|
1292
1673
|
"",
|
|
1293
1674
|
"Resources:",
|
|
1294
1675
|
" arker vms <ls|get|rm|fork|run> ...",
|
|
1295
1676
|
" arker runs <ls|get|rm> <vm_id> ...",
|
|
1296
1677
|
" arker sessions <ls|get|create|rm> <vm_id> ...",
|
|
1297
1678
|
" arker syncs <ls|create|rm> <vm_id> ...",
|
|
1298
|
-
" arker tunnels <ls|get|rm> <vm_id> ...",
|
|
1299
1679
|
" arker filesystems <ls|create|get|rm> ... (alias: fs)",
|
|
1680
|
+
" arker ssh-keys <ls|add|rm> ... manage account SSH keys",
|
|
1300
1681
|
"",
|
|
1301
1682
|
"Flags:",
|
|
1302
1683
|
" --api-key <key> (or env ARKER_API_KEY)",
|
|
@@ -1306,6 +1687,35 @@ function usage() {
|
|
|
1306
1687
|
" --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
|
|
1307
1688
|
" --json emit JSON instead of tabular output",
|
|
1308
1689
|
"",
|
|
1690
|
+
"Fork flags:",
|
|
1691
|
+
" --vcpu <n> vCPU count for the new VM (capped by source max_vcpus)",
|
|
1692
|
+
" --memory-mib <n> memory (MiB) for the new VM",
|
|
1693
|
+
" --disk-mib <n> disk size (MiB) for the new VM",
|
|
1694
|
+
" --no-disk fork a memory-backed (nodisk) VM",
|
|
1695
|
+
"",
|
|
1696
|
+
"Run flags:",
|
|
1697
|
+
" --session-id <ulid> run in a specific existing session",
|
|
1698
|
+
" --session-idx <n> run in the session at this index (default 0)",
|
|
1699
|
+
" --background return a run id instead of blocking",
|
|
1700
|
+
" --timeout <secs> per-run timeout",
|
|
1701
|
+
" --acquire <list> warm resources before the run (cpu,memory,disk)",
|
|
1702
|
+
" --release <list> release resources after the run (cpu,memory,disk)",
|
|
1703
|
+
"",
|
|
1704
|
+
"Shell flags:",
|
|
1705
|
+
" --session-id <id> reconnect to an existing PTY session",
|
|
1706
|
+
" --command <path> shell executable path (default: /bin/bash)",
|
|
1707
|
+
" --cols <n> --rows <n> initial terminal size",
|
|
1708
|
+
" --no-persist close the remote PTY process on disconnect",
|
|
1709
|
+
"",
|
|
1710
|
+
"SSH flags:",
|
|
1711
|
+
" --identity <path> local key (private or .pub); default ~/.ssh/id_ed25519",
|
|
1712
|
+
" --generate create an ed25519 key pair if none exists",
|
|
1713
|
+
" --host <hostname> SSH host (or env ARKER_SSH_HOST; default aws-<region>.arker.ai)",
|
|
1714
|
+
" --port <n> SSH port (default 22)",
|
|
1715
|
+
" --connect, -c exec ssh instead of just printing the command",
|
|
1716
|
+
" --skip-register don't register the key (assume already registered)",
|
|
1717
|
+
" --label <text> label for the registered key",
|
|
1718
|
+
"",
|
|
1309
1719
|
`Arker org id: ${ARKER_ORG_ID}`
|
|
1310
1720
|
].join("\n")
|
|
1311
1721
|
);
|
|
@@ -1336,6 +1746,11 @@ async function main() {
|
|
|
1336
1746
|
return await cmdSyncs(args, client);
|
|
1337
1747
|
case "shell":
|
|
1338
1748
|
return await cmdShell(args, client);
|
|
1749
|
+
case "ssh":
|
|
1750
|
+
return await cmdSsh(args, client);
|
|
1751
|
+
case "ssh-keys":
|
|
1752
|
+
case "ssh_keys":
|
|
1753
|
+
return await cmdSshKeys(args, client);
|
|
1339
1754
|
// Resources.
|
|
1340
1755
|
case "vms":
|
|
1341
1756
|
return await cmdVms(args, client);
|
|
@@ -1343,8 +1758,8 @@ async function main() {
|
|
|
1343
1758
|
return await cmdRuns(args, client);
|
|
1344
1759
|
case "sessions":
|
|
1345
1760
|
return await cmdSessions(args, client);
|
|
1346
|
-
case "
|
|
1347
|
-
return await
|
|
1761
|
+
case "resize":
|
|
1762
|
+
return await cmdResize(args, client);
|
|
1348
1763
|
case "filesystems":
|
|
1349
1764
|
case "fs":
|
|
1350
1765
|
return await cmdFilesystems(args, client);
|