@arker-ai/sdk 0.6.2 → 0.7.0
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/{chunk-7BHPVQNG.js → chunk-4NHUAVXK.js} +132 -10
- package/dist/{chunk-35IEV6BU.js → chunk-YFJEL7PF.js} +1 -1
- package/dist/cli.cjs +150 -164
- package/dist/cli.js +18 -155
- package/dist/daytona.cjs +132 -10
- package/dist/daytona.js +2 -2
- package/dist/e2b.cjs +132 -10
- package/dist/e2b.js +2 -2
- package/dist/index.cjs +132 -10
- package/dist/index.d.cts +103 -2
- package/dist/index.d.ts +103 -2
- package/dist/index.js +1 -1
- package/dist/modal.cjs +132 -10
- package/dist/modal.js +2 -2
- package/package.json +2 -2
|
@@ -56,6 +56,7 @@ var Arker = class {
|
|
|
56
56
|
provider;
|
|
57
57
|
apiKey;
|
|
58
58
|
fetchImpl;
|
|
59
|
+
http2;
|
|
59
60
|
retry;
|
|
60
61
|
constructor(opts = {}) {
|
|
61
62
|
const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
|
|
@@ -77,6 +78,7 @@ var Arker = class {
|
|
|
77
78
|
this.region = region ? normalizeRegion(region) : void 0;
|
|
78
79
|
this.provider = effectiveProvider;
|
|
79
80
|
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
81
|
+
this.http2 = opts.fetch === void 0;
|
|
80
82
|
this.retry = normalizeRetry(opts.retry);
|
|
81
83
|
if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
|
|
82
84
|
}
|
|
@@ -137,7 +139,10 @@ var Arker = class {
|
|
|
137
139
|
egress: src.egress ?? null,
|
|
138
140
|
disk: src.disk ?? true,
|
|
139
141
|
durable: src.durable ?? null,
|
|
140
|
-
resources
|
|
142
|
+
resources,
|
|
143
|
+
// ARK-125: omit to inherit the source's policy; pass a doc to override
|
|
144
|
+
// (an empty `{ policies: [] }` clears to allow-all, NOT inherit).
|
|
145
|
+
policies: src.policies ?? null
|
|
141
146
|
};
|
|
142
147
|
const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
|
|
143
148
|
const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
|
|
@@ -232,30 +237,29 @@ var Arker = class {
|
|
|
232
237
|
if (value !== void 0) headers[key] = value;
|
|
233
238
|
}
|
|
234
239
|
}
|
|
235
|
-
|
|
240
|
+
let requestBody;
|
|
236
241
|
if (body !== void 0) {
|
|
237
242
|
headers["content-type"] = "application/json";
|
|
238
|
-
|
|
243
|
+
requestBody = JSON.stringify(withoutUndefined(body));
|
|
239
244
|
}
|
|
240
245
|
let lastStatus = 0;
|
|
241
246
|
let lastText = "";
|
|
242
247
|
let lastError;
|
|
243
248
|
for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
|
|
244
249
|
try {
|
|
245
|
-
const
|
|
246
|
-
const text = await response.text();
|
|
250
|
+
const { status, ok, text } = await sendRequest(url, { method, headers, body: requestBody }, this.fetchImpl, this.http2);
|
|
247
251
|
const payload = parseJson(text);
|
|
248
252
|
const parsedError = extractError(payload);
|
|
249
|
-
lastStatus =
|
|
253
|
+
lastStatus = status;
|
|
250
254
|
lastText = text;
|
|
251
255
|
lastError = parsedError;
|
|
252
|
-
if (isRetryable(
|
|
256
|
+
if (isRetryable(status, parsedError) && attempt < this.retry.attempts - 1) {
|
|
253
257
|
await sleep(retryDelay(this.retry, attempt));
|
|
254
258
|
continue;
|
|
255
259
|
}
|
|
256
|
-
if (parsedError) throw new ArkerError(parsedError.code, parsedError.message,
|
|
257
|
-
if (!
|
|
258
|
-
throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${
|
|
260
|
+
if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, status);
|
|
261
|
+
if (!ok) {
|
|
262
|
+
throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${status}`, status);
|
|
259
263
|
}
|
|
260
264
|
return payload;
|
|
261
265
|
} catch (error) {
|
|
@@ -467,6 +471,32 @@ var VM = class _VM {
|
|
|
467
471
|
async delete() {
|
|
468
472
|
return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
|
|
469
473
|
}
|
|
474
|
+
// ── ARK-125 egress policy ────────────────────────────────────────
|
|
475
|
+
/**
|
|
476
|
+
* Read this VM's outbound egress policy document (ARK-125). Returns an
|
|
477
|
+
* empty doc (`{}`) when no policy is set.
|
|
478
|
+
*/
|
|
479
|
+
async getPolicies() {
|
|
480
|
+
return this._client._request("GET", `${vmPath(this.id)}/policies`, void 0, this.baseUrl);
|
|
481
|
+
}
|
|
482
|
+
/**
|
|
483
|
+
* Replace this VM's outbound egress policy with `doc` — an ordered,
|
|
484
|
+
* first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
|
|
485
|
+
* clears the policy to allow-all. Returns the stored policy plus the
|
|
486
|
+
* domains it escalates to MITM and any degrade warnings.
|
|
487
|
+
*
|
|
488
|
+
* await vm.setPolicies({
|
|
489
|
+
* policies: [
|
|
490
|
+
* { type: "network.outbound",
|
|
491
|
+
* match: { domains: ["github.com"], ports: [443] },
|
|
492
|
+
* action: "allow" },
|
|
493
|
+
* { type: "network.outbound", action: "deny" },
|
|
494
|
+
* ],
|
|
495
|
+
* });
|
|
496
|
+
*/
|
|
497
|
+
async setPolicies(doc) {
|
|
498
|
+
return this._client._request("PUT", `${vmPath(this.id)}/policies`, doc, this.baseUrl);
|
|
499
|
+
}
|
|
470
500
|
// ── Syncs: bindings of a filesystem into this VM at a path ────────
|
|
471
501
|
async listSyncs(opts = {}) {
|
|
472
502
|
return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
|
|
@@ -518,6 +548,20 @@ var VM = class _VM {
|
|
|
518
548
|
async deleteSession(sessionId) {
|
|
519
549
|
return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
|
|
520
550
|
}
|
|
551
|
+
/**
|
|
552
|
+
* Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
|
|
553
|
+
* (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
|
|
554
|
+
* PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
|
|
555
|
+
* (which sends an in-band control frame on the live WebSocket).
|
|
556
|
+
*/
|
|
557
|
+
async updateSession(sessionId, update) {
|
|
558
|
+
return this._client._request(
|
|
559
|
+
"PATCH",
|
|
560
|
+
`${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`,
|
|
561
|
+
{ cols: update.cols, rows: update.rows, timeout_secs: update.timeoutSecs },
|
|
562
|
+
this.baseUrl
|
|
563
|
+
);
|
|
564
|
+
}
|
|
521
565
|
async connectPty(options = {}) {
|
|
522
566
|
const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
|
|
523
567
|
const useTicket = options.useTicket ?? !isNodeRuntime();
|
|
@@ -929,6 +973,84 @@ function base64ToBytes(text) {
|
|
|
929
973
|
function bufferConstructor() {
|
|
930
974
|
return globalThis.Buffer;
|
|
931
975
|
}
|
|
976
|
+
var http2Module;
|
|
977
|
+
function loadHttp2() {
|
|
978
|
+
return http2Module ??= (async () => {
|
|
979
|
+
const proc = globalThis.process;
|
|
980
|
+
if (!proc?.versions?.node) return null;
|
|
981
|
+
try {
|
|
982
|
+
return await import(
|
|
983
|
+
/* webpackIgnore: true */
|
|
984
|
+
/* @vite-ignore */
|
|
985
|
+
"http2"
|
|
986
|
+
);
|
|
987
|
+
} catch {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
})();
|
|
991
|
+
}
|
|
992
|
+
var HTTP2_REQUEST_TIMEOUT_MS = 12e4;
|
|
993
|
+
var Http2Connection = class {
|
|
994
|
+
confirmed = false;
|
|
995
|
+
streams = 0;
|
|
996
|
+
session;
|
|
997
|
+
constructor(http2, origin) {
|
|
998
|
+
this.session = http2.connect(origin);
|
|
999
|
+
this.session.on("error", () => {
|
|
1000
|
+
});
|
|
1001
|
+
}
|
|
1002
|
+
get closed() {
|
|
1003
|
+
return this.session.closed || this.session.destroyed;
|
|
1004
|
+
}
|
|
1005
|
+
request(method, path, headers, body) {
|
|
1006
|
+
if (this.streams === 0) this.session.ref();
|
|
1007
|
+
this.streams++;
|
|
1008
|
+
return new Promise((resolve, reject) => {
|
|
1009
|
+
const stream = this.session.request({ ...headers, ":method": method, ":path": path });
|
|
1010
|
+
let status = 0;
|
|
1011
|
+
let text = "";
|
|
1012
|
+
stream.setEncoding("utf8");
|
|
1013
|
+
stream.setTimeout(HTTP2_REQUEST_TIMEOUT_MS, () => stream.destroy(new Error("HTTP/2 request timed out")));
|
|
1014
|
+
stream.on("response", (responseHeaders) => {
|
|
1015
|
+
this.confirmed = true;
|
|
1016
|
+
status = Number(responseHeaders[":status"]) || 0;
|
|
1017
|
+
});
|
|
1018
|
+
stream.on("data", (chunk) => {
|
|
1019
|
+
text += chunk;
|
|
1020
|
+
});
|
|
1021
|
+
stream.on("end", () => resolve({ status, ok: status >= 200 && status < 300, text }));
|
|
1022
|
+
stream.on("error", reject);
|
|
1023
|
+
stream.end(body);
|
|
1024
|
+
}).finally(() => {
|
|
1025
|
+
if (--this.streams === 0) this.session.unref();
|
|
1026
|
+
});
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
var http2Connections = /* @__PURE__ */ new Map();
|
|
1030
|
+
async function sendRequest(url, init, fetchImpl, http2Enabled) {
|
|
1031
|
+
if (http2Enabled) {
|
|
1032
|
+
const http2 = await loadHttp2();
|
|
1033
|
+
if (http2) {
|
|
1034
|
+
const { origin, pathname, search } = new URL(url);
|
|
1035
|
+
const cached = http2Connections.get(origin);
|
|
1036
|
+
if (cached !== null) {
|
|
1037
|
+
let connection = cached;
|
|
1038
|
+
if (!connection || connection.closed) {
|
|
1039
|
+
connection = new Http2Connection(http2, origin);
|
|
1040
|
+
http2Connections.set(origin, connection);
|
|
1041
|
+
}
|
|
1042
|
+
try {
|
|
1043
|
+
return await connection.request(init.method, `${pathname}${search}`, init.headers, init.body);
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
if (connection.confirmed) throw error;
|
|
1046
|
+
http2Connections.set(origin, null);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const response = await fetchImpl(url, init);
|
|
1052
|
+
return { status: response.status, ok: response.ok, text: await response.text() };
|
|
1053
|
+
}
|
|
932
1054
|
|
|
933
1055
|
export {
|
|
934
1056
|
CHUNK_SIZE,
|
package/dist/cli.cjs
CHANGED
|
@@ -26,6 +26,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
26
26
|
// src/cli.ts
|
|
27
27
|
var import_node_fs = require("fs");
|
|
28
28
|
var import_node_child_process = require("child_process");
|
|
29
|
+
var import_node_module = require("module");
|
|
29
30
|
var import_node_os = require("os");
|
|
30
31
|
var import_node_path = require("path");
|
|
31
32
|
var import_node_process = require("process");
|
|
@@ -88,6 +89,7 @@ var Arker = class {
|
|
|
88
89
|
provider;
|
|
89
90
|
apiKey;
|
|
90
91
|
fetchImpl;
|
|
92
|
+
http2;
|
|
91
93
|
retry;
|
|
92
94
|
constructor(opts = {}) {
|
|
93
95
|
const apiKey = opts.apiKey ?? env("ARKER_API_KEY") ?? env("AUTH_KEY");
|
|
@@ -109,6 +111,7 @@ var Arker = class {
|
|
|
109
111
|
this.region = region ? normalizeRegion(region) : void 0;
|
|
110
112
|
this.provider = effectiveProvider;
|
|
111
113
|
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
114
|
+
this.http2 = opts.fetch === void 0;
|
|
112
115
|
this.retry = normalizeRetry(opts.retry);
|
|
113
116
|
if (!this.fetchImpl) throw new Error("fetch is required in this runtime");
|
|
114
117
|
}
|
|
@@ -169,7 +172,10 @@ var Arker = class {
|
|
|
169
172
|
egress: src.egress ?? null,
|
|
170
173
|
disk: src.disk ?? true,
|
|
171
174
|
durable: src.durable ?? null,
|
|
172
|
-
resources
|
|
175
|
+
resources,
|
|
176
|
+
// ARK-125: omit to inherit the source's policy; pass a doc to override
|
|
177
|
+
// (an empty `{ policies: [] }` clears to allow-all, NOT inherit).
|
|
178
|
+
policies: src.policies ?? null
|
|
173
179
|
};
|
|
174
180
|
const useBurst = sourceOrgId === ARKER_ORG_ID && src.sourceVmName !== void 0 && isBurstRef(src.sourceVmName);
|
|
175
181
|
const baseUrl = useBurst && this.burstBaseUrl ? this.burstBaseUrl : this.baseUrl;
|
|
@@ -264,30 +270,29 @@ var Arker = class {
|
|
|
264
270
|
if (value !== void 0) headers[key] = value;
|
|
265
271
|
}
|
|
266
272
|
}
|
|
267
|
-
|
|
273
|
+
let requestBody;
|
|
268
274
|
if (body !== void 0) {
|
|
269
275
|
headers["content-type"] = "application/json";
|
|
270
|
-
|
|
276
|
+
requestBody = JSON.stringify(withoutUndefined(body));
|
|
271
277
|
}
|
|
272
278
|
let lastStatus = 0;
|
|
273
279
|
let lastText = "";
|
|
274
280
|
let lastError;
|
|
275
281
|
for (let attempt = 0; attempt < this.retry.attempts; attempt++) {
|
|
276
282
|
try {
|
|
277
|
-
const
|
|
278
|
-
const text = await response.text();
|
|
283
|
+
const { status, ok, text } = await sendRequest(url, { method, headers, body: requestBody }, this.fetchImpl, this.http2);
|
|
279
284
|
const payload = parseJson(text);
|
|
280
285
|
const parsedError = extractError(payload);
|
|
281
|
-
lastStatus =
|
|
286
|
+
lastStatus = status;
|
|
282
287
|
lastText = text;
|
|
283
288
|
lastError = parsedError;
|
|
284
|
-
if (isRetryable(
|
|
289
|
+
if (isRetryable(status, parsedError) && attempt < this.retry.attempts - 1) {
|
|
285
290
|
await sleep(retryDelay(this.retry, attempt));
|
|
286
291
|
continue;
|
|
287
292
|
}
|
|
288
|
-
if (parsedError) throw new ArkerError(parsedError.code, parsedError.message,
|
|
289
|
-
if (!
|
|
290
|
-
throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${
|
|
293
|
+
if (parsedError) throw new ArkerError(parsedError.code, parsedError.message, status);
|
|
294
|
+
if (!ok) {
|
|
295
|
+
throw new ArkerError("internal", lastText.slice(0, 300) || `HTTP ${status}`, status);
|
|
291
296
|
}
|
|
292
297
|
return payload;
|
|
293
298
|
} catch (error) {
|
|
@@ -499,6 +504,32 @@ var VM = class _VM {
|
|
|
499
504
|
async delete() {
|
|
500
505
|
return this._client._request("DELETE", vmPath(this.id), void 0, this.baseUrl);
|
|
501
506
|
}
|
|
507
|
+
// ── ARK-125 egress policy ────────────────────────────────────────
|
|
508
|
+
/**
|
|
509
|
+
* Read this VM's outbound egress policy document (ARK-125). Returns an
|
|
510
|
+
* empty doc (`{}`) when no policy is set.
|
|
511
|
+
*/
|
|
512
|
+
async getPolicies() {
|
|
513
|
+
return this._client._request("GET", `${vmPath(this.id)}/policies`, void 0, this.baseUrl);
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Replace this VM's outbound egress policy with `doc` — an ordered,
|
|
517
|
+
* first-match-wins rule list. An empty doc (`{}` or `{ policies: [] }`)
|
|
518
|
+
* clears the policy to allow-all. Returns the stored policy plus the
|
|
519
|
+
* domains it escalates to MITM and any degrade warnings.
|
|
520
|
+
*
|
|
521
|
+
* await vm.setPolicies({
|
|
522
|
+
* policies: [
|
|
523
|
+
* { type: "network.outbound",
|
|
524
|
+
* match: { domains: ["github.com"], ports: [443] },
|
|
525
|
+
* action: "allow" },
|
|
526
|
+
* { type: "network.outbound", action: "deny" },
|
|
527
|
+
* ],
|
|
528
|
+
* });
|
|
529
|
+
*/
|
|
530
|
+
async setPolicies(doc) {
|
|
531
|
+
return this._client._request("PUT", `${vmPath(this.id)}/policies`, doc, this.baseUrl);
|
|
532
|
+
}
|
|
502
533
|
// ── Syncs: bindings of a filesystem into this VM at a path ────────
|
|
503
534
|
async listSyncs(opts = {}) {
|
|
504
535
|
return this._client._request("GET", buildQuery(`${vmPath(this.id)}/syncs`, {
|
|
@@ -550,6 +581,20 @@ var VM = class _VM {
|
|
|
550
581
|
async deleteSession(sessionId) {
|
|
551
582
|
return this._client._request("DELETE", `${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`, void 0, this.baseUrl);
|
|
552
583
|
}
|
|
584
|
+
/**
|
|
585
|
+
* Update a session via `PATCH /v1/vms/{id}/sessions/{sid}`: resize its PTY
|
|
586
|
+
* (`cols`/`rows`) and/or set the idle `timeoutSecs`. Works whether or not a
|
|
587
|
+
* PTY is currently attached — the REST equivalent of {@link PtyConnection.resize}
|
|
588
|
+
* (which sends an in-band control frame on the live WebSocket).
|
|
589
|
+
*/
|
|
590
|
+
async updateSession(sessionId, update) {
|
|
591
|
+
return this._client._request(
|
|
592
|
+
"PATCH",
|
|
593
|
+
`${vmPath(this.id)}/sessions/${pathSegment(sessionId)}`,
|
|
594
|
+
{ cols: update.cols, rows: update.rows, timeout_secs: update.timeoutSecs },
|
|
595
|
+
this.baseUrl
|
|
596
|
+
);
|
|
597
|
+
}
|
|
553
598
|
async connectPty(options = {}) {
|
|
554
599
|
const sessionId = options.sessionId ?? sessionIdFrom(await this.createSession());
|
|
555
600
|
const useTicket = options.useTicket ?? !isNodeRuntime();
|
|
@@ -961,8 +1006,94 @@ function base64ToBytes(text) {
|
|
|
961
1006
|
function bufferConstructor() {
|
|
962
1007
|
return globalThis.Buffer;
|
|
963
1008
|
}
|
|
1009
|
+
var http2Module;
|
|
1010
|
+
function loadHttp2() {
|
|
1011
|
+
return http2Module ??= (async () => {
|
|
1012
|
+
const proc = globalThis.process;
|
|
1013
|
+
if (!proc?.versions?.node) return null;
|
|
1014
|
+
try {
|
|
1015
|
+
return await import(
|
|
1016
|
+
/* webpackIgnore: true */
|
|
1017
|
+
/* @vite-ignore */
|
|
1018
|
+
"http2"
|
|
1019
|
+
);
|
|
1020
|
+
} catch {
|
|
1021
|
+
return null;
|
|
1022
|
+
}
|
|
1023
|
+
})();
|
|
1024
|
+
}
|
|
1025
|
+
var HTTP2_REQUEST_TIMEOUT_MS = 12e4;
|
|
1026
|
+
var Http2Connection = class {
|
|
1027
|
+
confirmed = false;
|
|
1028
|
+
streams = 0;
|
|
1029
|
+
session;
|
|
1030
|
+
constructor(http2, origin) {
|
|
1031
|
+
this.session = http2.connect(origin);
|
|
1032
|
+
this.session.on("error", () => {
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
get closed() {
|
|
1036
|
+
return this.session.closed || this.session.destroyed;
|
|
1037
|
+
}
|
|
1038
|
+
request(method, path, headers, body) {
|
|
1039
|
+
if (this.streams === 0) this.session.ref();
|
|
1040
|
+
this.streams++;
|
|
1041
|
+
return new Promise((resolve, reject) => {
|
|
1042
|
+
const stream = this.session.request({ ...headers, ":method": method, ":path": path });
|
|
1043
|
+
let status = 0;
|
|
1044
|
+
let text = "";
|
|
1045
|
+
stream.setEncoding("utf8");
|
|
1046
|
+
stream.setTimeout(HTTP2_REQUEST_TIMEOUT_MS, () => stream.destroy(new Error("HTTP/2 request timed out")));
|
|
1047
|
+
stream.on("response", (responseHeaders) => {
|
|
1048
|
+
this.confirmed = true;
|
|
1049
|
+
status = Number(responseHeaders[":status"]) || 0;
|
|
1050
|
+
});
|
|
1051
|
+
stream.on("data", (chunk) => {
|
|
1052
|
+
text += chunk;
|
|
1053
|
+
});
|
|
1054
|
+
stream.on("end", () => resolve({ status, ok: status >= 200 && status < 300, text }));
|
|
1055
|
+
stream.on("error", reject);
|
|
1056
|
+
stream.end(body);
|
|
1057
|
+
}).finally(() => {
|
|
1058
|
+
if (--this.streams === 0) this.session.unref();
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
};
|
|
1062
|
+
var http2Connections = /* @__PURE__ */ new Map();
|
|
1063
|
+
async function sendRequest(url, init, fetchImpl, http2Enabled) {
|
|
1064
|
+
if (http2Enabled) {
|
|
1065
|
+
const http2 = await loadHttp2();
|
|
1066
|
+
if (http2) {
|
|
1067
|
+
const { origin, pathname, search } = new URL(url);
|
|
1068
|
+
const cached = http2Connections.get(origin);
|
|
1069
|
+
if (cached !== null) {
|
|
1070
|
+
let connection = cached;
|
|
1071
|
+
if (!connection || connection.closed) {
|
|
1072
|
+
connection = new Http2Connection(http2, origin);
|
|
1073
|
+
http2Connections.set(origin, connection);
|
|
1074
|
+
}
|
|
1075
|
+
try {
|
|
1076
|
+
return await connection.request(init.method, `${pathname}${search}`, init.headers, init.body);
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
if (connection.confirmed) throw error;
|
|
1079
|
+
http2Connections.set(origin, null);
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const response = await fetchImpl(url, init);
|
|
1085
|
+
return { status: response.status, ok: response.ok, text: await response.text() };
|
|
1086
|
+
}
|
|
964
1087
|
|
|
965
1088
|
// src/cli.ts
|
|
1089
|
+
var import_meta = {};
|
|
1090
|
+
var VERSION = (() => {
|
|
1091
|
+
try {
|
|
1092
|
+
return (0, import_node_module.createRequire)(import_meta.url)("../package.json").version;
|
|
1093
|
+
} catch {
|
|
1094
|
+
return "unknown";
|
|
1095
|
+
}
|
|
1096
|
+
})();
|
|
966
1097
|
function parseArgs(argv) {
|
|
967
1098
|
const positional = [];
|
|
968
1099
|
const flags = {};
|
|
@@ -1001,6 +1132,7 @@ function readFileConfig() {
|
|
|
1001
1132
|
}
|
|
1002
1133
|
return {};
|
|
1003
1134
|
}
|
|
1135
|
+
var DEFAULT_REGION = "us-west-2";
|
|
1004
1136
|
function clientFromArgs(args) {
|
|
1005
1137
|
const file = readFileConfig();
|
|
1006
1138
|
const explicitBaseUrl = args.flags["base-url"] ?? process.env.ARKER_BASE_URL;
|
|
@@ -1008,14 +1140,11 @@ function clientFromArgs(args) {
|
|
|
1008
1140
|
const apiKey = args.flags["api-key"] ?? process.env.ARKER_API_KEY ?? file.apiKey;
|
|
1009
1141
|
const baseUrl = explicitBaseUrl ?? (explicitRegion ? void 0 : file.baseUrl);
|
|
1010
1142
|
const controlBaseUrl = args.flags["control-base-url"] ?? process.env.ARKER_CONTROL_BASE_URL;
|
|
1011
|
-
const region = explicitRegion ?? file.region;
|
|
1143
|
+
const region = explicitRegion ?? file.region ?? (baseUrl ? void 0 : DEFAULT_REGION);
|
|
1012
1144
|
const provider = args.flags.provider ?? process.env.ARKER_PROVIDER;
|
|
1013
1145
|
if (!apiKey) {
|
|
1014
1146
|
die("Missing API key. Set ARKER_API_KEY or pass --api-key.");
|
|
1015
1147
|
}
|
|
1016
|
-
if (!baseUrl && !region) {
|
|
1017
|
-
die("Missing region. Set ARKER_REGION or pass --region (e.g. us-west-2). --provider (aws|aws-burst) defaults to aws.");
|
|
1018
|
-
}
|
|
1019
1148
|
return new Arker({ apiKey, baseUrl, region, provider, controlBaseUrl });
|
|
1020
1149
|
}
|
|
1021
1150
|
function out(value) {
|
|
@@ -1503,134 +1632,6 @@ function bridgePty(pty, options) {
|
|
|
1503
1632
|
});
|
|
1504
1633
|
});
|
|
1505
1634
|
}
|
|
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");
|
|
1527
|
-
}
|
|
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
|
-
);
|
|
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`;
|
|
1544
|
-
try {
|
|
1545
|
-
return new URL(client.baseUrl).hostname;
|
|
1546
|
-
} catch {
|
|
1547
|
-
die("could not determine SSH host; pass --host <hostname>");
|
|
1548
|
-
}
|
|
1549
|
-
}
|
|
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
|
-
}
|
|
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> ...");
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
1635
|
function numFlag(args, name) {
|
|
1635
1636
|
const v = args.flags[name];
|
|
1636
1637
|
if (typeof v === "string") return Number(v);
|
|
@@ -1651,7 +1652,7 @@ async function readAllStdin() {
|
|
|
1651
1652
|
function usage() {
|
|
1652
1653
|
out(
|
|
1653
1654
|
[
|
|
1654
|
-
|
|
1655
|
+
`arker v${VERSION}`,
|
|
1655
1656
|
"",
|
|
1656
1657
|
"Usage:",
|
|
1657
1658
|
" arker <command> [args]",
|
|
@@ -1668,8 +1669,6 @@ function usage() {
|
|
|
1668
1669
|
" arker run <vm> <command> [--session-id <id>] [--session-idx N] run a command",
|
|
1669
1670
|
" arker resize <vm> [--memory-mib N] [--vcpu N] [--disk-mib N] resize a VM (PATCH)",
|
|
1670
1671
|
" 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",
|
|
1673
1672
|
"",
|
|
1674
1673
|
"Resources:",
|
|
1675
1674
|
" arker vms <ls|get|rm|fork|run> ...",
|
|
@@ -1677,12 +1676,11 @@ function usage() {
|
|
|
1677
1676
|
" arker sessions <ls|get|create|rm> <vm_id> ...",
|
|
1678
1677
|
" arker syncs <ls|create|rm> <vm_id> ...",
|
|
1679
1678
|
" arker filesystems <ls|create|get|rm> ... (alias: fs)",
|
|
1680
|
-
" arker ssh-keys <ls|add|rm> ... manage account SSH keys",
|
|
1681
1679
|
"",
|
|
1682
1680
|
"Flags:",
|
|
1683
1681
|
" --api-key <key> (or env ARKER_API_KEY)",
|
|
1684
1682
|
" --region <region> (or env ARKER_REGION; e.g. us-west-2)",
|
|
1685
|
-
" --provider <aws
|
|
1683
|
+
" --provider <aws> (or env ARKER_PROVIDER; default aws)",
|
|
1686
1684
|
" --base-url <url> override compute URL (env ARKER_BASE_URL)",
|
|
1687
1685
|
" --control-base-url <url> override CF Worker URL (env ARKER_CONTROL_BASE_URL)",
|
|
1688
1686
|
" --json emit JSON instead of tabular output",
|
|
@@ -1705,18 +1703,7 @@ function usage() {
|
|
|
1705
1703
|
" --session-id <id> reconnect to an existing PTY session",
|
|
1706
1704
|
" --command <path> shell executable path (default: /bin/bash)",
|
|
1707
1705
|
" --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
|
-
"",
|
|
1719
|
-
`Arker org id: ${ARKER_ORG_ID}`
|
|
1706
|
+
" --no-persist close the remote PTY process on disconnect"
|
|
1720
1707
|
].join("\n")
|
|
1721
1708
|
);
|
|
1722
1709
|
process.exit(2);
|
|
@@ -1746,11 +1733,10 @@ async function main() {
|
|
|
1746
1733
|
return await cmdSyncs(args, client);
|
|
1747
1734
|
case "shell":
|
|
1748
1735
|
return await cmdShell(args, client);
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
return await cmdSshKeys(args, client);
|
|
1736
|
+
// SSH is descoped/unsupported and hidden from the interface. The
|
|
1737
|
+
// implementation below (cmdSsh / cmdSshKeys) is kept intact; re-add
|
|
1738
|
+
// the `ssh` / `ssh-keys` cases here and their help entries to expose
|
|
1739
|
+
// it once the server-side SSH path is supported.
|
|
1754
1740
|
// Resources.
|
|
1755
1741
|
case "vms":
|
|
1756
1742
|
return await cmdVms(args, client);
|