@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/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-PI3H3TGC.js → chunk-7BHPVQNG.js} +265 -23
- package/dist/cli.cjs +621 -160
- package/dist/cli.js +356 -137
- 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 +275 -23
- package/dist/index.d.cts +229 -101
- package/dist/index.d.ts +229 -101
- 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,12 +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
|
-
|
|
165
|
-
|
|
166
|
-
max_memory_mib: src.max_memory_mib ?? null,
|
|
167
|
-
disk_mib: src.disk_mib ?? null,
|
|
168
|
-
durable: src.durable ?? null
|
|
171
|
+
durable: src.durable ?? null,
|
|
172
|
+
resources
|
|
169
173
|
};
|
|
170
174
|
const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
|
|
171
175
|
const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
|
|
@@ -196,6 +200,33 @@ var Arker = class {
|
|
|
196
200
|
});
|
|
197
201
|
return { vms, nextCursor: resp.next_cursor ?? null };
|
|
198
202
|
}
|
|
203
|
+
/**
|
|
204
|
+
* List run activity visible to the authenticated caller across VMs,
|
|
205
|
+
* providers, and regions. Admin call — routed through the control plane.
|
|
206
|
+
*/
|
|
207
|
+
async listRuns(opts = {}) {
|
|
208
|
+
return this._request("GET", buildQuery("/v1/runs", {
|
|
209
|
+
since: opts.since,
|
|
210
|
+
until: opts.until,
|
|
211
|
+
vm: opts.vm,
|
|
212
|
+
vms: opts.vmIds && opts.vmIds.length > 0 ? opts.vmIds.join(",") : void 0,
|
|
213
|
+
region: opts.region,
|
|
214
|
+
provider: opts.provider,
|
|
215
|
+
source: opts.source,
|
|
216
|
+
search: opts.search,
|
|
217
|
+
limit: opts.limit,
|
|
218
|
+
offset: opts.offset,
|
|
219
|
+
lite: opts.lite === void 0 ? void 0 : opts.lite,
|
|
220
|
+
runtime: opts.runtime,
|
|
221
|
+
endpoint: opts.endpoint,
|
|
222
|
+
actions: opts.actions && opts.actions.length > 0 ? opts.actions.join(",") : void 0,
|
|
223
|
+
status: opts.status && opts.status.length > 0 ? opts.status.join(",") : void 0,
|
|
224
|
+
status_min: opts.statusMin,
|
|
225
|
+
status_max: opts.statusMax,
|
|
226
|
+
sort: opts.sort,
|
|
227
|
+
dir: opts.dir
|
|
228
|
+
}), void 0, this.controlBaseUrl);
|
|
229
|
+
}
|
|
199
230
|
/** Compute call — goes direct to the backend hosting this VM (no
|
|
200
231
|
* control-plane hop). Returns a fully-populated VM handle. */
|
|
201
232
|
async getVm(vmId) {
|
|
@@ -285,6 +316,10 @@ var Arker = class {
|
|
|
285
316
|
return retryDelay(this.retry, attempt);
|
|
286
317
|
}
|
|
287
318
|
/** @internal */
|
|
319
|
+
_authHeaders() {
|
|
320
|
+
return { authorization: `Bearer ${this.apiKey}` };
|
|
321
|
+
}
|
|
322
|
+
/** @internal */
|
|
288
323
|
_baseUrlFor(ref) {
|
|
289
324
|
if (isBurstRef(ref) && this.burstBaseUrl) return this.burstBaseUrl;
|
|
290
325
|
return this.baseUrl;
|
|
@@ -310,12 +345,15 @@ var VM = class _VM {
|
|
|
310
345
|
vcpu_count;
|
|
311
346
|
memory_mib;
|
|
312
347
|
disk_mib;
|
|
348
|
+
network;
|
|
349
|
+
max_vcpus;
|
|
350
|
+
max_memory_mib;
|
|
351
|
+
min_memory_mib;
|
|
313
352
|
started_at;
|
|
314
353
|
root_source_vm_id;
|
|
315
354
|
root_source_vm_name;
|
|
316
355
|
worker_id;
|
|
317
356
|
sessions;
|
|
318
|
-
tunnels;
|
|
319
357
|
constructor(client, vmId, baseUrl = client._baseUrlFor(vmId), data) {
|
|
320
358
|
this._client = client;
|
|
321
359
|
this.id = vmId;
|
|
@@ -438,8 +476,25 @@ var VM = class _VM {
|
|
|
438
476
|
}
|
|
439
477
|
throw new ArkerError(lastError?.code ?? "internal", lastError?.message ?? "write failed", 200);
|
|
440
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
|
+
*/
|
|
441
487
|
async resize(request) {
|
|
442
|
-
|
|
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);
|
|
443
498
|
}
|
|
444
499
|
async delete() {
|
|
445
500
|
return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
|
|
@@ -495,19 +550,30 @@ var VM = class _VM {
|
|
|
495
550
|
async deleteSession(sessionId) {
|
|
496
551
|
return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
|
|
497
552
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
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);
|
|
511
577
|
}
|
|
512
578
|
};
|
|
513
579
|
function buildQuery(path, params) {
|
|
@@ -519,6 +585,176 @@ function buildQuery(path, params) {
|
|
|
519
585
|
const qs = usp.toString();
|
|
520
586
|
return qs ? `${path}?${qs}` : path;
|
|
521
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
|
+
}
|
|
522
758
|
function normalizeBaseUrl(baseUrl) {
|
|
523
759
|
const trimmed = baseUrl.trim().replace(/\/+$/, "");
|
|
524
760
|
if (!trimmed) throw new Error("baseUrl must not be empty");
|
|
@@ -601,15 +837,17 @@ function parseRunResponse(payload) {
|
|
|
601
837
|
stderr: decodeBytes(stderr, stderrEncoding),
|
|
602
838
|
stderrEncoding,
|
|
603
839
|
exitCode: numberField(body.exit_code, "run response.exit_code"),
|
|
604
|
-
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
|
|
605
844
|
};
|
|
606
845
|
}
|
|
607
846
|
if (typeof body.run_id === "string") {
|
|
608
847
|
return {
|
|
609
848
|
type: "background",
|
|
610
849
|
runId: body.run_id,
|
|
611
|
-
state: typeof body.state === "string" ? body.state : "running"
|
|
612
|
-
tunnels: Array.isArray(body.tunnels) ? body.tunnels : []
|
|
850
|
+
state: typeof body.state === "string" ? body.state : "running"
|
|
613
851
|
};
|
|
614
852
|
}
|
|
615
853
|
throw new ArkerError("internal", "unrecognized run response shape", 200);
|
|
@@ -673,6 +911,10 @@ function numberField(value, context) {
|
|
|
673
911
|
if (typeof value !== "number") throw new ArkerError("internal", `${context} must be a number`, 200);
|
|
674
912
|
return value;
|
|
675
913
|
}
|
|
914
|
+
function optionalNumberOrNull(value) {
|
|
915
|
+
if (value === null || typeof value === "number") return value;
|
|
916
|
+
return void 0;
|
|
917
|
+
}
|
|
676
918
|
function assertWriteComplete(result, context) {
|
|
677
919
|
if (result.complete && result.written) return;
|
|
678
920
|
throw new ArkerError("internal", `${context} did not complete`, 200);
|
|
@@ -748,20 +990,25 @@ function parseArgs(argv) {
|
|
|
748
990
|
return { positional, flags };
|
|
749
991
|
}
|
|
750
992
|
function readFileConfig() {
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
757
1001
|
}
|
|
1002
|
+
return {};
|
|
758
1003
|
}
|
|
759
1004
|
function clientFromArgs(args) {
|
|
760
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;
|
|
761
1008
|
const apiKey = args.flags["api-key"] ?? process.env.ARKER_API_KEY ?? file.apiKey;
|
|
762
|
-
const baseUrl =
|
|
1009
|
+
const baseUrl = explicitBaseUrl ?? (explicitRegion ? void 0 : file.baseUrl);
|
|
763
1010
|
const controlBaseUrl = args.flags["control-base-url"] ?? process.env.ARKER_CONTROL_BASE_URL;
|
|
764
|
-
const region =
|
|
1011
|
+
const region = explicitRegion ?? file.region;
|
|
765
1012
|
const provider = args.flags.provider ?? process.env.ARKER_PROVIDER;
|
|
766
1013
|
if (!apiKey) {
|
|
767
1014
|
die("Missing API key. Set ARKER_API_KEY or pass --api-key.");
|
|
@@ -791,7 +1038,8 @@ function fmtVm(vm) {
|
|
|
791
1038
|
const region = vm.region ?? "?";
|
|
792
1039
|
const name = vm.name ?? "\u2014";
|
|
793
1040
|
const state = vm.state ?? "?";
|
|
794
|
-
|
|
1041
|
+
const id = vm.vm_id ?? vm.id;
|
|
1042
|
+
return `${id} ${provider}-${region} ${state} ${name}`;
|
|
795
1043
|
}
|
|
796
1044
|
async function cmdVms(args, client) {
|
|
797
1045
|
const sub = args.positional[0];
|
|
@@ -836,6 +1084,10 @@ async function cmdVms(args, client) {
|
|
|
836
1084
|
await cmdRun({ ...args, positional: rest }, client);
|
|
837
1085
|
return;
|
|
838
1086
|
}
|
|
1087
|
+
case "resize": {
|
|
1088
|
+
await cmdResize({ ...args, positional: rest }, client);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
839
1091
|
default:
|
|
840
1092
|
die(`unknown vms subcommand: ${sub}`);
|
|
841
1093
|
}
|
|
@@ -854,14 +1106,22 @@ async function cmdFork(args, client) {
|
|
|
854
1106
|
sourceVmName = refPositional;
|
|
855
1107
|
}
|
|
856
1108
|
if (!sourceVmId && !sourceVmName) {
|
|
857
|
-
die("usage: arker fork <vm_name> | --source-vm-id <id> | --source-vm-name <name> [--source-org-id <org>]");
|
|
858
|
-
}
|
|
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;
|
|
859
1117
|
const computer = await client.fork({
|
|
860
1118
|
sourceVmId,
|
|
861
1119
|
sourceVmName,
|
|
862
1120
|
sourceOrgId,
|
|
863
1121
|
name,
|
|
864
|
-
public: publicFlag
|
|
1122
|
+
public: publicFlag,
|
|
1123
|
+
...resources ? { resources } : {},
|
|
1124
|
+
...disk !== void 0 ? { disk } : {}
|
|
865
1125
|
});
|
|
866
1126
|
out({ vm_id: computer.id });
|
|
867
1127
|
}
|
|
@@ -869,19 +1129,54 @@ async function cmdRun(args, client) {
|
|
|
869
1129
|
const vmId = args.positional[0] ?? die("usage: arker run <vm_id> <command...>");
|
|
870
1130
|
const command = args.positional.slice(1).join(" ");
|
|
871
1131
|
if (!command) die("missing command to run");
|
|
1132
|
+
const sessionIdx = numFlag(args, "session-idx");
|
|
872
1133
|
const result = await client.vm(vmId).run(command, {
|
|
873
1134
|
background: boolFlag(args, "background"),
|
|
874
1135
|
timeout: numFlag(args, "timeout"),
|
|
875
1136
|
acquire: args.flags.acquire,
|
|
876
|
-
release: args.flags.release
|
|
1137
|
+
release: args.flags.release,
|
|
1138
|
+
session_id: args.flags["session-id"],
|
|
1139
|
+
...sessionIdx !== void 0 ? { session_idx: sessionIdx } : {}
|
|
877
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
|
+
}
|
|
878
1146
|
if (result.type === "completed") {
|
|
1147
|
+
if (result.memoryPartial) {
|
|
1148
|
+
err(`Memory target partially applied: requested ${formatMib(result.memoryRequestedMib)}, achieved ${formatMib(result.memoryAchievedMib)}.`);
|
|
1149
|
+
}
|
|
879
1150
|
process.stdout.write(new TextDecoder().decode(result.stdout));
|
|
880
1151
|
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
881
1152
|
process.exitCode = result.exitCode === 0 ? 0 : result.exitCode;
|
|
882
1153
|
return;
|
|
883
1154
|
}
|
|
884
|
-
out({ run_id: result.runId,
|
|
1155
|
+
out({ run_id: result.runId, state: result.state });
|
|
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
|
+
}
|
|
885
1180
|
}
|
|
886
1181
|
async function cmdRuns(args, client) {
|
|
887
1182
|
const sub = args.positional[0];
|
|
@@ -1024,43 +1319,24 @@ async function cmdSync(args, client) {
|
|
|
1024
1319
|
}
|
|
1025
1320
|
import_node_process.stdout.write(await client.vm(vm).sync(path));
|
|
1026
1321
|
}
|
|
1027
|
-
async function
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
const
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
for (const t of res.tunnels) {
|
|
1042
|
-
out(`${t.port} ${t.state} ${t.protocol} ${t.url ?? "-"}`);
|
|
1043
|
-
}
|
|
1044
|
-
if (res.next_cursor) out(`# next_cursor=${res.next_cursor}`);
|
|
1045
|
-
return;
|
|
1046
|
-
}
|
|
1047
|
-
case "get": {
|
|
1048
|
-
if (!vm) die("usage: arker tunnels get <vm_id> <port>");
|
|
1049
|
-
const port = Number(rest[1] ?? die("missing port"));
|
|
1050
|
-
out(await client.vm(vm).getTunnel(port));
|
|
1051
|
-
return;
|
|
1052
|
-
}
|
|
1053
|
-
case "rm":
|
|
1054
|
-
case "delete": {
|
|
1055
|
-
if (!vm) die("usage: arker tunnels rm <vm_id> <port>");
|
|
1056
|
-
const port = Number(rest[1] ?? die("missing port"));
|
|
1057
|
-
const r = await client.vm(vm).deleteTunnel(port);
|
|
1058
|
-
out(r.deleted ? `deleted tunnel ${port}` : "delete failed");
|
|
1059
|
-
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
|
|
1060
1336
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1337
|
+
});
|
|
1338
|
+
if (args.flags.json) return out(updated);
|
|
1339
|
+
out(fmtVm(updated));
|
|
1064
1340
|
}
|
|
1065
1341
|
async function cmdFilesystems(args, client) {
|
|
1066
1342
|
const sub = args.positional[0];
|
|
@@ -1106,6 +1382,10 @@ async function cmdFilesystems(args, client) {
|
|
|
1106
1382
|
async function cmdShell(args, client) {
|
|
1107
1383
|
let computer;
|
|
1108
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
|
+
}
|
|
1109
1389
|
if (vmIdArg) {
|
|
1110
1390
|
computer = await client.vm(vmIdArg).refresh();
|
|
1111
1391
|
} else {
|
|
@@ -1114,100 +1394,242 @@ async function cmdShell(args, client) {
|
|
|
1114
1394
|
sourceVmName,
|
|
1115
1395
|
sourceOrgId: ARKER_ORG_ID
|
|
1116
1396
|
});
|
|
1397
|
+
err(`forked ${computer.id}`);
|
|
1117
1398
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
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
|
|
1121
1420
|
});
|
|
1122
|
-
|
|
1123
|
-
const
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
if (
|
|
1137
|
-
|
|
1138
|
-
exitCode = 1;
|
|
1139
|
-
} else {
|
|
1140
|
-
exitCode = step.exitCode;
|
|
1141
|
-
}
|
|
1142
|
-
}
|
|
1143
|
-
if (exitAfter || preload === "exit") {
|
|
1144
|
-
process.exit(exitCode);
|
|
1145
|
-
}
|
|
1146
|
-
const rl = readline.createInterface({ input: import_node_process.stdin, output: import_node_process.stdout, prompt: "> " });
|
|
1147
|
-
rl.on("SIGINT", () => {
|
|
1148
|
-
if (inFlight) {
|
|
1149
|
-
process.stderr.write("^C\n");
|
|
1150
|
-
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);
|
|
1151
1437
|
}
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
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);
|
|
1155
1478
|
});
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
const
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
if (
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
if (step.kind === "fatal") {
|
|
1168
|
-
err(`shell ended: ${step.message}`);
|
|
1169
|
-
exitCode = 1;
|
|
1170
|
-
break;
|
|
1171
|
-
}
|
|
1172
|
-
if (step.kind === "recoverable") {
|
|
1173
|
-
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;
|
|
1174
1490
|
}
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
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);
|
|
1180
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");
|
|
1181
1527
|
}
|
|
1182
|
-
|
|
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 };
|
|
1531
|
+
}
|
|
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
|
+
);
|
|
1183
1539
|
}
|
|
1184
|
-
|
|
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`;
|
|
1185
1544
|
try {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
if (result.stderr.length) process.stderr.write(new TextDecoder().decode(result.stderr));
|
|
1190
|
-
const tail = result.stderr.length > 0 ? result.stderr[result.stderr.length - 1] : result.stdout.length > 0 ? result.stdout[result.stdout.length - 1] : void 0;
|
|
1191
|
-
if (tail !== void 0 && tail !== 10) process.stdout.write("\n");
|
|
1192
|
-
return { kind: "ok", exitCode: result.exitCode };
|
|
1193
|
-
}
|
|
1194
|
-
out({ run_id: result.runId });
|
|
1195
|
-
return { kind: "ok", exitCode: 0 };
|
|
1196
|
-
} catch (e) {
|
|
1197
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1198
|
-
const kind = classifyShellError(message, computer.id);
|
|
1199
|
-
return { kind, message };
|
|
1545
|
+
return new URL(client.baseUrl).hostname;
|
|
1546
|
+
} catch {
|
|
1547
|
+
die("could not determine SSH host; pass --host <hostname>");
|
|
1200
1548
|
}
|
|
1201
1549
|
}
|
|
1202
|
-
function
|
|
1203
|
-
const
|
|
1204
|
-
if (
|
|
1205
|
-
|
|
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
|
+
);
|
|
1556
|
+
}
|
|
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
|
+
}
|
|
1206
1574
|
}
|
|
1207
|
-
|
|
1208
|
-
|
|
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> ...");
|
|
1209
1632
|
}
|
|
1210
|
-
return "recoverable";
|
|
1211
1633
|
}
|
|
1212
1634
|
function numFlag(args, name) {
|
|
1213
1635
|
const v = args.flags[name];
|
|
@@ -1241,16 +1663,21 @@ function usage() {
|
|
|
1241
1663
|
" arker fork --source-vm-id <id> fork by global id",
|
|
1242
1664
|
" arker fork --source-vm-name <n> --source-org-id <org>",
|
|
1243
1665
|
" fork by name in another org",
|
|
1244
|
-
" arker
|
|
1245
|
-
"
|
|
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",
|
|
1246
1673
|
"",
|
|
1247
1674
|
"Resources:",
|
|
1248
1675
|
" arker vms <ls|get|rm|fork|run> ...",
|
|
1249
1676
|
" arker runs <ls|get|rm> <vm_id> ...",
|
|
1250
1677
|
" arker sessions <ls|get|create|rm> <vm_id> ...",
|
|
1251
1678
|
" arker syncs <ls|create|rm> <vm_id> ...",
|
|
1252
|
-
" arker tunnels <ls|get|rm> <vm_id> ...",
|
|
1253
1679
|
" arker filesystems <ls|create|get|rm> ... (alias: fs)",
|
|
1680
|
+
" arker ssh-keys <ls|add|rm> ... manage account SSH keys",
|
|
1254
1681
|
"",
|
|
1255
1682
|
"Flags:",
|
|
1256
1683
|
" --api-key <key> (or env ARKER_API_KEY)",
|
|
@@ -1260,6 +1687,35 @@ function usage() {
|
|
|
1260
1687
|
" --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
|
|
1261
1688
|
" --json emit JSON instead of tabular output",
|
|
1262
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
|
+
"",
|
|
1263
1719
|
`Arker org id: ${ARKER_ORG_ID}`
|
|
1264
1720
|
].join("\n")
|
|
1265
1721
|
);
|
|
@@ -1290,6 +1746,11 @@ async function main() {
|
|
|
1290
1746
|
return await cmdSyncs(args, client);
|
|
1291
1747
|
case "shell":
|
|
1292
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);
|
|
1293
1754
|
// Resources.
|
|
1294
1755
|
case "vms":
|
|
1295
1756
|
return await cmdVms(args, client);
|
|
@@ -1297,8 +1758,8 @@ async function main() {
|
|
|
1297
1758
|
return await cmdRuns(args, client);
|
|
1298
1759
|
case "sessions":
|
|
1299
1760
|
return await cmdSessions(args, client);
|
|
1300
|
-
case "
|
|
1301
|
-
return await
|
|
1761
|
+
case "resize":
|
|
1762
|
+
return await cmdResize(args, client);
|
|
1302
1763
|
case "filesystems":
|
|
1303
1764
|
case "fs":
|
|
1304
1765
|
return await cmdFilesystems(args, client);
|