airgate 0.1.0 → 0.1.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 +14 -2
- package/dist/airgate.mjs +985 -475
- package/package.json +1 -1
package/dist/airgate.mjs
CHANGED
|
@@ -229,7 +229,76 @@ function spaceName(taken = [], random = Math.random) {
|
|
|
229
229
|
if (!used.has(name)) return name;
|
|
230
230
|
}
|
|
231
231
|
const base = `${pick(FIRST, random)}-${pick(SECOND, random)}`;
|
|
232
|
-
for (let
|
|
232
|
+
for (let n2 = 2; ; n2++) if (!used.has(`${base}-${n2}`)) return `${base}-${n2}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ../sandbox/src/dotenv.ts
|
|
236
|
+
var SECRET_NAME_RE = /^[A-Z_][A-Z0-9_]{0,63}$/;
|
|
237
|
+
function reservedSecretName(name) {
|
|
238
|
+
return name.startsWith("AIRGATE_") || name.startsWith("LD_") || name.startsWith("DYLD_") || ["PATH", "HOME", "USER", "SHELL", "PWD", "BASH_ENV", "ENV", "PROMPT_COMMAND", "IFS", "TERM", "TMUX"].includes(name);
|
|
239
|
+
}
|
|
240
|
+
function suggestSecretName(raw) {
|
|
241
|
+
const s2 = raw.trim().toUpperCase().replace(/[^A-Z0-9_]/g, "_").replace(/^([0-9])/, "_$1").slice(0, 64);
|
|
242
|
+
return SECRET_NAME_RE.test(s2) && !reservedSecretName(s2) ? s2 : null;
|
|
243
|
+
}
|
|
244
|
+
var unescapeDouble = (s2) => s2.replace(/\\([nrt"\\$])/g, (_m, c) => ({ n: "\n", r: "\r", t: " " })[c] ?? c);
|
|
245
|
+
function parseDotenv(input) {
|
|
246
|
+
const lines = input.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n").split("\n");
|
|
247
|
+
const entries = [];
|
|
248
|
+
const unparsed = [];
|
|
249
|
+
for (let i = 0; i < lines.length; i++) {
|
|
250
|
+
const lineNo = i + 1;
|
|
251
|
+
const line2 = lines[i].trim();
|
|
252
|
+
if (!line2 || line2.startsWith("#")) continue;
|
|
253
|
+
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line2);
|
|
254
|
+
if (!m) {
|
|
255
|
+
unparsed.push(lineNo);
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
const key = m[1];
|
|
259
|
+
let rest = m[2];
|
|
260
|
+
let value;
|
|
261
|
+
const q = rest[0];
|
|
262
|
+
if (q === '"' || q === "'" || q === "`") {
|
|
263
|
+
let body = rest.slice(1);
|
|
264
|
+
let close = findClose(body, q);
|
|
265
|
+
while (close < 0 && i + 1 < lines.length) {
|
|
266
|
+
i++;
|
|
267
|
+
body += "\n" + lines[i];
|
|
268
|
+
close = findClose(body, q);
|
|
269
|
+
}
|
|
270
|
+
const raw = close < 0 ? body : body.slice(0, close);
|
|
271
|
+
value = q === '"' ? unescapeDouble(raw) : raw;
|
|
272
|
+
} else {
|
|
273
|
+
value = rest.replace(/\s+#.*$/, "").trim();
|
|
274
|
+
}
|
|
275
|
+
let name = key;
|
|
276
|
+
let note;
|
|
277
|
+
if (!SECRET_NAME_RE.test(key)) {
|
|
278
|
+
name = suggestSecretName(key);
|
|
279
|
+
note = name ? `saved as ${name}` : "not a usable name";
|
|
280
|
+
} else if (reservedSecretName(key)) {
|
|
281
|
+
name = null;
|
|
282
|
+
note = "reserved (the pod sets it)";
|
|
283
|
+
}
|
|
284
|
+
entries.push({ key, name, value, line: lineNo, ...note ? { note } : {} });
|
|
285
|
+
}
|
|
286
|
+
const values = {};
|
|
287
|
+
for (const e of entries) if (e.name) values[e.name] = e.value;
|
|
288
|
+
return { entries, values, unparsed };
|
|
289
|
+
}
|
|
290
|
+
function findClose(s2, q) {
|
|
291
|
+
for (let i = 0; i < s2.length; i++) {
|
|
292
|
+
if (q === '"' && s2[i] === "\\") {
|
|
293
|
+
i++;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
if (s2[i] === q) return i;
|
|
297
|
+
}
|
|
298
|
+
return -1;
|
|
299
|
+
}
|
|
300
|
+
function dotenvExample(names) {
|
|
301
|
+
return names.map((n2) => `${n2}=`).join("\n") + (names.length ? "\n" : "");
|
|
233
302
|
}
|
|
234
303
|
|
|
235
304
|
// src/api.ts
|
|
@@ -356,8 +425,8 @@ var Api = class {
|
|
|
356
425
|
pod(id) {
|
|
357
426
|
return this.request("GET", `/v1/pods/${enc(id)}`).then((r) => r.pod);
|
|
358
427
|
}
|
|
359
|
-
launch(spec, returnUrl) {
|
|
360
|
-
return this.request("POST", "/v1/pods", { body: { spec, returnUrl } }).then((r) => r.pod);
|
|
428
|
+
launch(spec, returnUrl, secrets2) {
|
|
429
|
+
return this.request("POST", "/v1/pods", { body: { spec, returnUrl, ...secrets2 && Object.keys(secrets2).length ? { secrets: secrets2 } : {} } }).then((r) => r.pod);
|
|
361
430
|
}
|
|
362
431
|
stop(id) {
|
|
363
432
|
return this.request("POST", `/v1/pods/${enc(id)}/stop`, { body: {} }).then((r) => r.pod);
|
|
@@ -436,6 +505,9 @@ var Api = class {
|
|
|
436
505
|
githubConnect(returnUrl) {
|
|
437
506
|
return this.request("POST", "/v1/github/connect", { body: { returnUrl } });
|
|
438
507
|
}
|
|
508
|
+
inspectRepo(repo) {
|
|
509
|
+
return this.request("GET", `/v1/github/inspect?repo=${encodeURIComponent(repo)}`);
|
|
510
|
+
}
|
|
439
511
|
githubRepos(fresh = false) {
|
|
440
512
|
return this.request("GET", `/v1/github/repos${fresh ? "?fresh=1" : ""}`).then((r) => r.repos);
|
|
441
513
|
}
|
|
@@ -448,6 +520,19 @@ var Api = class {
|
|
|
448
520
|
setSecret(name, value, podId) {
|
|
449
521
|
return this.request("PUT", `/v1/secrets/${enc(name)}`, { body: { value, ...podId ? { podId } : {} } });
|
|
450
522
|
}
|
|
523
|
+
previews(id) {
|
|
524
|
+
return this.request("GET", `/v1/pods/${enc(id)}/previews`);
|
|
525
|
+
}
|
|
526
|
+
ensurePreview(id, body) {
|
|
527
|
+
return this.request("POST", `/v1/pods/${enc(id)}/previews`, { body }).then((r) => r.preview);
|
|
528
|
+
}
|
|
529
|
+
openPreview(slug, path = "/") {
|
|
530
|
+
return this.request("POST", `/v1/previews/${enc(slug)}/open`, { body: { path } }).then((r) => r.url);
|
|
531
|
+
}
|
|
532
|
+
/** many at once (a .env file), saved together or not at all */
|
|
533
|
+
setSecrets(values, podId) {
|
|
534
|
+
return this.request("PUT", "/v1/secrets", { body: { values, ...podId ? { podId } : {} } });
|
|
535
|
+
}
|
|
451
536
|
deleteSecret(name, podId) {
|
|
452
537
|
return this.request("DELETE", `/v1/secrets/${enc(name)}${podId ? `?podId=${enc(podId)}` : ""}`);
|
|
453
538
|
}
|
|
@@ -1015,10 +1100,10 @@ function drawable(text2, font = "block") {
|
|
|
1015
1100
|
return [...String(text2)].filter((ch) => [...f.cased ? ch : ch.toUpperCase()].some((c) => Object.hasOwn(f.glyphs, c))).join("");
|
|
1016
1101
|
}
|
|
1017
1102
|
var shapes = (text2, f) => [...String(text2)].flatMap((ch) => [...f.cased ? ch : ch.toUpperCase()]).map((c) => Object.hasOwn(f.glyphs, c) ? f.glyphs[c] : void 0).filter((g) => Boolean(g)).map((g) => g.split("|"));
|
|
1018
|
-
function fade(stops,
|
|
1103
|
+
function fade(stops, n2) {
|
|
1019
1104
|
const rgb2 = stops.map((s2) => [1, 3, 5].map((i) => parseInt(s2.slice(i, i + 2), 16)));
|
|
1020
|
-
return Array.from({ length:
|
|
1021
|
-
const k =
|
|
1105
|
+
return Array.from({ length: n2 }, (_, i) => {
|
|
1106
|
+
const k = n2 > 1 ? i / (n2 - 1) * (rgb2.length - 1) : 0;
|
|
1022
1107
|
const a = Math.min(rgb2.length - 2, Math.floor(k)), f = k - a;
|
|
1023
1108
|
const c = rgb2.length === 1 ? rgb2[0] : rgb2[a].map((v, j) => Math.round(v + (rgb2[a + 1][j] - v) * f));
|
|
1024
1109
|
return `#${c.map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
@@ -1446,14 +1531,14 @@ function merge(parts, extra = [], plain = false) {
|
|
|
1446
1531
|
};
|
|
1447
1532
|
const idx = lists.map((p) => (p ?? []).map(add));
|
|
1448
1533
|
const inked = lists.some((p) => !p) ? { light: add(INK2.light), dark: add(INK2.dark) } : null;
|
|
1449
|
-
const
|
|
1534
|
+
const n2 = all.length;
|
|
1450
1535
|
const values = all.map(rgb);
|
|
1451
1536
|
const into = all.map((_, i) => i);
|
|
1452
|
-
const alive = new Uint8Array(
|
|
1453
|
-
const near = new Int32Array(
|
|
1537
|
+
const alive = new Uint8Array(n2).fill(1);
|
|
1538
|
+
const near = new Int32Array(n2), gap = new Float64Array(n2);
|
|
1454
1539
|
const look = (i) => {
|
|
1455
1540
|
let best = Infinity, at4 = -1;
|
|
1456
|
-
for (let j = 0; j <
|
|
1541
|
+
for (let j = 0; j < n2; j++)
|
|
1457
1542
|
if (j !== i && alive[j]) {
|
|
1458
1543
|
const d = apart(values[i], values[j]);
|
|
1459
1544
|
if (d < best)
|
|
@@ -1462,19 +1547,19 @@ function merge(parts, extra = [], plain = false) {
|
|
|
1462
1547
|
near[i] = at4;
|
|
1463
1548
|
gap[i] = best;
|
|
1464
1549
|
};
|
|
1465
|
-
for (let i = 0; i <
|
|
1550
|
+
for (let i = 0; i < n2; i++)
|
|
1466
1551
|
look(i);
|
|
1467
|
-
for (let left =
|
|
1552
|
+
for (let left = n2; left > 64; left--) {
|
|
1468
1553
|
let a = -1;
|
|
1469
|
-
for (let i = 0; i <
|
|
1554
|
+
for (let i = 0; i < n2; i++)
|
|
1470
1555
|
if (alive[i] && (a < 0 || gap[i] < gap[a]))
|
|
1471
1556
|
a = i;
|
|
1472
1557
|
const keep = Math.min(a, near[a]), drop = Math.max(a, near[a]);
|
|
1473
1558
|
alive[drop] = 0;
|
|
1474
|
-
for (let i = 0; i <
|
|
1559
|
+
for (let i = 0; i < n2; i++)
|
|
1475
1560
|
if (into[i] === drop)
|
|
1476
1561
|
into[i] = keep;
|
|
1477
|
-
for (let i = 0; i <
|
|
1562
|
+
for (let i = 0; i < n2; i++)
|
|
1478
1563
|
if (alive[i] && near[i] === drop)
|
|
1479
1564
|
look(i);
|
|
1480
1565
|
}
|
|
@@ -1679,8 +1764,8 @@ function knockout(s2, g, x, y, h, mask2) {
|
|
|
1679
1764
|
}
|
|
1680
1765
|
}
|
|
1681
1766
|
var at = (v, t) => {
|
|
1682
|
-
const
|
|
1683
|
-
return finite(
|
|
1767
|
+
const n2 = typeof v === "function" ? v(t) : v;
|
|
1768
|
+
return finite(n2) ? Math.floor(n2) : 0;
|
|
1684
1769
|
};
|
|
1685
1770
|
var around = (p, size2, room) => {
|
|
1686
1771
|
const span = room + size2;
|
|
@@ -1870,19 +1955,19 @@ function crop(src, region) {
|
|
|
1870
1955
|
fail2(`crop's region, ${cols} by ${rows2} at ${x}, ${y}, misses ${p.meta.name}, which is ${p.cols} by ${p.rows}`);
|
|
1871
1956
|
return reshape("crop", p, x1 - x0, y1 - y0, (g, s2) => s2.paste(g, -x0, -y0));
|
|
1872
1957
|
}
|
|
1873
|
-
function pad(src,
|
|
1958
|
+
function pad(src, n2) {
|
|
1874
1959
|
const p = prepare("pad", src);
|
|
1875
1960
|
let sides;
|
|
1876
|
-
if (typeof
|
|
1877
|
-
sides = Array(4).fill(whole2(
|
|
1878
|
-
else if (Array.isArray(
|
|
1879
|
-
const [r, c] = [whole2(
|
|
1961
|
+
if (typeof n2 === "number")
|
|
1962
|
+
sides = Array(4).fill(whole2(n2, "pad", 0, 0));
|
|
1963
|
+
else if (Array.isArray(n2) && n2.length === 2) {
|
|
1964
|
+
const [r, c] = [whole2(n2[0], "pad's rows", 0, 0), whole2(n2[1], "pad's columns", 0, 0)];
|
|
1880
1965
|
sides = [r, c, r, c];
|
|
1881
|
-
} else if (
|
|
1882
|
-
const o =
|
|
1966
|
+
} else if (n2 !== null && typeof n2 === "object" && !Array.isArray(n2)) {
|
|
1967
|
+
const o = n2;
|
|
1883
1968
|
sides = ["top", "right", "bottom", "left"].map((k) => whole2(o[k], `pad's ${k}`, 0, 0));
|
|
1884
1969
|
} else
|
|
1885
|
-
return fail2(`pad takes a number, [rows, columns] or { top, right, bottom, left }, not ${shown(
|
|
1970
|
+
return fail2(`pad takes a number, [rows, columns] or { top, right, bottom, left }, not ${shown(n2)}`);
|
|
1886
1971
|
const [top, right, bottom, left] = sides;
|
|
1887
1972
|
return reshape("pad", p, p.cols + left + right, p.rows + top + bottom, (g, s2) => s2.paste(g, left, top));
|
|
1888
1973
|
}
|
|
@@ -2069,7 +2154,7 @@ function optionsOf2(o, fx, keys) {
|
|
|
2069
2154
|
fail2(`${fx}() has no option ${JSON.stringify(k)}${RENAMED[`${fx}.${k}`] ? "" : suggest(k, known)}: ${RENAMED[`${fx}.${k}`] ?? `it takes ${or2(known)}`}`);
|
|
2070
2155
|
return o;
|
|
2071
2156
|
}
|
|
2072
|
-
var mod = (a,
|
|
2157
|
+
var mod = (a, n2) => (a % n2 + n2) % n2;
|
|
2073
2158
|
var gcd2 = (a, b2) => b2 ? gcd2(b2, a % b2) : a;
|
|
2074
2159
|
function lcm(a, b2) {
|
|
2075
2160
|
const x = Math.round(a * 100), y = Math.round(b2 * 100);
|
|
@@ -2079,14 +2164,14 @@ function lcm(a, b2) {
|
|
|
2079
2164
|
return k * y <= 6e3 ? +(k * b2).toFixed(6) : void 0;
|
|
2080
2165
|
}
|
|
2081
2166
|
function quiet(t, busy, span) {
|
|
2082
|
-
const
|
|
2167
|
+
const n2 = 480;
|
|
2083
2168
|
let from = -1;
|
|
2084
|
-
for (let k = 0; k <=
|
|
2085
|
-
const u = t + span * k /
|
|
2169
|
+
for (let k = 0; k <= n2; k++) {
|
|
2170
|
+
const u = t + span * k / n2;
|
|
2086
2171
|
if (from < 0 && !busy(u))
|
|
2087
2172
|
from = u;
|
|
2088
2173
|
else if (from >= 0 && busy(u))
|
|
2089
|
-
return +((from + u) / 2 - span /
|
|
2174
|
+
return +((from + u) / 2 - span / n2 / 2).toFixed(3);
|
|
2090
2175
|
}
|
|
2091
2176
|
return from < 0 ? t : +((from + t + span) / 2).toFixed(3);
|
|
2092
2177
|
}
|
|
@@ -2152,7 +2237,7 @@ function mainInk(p, at3, options, map) {
|
|
|
2152
2237
|
const most = (paper) => {
|
|
2153
2238
|
const { text: text2, color } = snapshot(p, at3, { paper, options });
|
|
2154
2239
|
const count = /* @__PURE__ */ new Map();
|
|
2155
|
-
let best = 0,
|
|
2240
|
+
let best = 0, n2 = 0;
|
|
2156
2241
|
for (let k = 0, i = 0; k < text2.length; k++) {
|
|
2157
2242
|
const c = text2.charCodeAt(k);
|
|
2158
2243
|
if (c === 10)
|
|
@@ -2160,8 +2245,8 @@ function mainInk(p, at3, options, map) {
|
|
|
2160
2245
|
if (c !== 32 && color && i < color.length) {
|
|
2161
2246
|
const v = (count.get(color[i]) ?? 0) + 1;
|
|
2162
2247
|
count.set(color[i], v);
|
|
2163
|
-
if (v >
|
|
2164
|
-
|
|
2248
|
+
if (v > n2)
|
|
2249
|
+
n2 = v, best = color[i];
|
|
2165
2250
|
}
|
|
2166
2251
|
i++;
|
|
2167
2252
|
}
|
|
@@ -2380,14 +2465,14 @@ function typeIn(src, options) {
|
|
|
2380
2465
|
what: "typed in",
|
|
2381
2466
|
setup: () => (t, s2, g) => {
|
|
2382
2467
|
const local = cycle2 ? mod(t, cycle2) : t;
|
|
2383
|
-
const
|
|
2468
|
+
const n2 = Math.floor((local - start) * speed2 + 1e-6);
|
|
2384
2469
|
const all = g.chars.length;
|
|
2385
2470
|
let shown2 = 0, next = -1, last = -1;
|
|
2386
2471
|
for (let k = 0; k < all; k++) {
|
|
2387
2472
|
const i = seq ? seq[k] : k;
|
|
2388
2473
|
if (g.chars[i] === EMPTY)
|
|
2389
2474
|
continue;
|
|
2390
|
-
if (shown2 >=
|
|
2475
|
+
if (shown2 >= n2) {
|
|
2391
2476
|
next = i;
|
|
2392
2477
|
break;
|
|
2393
2478
|
}
|
|
@@ -2432,13 +2517,13 @@ function dissolve(src, options) {
|
|
|
2432
2517
|
const edge = o.edge === "" ? "" : chars2(o.edge, "dissolve.edge", ".:", 1);
|
|
2433
2518
|
const seed = num(o.seed, "dissolve.seed", 1, "whole");
|
|
2434
2519
|
return build2("dissolve", src, o, ({ meta: m }) => {
|
|
2435
|
-
const
|
|
2436
|
-
const v = new Float64Array(
|
|
2520
|
+
const n2 = m.cols * m.rows, aspect = m.cell ?? 2;
|
|
2521
|
+
const v = new Float64Array(n2);
|
|
2437
2522
|
for (let y = 0, i = 0; y < m.rows; y++)
|
|
2438
2523
|
for (let x = 0; x < m.cols; x++, i++)
|
|
2439
2524
|
v[i] = 0.8 * valueNoise(x * scale2, y * scale2 * aspect, seed) + 0.2 * hash(x, y, seed, 3);
|
|
2440
|
-
const at3 = new Float32Array(
|
|
2441
|
-
Int32Array.from({ length:
|
|
2525
|
+
const at3 = new Float32Array(n2);
|
|
2526
|
+
Int32Array.from({ length: n2 }, (_, i) => i).sort((a, b2) => v[a] - v[b2]).forEach((i, rank) => at3[i] = (rank + 0.5) / n2);
|
|
2442
2527
|
const band = edge ? 0.12 : 0;
|
|
2443
2528
|
const codes = [...edge].map((c) => c.charCodeAt(0)), blocks = auto ? [9617, 9618] : codes;
|
|
2444
2529
|
return {
|
|
@@ -2451,7 +2536,7 @@ function dissolve(src, options) {
|
|
|
2451
2536
|
if (k >= 1)
|
|
2452
2537
|
return copy(s2, g);
|
|
2453
2538
|
const front = k * (1 + band);
|
|
2454
|
-
for (let i = 0; i <
|
|
2539
|
+
for (let i = 0; i < n2; i++) {
|
|
2455
2540
|
const ch = g.chars[i];
|
|
2456
2541
|
if (ch === EMPTY)
|
|
2457
2542
|
continue;
|
|
@@ -2625,7 +2710,7 @@ function wave(src, options) {
|
|
|
2625
2710
|
const lines = rows2 ? h : w;
|
|
2626
2711
|
for (let k = 0; k < lines; k++) {
|
|
2627
2712
|
const off = room + Math.round(amplitude * Math.sin(TAU * (k / wavelength - t / period2)));
|
|
2628
|
-
for (let j = 0,
|
|
2713
|
+
for (let j = 0, n2 = rows2 ? w : h; j < n2; j++) {
|
|
2629
2714
|
const i = rows2 ? k * w + j : j * w + k;
|
|
2630
2715
|
if (g.chars[i] === EMPTY)
|
|
2631
2716
|
continue;
|
|
@@ -2637,8 +2722,8 @@ function wave(src, options) {
|
|
|
2637
2722
|
}
|
|
2638
2723
|
function hsl(h, s2, l) {
|
|
2639
2724
|
const a = s2 * Math.min(l, 1 - l);
|
|
2640
|
-
const f = (
|
|
2641
|
-
const k = (
|
|
2725
|
+
const f = (n2) => {
|
|
2726
|
+
const k = (n2 + h / 30) % 12;
|
|
2642
2727
|
return Math.round(255 * (l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))));
|
|
2643
2728
|
};
|
|
2644
2729
|
return "#" + [f(0), f(8), f(4)].map((v) => v.toString(16).padStart(2, "0")).join("");
|
|
@@ -2647,7 +2732,7 @@ var hues = {
|
|
|
2647
2732
|
light: Array.from({ length: 12 }, (_, i) => hsl(i * 30, 0.8, 0.4)),
|
|
2648
2733
|
dark: Array.from({ length: 12 }, (_, i) => hsl(i * 30, 0.9, 0.66))
|
|
2649
2734
|
};
|
|
2650
|
-
var cycle = (stops,
|
|
2735
|
+
var cycle = (stops, n2) => stops.length === 1 ? Array(n2).fill(stops[0]) : gradient([...stops, stops[0]], n2 + 1).slice(0, n2);
|
|
2651
2736
|
function rainbowOf(fx, src, options, cyclesBy) {
|
|
2652
2737
|
const o = optionsOf2(options, fx, ["colors", "steps", "period", "cycles", "direction"]);
|
|
2653
2738
|
const steps = num(o.steps, `${fx}.steps`, 12, "steps");
|
|
@@ -2919,14 +3004,14 @@ var mix = (a, b2, k) => {
|
|
|
2919
3004
|
const x = rgb(a), y = rgb(b2);
|
|
2920
3005
|
return hex(x.map((v, i) => lerp(v, y[i], clamp(k))));
|
|
2921
3006
|
};
|
|
2922
|
-
function gradient(stops,
|
|
3007
|
+
function gradient(stops, n2) {
|
|
2923
3008
|
if (!Array.isArray(stops) || !stops.length)
|
|
2924
3009
|
fail2("a gradient takes one or more colours as #rrggbb");
|
|
2925
|
-
if (!Number.isInteger(
|
|
2926
|
-
fail2(`a gradient takes a whole number of colours of 1 or more, not ${String(
|
|
3010
|
+
if (!Number.isInteger(n2) || n2 < 1)
|
|
3011
|
+
fail2(`a gradient takes a whole number of colours of 1 or more, not ${String(n2)}`);
|
|
2927
3012
|
const c = stops.map(rgb);
|
|
2928
|
-
return Array.from({ length:
|
|
2929
|
-
const k =
|
|
3013
|
+
return Array.from({ length: n2 }, (_, i) => {
|
|
3014
|
+
const k = n2 > 1 ? i / (n2 - 1) * (c.length - 1) : 0;
|
|
2930
3015
|
const a = Math.min(c.length - 2, Math.floor(k));
|
|
2931
3016
|
return c.length === 1 ? hex(c[0]) : hex(c[a].map((v, j) => lerp(v, c[a + 1][j], k - a)));
|
|
2932
3017
|
});
|
|
@@ -3162,7 +3247,7 @@ function colorOf(v, what = "color") {
|
|
|
3162
3247
|
function didYouMean(word) {
|
|
3163
3248
|
const all = [.../* @__PURE__ */ new Set([...Object.keys(schemes), ...Object.keys(materialColors)])];
|
|
3164
3249
|
const near = nearWords(word, all);
|
|
3165
|
-
return near.length ? `did you mean ${near.map((
|
|
3250
|
+
return near.length ? `did you mean ${near.map((n2) => JSON.stringify(n2)).join(" or ")}?` : `a colour of your own is #rrggbb, and the names are ${and2(all)}`;
|
|
3166
3251
|
}
|
|
3167
3252
|
function lettersApart(a, b2) {
|
|
3168
3253
|
const d = Array.from({ length: b2.length + 1 }, (_, j) => j);
|
|
@@ -3230,14 +3315,14 @@ var SYNONYMS = {
|
|
|
3230
3315
|
function nearWords(word, words2) {
|
|
3231
3316
|
const w = word.toLowerCase().slice(0, 40);
|
|
3232
3317
|
const same = (SYNONYMS[w] ?? []).filter((s2) => words2.includes(s2) && s2 !== word);
|
|
3233
|
-
const close = words2.filter((
|
|
3318
|
+
const close = words2.filter((n2) => !same.includes(n2) && lettersApart(w, n2.toLowerCase()) <= (w.length <= 4 ? 1 : 2));
|
|
3234
3319
|
return [...same, ...close].slice(0, 3);
|
|
3235
3320
|
}
|
|
3236
3321
|
function suggest(word, words2) {
|
|
3237
3322
|
if (typeof word !== "string")
|
|
3238
3323
|
return "";
|
|
3239
3324
|
const near = nearWords(word, words2);
|
|
3240
|
-
return near.length ? ` (did you mean ${near.map((
|
|
3325
|
+
return near.length ? ` (did you mean ${near.map((n2) => JSON.stringify(n2)).join(" or ")}?)` : "";
|
|
3241
3326
|
}
|
|
3242
3327
|
function mergePalettes(parts) {
|
|
3243
3328
|
const palette = [];
|
|
@@ -3426,8 +3511,8 @@ var Surface = class _Surface {
|
|
|
3426
3511
|
const color = env.color;
|
|
3427
3512
|
if (color && this.palette) {
|
|
3428
3513
|
const ink = this.palette.ink(this.paper);
|
|
3429
|
-
const
|
|
3430
|
-
for (let i = 0; i <
|
|
3514
|
+
const n2 = Math.min(color.length, chars3.length);
|
|
3515
|
+
for (let i = 0; i < n2; i++)
|
|
3431
3516
|
color[i] = colors[i] === NONE ? ink : colors[i];
|
|
3432
3517
|
}
|
|
3433
3518
|
const line2 = this.#line;
|
|
@@ -3515,8 +3600,8 @@ var Chainable = class {
|
|
|
3515
3600
|
freeze(at3) {
|
|
3516
3601
|
return freeze(this, at3);
|
|
3517
3602
|
}
|
|
3518
|
-
pad(
|
|
3519
|
-
return pad(this,
|
|
3603
|
+
pad(n2) {
|
|
3604
|
+
return pad(this, n2);
|
|
3520
3605
|
}
|
|
3521
3606
|
border(o) {
|
|
3522
3607
|
return border(this, o);
|
|
@@ -3631,11 +3716,11 @@ function asPiece(src, name = "text") {
|
|
|
3631
3716
|
const meta3 = checkMeta({ name, category: "type", note: name, cols: grid2.cols, rows: grid2.rows, fps: 0, ...palette ? { palette: palette.colors } : {} });
|
|
3632
3717
|
const other = grid2.clone();
|
|
3633
3718
|
if (palette?.themed) {
|
|
3634
|
-
const
|
|
3719
|
+
const n2 = palette.size;
|
|
3635
3720
|
for (let i = 0; i < other.colors.length; i++) {
|
|
3636
3721
|
const c = other.colors[i];
|
|
3637
3722
|
if (c !== NONE)
|
|
3638
|
-
other.colors[i] = grid2.paper ? c <
|
|
3723
|
+
other.colors[i] = grid2.paper ? c < n2 ? c + n2 : c : c >= n2 ? c - n2 : c;
|
|
3639
3724
|
}
|
|
3640
3725
|
}
|
|
3641
3726
|
other.paper = !grid2.paper;
|
|
@@ -3651,7 +3736,7 @@ function asPiece(src, name = "text") {
|
|
|
3651
3736
|
lines.shift();
|
|
3652
3737
|
while (lines.length > 1 && !lines[lines.length - 1].trim())
|
|
3653
3738
|
lines.pop();
|
|
3654
|
-
const indent = lines.reduce((
|
|
3739
|
+
const indent = lines.reduce((n2, l) => l.trim() ? Math.min(n2, l.length - l.trimStart().length) : n2, Infinity);
|
|
3655
3740
|
if (Number.isFinite(indent))
|
|
3656
3741
|
lines = lines.map((l) => l.slice(indent));
|
|
3657
3742
|
}
|
|
@@ -3695,9 +3780,9 @@ function snapshot(src, t = 0, { paper = false, mono = false, options = {} } = {}
|
|
|
3695
3780
|
// ../../node_modules/ascii.rest/dist/kit/math.js
|
|
3696
3781
|
var said = (v) => typeof v === "string" ? JSON.stringify(v) : Array.isArray(v) ? `[${v.map(said).join(", ")}]` : String(v);
|
|
3697
3782
|
var finite2 = (v) => typeof v === "number" && Number.isFinite(v);
|
|
3698
|
-
function mod2(a,
|
|
3699
|
-
const r = a %
|
|
3700
|
-
return r < 0 ? r +
|
|
3783
|
+
function mod2(a, n2) {
|
|
3784
|
+
const r = a % n2;
|
|
3785
|
+
return r < 0 ? r + n2 < n2 ? r + n2 : 0 : r + 0;
|
|
3701
3786
|
}
|
|
3702
3787
|
var tables = [];
|
|
3703
3788
|
function table(seed) {
|
|
@@ -3862,7 +3947,7 @@ var H3 = 1 / 6;
|
|
|
3862
3947
|
var F4 = (Math.sqrt(5) - 1) / 4;
|
|
3863
3948
|
var H4 = (5 - Math.sqrt(5)) / 20;
|
|
3864
3949
|
var S4 = 27;
|
|
3865
|
-
var unit = (
|
|
3950
|
+
var unit = (n2) => n2 <= -1 ? 0 : n2 >= 1 ? 1 : 0.5 + 0.5 * n2;
|
|
3866
3951
|
var dot4 = (g, a, b2, c, d) => G4[g] * a + G4[g + 1] * b2 + G4[g + 2] * c + G4[g + 3] * d;
|
|
3867
3952
|
function simplex4(x, y, z, w, seed) {
|
|
3868
3953
|
const { perm } = table(seed);
|
|
@@ -3905,26 +3990,26 @@ function simplex4(x, y, z, w, seed) {
|
|
|
3905
3990
|
const x3 = x0 - i3 + 3 * H4, y3 = y0 - j3 + 3 * H4, z3 = z0 - l3 + 3 * H4, w3 = w0 - m3 + 3 * H4;
|
|
3906
3991
|
const x4 = x0 - 1 + 4 * H4, y4 = y0 - 1 + 4 * H4, z4 = z0 - 1 + 4 * H4, w4 = w0 - 1 + 4 * H4;
|
|
3907
3992
|
const ii = i & 255, jj = j & 255, ll = l & 255, mm = m & 255;
|
|
3908
|
-
let
|
|
3993
|
+
let n2 = 0, k;
|
|
3909
3994
|
if ((k = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0) > 0)
|
|
3910
|
-
k *= k,
|
|
3995
|
+
k *= k, n2 += k * k * dot4((perm[ii + perm[jj + perm[ll + perm[mm]]]] & 31) * 4, x0, y0, z0, w0);
|
|
3911
3996
|
if ((k = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1) > 0)
|
|
3912
|
-
k *= k,
|
|
3997
|
+
k *= k, n2 += k * k * dot4((perm[ii + i1 + perm[jj + j1 + perm[ll + l1 + perm[mm + m1]]]] & 31) * 4, x1, y1, z1, w1);
|
|
3913
3998
|
if ((k = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2) > 0)
|
|
3914
|
-
k *= k,
|
|
3999
|
+
k *= k, n2 += k * k * dot4((perm[ii + i2 + perm[jj + j2 + perm[ll + l2 + perm[mm + m2]]]] & 31) * 4, x2, y2, z2, w2);
|
|
3915
4000
|
if ((k = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3) > 0)
|
|
3916
|
-
k *= k,
|
|
4001
|
+
k *= k, n2 += k * k * dot4((perm[ii + i3 + perm[jj + j3 + perm[ll + l3 + perm[mm + m3]]]] & 31) * 4, x3, y3, z3, w3);
|
|
3917
4002
|
if ((k = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4) > 0)
|
|
3918
|
-
k *= k,
|
|
3919
|
-
return S4 *
|
|
4003
|
+
k *= k, n2 += k * k * dot4((perm[ii + 1 + perm[jj + 1 + perm[ll + 1 + perm[mm + 1]]]] & 31) * 4, x4, y4, z4, w4);
|
|
4004
|
+
return S4 * n2;
|
|
3920
4005
|
}
|
|
3921
4006
|
var OFF = Array.from({ length: 16 }, (_, i) => [i * 0.6180339887 % 1 * 97.3, i * 0.7548776662 % 1 * 89.1]);
|
|
3922
4007
|
var octN = 4;
|
|
3923
4008
|
var octLac = 2;
|
|
3924
4009
|
var octGain = 0.5;
|
|
3925
4010
|
var octSeed = 0;
|
|
3926
|
-
function octaves(o,
|
|
3927
|
-
octN = o?.octaves ??
|
|
4011
|
+
function octaves(o, n2 = 4) {
|
|
4012
|
+
octN = o?.octaves ?? n2;
|
|
3928
4013
|
octLac = o?.lacunarity ?? 2;
|
|
3929
4014
|
octGain = o?.gain ?? 0.5;
|
|
3930
4015
|
octSeed = o?.seed ?? 0;
|
|
@@ -3939,8 +4024,8 @@ function octaves(o, n = 4) {
|
|
|
3939
4024
|
}
|
|
3940
4025
|
var SPREAD = 1.45;
|
|
3941
4026
|
var WIDEN4 = 1.35;
|
|
3942
|
-
var crest = (
|
|
3943
|
-
const r = 1 - (
|
|
4027
|
+
var crest = (n2) => {
|
|
4028
|
+
const r = 1 - (n2 < 0 ? -n2 : n2);
|
|
3944
4029
|
return r < 0 ? 0 : r * r;
|
|
3945
4030
|
};
|
|
3946
4031
|
var weigh = (r) => r > 0.5 ? 1 : 2 * r;
|
|
@@ -3955,10 +4040,10 @@ function loopNoise(x, y, t, period2, o) {
|
|
|
3955
4040
|
const z = radius * Math.cos(a), w = radius * Math.sin(a);
|
|
3956
4041
|
let sum = 0, amp = 1, norm = 0, f = 1, weight = 1;
|
|
3957
4042
|
for (let i = 0; i < octN; i++) {
|
|
3958
|
-
let
|
|
4043
|
+
let n2 = simplex4(x * f + OFF[i][0], y * f + OFF[i][1], z * f, w * f, octSeed) * WIDEN4;
|
|
3959
4044
|
if (ridge)
|
|
3960
|
-
|
|
3961
|
-
sum += amp *
|
|
4045
|
+
n2 = crest(n2) * weight, weight = weigh(n2);
|
|
4046
|
+
sum += amp * n2;
|
|
3962
4047
|
norm += amp;
|
|
3963
4048
|
amp *= octGain;
|
|
3964
4049
|
f *= octLac;
|
|
@@ -3971,14 +4056,14 @@ var B3 = B1 + 1;
|
|
|
3971
4056
|
var E4 = TAU / 3;
|
|
3972
4057
|
var E5 = TAU / 4.5;
|
|
3973
4058
|
var bounce = (k) => {
|
|
3974
|
-
const
|
|
4059
|
+
const n2 = 7.5625, d = 2.75;
|
|
3975
4060
|
if (k < 1 / d)
|
|
3976
|
-
return
|
|
4061
|
+
return n2 * k * k;
|
|
3977
4062
|
if (k < 2 / d)
|
|
3978
|
-
return
|
|
4063
|
+
return n2 * (k -= 1.5 / d) * k + 0.75;
|
|
3979
4064
|
if (k < 2.5 / d)
|
|
3980
|
-
return
|
|
3981
|
-
return
|
|
4065
|
+
return n2 * (k -= 2.25 / d) * k + 0.9375;
|
|
4066
|
+
return n2 * (k -= 2.625 / d) * k + 0.984375;
|
|
3982
4067
|
};
|
|
3983
4068
|
var curves = {
|
|
3984
4069
|
linear: (k) => k,
|
|
@@ -4149,7 +4234,7 @@ function brush(ch, what) {
|
|
|
4149
4234
|
fail2(`${what} takes one character, or "" to clear, not ${show3(ch)}`);
|
|
4150
4235
|
return code(ch);
|
|
4151
4236
|
}
|
|
4152
|
-
var finite3 = (...v) => v.every((
|
|
4237
|
+
var finite3 = (...v) => v.every((n2) => typeof n2 === "number" && Number.isFinite(n2));
|
|
4153
4238
|
function show3(v, depth = 0) {
|
|
4154
4239
|
if (typeof v === "string")
|
|
4155
4240
|
return JSON.stringify(v.length > 40 ? v.slice(0, 40) + "\u2026" : v);
|
|
@@ -4159,7 +4244,7 @@ function show3(v, depth = 0) {
|
|
|
4159
4244
|
return String(v);
|
|
4160
4245
|
if (depth > 1)
|
|
4161
4246
|
return Array.isArray(v) ? "[...]" : "{...}";
|
|
4162
|
-
const more = (
|
|
4247
|
+
const more = (n2) => n2 > 6 ? ", ..." : "";
|
|
4163
4248
|
if (Array.isArray(v))
|
|
4164
4249
|
return `[${Array.from(v.slice(0, 6), (x) => show3(x, depth + 1)).join(", ")}${more(v.length)}]`;
|
|
4165
4250
|
const entries = Object.entries(v);
|
|
@@ -4219,12 +4304,12 @@ function trace(x0, y0, x1, y1, w, h) {
|
|
|
4219
4304
|
const dx = Math.abs(x1 - x0), dy = -Math.abs(y1 - y0), sx = x0 < x1 ? 1 : -1, sy = y0 < y1 ? 1 : -1;
|
|
4220
4305
|
if (XS.length < dx - dy + 1)
|
|
4221
4306
|
XS = new Int32Array(dx - dy + 1), YS = new Int32Array(dx - dy + 1);
|
|
4222
|
-
let err = dx + dy,
|
|
4307
|
+
let err = dx + dy, n2 = 0;
|
|
4223
4308
|
for (; ; ) {
|
|
4224
|
-
XS[
|
|
4225
|
-
YS[
|
|
4309
|
+
XS[n2] = x0;
|
|
4310
|
+
YS[n2++] = y0;
|
|
4226
4311
|
if (x0 === x1 && y0 === y1)
|
|
4227
|
-
return
|
|
4312
|
+
return n2;
|
|
4228
4313
|
const e2 = 2 * err;
|
|
4229
4314
|
if (e2 >= dy)
|
|
4230
4315
|
err += dy, x0 += sx;
|
|
@@ -4261,11 +4346,11 @@ function glyph(a, round, weight) {
|
|
|
4261
4346
|
return ROUND.get(a);
|
|
4262
4347
|
let c = GLYPH.get(a);
|
|
4263
4348
|
if (c === void 0) {
|
|
4264
|
-
let
|
|
4349
|
+
let n2 = 0;
|
|
4265
4350
|
for (let k = 0; k < 8; k += 2)
|
|
4266
4351
|
if (a >> k & 3)
|
|
4267
|
-
|
|
4268
|
-
c = GLYPH.get(
|
|
4352
|
+
n2 |= weight << k;
|
|
4353
|
+
c = GLYPH.get(n2);
|
|
4269
4354
|
}
|
|
4270
4355
|
return c ?? DOT;
|
|
4271
4356
|
}
|
|
@@ -4283,14 +4368,14 @@ function linked(s2, j, b2, ascii) {
|
|
|
4283
4368
|
const nx = x + (k === AR ? 1 : k === AL ? -1 : 0), ny = y + (k === AD ? 1 : k === AU ? -1 : 0);
|
|
4284
4369
|
if (nx < 0 || ny < 0 || nx >= s2.cols || ny >= s2.rows)
|
|
4285
4370
|
continue;
|
|
4286
|
-
const
|
|
4371
|
+
const n2 = s2.chars[ny * s2.cols + nx], nb = ascii ? asciiArms(n2) : ARMS.get(n2);
|
|
4287
4372
|
if (nb !== void 0 && nb >> (k ^ 4) & 3)
|
|
4288
4373
|
m |= b2 & 3 << k;
|
|
4289
4374
|
}
|
|
4290
4375
|
return m;
|
|
4291
4376
|
}
|
|
4292
|
-
function place3(s2, j, c, k,
|
|
4293
|
-
if (
|
|
4377
|
+
function place3(s2, j, c, k, join6, a = ARMS.get(c), ascii = false, round = false) {
|
|
4378
|
+
if (join6 && a !== void 0) {
|
|
4294
4379
|
const old = s2.chars[j], b2 = ascii ? asciiArms(old) : ARMS.get(old);
|
|
4295
4380
|
if (b2 !== void 0) {
|
|
4296
4381
|
const kept = linked(s2, j, b2, ascii);
|
|
@@ -4408,8 +4493,8 @@ function write(s2, x, y, str2, o, region) {
|
|
|
4408
4493
|
let lines = layout(t, bw, wrap2);
|
|
4409
4494
|
if (lines.length > bh)
|
|
4410
4495
|
lines = lines.slice(0, bh);
|
|
4411
|
-
const
|
|
4412
|
-
const top = valign === "top" ? fy : valign === "middle" ? fy + Math.floor((bh -
|
|
4496
|
+
const n2 = lines.length;
|
|
4497
|
+
const top = valign === "top" ? fy : valign === "middle" ? fy + Math.floor((bh - n2) / 2) : fy + bh - n2;
|
|
4413
4498
|
const lefts = lines.map((l) => align === "left" ? fx : align === "center" ? fx + Math.floor((bw - l.length) / 2) : fx + bw - l.length);
|
|
4414
4499
|
return writeLines(s2, lines, lefts, top, k, transparent);
|
|
4415
4500
|
}
|
|
@@ -4422,7 +4507,7 @@ function label(s2, x, y, str2, o) {
|
|
|
4422
4507
|
if (!finite3(x, y) || t === "")
|
|
4423
4508
|
return { x: finite3(x) ? Math.floor(x) : 0, y: finite3(y) ? Math.floor(y) : 0, cols: 0, rows: 0 };
|
|
4424
4509
|
const lines = t.split("\n");
|
|
4425
|
-
const start = (v,
|
|
4510
|
+
const start = (v, n2) => Math.floor(v - n2 / 2 + 0.5);
|
|
4426
4511
|
return writeLines(s2, lines, lines.map((l) => start(x, l.length)), start(y, lines.length), k, transparent);
|
|
4427
4512
|
}
|
|
4428
4513
|
function around2(on, cx, cy, r, turn) {
|
|
@@ -4456,7 +4541,7 @@ function rect(s2, x, y, w, h, o) {
|
|
|
4456
4541
|
if (title)
|
|
4457
4542
|
for (const ch of title)
|
|
4458
4543
|
code(ch);
|
|
4459
|
-
const
|
|
4544
|
+
const join6 = yes(o?.join, "join", true);
|
|
4460
4545
|
const k = paint(s2, color), fk = fillColor === void 0 ? k : paint(s2, fillColor);
|
|
4461
4546
|
if (!finite3(x, y, w, h))
|
|
4462
4547
|
return { x: 0, y: 0, cols: 0, rows: 0 };
|
|
@@ -4479,10 +4564,10 @@ function rect(s2, x, y, w, h, o) {
|
|
|
4479
4564
|
const put = (cx, cy, e, reach = 255) => {
|
|
4480
4565
|
const j = s2.index(cx, cy), a = ascii ? EDGE_ARMS[e] : ARMS.get(ed[e]);
|
|
4481
4566
|
if (j >= 0)
|
|
4482
|
-
place3(s2, j, ed[e], k,
|
|
4567
|
+
place3(s2, j, ed[e], k, join6, a === void 0 ? a : a & reach, ascii, round);
|
|
4483
4568
|
};
|
|
4484
|
-
const end = (i,
|
|
4485
|
-
const from = (o2, lo) => Math.max(lo, -o2), to = (o2, hi,
|
|
4569
|
+
const end = (i, n2, first, last) => n2 < 2 ? 255 : i === 0 ? 3 << first : i === n2 - 1 ? 3 << last : 255;
|
|
4570
|
+
const from = (o2, lo) => Math.max(lo, -o2), to = (o2, hi, n2) => Math.min(hi, n2 - o2);
|
|
4486
4571
|
if (h === 1)
|
|
4487
4572
|
for (let i = from(x, 0); i < to(x, w, s2.cols); i++)
|
|
4488
4573
|
put(x + i, y, 4, end(i, w, AR, AL));
|
|
@@ -4530,11 +4615,11 @@ function ellipseCells(w, h, cx, cy, rx, ry, inside, visit, aspect = 0, from = 0,
|
|
|
4530
4615
|
const j0 = Math.max(0, Math.floor(cy - ry) - 1), j1 = Math.min(h - 1, Math.ceil(cy + ry));
|
|
4531
4616
|
if (i0 > i1 || j0 > j1)
|
|
4532
4617
|
return;
|
|
4533
|
-
const bw = i1 - i0 + 1,
|
|
4534
|
-
if (EDGE.length <
|
|
4535
|
-
EDGE = new Uint8Array(
|
|
4618
|
+
const bw = i1 - i0 + 1, n2 = bw * (j1 - j0 + 1);
|
|
4619
|
+
if (EDGE.length < n2)
|
|
4620
|
+
EDGE = new Uint8Array(n2);
|
|
4536
4621
|
const edge = EDGE;
|
|
4537
|
-
edge.fill(0, 0,
|
|
4622
|
+
edge.fill(0, 0, n2);
|
|
4538
4623
|
const mark = (i2, j2, kind) => {
|
|
4539
4624
|
if (i2 >= i0 && i2 <= i1 && j2 >= j0 && j2 <= j1)
|
|
4540
4625
|
edge[(j2 - j0) * bw + i2 - i0] |= kind;
|
|
@@ -4558,7 +4643,7 @@ function ellipseCells(w, h, cx, cy, rx, ry, inside, visit, aspect = 0, from = 0,
|
|
|
4558
4643
|
mark(Math.floor(cx - xo), j2, steep ? 2 : 8);
|
|
4559
4644
|
mark(Math.ceil(cx + xo) - 1, j2, steep ? 2 : 8);
|
|
4560
4645
|
}
|
|
4561
|
-
for (let k = 0; k <
|
|
4646
|
+
for (let k = 0; k < n2; k++)
|
|
4562
4647
|
edge[k] = edge[k] & 3 | ((edge[k] & 12) === 12 ? 1 : 0);
|
|
4563
4648
|
for (let j2 = j0; j2 <= j1; j2++)
|
|
4564
4649
|
for (let i2 = i0; i2 <= i1; i2++) {
|
|
@@ -4615,11 +4700,11 @@ function shape(s2, cx, cy, rx, ry, o, what, from = 0, span = 1) {
|
|
|
4615
4700
|
const ch = char === "auto" ? "auto" : brush(char, `${/^[aeiou]/.test(what) ? "an" : "a"} ${what}'s char`);
|
|
4616
4701
|
const f = fill === void 0 || fill === false ? null : brush(fill, "fill");
|
|
4617
4702
|
const k = paint(s2, color), fk = fillColor === void 0 ? k : paint(s2, fillColor);
|
|
4618
|
-
const
|
|
4703
|
+
const join6 = yes(o?.join, "join", true);
|
|
4619
4704
|
ellipseCells(s2.cols, s2.rows, cx, cy, rx, ry, f !== null, (x, y, edge, slope) => {
|
|
4620
4705
|
const j = y * s2.cols + x;
|
|
4621
4706
|
if (edge)
|
|
4622
|
-
place3(s2, j, ch === "auto" ? slope : ch, k,
|
|
4707
|
+
place3(s2, j, ch === "auto" ? slope : ch, k, join6 && ch !== "auto");
|
|
4623
4708
|
else
|
|
4624
4709
|
s2.put(j, f, f === EMPTY ? NONE : fk);
|
|
4625
4710
|
}, ch === "auto" ? s2.aspect : 0, from, span);
|
|
@@ -4700,8 +4785,8 @@ var BrailleCanvas = class {
|
|
|
4700
4785
|
}
|
|
4701
4786
|
line(x0, y0, x1, y1, color) {
|
|
4702
4787
|
const k = this.#k(color);
|
|
4703
|
-
const
|
|
4704
|
-
for (let i = 0; i <
|
|
4788
|
+
const n2 = trace(x0, y0, x1, y1, this.width, this.height);
|
|
4789
|
+
for (let i = 0; i < n2; i++)
|
|
4705
4790
|
this.#dot(XS[i], YS[i], k);
|
|
4706
4791
|
}
|
|
4707
4792
|
ray(x, y, length, turn, color) {
|
|
@@ -4750,12 +4835,12 @@ var BrailleCanvas = class {
|
|
|
4750
4835
|
y1 ??= 1;
|
|
4751
4836
|
if (y0 === y1)
|
|
4752
4837
|
fail2(`plot's y0 and y1 take two different numbers, the values at the bottom and the top, not ${y0} and ${y1}`);
|
|
4753
|
-
const w = this.width, h = this.height,
|
|
4838
|
+
const w = this.width, h = this.height, n2 = list2 ? list2.length : w;
|
|
4754
4839
|
const top = fill ? new Int32Array(w).fill(h) : null;
|
|
4755
4840
|
let px = 0, py = NaN;
|
|
4756
|
-
for (let i = 0; i <
|
|
4841
|
+
for (let i = 0; i < n2; i++) {
|
|
4757
4842
|
const v = list2 ? list2[i] : f(x0 + (x1 - x0) * i / Math.max(1, w - 1));
|
|
4758
|
-
const x = list2 ?
|
|
4843
|
+
const x = list2 ? n2 > 1 ? Math.round(i * (w - 1) / (n2 - 1)) : 0 : i;
|
|
4759
4844
|
const y = typeof v === "number" && Number.isFinite(v) ? Math.round((y1 - v) / (y1 - y0) * (h - 1)) : NaN;
|
|
4760
4845
|
if (Number.isFinite(y)) {
|
|
4761
4846
|
const m = Number.isFinite(py) ? trace(px, py, x, y, w, h) : trace(x, y, x, y, w, h);
|
|
@@ -4854,7 +4939,7 @@ function tones(accent, more = []) {
|
|
|
4854
4939
|
var WIDGET = ["color", ...NAMING];
|
|
4855
4940
|
var naming2 = (o, name, note) => ({ name: o.name ?? name, note: (o.note ?? note).slice(0, 72) });
|
|
4856
4941
|
var framing = (p, framed) => framed ? Object.assign(p, { frames: true }) : p;
|
|
4857
|
-
function amountOf(what, v, min, max,
|
|
4942
|
+
function amountOf(what, v, min, max, share2) {
|
|
4858
4943
|
if (v === void 0)
|
|
4859
4944
|
return void 0;
|
|
4860
4945
|
if (typeof v === "function") {
|
|
@@ -4870,10 +4955,10 @@ function amountOf(what, v, min, max, share) {
|
|
|
4870
4955
|
return min + +m[1] / 100 * (max - min);
|
|
4871
4956
|
}
|
|
4872
4957
|
if (typeof v !== "number" || !Number.isFinite(v) || v < min || v > max) {
|
|
4873
|
-
const hint =
|
|
4958
|
+
const hint = share2 && typeof v === "number" && v > 1 && v <= 100 ? `: did you mean ${+(v / 100).toFixed(4)}, or "${v}%"?` : "";
|
|
4874
4959
|
fail2(`${what}'s value takes a number from ${min} to ${max}, a percentage such as "64%", or a function that reads one, not ${show2(v)}${hint}`);
|
|
4875
4960
|
}
|
|
4876
|
-
if (!
|
|
4961
|
+
if (!share2 && min === 0 && max === 100 && v > 0 && v < 1)
|
|
4877
4962
|
fail2(`${what}'s value is a reading from 0 to 100, and ${v} is less than 1: did you mean ${+(v * 100).toFixed(2)}, or "${+(v * 100).toFixed(2)}%"? Give it max: 1 for a reading from 0 to 1`);
|
|
4878
4963
|
return v;
|
|
4879
4964
|
}
|
|
@@ -4894,24 +4979,24 @@ function progressBar(o) {
|
|
|
4894
4979
|
const smooth = ease.inOutSine;
|
|
4895
4980
|
const loop = loopOf(fill + 1, true);
|
|
4896
4981
|
const cols = label2.length + width + (percent ? 5 : 0);
|
|
4897
|
-
const
|
|
4982
|
+
const share2 = (v) => (v - min) / (max - min);
|
|
4898
4983
|
const timing = live ? { fps: 4, clock: true } : value === void 0 ? { fps: 24, loop, still: fill } : { fps: 0 };
|
|
4899
|
-
const note = live ? "a progress bar, live" : value === void 0 ? "a progress bar filling" : `a progress bar at ${Math.round(
|
|
4984
|
+
const note = live ? "a progress bar, live" : value === void 0 ? "a progress bar filling" : `a progress bar at ${Math.round(share2(value) * 100)}%`;
|
|
4900
4985
|
return piece({ ...naming2(p, label2.trim() || "progress", note), category: "ui", cols, rows: 1, ...timing, palette: tones(themed2("progressBar's color", p.color)) }, (t, s2) => {
|
|
4901
|
-
const k = typeof value === "function" ?
|
|
4986
|
+
const k = typeof value === "function" ? share2(value()) : value !== void 0 ? share2(value) : smooth(Math.min(1, (t % loop + loop) % loop / fill));
|
|
4902
4987
|
const color = k >= 1 ? GOOD : ACCENT;
|
|
4903
4988
|
s2.write(0, 0, label2);
|
|
4904
4989
|
const x = label2.length;
|
|
4905
4990
|
if (style === "ascii") {
|
|
4906
|
-
const
|
|
4991
|
+
const n2 = Math.round(k * (width - 2));
|
|
4907
4992
|
s2.write(x, 0, "[", MUTED);
|
|
4908
4993
|
for (let i = 0; i < width - 2; i++)
|
|
4909
|
-
s2.set(x + 1 + i, 0, i <
|
|
4994
|
+
s2.set(x + 1 + i, 0, i < n2 ? "#" : "-", i < n2 ? color : MUTED);
|
|
4910
4995
|
s2.write(x + width - 1, 0, "]", MUTED);
|
|
4911
4996
|
} else if (style === "dots") {
|
|
4912
|
-
const
|
|
4997
|
+
const n2 = Math.round(k * width);
|
|
4913
4998
|
for (let i = 0; i < width; i++)
|
|
4914
|
-
s2.set(x + i, 0, i <
|
|
4999
|
+
s2.set(x + i, 0, i < n2 ? "\u25CF" : "\u25CB", i < n2 ? color : MUTED);
|
|
4915
5000
|
} else {
|
|
4916
5001
|
const eighths = Math.round(k * width * 8);
|
|
4917
5002
|
for (let i = 0; i < width; i++) {
|
|
@@ -4957,7 +5042,7 @@ function sparkline(data, o) {
|
|
|
4957
5042
|
[data, o] = [void 0, data];
|
|
4958
5043
|
}
|
|
4959
5044
|
const p = optionsOf3("sparkline()", o, ["label", "width", "height", "fill", ...WIDGET]);
|
|
4960
|
-
const numbers = (v) => Array.isArray(v) && v.length >= 2 && v.every((
|
|
5045
|
+
const numbers = (v) => Array.isArray(v) && v.length >= 2 && v.every((n2) => typeof n2 === "number" && Number.isFinite(n2));
|
|
4961
5046
|
const live = typeof data === "function" ? data : null;
|
|
4962
5047
|
if (data !== void 0 && !live && !numbers(data))
|
|
4963
5048
|
fail2(`sparkline() takes a list of two or more numbers, such as [3, 5, 2, 8], or a function that returns one, not ${show2(data)}`);
|
|
@@ -5111,7 +5196,7 @@ var meta2 = {
|
|
|
5111
5196
|
};
|
|
5112
5197
|
var QUAD = " \u2597\u2596\u2584\u259D\u2590\u259E\u259F\u2598\u259A\u258C\u2599\u2580\u259C\u259B\u2588";
|
|
5113
5198
|
var quad = (ul, ur, ll, lr) => QUAD[ul << 3 | ur << 2 | ll << 1 | lr];
|
|
5114
|
-
var ping = (
|
|
5199
|
+
var ping = (n2, i) => i % (2 * n2 - 2) < n2 ? i % (2 * n2 - 2) : 2 * n2 - 2 - i % (2 * n2 - 2);
|
|
5115
5200
|
var blank = (w, h) => Array.from({ length: h }, () => new Array(w).fill(" "));
|
|
5116
5201
|
var rows = (g) => g.map((r) => r.join(""));
|
|
5117
5202
|
var plot = (w, h, cells) => {
|
|
@@ -5143,8 +5228,8 @@ var SPINNERS = [
|
|
|
5143
5228
|
}],
|
|
5144
5229
|
["arc", 1, (i) => {
|
|
5145
5230
|
const ring2 = [[0, 0, "\u256D"], [1, 0, "\u2500"], [2, 0, "\u2500"], [3, 0, "\u2500"], [4, 0, "\u256E"], [4, 1, "\u2502"], [4, 2, "\u256F"], [3, 2, "\u2500"], [2, 2, "\u2500"], [1, 2, "\u2500"], [0, 2, "\u2570"], [0, 1, "\u2502"]];
|
|
5146
|
-
const
|
|
5147
|
-
return plot(5, 3, [...Array(
|
|
5231
|
+
const n2 = 3 + Math.round(ping(8, i) * 4 / 7);
|
|
5232
|
+
return plot(5, 3, [...Array(n2)].map((_, k) => ring2[(i + 12 - k) % 12]));
|
|
5148
5233
|
}],
|
|
5149
5234
|
["bounce", 1, (i) => ["[" + " ".repeat(ping(6, i)) + "o" + " ".repeat(5 - ping(6, i)) + "]"]],
|
|
5150
5235
|
["clock", 3, (i) => {
|
|
@@ -5176,7 +5261,7 @@ var SPINNERS = [
|
|
|
5176
5261
|
}]
|
|
5177
5262
|
];
|
|
5178
5263
|
function spinners2({ names = meta2.options.names, labels = meta2.options.labels, speed: speed2 = meta2.options.speed } = {}) {
|
|
5179
|
-
const chosen = names && names.length ? names.map((
|
|
5264
|
+
const chosen = names && names.length ? names.map((n2) => SPINNERS.find((s2) => s2[0] === n2)).filter(Boolean) : SPINNERS;
|
|
5180
5265
|
const all = chosen === SPINNERS;
|
|
5181
5266
|
const slot2 = 11, per = all ? 6 : Math.min(6, chosen.length);
|
|
5182
5267
|
const block = labels || all ? 5 : 3;
|
|
@@ -5184,8 +5269,8 @@ function spinners2({ names = meta2.options.names, labels = meta2.options.labels,
|
|
|
5184
5269
|
const height = all ? meta2.rows : Math.ceil(chosen.length / per) * (block + 1) + 1;
|
|
5185
5270
|
return (t) => {
|
|
5186
5271
|
const grid2 = Array.from({ length: height }, () => new Array(cols).fill(" "));
|
|
5187
|
-
chosen.forEach(([name, frames, draw2],
|
|
5188
|
-
const x0 =
|
|
5272
|
+
chosen.forEach(([name, frames, draw2], n2) => {
|
|
5273
|
+
const x0 = n2 % per * slot2, y0 = 1 + Math.floor(n2 / per) * (block + 1);
|
|
5189
5274
|
const art = draw2(Math.floor(t * meta2.fps * speed2 / frames + 1e-3));
|
|
5190
5275
|
const top = y0 + Math.floor((3 - art.length) / 2);
|
|
5191
5276
|
art.forEach((line2, j) => {
|
|
@@ -5293,8 +5378,8 @@ function cached(cache, key, make) {
|
|
|
5293
5378
|
return v;
|
|
5294
5379
|
}
|
|
5295
5380
|
var bars = /* @__PURE__ */ new Map();
|
|
5296
|
-
function bar(
|
|
5297
|
-
const value = Math.max(0, Math.min(1, Number.isFinite(
|
|
5381
|
+
function bar(share2, width, opts2 = {}) {
|
|
5382
|
+
const value = Math.max(0, Math.min(1, Number.isFinite(share2) ? share2 : 0));
|
|
5298
5383
|
const w = Math.max(4, Math.round(width));
|
|
5299
5384
|
const key = `${w}|${opts2.label ?? ""}|${opts2.percent !== false}|${value.toFixed(4)}`;
|
|
5300
5385
|
const b2 = cached(bars, key, () => {
|
|
@@ -5330,12 +5415,12 @@ function dial(value, max, opts2 = {}) {
|
|
|
5330
5415
|
return frameOf(g.piece, g.frame, 0);
|
|
5331
5416
|
}
|
|
5332
5417
|
var DIAL_SIZE = { cols: 25, rows: 12 };
|
|
5333
|
-
function divider(
|
|
5334
|
-
if (
|
|
5335
|
-
if (!label2) return "\u2500".repeat(
|
|
5418
|
+
function divider(n2, label2) {
|
|
5419
|
+
if (n2 <= 0) return "";
|
|
5420
|
+
if (!label2) return "\u2500".repeat(n2);
|
|
5336
5421
|
const l = ` ${label2} `;
|
|
5337
|
-
const side = Math.max(2, Math.floor((
|
|
5338
|
-
return [..."\u2500".repeat(side) + l + "\u2500".repeat(
|
|
5422
|
+
const side = Math.max(2, Math.floor((n2 - l.length) / 2));
|
|
5423
|
+
return [..."\u2500".repeat(side) + l + "\u2500".repeat(n2)].slice(0, n2).join("");
|
|
5339
5424
|
}
|
|
5340
5425
|
|
|
5341
5426
|
// src/args.ts
|
|
@@ -5526,9 +5611,9 @@ async function resolvePod(ctx, ref) {
|
|
|
5526
5611
|
}
|
|
5527
5612
|
function resolveCheckpoint(cps, ref) {
|
|
5528
5613
|
if (/^#\d+$/.test(ref)) {
|
|
5529
|
-
const
|
|
5614
|
+
const n2 = Number(ref.slice(1));
|
|
5530
5615
|
const sorted = [...cps].sort((a, b2) => b2.at - a.at);
|
|
5531
|
-
const cp = sorted[
|
|
5616
|
+
const cp = sorted[n2 - 1];
|
|
5532
5617
|
if (!cp) throw new CliError(`no checkpoint ${ref}`, "list them with: airgate checkpoints <pod>");
|
|
5533
5618
|
return cp;
|
|
5534
5619
|
}
|
|
@@ -5656,13 +5741,13 @@ function duration2(seconds) {
|
|
|
5656
5741
|
function hours(h) {
|
|
5657
5742
|
return `${Math.round(h * 100) / 100}h`;
|
|
5658
5743
|
}
|
|
5659
|
-
function usd(
|
|
5660
|
-
return `$${
|
|
5744
|
+
function usd(n2) {
|
|
5745
|
+
return `$${n2.toFixed(2)}`;
|
|
5661
5746
|
}
|
|
5662
|
-
function bytes(
|
|
5663
|
-
if (
|
|
5664
|
-
if (
|
|
5665
|
-
return `${(
|
|
5747
|
+
function bytes(n2) {
|
|
5748
|
+
if (n2 < 1024) return `${n2} B`;
|
|
5749
|
+
if (n2 < 1024 * 1024) return `${(n2 / 1024).toFixed(1)} KiB`;
|
|
5750
|
+
return `${(n2 / 1024 / 1024).toFixed(1)} MiB`;
|
|
5666
5751
|
}
|
|
5667
5752
|
function date(ts) {
|
|
5668
5753
|
if (!ts) return "-";
|
|
@@ -6132,8 +6217,8 @@ var US_PHONE_RE = /(?<![\w+.-])(?:1[-. ]?)?(?:\([2-9]\d{2}\)[ ]?|[2-9]\d{2}[-. ]
|
|
|
6132
6217
|
var INTL_PHONE_RE = /(?<![\w+])\+\d{1,3}(?:[ .-]?\(?\d{1,5}\)?){1,6}(?![\w-]|\.\d)/g;
|
|
6133
6218
|
var CUED_PHONE_RE = /\b(?:phone|tel|telephone|cell|mobile|fax|call|ph|whatsapp)\b\.?(?:[ \t]*(?:no\.?|number|#))?[ \t]*[:.]?[ \t]*(\+?\(?\d[\d \t().-]{5,}\d)/gi;
|
|
6134
6219
|
var phoneDigitsOk = (min) => (s2) => {
|
|
6135
|
-
const
|
|
6136
|
-
return
|
|
6220
|
+
const n2 = digitsOf(s2).length;
|
|
6221
|
+
return n2 >= min && n2 <= 15;
|
|
6137
6222
|
};
|
|
6138
6223
|
var SSN_RE = /(?<![\d-])(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}(?![\d-])/g;
|
|
6139
6224
|
var CUED_SSN_RE = /\b(?:SSN|social security(?:[ \t]+(?:number|no\.?))?)[ \t]*(?:#|no\.?|number)?[ \t]*[:.]?[ \t]*(\d{3}[ -]?\d{2}[ -]?\d{4})(?!\d)/gi;
|
|
@@ -6409,7 +6494,7 @@ var DETECTORS = [
|
|
|
6409
6494
|
];
|
|
6410
6495
|
|
|
6411
6496
|
// ../mask/src/index.ts
|
|
6412
|
-
var defaultPlaceholder = (kind,
|
|
6497
|
+
var defaultPlaceholder = (kind, n2) => `[${kind}_${n2}]`;
|
|
6413
6498
|
var escapeRe = (s2) => s2.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
|
|
6414
6499
|
function vaultKey(kind, value) {
|
|
6415
6500
|
switch (kind) {
|
|
@@ -6456,9 +6541,9 @@ function createMasker(options = {}) {
|
|
|
6456
6541
|
const key = vaultKey(kind, value);
|
|
6457
6542
|
const existing = byKey.get(key);
|
|
6458
6543
|
if (existing) return existing.placeholder;
|
|
6459
|
-
const
|
|
6460
|
-
counters.set(kind,
|
|
6461
|
-
const entry = { placeholder: makePlaceholder(kind,
|
|
6544
|
+
const n2 = (counters.get(kind) ?? 0) + 1;
|
|
6545
|
+
counters.set(kind, n2);
|
|
6546
|
+
const entry = { placeholder: makePlaceholder(kind, n2), kind, original: value };
|
|
6462
6547
|
byKey.set(key, entry);
|
|
6463
6548
|
byLoose.set(looseKey(coreOf(entry.placeholder)), entry);
|
|
6464
6549
|
unmaskRe = null;
|
|
@@ -6471,8 +6556,8 @@ function createMasker(options = {}) {
|
|
|
6471
6556
|
const entry = { placeholder: e.placeholder, kind: e.kind, original: e.original };
|
|
6472
6557
|
byKey.set(key, entry);
|
|
6473
6558
|
byLoose.set(looseKey(coreOf(entry.placeholder)), entry);
|
|
6474
|
-
const
|
|
6475
|
-
counters.set(e.kind, Math.max(counters.get(e.kind) ?? 0,
|
|
6559
|
+
const n2 = Number(/(\d+)\D*$/.exec(e.placeholder)?.[1] ?? 0);
|
|
6560
|
+
counters.set(e.kind, Math.max(counters.get(e.kind) ?? 0, n2));
|
|
6476
6561
|
}
|
|
6477
6562
|
unmaskRe = null;
|
|
6478
6563
|
}
|
|
@@ -6690,7 +6775,7 @@ var mask = {
|
|
|
6690
6775
|
if (r.spans.length) {
|
|
6691
6776
|
const kinds = /* @__PURE__ */ new Map();
|
|
6692
6777
|
for (const e of masker.entries()) kinds.set(e.kind, (kinds.get(e.kind) ?? 0) + 1);
|
|
6693
|
-
ctx.ui.err(ctx.ui.ce("dim", `masked ${r.spans.length} \xB7 ${[...kinds].map(([k,
|
|
6778
|
+
ctx.ui.err(ctx.ui.ce("dim", `masked ${r.spans.length} \xB7 ${[...kinds].map(([k, n2]) => `${k.toLowerCase()} ${n2}`).join(", ")}`));
|
|
6694
6779
|
} else ctx.ui.err(ctx.ui.ce("dim", "nothing to mask."));
|
|
6695
6780
|
}
|
|
6696
6781
|
};
|
|
@@ -6724,7 +6809,7 @@ function attachSession(o) {
|
|
|
6724
6809
|
return;
|
|
6725
6810
|
}
|
|
6726
6811
|
if (i > 0) send({ type: "input", data: s2.slice(0, i) });
|
|
6727
|
-
|
|
6812
|
+
finish2({ reason: "detached" });
|
|
6728
6813
|
};
|
|
6729
6814
|
const restore = () => {
|
|
6730
6815
|
if (restored) return;
|
|
@@ -6745,7 +6830,7 @@ function attachSession(o) {
|
|
|
6745
6830
|
} catch {
|
|
6746
6831
|
}
|
|
6747
6832
|
};
|
|
6748
|
-
function
|
|
6833
|
+
function finish2(end) {
|
|
6749
6834
|
restore();
|
|
6750
6835
|
try {
|
|
6751
6836
|
if (ws.readyState === 0 || ws.readyState === 1) ws.close(1e3, end.reason);
|
|
@@ -6754,7 +6839,7 @@ function attachSession(o) {
|
|
|
6754
6839
|
settle(end);
|
|
6755
6840
|
}
|
|
6756
6841
|
function onTerm() {
|
|
6757
|
-
|
|
6842
|
+
finish2({ reason: "closed", message: "signal" });
|
|
6758
6843
|
}
|
|
6759
6844
|
try {
|
|
6760
6845
|
stdin.setRawMode?.(true);
|
|
@@ -6782,14 +6867,14 @@ function attachSession(o) {
|
|
|
6782
6867
|
return;
|
|
6783
6868
|
}
|
|
6784
6869
|
if (msg.type === "output" && typeof msg.data === "string") stdout.write(msg.data);
|
|
6785
|
-
else if (msg.type === "exit")
|
|
6870
|
+
else if (msg.type === "exit") finish2({ reason: "exit", code: typeof msg.code === "number" ? msg.code : 0 });
|
|
6786
6871
|
else if (msg.type === "status" && msg.state === "waking") o.status?.("waking the pod\u2026");
|
|
6787
6872
|
};
|
|
6788
6873
|
ws.onerror = () => {
|
|
6789
|
-
if (!restored)
|
|
6874
|
+
if (!restored) finish2({ reason: "error", message: "connection error" });
|
|
6790
6875
|
};
|
|
6791
6876
|
ws.onclose = (ev) => {
|
|
6792
|
-
if (!restored)
|
|
6877
|
+
if (!restored) finish2({ reason: "closed", code: ev?.code, message: ev?.reason });
|
|
6793
6878
|
};
|
|
6794
6879
|
return done;
|
|
6795
6880
|
}
|
|
@@ -6944,71 +7029,71 @@ async function readValue(ctx, name) {
|
|
|
6944
7029
|
if (!v) throw new CliError("no value on stdin", `printf %s "$VALUE" | airgate secrets set ${name}`);
|
|
6945
7030
|
return v;
|
|
6946
7031
|
}
|
|
6947
|
-
function
|
|
6948
|
-
const
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6953
|
-
|
|
6954
|
-
if (!m) continue;
|
|
6955
|
-
let v = m[2];
|
|
6956
|
-
if (v.startsWith('"')) {
|
|
6957
|
-
const end = v.lastIndexOf('"');
|
|
6958
|
-
v = v.slice(1, end > 0 ? end : void 0).replace(/\\(["\\n])/g, (_s, c) => c === "n" ? "\n" : c);
|
|
6959
|
-
} else if (v.startsWith("'")) {
|
|
6960
|
-
const end = v.lastIndexOf("'");
|
|
6961
|
-
v = v.slice(1, end > 0 ? end : void 0);
|
|
6962
|
-
} else v = v.replace(/\s+#.*$/, "").trim();
|
|
6963
|
-
if (NAME_RE.test(m[1]) && !m[1].startsWith("AIRGATE_")) values[m[1]] = v;
|
|
6964
|
-
else skipped.push(m[1]);
|
|
6965
|
-
}
|
|
6966
|
-
return { values, skipped };
|
|
7032
|
+
function parseDotenv2(text2) {
|
|
7033
|
+
const r = parseDotenv(text2);
|
|
7034
|
+
return {
|
|
7035
|
+
values: r.values,
|
|
7036
|
+
skipped: r.entries.filter((e) => !e.name).map((e) => e.key),
|
|
7037
|
+
renamed: r.entries.filter((e) => e.name && e.name !== e.key).map((e) => `${e.key} \u2192 ${e.name}`)
|
|
7038
|
+
};
|
|
6967
7039
|
}
|
|
6968
|
-
|
|
6969
|
-
const path = resolve3(ctx.io.cwd, file);
|
|
6970
|
-
let text2;
|
|
7040
|
+
function readDotenvFile(ctx, file, hint) {
|
|
6971
7041
|
try {
|
|
6972
|
-
|
|
7042
|
+
return readFileSync2(resolve3(ctx.io.cwd, file), "utf8");
|
|
6973
7043
|
} catch {
|
|
6974
|
-
throw new CliError(`can't read ${file}`,
|
|
7044
|
+
throw new CliError(`can't read ${file}`, hint);
|
|
6975
7045
|
}
|
|
6976
|
-
|
|
6977
|
-
|
|
6978
|
-
const
|
|
6979
|
-
|
|
6980
|
-
|
|
6981
|
-
|
|
6982
|
-
|
|
6983
|
-
|
|
6984
|
-
|
|
6985
|
-
|
|
6986
|
-
}
|
|
7046
|
+
}
|
|
7047
|
+
async function sendDotenv(ctx, file, podId) {
|
|
7048
|
+
const { values, skipped, renamed } = parseDotenv2(readDotenvFile(ctx, file, "airgate up --env path/to/.env"));
|
|
7049
|
+
if (!Object.keys(values).length) return { sent: [], skipped, renamed, failed: [] };
|
|
7050
|
+
try {
|
|
7051
|
+
const r = await ctx.api.setSecrets(values, podId);
|
|
7052
|
+
return { sent: [...r.created, ...r.updated], skipped, renamed, failed: [] };
|
|
7053
|
+
} catch (e) {
|
|
7054
|
+
if (e instanceof ApiError && e.status < 500) return { sent: [], skipped, renamed, failed: [e.message] };
|
|
7055
|
+
throw e;
|
|
6987
7056
|
}
|
|
6988
|
-
return { sent, skipped, failed };
|
|
6989
7057
|
}
|
|
6990
7058
|
var secrets = {
|
|
6991
7059
|
name: "secrets",
|
|
6992
7060
|
aliases: ["secret"],
|
|
6993
7061
|
group: "pods",
|
|
6994
|
-
usage: "[set NAME | rm NAME] [--pod p]",
|
|
7062
|
+
usage: "[set NAME | rm NAME | import FILE] [--pod p] [--dotenv]",
|
|
6995
7063
|
summary: "environment variables for pods, stored encrypted, never shown again",
|
|
6996
|
-
example: "airgate secrets
|
|
7064
|
+
example: "airgate secrets import .env --pod api",
|
|
6997
7065
|
notes: [
|
|
6998
7066
|
"set reads the value from a hidden prompt, or from stdin when piped (never from the command line).",
|
|
6999
7067
|
"account-wide unless --pod; a pod value overrides an account one. pods load them into ~/.airgate/secrets.env when they start or wake.",
|
|
7000
|
-
"every value of 6+ characters is masked before it reaches a model, even if the agent reads it."
|
|
7068
|
+
"every value of 6+ characters is masked before it reaches a model, even if the agent reads it.",
|
|
7069
|
+
"import FILE saves every KEY=value in a .env file at once (quotes, export, multiline values); ls --dotenv prints the names as a .env.example."
|
|
7001
7070
|
],
|
|
7002
7071
|
flags: {
|
|
7003
|
-
pod: { type: "string", value: "pod", help: "just this pod (default: every pod)" }
|
|
7072
|
+
pod: { type: "string", value: "pod", help: "just this pod (default: every pod)" },
|
|
7073
|
+
dotenv: { type: "boolean", help: "ls: print the names as a .env.example (no values)" }
|
|
7004
7074
|
},
|
|
7005
7075
|
async run(ctx) {
|
|
7006
7076
|
const [action, name, extra] = ctx.args.positionals;
|
|
7007
7077
|
if (extra !== void 0) throw new UsageError(`unexpected "${extra}"`, "values are read from a hidden prompt or stdin, never from arguments");
|
|
7008
7078
|
const podRef = str(ctx.args, "pod");
|
|
7009
7079
|
const pod = podRef ? await resolvePod(ctx, podRef) : void 0;
|
|
7080
|
+
if (action === "import") {
|
|
7081
|
+
if (!name) throw new UsageError("which file? airgate secrets import .env [--pod p]");
|
|
7082
|
+
const r = await ctx.ui.spin(`saving ${name}`, sendDotenv(ctx, name, pod?.id));
|
|
7083
|
+
if (ctx.json) return ctx.ui.json(r);
|
|
7084
|
+
if (r.failed.length) throw new CliError(`nothing saved: ${r.failed.join("; ")}`);
|
|
7085
|
+
if (!r.sent.length) throw new CliError(`no KEY=value lines in ${name}`);
|
|
7086
|
+
ctx.ui.out(`${ctx.ui.c("ok", "saved")} ${r.sent.length} for ${pod ? pod.spec.name : "every pod"}: ${r.sent.join(", ")}`);
|
|
7087
|
+
if (r.renamed.length) ctx.ui.out(ctx.ui.c("dim", `renamed: ${r.renamed.join(", ")}`));
|
|
7088
|
+
if (r.skipped.length) ctx.ui.err(ctx.ui.ce("warn", `skipped (reserved or unusable names): ${r.skipped.join(", ")}`));
|
|
7089
|
+
return;
|
|
7090
|
+
}
|
|
7010
7091
|
if (!action || action === "list" || action === "ls") {
|
|
7011
7092
|
const all = await ctx.api.secrets();
|
|
7093
|
+
if (bool(ctx.args, "dotenv")) {
|
|
7094
|
+
const names = [...all.account.map((s2) => s2.name), ...pod ? (all.pods[pod.id] ?? []).map((s2) => s2.name) : []];
|
|
7095
|
+
return void ctx.io.stdout.write(dotenvExample([...new Set(names)].sort()));
|
|
7096
|
+
}
|
|
7012
7097
|
if (ctx.json) return ctx.ui.json(pod ? { account: all.account, pod: all.pods[pod.id] ?? [] } : all);
|
|
7013
7098
|
const podNames2 = new Map((await ctx.api.pods()).map((p) => [p.id, p.spec.name]));
|
|
7014
7099
|
const rows2 = [];
|
|
@@ -7040,11 +7125,14 @@ var secrets = {
|
|
|
7040
7125
|
ctx.ui.out(`removed ${name} from ${pod ? pod.spec.name : "the account"}. ${r.note}.`);
|
|
7041
7126
|
return;
|
|
7042
7127
|
}
|
|
7043
|
-
throw new UsageError(`unknown action "${action}"`, "airgate secrets [set NAME | rm NAME] [--pod p]");
|
|
7128
|
+
throw new UsageError(`unknown action "${action}"`, "airgate secrets [set NAME | rm NAME | import FILE] [--pod p]");
|
|
7044
7129
|
}
|
|
7045
7130
|
};
|
|
7046
7131
|
|
|
7047
7132
|
// src/commands/pods.ts
|
|
7133
|
+
import { execFileSync } from "node:child_process";
|
|
7134
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7135
|
+
import { resolve as resolvePath } from "node:path";
|
|
7048
7136
|
var AGENT_NAMES = Object.keys(AGENTS);
|
|
7049
7137
|
var SIZE_NAMES = Object.keys(SIZES);
|
|
7050
7138
|
var NETWORK_NAMES = ["allowlist", "open", "off"];
|
|
@@ -7060,9 +7148,9 @@ function cronProblem(cron) {
|
|
|
7060
7148
|
if (!cronValid(cron)) return `"${cron}" isn't a 5-field cron (min hour day month weekday, utc)`;
|
|
7061
7149
|
let t = cronNext(cron);
|
|
7062
7150
|
for (let i = 0; i < 12 && t !== null; i++) {
|
|
7063
|
-
const
|
|
7064
|
-
if (
|
|
7065
|
-
t =
|
|
7151
|
+
const n2 = cronNext(cron, t);
|
|
7152
|
+
if (n2 !== null && n2 - t < 15 * 6e4) return `"${cron}" runs more often than every 15 minutes`;
|
|
7153
|
+
t = n2;
|
|
7066
7154
|
}
|
|
7067
7155
|
return null;
|
|
7068
7156
|
}
|
|
@@ -7125,7 +7213,7 @@ var pods = {
|
|
|
7125
7213
|
var launch = {
|
|
7126
7214
|
name: "launch",
|
|
7127
7215
|
group: "pods",
|
|
7128
|
-
usage: "[--name n] [--template t] [--agent a] [--memory m] [--browser] [--repo owner/name] [--cron c --task t \u2026] [--model m] [--no-mask] [--no-undo] [--network n]",
|
|
7216
|
+
usage: "[--name n] [--template t] [--agent a] [--memory m] [--browser] [--repo owner/name|.] [--env .env] [--cron c --task t \u2026] [--model m] [--no-mask] [--no-undo] [--network n]",
|
|
7129
7217
|
summary: "start a new pod",
|
|
7130
7218
|
example: "airgate launch --name fix-nav --agent codex --repo acme/web",
|
|
7131
7219
|
notes: [
|
|
@@ -7143,7 +7231,8 @@ var launch = {
|
|
|
7143
7231
|
"builtin-model": { type: "boolean", help: "autopilot with opencode: use airgate's built-in model (no subscription)" },
|
|
7144
7232
|
model: { type: "string", value: "id", help: "built-in model: an openrouter id (default: the gateway's); see airgate templates" },
|
|
7145
7233
|
memory: { type: "string", value: "size", choices: Object.keys(SIZES), default: "standard", help: `standard 8 GB | large 16 GB (pro, ${addOnPrice(ADD_ONS.large.usdPerHour)})` },
|
|
7146
|
-
repo: { type: "string", value: "owner/name", help: "GitHub repo to clone (private ones: airgate github connect)" },
|
|
7234
|
+
repo: { type: "string", value: "owner/name", help: "GitHub repo to clone, a github link, or . for this folder's repo (private ones: airgate github connect)" },
|
|
7235
|
+
env: { type: "string", value: "file", help: "a .env file: its keys become this pod's secrets before it boots" },
|
|
7147
7236
|
"public-repo": { type: "boolean", help: "the repo is public: don't check it against your GitHub App" },
|
|
7148
7237
|
mask: { type: "boolean", default: true, help: "mask model traffic (--no-mask to turn off)" },
|
|
7149
7238
|
undo: { type: "boolean", default: true, help: "checkpoint after every agent step (--no-undo to turn off)" },
|
|
@@ -7156,16 +7245,48 @@ var launch = {
|
|
|
7156
7245
|
if (ctx.args.positionals.length) throw new UsageError(`unexpected "${ctx.args.positionals[0]}"`, "name the pod with --name");
|
|
7157
7246
|
const spec = specFromFlags(ctx);
|
|
7158
7247
|
if (spec.repo) await checkRepo(ctx, spec.repo);
|
|
7159
|
-
const
|
|
7248
|
+
const envFile = str(ctx.args, "env");
|
|
7249
|
+
let secrets2;
|
|
7250
|
+
if (envFile) {
|
|
7251
|
+
const { values, skipped, renamed } = parseDotenv2(readDotenv(ctx, envFile));
|
|
7252
|
+
secrets2 = values;
|
|
7253
|
+
if (renamed.length) ctx.ui.err(ctx.ui.ce("dim", `renamed: ${renamed.join(", ")}`));
|
|
7254
|
+
if (skipped.length) ctx.ui.err(ctx.ui.ce("warn", `skipped (reserved or unusable names): ${skipped.join(", ")}`));
|
|
7255
|
+
}
|
|
7256
|
+
const pod = await ctx.ui.spin(`launching ${spec.name}`, async () => waitReady(ctx, await ctx.api.launch(spec, void 0, secrets2)));
|
|
7257
|
+
if (secrets2 && Object.keys(secrets2).length && !ctx.json) ctx.ui.out(`secrets for ${pod.spec.name}: ${Object.keys(secrets2).join(", ")}`);
|
|
7160
7258
|
if (ctx.json) return ctx.ui.json(pod);
|
|
7161
7259
|
ctx.ui.out(readyLine(ctx, pod));
|
|
7162
7260
|
if ((pod.spec.template ?? "code") === "autopilot") {
|
|
7163
|
-
const
|
|
7261
|
+
const n2 = pod.spec.jobs?.length ?? 0;
|
|
7164
7262
|
if (!pod.spec.builtinModel) ctx.ui.out(`sign ${AGENTS[pod.spec.agent].label} in once: airgate attach ${pod.spec.name}`);
|
|
7165
|
-
ctx.ui.out(
|
|
7263
|
+
ctx.ui.out(n2 ? `${n2} job${n2 === 1 ? "" : "s"}: airgate jobs ${pod.spec.name}` : `add a job: airgate job add ${pod.spec.name} --cron "0 2 * * *" --task "\u2026"`);
|
|
7166
7264
|
} else ctx.ui.out(`next: airgate attach ${pod.spec.name}`);
|
|
7167
7265
|
}
|
|
7168
7266
|
};
|
|
7267
|
+
function repoArg(ctx, raw) {
|
|
7268
|
+
if (raw === void 0) return void 0;
|
|
7269
|
+
let input = raw;
|
|
7270
|
+
if (raw === ".") {
|
|
7271
|
+
try {
|
|
7272
|
+
input = execFileSync("git", ["remote", "get-url", "origin"], { cwd: ctx.io.cwd, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
7273
|
+
} catch {
|
|
7274
|
+
throw new UsageError("--repo .: this folder has no git remote called origin");
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
const s2 = input.trim().replace(/\.git$/, "").replace(/^git@github\.com:/, "").replace(/^(?:https?:\/\/)?(?:www\.)?github\.com\//, "");
|
|
7278
|
+
const [owner, name] = s2.split(/[/#?]/);
|
|
7279
|
+
const repo = owner && name ? `${owner}/${name}` : "";
|
|
7280
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo)) throw new UsageError(raw === "." ? `--repo .: origin isn't a github repo (${input})` : "--repo: owner/name or a github link");
|
|
7281
|
+
return repo;
|
|
7282
|
+
}
|
|
7283
|
+
function readDotenv(ctx, file) {
|
|
7284
|
+
try {
|
|
7285
|
+
return readFileSync3(resolvePath(ctx.io.cwd, file), "utf8");
|
|
7286
|
+
} catch {
|
|
7287
|
+
throw new CliError(`can't read ${file}`, "airgate launch --env path/to/.env");
|
|
7288
|
+
}
|
|
7289
|
+
}
|
|
7169
7290
|
function specFromFlags(ctx, defaults = {}) {
|
|
7170
7291
|
const a = ctx.args;
|
|
7171
7292
|
const network = str(a, "network") ?? "allowlist";
|
|
@@ -7176,8 +7297,7 @@ function specFromFlags(ctx, defaults = {}) {
|
|
|
7176
7297
|
autoStopMinutes = Number(autoStop);
|
|
7177
7298
|
if (!Number.isInteger(autoStopMinutes) || autoStopMinutes < 1) throw new UsageError("--auto-stop: whole idle minutes \u2265 1, or keep-awake");
|
|
7178
7299
|
}
|
|
7179
|
-
const repo = str(a, "repo");
|
|
7180
|
-
if (repo && !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repo)) throw new UsageError("--repo: owner/name");
|
|
7300
|
+
const repo = repoArg(ctx, str(a, "repo"));
|
|
7181
7301
|
const template = str(a, "template") ?? "code";
|
|
7182
7302
|
const t = TEMPLATES[template];
|
|
7183
7303
|
const agent = str(a, "agent") ?? (defaults.agent && t.agents.includes(defaults.agent) ? defaults.agent : t.agents[0]);
|
|
@@ -7307,7 +7427,6 @@ var destroy = {
|
|
|
7307
7427
|
};
|
|
7308
7428
|
var exec = {
|
|
7309
7429
|
name: "exec",
|
|
7310
|
-
aliases: ["run"],
|
|
7311
7430
|
group: "pods",
|
|
7312
7431
|
usage: "<pod> -- <command\u2026>",
|
|
7313
7432
|
summary: "run a command in the pod (bash -c, in ~/workspace) and print its output",
|
|
@@ -7330,7 +7449,7 @@ var exec = {
|
|
|
7330
7449
|
};
|
|
7331
7450
|
|
|
7332
7451
|
// src/commands/jobs.ts
|
|
7333
|
-
var price = (
|
|
7452
|
+
var price = (n2) => `$${n2.toFixed(2)}/h`;
|
|
7334
7453
|
var templates = {
|
|
7335
7454
|
name: "templates",
|
|
7336
7455
|
group: "pods",
|
|
@@ -7528,199 +7647,16 @@ var log = {
|
|
|
7528
7647
|
// src/commands/mcp.ts
|
|
7529
7648
|
import { createInterface } from "node:readline";
|
|
7530
7649
|
|
|
7531
|
-
// src/io.ts
|
|
7532
|
-
import { spawn } from "node:child_process";
|
|
7533
|
-
import { EventEmitter } from "node:events";
|
|
7534
|
-
import { PassThrough } from "node:stream";
|
|
7535
|
-
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
7536
|
-
var LineReader = class {
|
|
7537
|
-
buf = "";
|
|
7538
|
-
ended = false;
|
|
7539
|
-
waiters = [];
|
|
7540
|
-
stream;
|
|
7541
|
-
attached = false;
|
|
7542
|
-
decoder = new StringDecoder2("utf8");
|
|
7543
|
-
constructor(stream) {
|
|
7544
|
-
this.stream = stream;
|
|
7545
|
-
}
|
|
7546
|
-
attach() {
|
|
7547
|
-
if (this.attached) return;
|
|
7548
|
-
this.attached = true;
|
|
7549
|
-
this.stream.on("data", (d) => {
|
|
7550
|
-
this.buf += typeof d === "string" ? d : this.decoder.write(d);
|
|
7551
|
-
this.wake();
|
|
7552
|
-
});
|
|
7553
|
-
this.stream.on("end", () => {
|
|
7554
|
-
this.ended = true;
|
|
7555
|
-
this.wake();
|
|
7556
|
-
});
|
|
7557
|
-
this.stream.on("error", () => {
|
|
7558
|
-
this.ended = true;
|
|
7559
|
-
this.wake();
|
|
7560
|
-
});
|
|
7561
|
-
}
|
|
7562
|
-
wake() {
|
|
7563
|
-
for (const w of this.waiters.splice(0)) w();
|
|
7564
|
-
}
|
|
7565
|
-
async line() {
|
|
7566
|
-
this.attach();
|
|
7567
|
-
this.stream.resume();
|
|
7568
|
-
for (; ; ) {
|
|
7569
|
-
const i = this.buf.indexOf("\n");
|
|
7570
|
-
if (i !== -1) {
|
|
7571
|
-
const l = this.buf.slice(0, i).replace(/\r$/, "");
|
|
7572
|
-
this.buf = this.buf.slice(i + 1);
|
|
7573
|
-
if (!this.buf) this.stream.pause();
|
|
7574
|
-
return l;
|
|
7575
|
-
}
|
|
7576
|
-
if (this.ended) {
|
|
7577
|
-
const rest = this.buf;
|
|
7578
|
-
this.buf = "";
|
|
7579
|
-
return rest ? rest : null;
|
|
7580
|
-
}
|
|
7581
|
-
await new Promise((r) => this.waiters.push(r));
|
|
7582
|
-
}
|
|
7583
|
-
}
|
|
7584
|
-
};
|
|
7585
|
-
function ttyPrompt(stdin, out) {
|
|
7586
|
-
return (question, opts2 = {}) => new Promise((resolve5, reject) => {
|
|
7587
|
-
out.write(question);
|
|
7588
|
-
const decoder = new StringDecoder2("utf8");
|
|
7589
|
-
let answer = "";
|
|
7590
|
-
const wasRaw = Boolean(stdin.isRaw);
|
|
7591
|
-
stdin.setRawMode?.(true);
|
|
7592
|
-
stdin.resume();
|
|
7593
|
-
const done = (err) => {
|
|
7594
|
-
stdin.off("data", onData);
|
|
7595
|
-
stdin.setRawMode?.(wasRaw);
|
|
7596
|
-
stdin.pause();
|
|
7597
|
-
out.write("\n");
|
|
7598
|
-
if (err) reject(err);
|
|
7599
|
-
else resolve5(answer);
|
|
7600
|
-
};
|
|
7601
|
-
const onData = (d) => {
|
|
7602
|
-
for (const ch of decoder.write(d)) {
|
|
7603
|
-
if (ch === "\r" || ch === "\n") return done();
|
|
7604
|
-
if (ch === "") return done(Object.assign(new Error("cancelled"), { code: "cancelled" }));
|
|
7605
|
-
if (ch === "") {
|
|
7606
|
-
if (!answer) return done(Object.assign(new Error("cancelled"), { code: "cancelled" }));
|
|
7607
|
-
continue;
|
|
7608
|
-
}
|
|
7609
|
-
if (ch === "\x7F" || ch === "\b") {
|
|
7610
|
-
if (answer) {
|
|
7611
|
-
answer = [...answer].slice(0, -1).join("");
|
|
7612
|
-
if (!opts2.hidden) out.write("\b \b");
|
|
7613
|
-
}
|
|
7614
|
-
continue;
|
|
7615
|
-
}
|
|
7616
|
-
if (ch < " ") continue;
|
|
7617
|
-
answer += ch;
|
|
7618
|
-
if (!opts2.hidden) out.write(ch);
|
|
7619
|
-
}
|
|
7620
|
-
};
|
|
7621
|
-
stdin.on("data", onData);
|
|
7622
|
-
});
|
|
7623
|
-
}
|
|
7624
|
-
function openInBrowser(url) {
|
|
7625
|
-
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : ["xdg-open", [url]];
|
|
7626
|
-
return new Promise((resolve5) => {
|
|
7627
|
-
try {
|
|
7628
|
-
const child = spawn(cmd, args, { stdio: "ignore", detached: true });
|
|
7629
|
-
child.on("error", () => resolve5(false));
|
|
7630
|
-
child.on("spawn", () => {
|
|
7631
|
-
child.unref();
|
|
7632
|
-
resolve5(true);
|
|
7633
|
-
});
|
|
7634
|
-
} catch {
|
|
7635
|
-
resolve5(false);
|
|
7636
|
-
}
|
|
7637
|
-
});
|
|
7638
|
-
}
|
|
7639
|
-
function processIo() {
|
|
7640
|
-
const stdin = process.stdin;
|
|
7641
|
-
const lines = new LineReader(stdin);
|
|
7642
|
-
const tty = ttyPrompt(stdin, process.stderr);
|
|
7643
|
-
return {
|
|
7644
|
-
stdin,
|
|
7645
|
-
stdout: process.stdout,
|
|
7646
|
-
stderr: process.stderr,
|
|
7647
|
-
env: process.env,
|
|
7648
|
-
// `npm run cli -- up` runs in packages/cli; INIT_CWD is where the user typed it
|
|
7649
|
-
cwd: process.env.AIRGATE_CWD || process.env.npm_lifecycle_event === "dev" && process.env.INIT_CWD || process.cwd(),
|
|
7650
|
-
async prompt(q, o) {
|
|
7651
|
-
if (stdin.isTTY) return tty(q, o);
|
|
7652
|
-
process.stderr.write(q);
|
|
7653
|
-
const l = await lines.line();
|
|
7654
|
-
if (l === null) throw Object.assign(new Error("no input (stdin closed)"), { code: "no_input" });
|
|
7655
|
-
process.stderr.write("\n");
|
|
7656
|
-
return l;
|
|
7657
|
-
},
|
|
7658
|
-
openUrl: openInBrowser,
|
|
7659
|
-
websocket: (url) => new WebSocket(url),
|
|
7660
|
-
signals: process
|
|
7661
|
-
};
|
|
7662
|
-
}
|
|
7663
|
-
function memoryIo(opts2 = {}) {
|
|
7664
|
-
const out = new PassThrough();
|
|
7665
|
-
const err = new PassThrough();
|
|
7666
|
-
let stdout = "";
|
|
7667
|
-
let stderr = "";
|
|
7668
|
-
out.on("data", (d) => stdout += d.toString());
|
|
7669
|
-
err.on("data", (d) => stderr += d.toString());
|
|
7670
|
-
const answers = [...opts2.answers ?? []];
|
|
7671
|
-
const opened = [];
|
|
7672
|
-
const prompts = [];
|
|
7673
|
-
const stdin = opts2.stdin ?? Object.assign(new PassThrough(), { isTTY: false });
|
|
7674
|
-
if (!opts2.stdin) stdin.end();
|
|
7675
|
-
const io = {
|
|
7676
|
-
stdin,
|
|
7677
|
-
stdout: out,
|
|
7678
|
-
stderr: err,
|
|
7679
|
-
env: opts2.env ?? {},
|
|
7680
|
-
cwd: opts2.cwd ?? process.cwd(),
|
|
7681
|
-
async prompt(q) {
|
|
7682
|
-
prompts.push(q);
|
|
7683
|
-
const a = answers.shift();
|
|
7684
|
-
if (a === void 0) throw Object.assign(new Error(`no answer queued for prompt: ${q}`), { code: "no_input" });
|
|
7685
|
-
return a;
|
|
7686
|
-
},
|
|
7687
|
-
async openUrl(u) {
|
|
7688
|
-
opened.push(u);
|
|
7689
|
-
return true;
|
|
7690
|
-
},
|
|
7691
|
-
websocket: () => {
|
|
7692
|
-
throw new Error("no websocket in memoryIo");
|
|
7693
|
-
},
|
|
7694
|
-
signals: new EventEmitter()
|
|
7695
|
-
};
|
|
7696
|
-
return {
|
|
7697
|
-
io,
|
|
7698
|
-
answers,
|
|
7699
|
-
opened,
|
|
7700
|
-
prompts,
|
|
7701
|
-
get stdout() {
|
|
7702
|
-
return stdout;
|
|
7703
|
-
},
|
|
7704
|
-
get stderr() {
|
|
7705
|
-
return stderr;
|
|
7706
|
-
},
|
|
7707
|
-
clear() {
|
|
7708
|
-
stdout = "";
|
|
7709
|
-
stderr = "";
|
|
7710
|
-
}
|
|
7711
|
-
};
|
|
7712
|
-
}
|
|
7713
|
-
|
|
7714
7650
|
// src/version.ts
|
|
7715
|
-
var VERSION = "0.1.
|
|
7651
|
+
var VERSION = "0.1.2";
|
|
7716
7652
|
|
|
7717
7653
|
// src/commands/updown.ts
|
|
7718
|
-
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as
|
|
7654
|
+
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
7719
7655
|
import { homedir as homedir2 } from "node:os";
|
|
7720
7656
|
import { basename, dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve4 } from "node:path";
|
|
7721
7657
|
|
|
7722
7658
|
// src/pack.ts
|
|
7723
|
-
import { spawn
|
|
7659
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
7724
7660
|
import { existsSync, lstatSync } from "node:fs";
|
|
7725
7661
|
import { join as join2 } from "node:path";
|
|
7726
7662
|
var UPLOAD_LIMIT = 32 * 6 * 1024 * 1024;
|
|
@@ -7759,8 +7695,8 @@ function gitFiles(dir) {
|
|
|
7759
7695
|
return out;
|
|
7760
7696
|
}
|
|
7761
7697
|
function runTar(cwd, args, input, limit = UPLOAD_LIMIT) {
|
|
7762
|
-
return new Promise((
|
|
7763
|
-
const child =
|
|
7698
|
+
return new Promise((resolve6, reject) => {
|
|
7699
|
+
const child = spawn("tar", args, { cwd, env: { ...process.env, COPYFILE_DISABLE: "1" }, stdio: ["pipe", "pipe", "pipe"] });
|
|
7764
7700
|
const chunks = [];
|
|
7765
7701
|
let size2 = 0;
|
|
7766
7702
|
let err = "";
|
|
@@ -7779,7 +7715,7 @@ function runTar(cwd, args, input, limit = UPLOAD_LIMIT) {
|
|
|
7779
7715
|
child.on("close", (code3) => {
|
|
7780
7716
|
if (over2) return reject(new TooLargeError(limit));
|
|
7781
7717
|
if (code3 !== 0) return reject(new Error(`tar failed: ${err.trim().split("\n").slice(-3).join(" ")}`));
|
|
7782
|
-
|
|
7718
|
+
resolve6(Buffer.concat(chunks));
|
|
7783
7719
|
});
|
|
7784
7720
|
child.stdin.on("error", () => void 0);
|
|
7785
7721
|
child.stdin.end(input ?? "");
|
|
@@ -7815,7 +7751,7 @@ import { gunzipSync } from "node:zlib";
|
|
|
7815
7751
|
|
|
7816
7752
|
// src/sessions.ts
|
|
7817
7753
|
import { createHash, randomUUID } from "node:crypto";
|
|
7818
|
-
import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync, openSync, readdirSync, readFileSync as
|
|
7754
|
+
import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync, openSync, readdirSync, readFileSync as readFileSync4, readSync, realpathSync, renameSync as renameSync2, rmSync as rmSync2, statSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7819
7755
|
import { tmpdir } from "node:os";
|
|
7820
7756
|
import { dirname, join as join3, relative } from "node:path";
|
|
7821
7757
|
var POD_HOME = "/home/sprite";
|
|
@@ -7843,15 +7779,15 @@ function findClaudeSession(home, projectDir, id, env = {}) {
|
|
|
7843
7779
|
const dir = join3(claudeHome(home, env), "projects", claudeKey(p));
|
|
7844
7780
|
let names = [];
|
|
7845
7781
|
try {
|
|
7846
|
-
names = readdirSync(dir).filter((
|
|
7782
|
+
names = readdirSync(dir).filter((n2) => n2.endsWith(".jsonl"));
|
|
7847
7783
|
} catch {
|
|
7848
7784
|
continue;
|
|
7849
7785
|
}
|
|
7850
|
-
for (const
|
|
7851
|
-
const sid =
|
|
7786
|
+
for (const n2 of names) {
|
|
7787
|
+
const sid = n2.slice(0, -".jsonl".length);
|
|
7852
7788
|
if (id && sid !== id) continue;
|
|
7853
7789
|
if (!ID_RE.test(sid)) continue;
|
|
7854
|
-
const file = join3(dir,
|
|
7790
|
+
const file = join3(dir, n2);
|
|
7855
7791
|
const side = join3(dir, sid);
|
|
7856
7792
|
best.push({ agent: "claude-code", id: sid, file, mtime: statSync(file).mtimeMs, ...existsSync2(side) && statSync(side).isDirectory() ? { sidecar: side } : {} });
|
|
7857
7793
|
}
|
|
@@ -7863,8 +7799,8 @@ function firstLine(file) {
|
|
|
7863
7799
|
const fd = openSync(file, "r");
|
|
7864
7800
|
try {
|
|
7865
7801
|
const buf = Buffer.alloc(1024 * 1024);
|
|
7866
|
-
const
|
|
7867
|
-
return buf.subarray(0,
|
|
7802
|
+
const n2 = readSync(fd, buf, 0, buf.length, 0);
|
|
7803
|
+
return buf.subarray(0, n2).toString("utf8").split("\n")[0];
|
|
7868
7804
|
} finally {
|
|
7869
7805
|
closeSync(fd);
|
|
7870
7806
|
}
|
|
@@ -7920,7 +7856,7 @@ async function packSession(s2, projectDir, podPath, limit = UPLOAD_LIMIT) {
|
|
|
7920
7856
|
const froms = spellings(projectDir);
|
|
7921
7857
|
const stage = mkdtempSync(join3(tmpdir(), "airgate-session-"));
|
|
7922
7858
|
try {
|
|
7923
|
-
const raw =
|
|
7859
|
+
const raw = readFileSync4(s2.file);
|
|
7924
7860
|
let podDir;
|
|
7925
7861
|
const names = [];
|
|
7926
7862
|
if (s2.agent === "claude-code") {
|
|
@@ -7936,7 +7872,7 @@ async function packSession(s2, projectDir, podPath, limit = UPLOAD_LIMIT) {
|
|
|
7936
7872
|
mkdirSync2(b2, { recursive: true });
|
|
7937
7873
|
copy2(a, b2);
|
|
7938
7874
|
} else if (x.isFile()) {
|
|
7939
|
-
const body =
|
|
7875
|
+
const body = readFileSync4(a);
|
|
7940
7876
|
writeFileSync2(b2, /\.(jsonl?|txt|md)$/.test(x.name) ? rewriteAll(body.toString("utf8"), froms, podPath) : body);
|
|
7941
7877
|
}
|
|
7942
7878
|
}
|
|
@@ -7968,7 +7904,7 @@ function bringBack(sent, podContent, now = /* @__PURE__ */ new Date()) {
|
|
|
7968
7904
|
let text2 = rewritePath(podContent.toString("utf8"), sent.podPath, sent.localPath);
|
|
7969
7905
|
const localId = sent.localId ?? sent.id;
|
|
7970
7906
|
if (localId !== sent.id) text2 = text2.split(sent.id).join(localId);
|
|
7971
|
-
const local = existsSync2(sent.file) ?
|
|
7907
|
+
const local = existsSync2(sent.file) ? readFileSync4(sent.file) : null;
|
|
7972
7908
|
if (local && local.toString("utf8") === text2) return { agent: sent.agent, id: localId, file: sent.file, outcome: "same", sha256: sha256(local) };
|
|
7973
7909
|
const atomic = (file2, body2) => {
|
|
7974
7910
|
mkdirSync2(dirname(file2), { recursive: true });
|
|
@@ -7985,7 +7921,7 @@ function bringBack(sent, podContent, now = /* @__PURE__ */ new Date()) {
|
|
|
7985
7921
|
let file;
|
|
7986
7922
|
if (sent.agent === "claude-code") file = join3(dirname(sent.file), `${id}.jsonl`);
|
|
7987
7923
|
else {
|
|
7988
|
-
const p = (
|
|
7924
|
+
const p = (n2) => String(n2).padStart(2, "0");
|
|
7989
7925
|
const root = join3(dirname(sent.file), "..", "..", "..");
|
|
7990
7926
|
const stamp = `${now.getFullYear()}-${p(now.getMonth() + 1)}-${p(now.getDate())}T${p(now.getHours())}-${p(now.getMinutes())}-${p(now.getSeconds())}`;
|
|
7991
7927
|
file = join3(root, String(now.getFullYear()), p(now.getMonth() + 1), p(now.getDate()), `rollout-${stamp}-${id}.jsonl`);
|
|
@@ -8031,11 +7967,11 @@ function unquote(p) {
|
|
|
8031
7967
|
bytes2.push(...Buffer.from(c, "utf8"));
|
|
8032
7968
|
continue;
|
|
8033
7969
|
}
|
|
8034
|
-
const
|
|
8035
|
-
if (/[0-7]/.test(
|
|
7970
|
+
const n2 = p[++i];
|
|
7971
|
+
if (/[0-7]/.test(n2)) {
|
|
8036
7972
|
bytes2.push(parseInt(p.slice(i, i + 3), 8));
|
|
8037
7973
|
i += 2;
|
|
8038
|
-
} else bytes2.push(simple[
|
|
7974
|
+
} else bytes2.push(simple[n2] ?? n2.charCodeAt(0));
|
|
8039
7975
|
}
|
|
8040
7976
|
return Buffer.from(bytes2).toString("utf8");
|
|
8041
7977
|
}
|
|
@@ -8171,7 +8107,7 @@ function findState(from) {
|
|
|
8171
8107
|
const p = statePath(dir);
|
|
8172
8108
|
if (existsSync3(p)) {
|
|
8173
8109
|
try {
|
|
8174
|
-
const s2 = JSON.parse(
|
|
8110
|
+
const s2 = JSON.parse(readFileSync5(p, "utf8"));
|
|
8175
8111
|
if (s2 && typeof s2.podId === "string" && typeof s2.dir === "string") return { root: dir, state: s2 };
|
|
8176
8112
|
} catch {
|
|
8177
8113
|
}
|
|
@@ -8187,7 +8123,7 @@ function excludeStateDir(projectDir) {
|
|
|
8187
8123
|
if (r.status !== 0) return false;
|
|
8188
8124
|
const raw = r.stdout.trim();
|
|
8189
8125
|
const file = isAbsolute(raw) ? raw : join4(projectDir, raw);
|
|
8190
|
-
const current = existsSync3(file) ?
|
|
8126
|
+
const current = existsSync3(file) ? readFileSync5(file, "utf8") : "";
|
|
8191
8127
|
if (current.split(/\r?\n/).some((l) => l.trim() === ".airgate/" || l.trim() === "/.airgate/" || l.trim() === ".airgate")) return true;
|
|
8192
8128
|
mkdirSync3(dirname2(file), { recursive: true });
|
|
8193
8129
|
appendFileSync(file, `${current && !current.endsWith("\n") ? "\n" : ""}# airgate up/down state
|
|
@@ -8196,8 +8132,8 @@ function excludeStateDir(projectDir) {
|
|
|
8196
8132
|
return true;
|
|
8197
8133
|
}
|
|
8198
8134
|
function podDirName(projectDir) {
|
|
8199
|
-
const
|
|
8200
|
-
return
|
|
8135
|
+
const n2 = basename(resolve4(projectDir)).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[.-]+/, "");
|
|
8136
|
+
return n2 && n2 !== "." && n2 !== ".." ? n2.slice(0, 64) : "project";
|
|
8201
8137
|
}
|
|
8202
8138
|
var up = {
|
|
8203
8139
|
name: "up",
|
|
@@ -8507,39 +8443,391 @@ ${check.stderr.trim()}`, "commit or stash local edits to those files, or look wi
|
|
|
8507
8443
|
}
|
|
8508
8444
|
};
|
|
8509
8445
|
|
|
8510
|
-
// src/
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
var
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8531
|
-
|
|
8532
|
-
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
|
|
8446
|
+
// src/io.ts
|
|
8447
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
8448
|
+
import { EventEmitter } from "node:events";
|
|
8449
|
+
import { PassThrough } from "node:stream";
|
|
8450
|
+
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
8451
|
+
var LineReader = class {
|
|
8452
|
+
buf = "";
|
|
8453
|
+
ended = false;
|
|
8454
|
+
waiters = [];
|
|
8455
|
+
stream;
|
|
8456
|
+
attached = false;
|
|
8457
|
+
decoder = new StringDecoder2("utf8");
|
|
8458
|
+
constructor(stream) {
|
|
8459
|
+
this.stream = stream;
|
|
8460
|
+
}
|
|
8461
|
+
attach() {
|
|
8462
|
+
if (this.attached) return;
|
|
8463
|
+
this.attached = true;
|
|
8464
|
+
this.stream.on("data", (d) => {
|
|
8465
|
+
this.buf += typeof d === "string" ? d : this.decoder.write(d);
|
|
8466
|
+
this.wake();
|
|
8467
|
+
});
|
|
8468
|
+
this.stream.on("end", () => {
|
|
8469
|
+
this.ended = true;
|
|
8470
|
+
this.wake();
|
|
8471
|
+
});
|
|
8472
|
+
this.stream.on("error", () => {
|
|
8473
|
+
this.ended = true;
|
|
8474
|
+
this.wake();
|
|
8475
|
+
});
|
|
8476
|
+
}
|
|
8477
|
+
wake() {
|
|
8478
|
+
for (const w of this.waiters.splice(0)) w();
|
|
8479
|
+
}
|
|
8480
|
+
async line() {
|
|
8481
|
+
this.attach();
|
|
8482
|
+
this.stream.resume();
|
|
8483
|
+
for (; ; ) {
|
|
8484
|
+
const i = this.buf.indexOf("\n");
|
|
8485
|
+
if (i !== -1) {
|
|
8486
|
+
const l = this.buf.slice(0, i).replace(/\r$/, "");
|
|
8487
|
+
this.buf = this.buf.slice(i + 1);
|
|
8488
|
+
if (!this.buf) this.stream.pause();
|
|
8489
|
+
return l;
|
|
8490
|
+
}
|
|
8491
|
+
if (this.ended) {
|
|
8492
|
+
const rest = this.buf;
|
|
8493
|
+
this.buf = "";
|
|
8494
|
+
return rest ? rest : null;
|
|
8495
|
+
}
|
|
8496
|
+
await new Promise((r) => this.waiters.push(r));
|
|
8497
|
+
}
|
|
8498
|
+
}
|
|
8499
|
+
};
|
|
8500
|
+
function ttyPrompt(stdin, out) {
|
|
8501
|
+
return (question, opts2 = {}) => new Promise((resolve6, reject) => {
|
|
8502
|
+
out.write(question);
|
|
8503
|
+
const decoder = new StringDecoder2("utf8");
|
|
8504
|
+
let answer = "";
|
|
8505
|
+
const wasRaw = Boolean(stdin.isRaw);
|
|
8506
|
+
stdin.setRawMode?.(true);
|
|
8507
|
+
stdin.resume();
|
|
8508
|
+
const done = (err) => {
|
|
8509
|
+
stdin.off("data", onData);
|
|
8510
|
+
stdin.setRawMode?.(wasRaw);
|
|
8511
|
+
stdin.pause();
|
|
8512
|
+
out.write("\n");
|
|
8513
|
+
if (err) reject(err);
|
|
8514
|
+
else resolve6(answer);
|
|
8515
|
+
};
|
|
8516
|
+
const onData = (d) => {
|
|
8517
|
+
for (const ch of decoder.write(d)) {
|
|
8518
|
+
if (ch === "\r" || ch === "\n") return done();
|
|
8519
|
+
if (ch === "") return done(Object.assign(new Error("cancelled"), { code: "cancelled" }));
|
|
8520
|
+
if (ch === "") {
|
|
8521
|
+
if (!answer) return done(Object.assign(new Error("cancelled"), { code: "cancelled" }));
|
|
8522
|
+
continue;
|
|
8523
|
+
}
|
|
8524
|
+
if (ch === "\x7F" || ch === "\b") {
|
|
8525
|
+
if (answer) {
|
|
8526
|
+
answer = [...answer].slice(0, -1).join("");
|
|
8527
|
+
if (!opts2.hidden) out.write("\b \b");
|
|
8528
|
+
}
|
|
8529
|
+
continue;
|
|
8530
|
+
}
|
|
8531
|
+
if (ch < " ") continue;
|
|
8532
|
+
answer += ch;
|
|
8533
|
+
if (!opts2.hidden) out.write(ch);
|
|
8534
|
+
}
|
|
8535
|
+
};
|
|
8536
|
+
stdin.on("data", onData);
|
|
8537
|
+
});
|
|
8538
|
+
}
|
|
8539
|
+
function openInBrowser(url) {
|
|
8540
|
+
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : ["xdg-open", [url]];
|
|
8541
|
+
return new Promise((resolve6) => {
|
|
8542
|
+
try {
|
|
8543
|
+
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
8544
|
+
child.on("error", () => resolve6(false));
|
|
8545
|
+
child.on("spawn", () => {
|
|
8546
|
+
child.unref();
|
|
8547
|
+
resolve6(true);
|
|
8548
|
+
});
|
|
8549
|
+
} catch {
|
|
8550
|
+
resolve6(false);
|
|
8551
|
+
}
|
|
8552
|
+
});
|
|
8553
|
+
}
|
|
8554
|
+
function processIo() {
|
|
8555
|
+
const stdin = process.stdin;
|
|
8556
|
+
const lines = new LineReader(stdin);
|
|
8557
|
+
const tty = ttyPrompt(stdin, process.stderr);
|
|
8558
|
+
return {
|
|
8559
|
+
stdin,
|
|
8560
|
+
stdout: process.stdout,
|
|
8561
|
+
stderr: process.stderr,
|
|
8562
|
+
env: process.env,
|
|
8563
|
+
// `npm run cli -- up` runs in packages/cli; INIT_CWD is where the user typed it
|
|
8564
|
+
cwd: process.env.AIRGATE_CWD || process.env.npm_lifecycle_event === "dev" && process.env.INIT_CWD || process.cwd(),
|
|
8565
|
+
async prompt(q, o) {
|
|
8566
|
+
if (stdin.isTTY) return tty(q, o);
|
|
8567
|
+
process.stderr.write(q);
|
|
8568
|
+
const l = await lines.line();
|
|
8569
|
+
if (l === null) throw Object.assign(new Error("no input (stdin closed)"), { code: "no_input" });
|
|
8570
|
+
process.stderr.write("\n");
|
|
8571
|
+
return l;
|
|
8572
|
+
},
|
|
8573
|
+
openUrl: openInBrowser,
|
|
8574
|
+
websocket: (url) => new WebSocket(url),
|
|
8575
|
+
signals: process
|
|
8576
|
+
};
|
|
8577
|
+
}
|
|
8578
|
+
function memoryIo(opts2 = {}) {
|
|
8579
|
+
const out = new PassThrough();
|
|
8580
|
+
const err = new PassThrough();
|
|
8581
|
+
let stdout = "";
|
|
8582
|
+
let stderr = "";
|
|
8583
|
+
out.on("data", (d) => stdout += d.toString());
|
|
8584
|
+
err.on("data", (d) => stderr += d.toString());
|
|
8585
|
+
const answers = [...opts2.answers ?? []];
|
|
8586
|
+
const opened = [];
|
|
8587
|
+
const prompts = [];
|
|
8588
|
+
const stdin = opts2.stdin ?? Object.assign(new PassThrough(), { isTTY: false });
|
|
8589
|
+
if (!opts2.stdin) stdin.end();
|
|
8590
|
+
const io = {
|
|
8591
|
+
stdin,
|
|
8592
|
+
stdout: out,
|
|
8593
|
+
stderr: err,
|
|
8594
|
+
env: opts2.env ?? {},
|
|
8595
|
+
cwd: opts2.cwd ?? process.cwd(),
|
|
8596
|
+
async prompt(q) {
|
|
8597
|
+
prompts.push(q);
|
|
8598
|
+
const a = answers.shift();
|
|
8599
|
+
if (a === void 0) throw Object.assign(new Error(`no answer queued for prompt: ${q}`), { code: "no_input" });
|
|
8600
|
+
return a;
|
|
8601
|
+
},
|
|
8602
|
+
async openUrl(u) {
|
|
8603
|
+
opened.push(u);
|
|
8604
|
+
return true;
|
|
8605
|
+
},
|
|
8606
|
+
websocket: () => {
|
|
8607
|
+
throw new Error("no websocket in memoryIo");
|
|
8608
|
+
},
|
|
8609
|
+
signals: new EventEmitter()
|
|
8610
|
+
};
|
|
8611
|
+
return {
|
|
8612
|
+
io,
|
|
8613
|
+
answers,
|
|
8614
|
+
opened,
|
|
8615
|
+
prompts,
|
|
8616
|
+
get stdout() {
|
|
8617
|
+
return stdout;
|
|
8618
|
+
},
|
|
8619
|
+
get stderr() {
|
|
8620
|
+
return stderr;
|
|
8621
|
+
},
|
|
8622
|
+
clear() {
|
|
8623
|
+
stdout = "";
|
|
8624
|
+
stderr = "";
|
|
8625
|
+
}
|
|
8626
|
+
};
|
|
8627
|
+
}
|
|
8628
|
+
|
|
8629
|
+
// src/commands/sub.ts
|
|
8630
|
+
async function runCommand(ctx, cmd, argv, cwd) {
|
|
8631
|
+
const m = memoryIo({ env: ctx.io.env, cwd });
|
|
8632
|
+
const args = parseArgs([...argv, "--json"], cmd.flags ?? {});
|
|
8633
|
+
const sub = { ...makeCtx(m.io, createUi(m.io), args, ctx.fetch), api: ctx.api };
|
|
8634
|
+
await cmd.run(sub);
|
|
8635
|
+
const out = m.stdout.trim();
|
|
8636
|
+
try {
|
|
8637
|
+
return out ? JSON.parse(out) : { ok: true };
|
|
8638
|
+
} catch {
|
|
8639
|
+
return { output: out };
|
|
8640
|
+
}
|
|
8641
|
+
}
|
|
8642
|
+
|
|
8643
|
+
// src/commands/run.ts
|
|
8644
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8645
|
+
import { basename as basename2, resolve as resolve5 } from "node:path";
|
|
8646
|
+
import { randomBytes } from "node:crypto";
|
|
8647
|
+
var SIGN_IN = {
|
|
8648
|
+
"claude-code": { names: ["CLAUDE_CODE_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], fix: "claude setup-token (on this laptop), then: airgate secrets set CLAUDE_CODE_OAUTH_TOKEN" },
|
|
8649
|
+
codex: { names: ["OPENAI_API_KEY"], fix: "airgate secrets set OPENAI_API_KEY (or sign in to codex once in a pod you keep)" }
|
|
8650
|
+
};
|
|
8651
|
+
var shortId = () => randomBytes(2).toString("hex");
|
|
8652
|
+
var runName = (base) => `${base.slice(0, 52)}-run-${shortId()}`;
|
|
8653
|
+
async function signInWarning(ctx, agent) {
|
|
8654
|
+
const need2 = SIGN_IN[agent];
|
|
8655
|
+
if (!need2) return null;
|
|
8656
|
+
const list2 = await ctx.api.secrets().catch(() => null);
|
|
8657
|
+
if (!list2) return null;
|
|
8658
|
+
if (list2.account.some((s2) => need2.names.includes(s2.name))) return null;
|
|
8659
|
+
return `no ${need2.names.join(" or ")} secret: ${AGENTS[agent].label} may not be signed in inside the pod. fix once for every pod: ${need2.fix}`;
|
|
8660
|
+
}
|
|
8661
|
+
async function finish(ctx, pod, keep) {
|
|
8662
|
+
if (keep === "destroy") await ctx.ui.spin(`deleting ${pod.spec.name}`, ctx.api.destroy(pod.id));
|
|
8663
|
+
else if (keep === "sleep") await ctx.ui.spin(`putting ${pod.spec.name} to sleep`, ctx.api.stop(pod.id)).catch(() => void 0);
|
|
8664
|
+
}
|
|
8665
|
+
var runTask = {
|
|
8666
|
+
name: "run",
|
|
8667
|
+
group: "up \xB7 down",
|
|
8668
|
+
usage: '"<task>" [dir] [--repo owner/name] [--agent a] [--env [.env]] [--keep] [--no-apply] [--pr] [--timeout min]',
|
|
8669
|
+
summary: "one task in a fresh pod: send the project, run it, bring the changes back, delete the pod",
|
|
8670
|
+
example: 'airgate run "get the failing tests passing"',
|
|
8671
|
+
notes: [
|
|
8672
|
+
"this folder (default): launches a pod, sends the project like `airgate up` (no conversation), hands the pod's agent the task, waits, applies the result here like `airgate down`, then deletes the pod.",
|
|
8673
|
+
"--no-apply prints the patch instead of applying it (the pod sleeps, so `airgate down --pod <it>` can still apply it); --keep keeps the pod (asleep).",
|
|
8674
|
+
"--repo owner/name (or a github link): runs on the repo itself instead (private repos need GitHub connected: airgate github connect). the work stays on a branch in the pod; --pr asks the agent to open a pull request, which waits in your airlock. the pod sleeps afterwards (free).",
|
|
8675
|
+
"if the task fails, the patch does not apply, or it runs past --timeout (default 60 minutes), the pod is kept so nothing is lost.",
|
|
8676
|
+
"the agent needs a sign-in that works in any pod: for Claude Code run `claude setup-token`, then `airgate secrets set CLAUDE_CODE_OAUTH_TOKEN`."
|
|
8677
|
+
],
|
|
8678
|
+
flags: {
|
|
8679
|
+
repo: { type: "string", value: "owner/name", help: "run on a GitHub repo instead of this folder" },
|
|
8680
|
+
agent: { type: "string", value: "agent", choices: Object.keys(AGENTS), help: "the pod's agent (default claude-code)" },
|
|
8681
|
+
memory: { type: "string", value: "size", choices: Object.keys(SIZES), help: "pod size (default standard)" },
|
|
8682
|
+
env: { type: "string", value: "file", implicit: ".env", help: "send a dotenv file's keys as the pod's secrets (default .env)" },
|
|
8683
|
+
keep: { type: "boolean", help: "keep the pod afterwards (asleep) instead of deleting it" },
|
|
8684
|
+
"no-apply": { type: "boolean", help: "print the patch, change nothing here (the pod is kept, asleep)" },
|
|
8685
|
+
pr: { type: "boolean", help: "with --repo: have the agent open a pull request (it waits in your airlock)" },
|
|
8686
|
+
timeout: { type: "string", value: "minutes", default: "60", help: "stop waiting after this long (the pod keeps working)" },
|
|
8687
|
+
yes: { type: "boolean", short: "y", help: "don't ask" }
|
|
8688
|
+
},
|
|
8689
|
+
async run(ctx) {
|
|
8690
|
+
const { ui: ui2 } = ctx;
|
|
8691
|
+
const [task2, dirArg, extra] = ctx.args.positionals;
|
|
8692
|
+
if (!task2?.trim()) throw new UsageError("what should the agent do?", 'airgate run "get the failing tests passing"');
|
|
8693
|
+
if (extra) throw new UsageError(`unexpected "${extra}" (put the task in quotes)`);
|
|
8694
|
+
if (task2.length > 3500) throw new UsageError("the task is 3500 characters at most");
|
|
8695
|
+
const agent = str(ctx.args, "agent") ?? "claude-code";
|
|
8696
|
+
const size2 = str(ctx.args, "memory") ?? "standard";
|
|
8697
|
+
const minutes = Number(str(ctx.args, "timeout") ?? "60");
|
|
8698
|
+
if (!Number.isFinite(minutes) || minutes <= 0) throw new UsageError("--timeout: minutes, e.g. 30");
|
|
8699
|
+
const repo = str(ctx.args, "repo");
|
|
8700
|
+
const warnings = [];
|
|
8701
|
+
const warn = await signInWarning(ctx, agent);
|
|
8702
|
+
if (warn) {
|
|
8703
|
+
warnings.push(warn);
|
|
8704
|
+
if (!ctx.json) ui2.err(ui2.ce("warn", warn));
|
|
8705
|
+
}
|
|
8706
|
+
const work = async (pod2, text3) => {
|
|
8707
|
+
let r2 = await ui2.spin(`handing ${pod2.spec.name} the task`, ctx.api.startTask(pod2.id, text3));
|
|
8708
|
+
r2 = await ui2.spin(`${pod2.spec.name} is working (${AGENTS[agent].label})`, () => waitRun(ctx, pod2.id, r2, minutes * 6e4));
|
|
8709
|
+
const log2 = await ctx.api.runLog(pod2.id, r2.id).catch(() => "");
|
|
8710
|
+
return { run: r2, log: log2, done: r2.endedAt !== void 0, ok: r2.endedAt !== void 0 && r2.exitCode === 0 };
|
|
8711
|
+
};
|
|
8712
|
+
if (repo) {
|
|
8713
|
+
if (dirArg) throw new UsageError("a folder or --repo, not both");
|
|
8714
|
+
const look = await ctx.api.inspectRepo(repo).catch((e) => {
|
|
8715
|
+
if (e instanceof ApiError && e.status === 404) return null;
|
|
8716
|
+
throw e;
|
|
8717
|
+
});
|
|
8718
|
+
if (look && !look.reachable) {
|
|
8719
|
+
const gh = await ctx.api.github().catch(() => null);
|
|
8720
|
+
if (gh && !gh.configured) throw new CliError(`${look.repo} is private, and private repos need the Airgate GitHub App, which isn't set up on this gateway yet`, 'run it from a local clone instead: cd into it, then airgate run "\u2026"');
|
|
8721
|
+
throw new CliError(`airgate can't reach ${look.repo} (private, and not shared with Airgate)`, "connect GitHub and install the app on it: airgate github connect");
|
|
8722
|
+
}
|
|
8723
|
+
const name2 = runName((look?.repo ?? repo).split("/").pop() ?? "repo");
|
|
8724
|
+
const spec = { name: name2, agent, size: size2, repo: look?.repo ?? repo, masking: true, network: "allowlist", allow: DEFAULT_ALLOW, autoStopMinutes: null, autoCheckpoint: true };
|
|
8725
|
+
const pod2 = await ui2.spin(`launching ${name2} on ${spec.repo}`, async () => waitReady(ctx, await ctx.api.launch(spec)));
|
|
8726
|
+
const envFile2 = str(ctx.args, "env");
|
|
8727
|
+
if (envFile2) await ui2.spin(`sending ${envFile2} as secrets`, sendDotenv(ctx, envFile2, pod2.id));
|
|
8728
|
+
const text3 = task2 + (bool(ctx.args, "pr") ? '\n\nWhen you are done: commit on a new branch and open a pull request with `airgate pr --title "\u2026"` (it waits for my approval).' : "\n\nWhen you are done: commit your work on a new branch here and say which branch.");
|
|
8729
|
+
const r2 = await work(pod2, text3);
|
|
8730
|
+
await finish(ctx, pod2, r2.done ? "sleep" : "running");
|
|
8731
|
+
if (ctx.json) return ui2.json({ pod: { id: pod2.id, name: name2 }, repo: spec.repo, run: r2.run, ok: r2.ok, log: r2.log, pod_state: r2.done ? "sleeping" : "running", warnings });
|
|
8732
|
+
ctx.io.stdout.write(r2.log.endsWith("\n") || !r2.log ? r2.log : r2.log + "\n");
|
|
8733
|
+
if (!r2.done) ui2.err(ui2.ce("warn", `still working after ${minutes} min; it carries on. follow it: airgate log ${name2}`));
|
|
8734
|
+
ui2.out(`${r2.ok ? ui2.c("ok", "done") : ui2.c("bad", "failed")} on ${name2}${r2.done ? " (asleep now, free)" : ""}. look: airgate attach ${name2}${bool(ctx.args, "pr") ? " \xB7 the pull request: airgate airlock" : ""}`);
|
|
8735
|
+
return r2.ok ? 0 : 1;
|
|
8736
|
+
}
|
|
8737
|
+
const projectDir = resolve5(ctx.io.cwd, dirArg ?? ".");
|
|
8738
|
+
if (!existsSync4(projectDir) || !statSync3(projectDir).isDirectory()) throw new CliError(`no directory ${projectDir}`);
|
|
8739
|
+
const noApply = bool(ctx.args, "no-apply");
|
|
8740
|
+
if (!noApply && !gitTopLevel(projectDir)) {
|
|
8741
|
+
throw new CliError(`${projectDir} isn't a git repo, so the result can't come back as a patch`, "git init && git add -A && git commit -m base, or use --no-apply");
|
|
8742
|
+
}
|
|
8743
|
+
const sp = statePath(projectDir);
|
|
8744
|
+
const before = existsSync4(sp) ? readFileSync6(sp, "utf8") : null;
|
|
8745
|
+
const restoreState = () => {
|
|
8746
|
+
try {
|
|
8747
|
+
if (before !== null) writeFileSync4(sp, before);
|
|
8748
|
+
else rmSync3(sp, { force: true });
|
|
8749
|
+
} catch {
|
|
8750
|
+
}
|
|
8751
|
+
};
|
|
8752
|
+
const name = runName(podDirName(projectDir));
|
|
8753
|
+
const upArgv = [projectDir, "--name", name, "--agent", agent, "--memory", size2, "--no-session", "--yes"];
|
|
8754
|
+
const envFile = str(ctx.args, "env");
|
|
8755
|
+
if (envFile) upArgv.push("--env", envFile);
|
|
8756
|
+
const sent = await ui2.spin(`sending ${basename2(projectDir)} to a fresh pod`, runCommand(ctx, up, upArgv, ctx.io.cwd));
|
|
8757
|
+
const pod = sent.pod;
|
|
8758
|
+
const text2 = `Work in ~/${sent.dir} (a project sent from my laptop). ${task2}
|
|
8759
|
+
|
|
8760
|
+
When you are done, leave your changes in the files there (no need to commit or push): they are brought back to my laptop as a patch.`;
|
|
8761
|
+
let r;
|
|
8762
|
+
try {
|
|
8763
|
+
r = await work(pod, text2);
|
|
8764
|
+
} catch (e) {
|
|
8765
|
+
await finish(ctx, pod, "sleep");
|
|
8766
|
+
throw e;
|
|
8767
|
+
}
|
|
8768
|
+
const keepNote = (why) => `${why}. the pod is kept (asleep): airgate attach ${name} \xB7 bring it back: airgate down --pod ${name} \xB7 delete it: airgate destroy ${name}`;
|
|
8769
|
+
const out = (o) => ui2.json({ pod: { id: pod.id, name }, run: r.run, ok: r.ok, log: r.log, warnings, ...o });
|
|
8770
|
+
if (!r.ok) {
|
|
8771
|
+
await finish(ctx, pod, r.done ? "sleep" : "running");
|
|
8772
|
+
if (ctx.json) return out({ applied: false, pod_state: r.done ? "sleeping" : "running" });
|
|
8773
|
+
ctx.io.stdout.write(r.log.endsWith("\n") || !r.log ? r.log : r.log + "\n");
|
|
8774
|
+
ui2.err(ui2.ce("bad", r.done ? keepNote(`the task failed (exit ${r.run.exitCode ?? "?"})`) : `still working after ${minutes} min; it carries on. follow it: airgate log ${name}, then: airgate down --pod ${name}`));
|
|
8775
|
+
return 1;
|
|
8776
|
+
}
|
|
8777
|
+
let result;
|
|
8778
|
+
try {
|
|
8779
|
+
result = await ui2.spin(`bringing the changes back from ${name}`, runCommand(ctx, down2, ["--pod", name, "--no-session", ...noApply ? ["--dry-run"] : ["--yes"]], projectDir));
|
|
8780
|
+
} catch (e) {
|
|
8781
|
+
await finish(ctx, pod, "sleep");
|
|
8782
|
+
const why = e instanceof Error ? e.message.split("\n")[0] : String(e);
|
|
8783
|
+
if (ctx.json) return out({ applied: false, error: why, pod_state: "sleeping" });
|
|
8784
|
+
ctx.io.stdout.write(r.log.endsWith("\n") || !r.log ? r.log : r.log + "\n");
|
|
8785
|
+
ui2.err(ui2.ce("bad", keepNote(`the task finished, but its changes didn't come back: ${why}`)));
|
|
8786
|
+
return 1;
|
|
8787
|
+
}
|
|
8788
|
+
const files = result.files ?? [];
|
|
8789
|
+
const keep = noApply || bool(ctx.args, "keep");
|
|
8790
|
+
await finish(ctx, pod, keep ? "sleep" : "destroy");
|
|
8791
|
+
if (!keep) restoreState();
|
|
8792
|
+
if (ctx.json) return out({ applied: !noApply && files.length > 0, files, ...noApply ? { patch: result.patch ?? "" } : {}, pod_state: keep ? "sleeping" : "deleted" });
|
|
8793
|
+
ctx.io.stdout.write(r.log.endsWith("\n") || !r.log ? r.log : r.log + "\n");
|
|
8794
|
+
if (noApply) {
|
|
8795
|
+
ctx.io.stdout.write(result.patch ?? "");
|
|
8796
|
+
ui2.err(`${files.length} file(s) changed, not applied. apply: airgate down --pod ${name} \xB7 delete the pod: airgate destroy ${name}`);
|
|
8797
|
+
return 0;
|
|
8798
|
+
}
|
|
8799
|
+
const risky = files.filter((f) => f.risky).length;
|
|
8800
|
+
ui2.out(
|
|
8801
|
+
`${ui2.c("ok", "done")}: ${files.length ? `${files.length} file(s) changed here${risky ? `, ${risky} risky` : ""}. review: git status \xB7 git diff` : "no changes came back"}` + (keep ? ` \xB7 the pod is kept (asleep): ${name}` : " \xB7 the pod is deleted")
|
|
8802
|
+
);
|
|
8803
|
+
return 0;
|
|
8804
|
+
}
|
|
8805
|
+
};
|
|
8806
|
+
|
|
8807
|
+
// src/commands/mcp.ts
|
|
8808
|
+
var PROTOCOL_VERSION = "2025-06-18";
|
|
8809
|
+
var obj = (properties, required = []) => ({ type: "object", properties, required, additionalProperties: false });
|
|
8810
|
+
var s = (description, extra = {}) => ({ type: "string", description, ...extra });
|
|
8811
|
+
var b = (description) => ({ type: "boolean", description });
|
|
8812
|
+
var POD = s("pod name or id (a unique prefix is enough)");
|
|
8813
|
+
var need = (args, key) => {
|
|
8814
|
+
const v = args[key];
|
|
8815
|
+
if (typeof v !== "string" || !v.trim()) throw new UsageError(`${key} is required`);
|
|
8816
|
+
return v;
|
|
8817
|
+
};
|
|
8818
|
+
var jobSchema = obj(
|
|
8819
|
+
{
|
|
8820
|
+
id: s("keep an existing job: its id"),
|
|
8821
|
+
name: s("a name to show"),
|
|
8822
|
+
cron: s('5-field cron in UTC, at most every 15 minutes, e.g. "0 2 * * *"'),
|
|
8538
8823
|
task: s("what the agent should do each time (\u2264 4000 chars)"),
|
|
8539
8824
|
enabled: b("false pauses it")
|
|
8540
8825
|
},
|
|
8541
8826
|
["cron", "task"]
|
|
8542
8827
|
);
|
|
8828
|
+
var OWN = /* @__PURE__ */ new Map();
|
|
8829
|
+
var n = (description) => ({ type: "number", description });
|
|
8830
|
+
var dirOf = (ctx, a) => typeof a.dir === "string" && a.dir ? a.dir : ctx.io.cwd;
|
|
8543
8831
|
var TOOLS = [
|
|
8544
8832
|
{
|
|
8545
8833
|
name: "pods_list",
|
|
@@ -8589,6 +8877,7 @@ var TOOLS = [
|
|
|
8589
8877
|
}
|
|
8590
8878
|
const spec = specFromFlags({ ...ctx, args: parseArgs(argv, launch.flags ?? {}) });
|
|
8591
8879
|
const pod = await waitReady(ctx, await ctx.api.launch(spec));
|
|
8880
|
+
OWN.set(pod.id, "launch");
|
|
8592
8881
|
return { name: pod.spec.name, id: pod.id, state: pod.state, readyMs: pod.readyMs, spec: pod.spec };
|
|
8593
8882
|
}
|
|
8594
8883
|
},
|
|
@@ -8709,6 +8998,119 @@ var TOOLS = [
|
|
|
8709
8998
|
if (typeof a.pod === "string" && a.pod) argv.push("--pod", a.pod);
|
|
8710
8999
|
return runCommand(ctx, down2, argv, typeof a.dir === "string" && a.dir ? a.dir : ctx.io.cwd);
|
|
8711
9000
|
}
|
|
9001
|
+
},
|
|
9002
|
+
{
|
|
9003
|
+
name: "pod_run",
|
|
9004
|
+
description: "One task in a fresh pod, start to finish: sends the local project (like pod_up, without the conversation), the pod's agent does the task, the changes come back into the local repo as a patch (pod_down's checks), and the pod is deleted. With repo: runs on a GitHub repo instead (private ones need GitHub connected); the work stays on a branch (or a PR with pr, which waits in the airlock) and the pod sleeps. On failure the pod is kept, asleep. Can take many minutes: for long work prefer pod_up or pod_launch + task_start and check back with run_status.",
|
|
9005
|
+
inputSchema: obj(
|
|
9006
|
+
{
|
|
9007
|
+
task: s("what the agent should do"),
|
|
9008
|
+
dir: s("the local project directory (default: where the server started)"),
|
|
9009
|
+
repo: s("run on this GitHub repo (owner/name) instead of the local project"),
|
|
9010
|
+
agent: s("claude-code | codex | opencode | pi (default claude-code)"),
|
|
9011
|
+
apply: b("false: return the patch without applying it (the pod is kept, asleep)"),
|
|
9012
|
+
keep: b("keep the pod afterwards (asleep) instead of deleting it"),
|
|
9013
|
+
pr: b("with repo: open a pull request at the end (it waits in the airlock)"),
|
|
9014
|
+
env: s("a dotenv file whose keys become the pod's secrets, e.g. .env"),
|
|
9015
|
+
timeoutMinutes: n("stop waiting after this many minutes (default 60); the pod keeps working")
|
|
9016
|
+
},
|
|
9017
|
+
["task"]
|
|
9018
|
+
),
|
|
9019
|
+
async call(ctx, a) {
|
|
9020
|
+
const argv = [need(a, "task")];
|
|
9021
|
+
const opt = (flag, v) => typeof v === "string" && v && argv.push(`--${flag}`, v);
|
|
9022
|
+
opt("repo", a.repo);
|
|
9023
|
+
opt("agent", a.agent);
|
|
9024
|
+
opt("env", a.env);
|
|
9025
|
+
if (a.apply === false) argv.push("--no-apply");
|
|
9026
|
+
if (a.keep === true) argv.push("--keep");
|
|
9027
|
+
if (a.pr === true) argv.push("--pr");
|
|
9028
|
+
if (typeof a.timeoutMinutes === "number") argv.push("--timeout", String(a.timeoutMinutes));
|
|
9029
|
+
const out = await runCommand(ctx, runTask, argv, dirOf(ctx, a));
|
|
9030
|
+
if (out.pod?.id && out.pod_state !== "deleted") OWN.set(out.pod.id, a.repo ? "launch" : "project");
|
|
9031
|
+
return out;
|
|
9032
|
+
}
|
|
9033
|
+
},
|
|
9034
|
+
{
|
|
9035
|
+
name: "pod_up",
|
|
9036
|
+
description: "Move the local project into a pod: its files (git-tracked and untracked-but-not-ignored), and by default the latest Claude Code / Codex conversation here, which resumes in the pod. Launches a new pod named after the folder unless pod is given. Bring the work back later with pod_diff / pod_down.",
|
|
9037
|
+
inputSchema: obj({
|
|
9038
|
+
dir: s("the local project directory (default: where the server started)"),
|
|
9039
|
+
pod: s("send into this existing pod instead of launching one"),
|
|
9040
|
+
name: s("name for a new pod"),
|
|
9041
|
+
agent: s("agent for a new pod: claude-code | codex | opencode | pi"),
|
|
9042
|
+
env: s("a dotenv file whose keys become the pod's secrets, e.g. .env"),
|
|
9043
|
+
conversation: b("false: don't send the agent conversation")
|
|
9044
|
+
}),
|
|
9045
|
+
async call(ctx, a) {
|
|
9046
|
+
const argv = ["--yes"];
|
|
9047
|
+
const opt = (flag, v) => typeof v === "string" && v && argv.push(`--${flag}`, v);
|
|
9048
|
+
opt("pod", a.pod);
|
|
9049
|
+
opt("name", a.name);
|
|
9050
|
+
opt("agent", a.agent);
|
|
9051
|
+
opt("env", a.env);
|
|
9052
|
+
if (a.conversation === false) argv.push("--no-session");
|
|
9053
|
+
const out = await runCommand(ctx, up, argv, dirOf(ctx, a));
|
|
9054
|
+
if (!a.pod && out.pod?.id) OWN.set(out.pod.id, "project");
|
|
9055
|
+
return out;
|
|
9056
|
+
}
|
|
9057
|
+
},
|
|
9058
|
+
{
|
|
9059
|
+
name: "pod_sleep",
|
|
9060
|
+
description: "Put a pod to sleep: it stops, keeps its files and checkpoints, and costs nothing until woken. Do this when you are done with a pod you launched.",
|
|
9061
|
+
inputSchema: obj({ pod: POD }, ["pod"]),
|
|
9062
|
+
async call(ctx, a) {
|
|
9063
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
9064
|
+
return ctx.api.stop(p.id);
|
|
9065
|
+
}
|
|
9066
|
+
},
|
|
9067
|
+
{
|
|
9068
|
+
name: "pod_wake",
|
|
9069
|
+
description: "Wake a sleeping pod (seconds).",
|
|
9070
|
+
inputSchema: obj({ pod: POD }, ["pod"]),
|
|
9071
|
+
async call(ctx, a) {
|
|
9072
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
9073
|
+
return ctx.api.resume(p.id);
|
|
9074
|
+
}
|
|
9075
|
+
},
|
|
9076
|
+
{
|
|
9077
|
+
name: "pod_destroy",
|
|
9078
|
+
description: "Delete a pod for good (its files and checkpoints). Allowed on your own for pods you launched in this session (pod_launch, pod_up, pod_run) once their work is safe: a pod holding a local project refuses while it has changes not brought back (pod_down first, or discard_changes); for a pod_launch pod, make sure its work is pushed or not needed. Any other pod: only when the user explicitly asked to delete that pod; then set user_confirmed.",
|
|
9079
|
+
inputSchema: obj({ pod: POD, user_confirmed: b("the user explicitly asked to delete this pod"), discard_changes: b("delete even though it has changes not brought back") }, ["pod"]),
|
|
9080
|
+
async call(ctx, a) {
|
|
9081
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
9082
|
+
if (!OWN.has(p.id) && a.user_confirmed !== true) {
|
|
9083
|
+
throw new CliError(`${p.spec.name} wasn't launched by you in this session: ask the user before deleting it, then call again with user_confirmed`);
|
|
9084
|
+
}
|
|
9085
|
+
if (a.discard_changes !== true && (OWN.get(p.id) ?? "project") === "project") {
|
|
9086
|
+
const pending = await ctx.api.diff(p.id, "up").catch(() => null);
|
|
9087
|
+
if (pending && pending.files.length) throw new CliError(`${p.spec.name} has ${pending.files.length} changed file(s) not brought back: pod_down first, or set discard_changes if the user doesn't want them`);
|
|
9088
|
+
}
|
|
9089
|
+
await ctx.api.destroy(p.id);
|
|
9090
|
+
OWN.delete(p.id);
|
|
9091
|
+
return { deleted: p.spec.name, id: p.id };
|
|
9092
|
+
}
|
|
9093
|
+
},
|
|
9094
|
+
{
|
|
9095
|
+
name: "pod_ports",
|
|
9096
|
+
description: "What is listening in a pod (dev servers on ports 1024+) and its preview links.",
|
|
9097
|
+
inputSchema: obj({ pod: POD }, ["pod"]),
|
|
9098
|
+
async call(ctx, a) {
|
|
9099
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
9100
|
+
return ctx.api.previews(p.id);
|
|
9101
|
+
}
|
|
9102
|
+
},
|
|
9103
|
+
{
|
|
9104
|
+
name: "pod_preview",
|
|
9105
|
+
description: "A link to a port in a pod, for the user to open: private (signed in as them) by default; public makes a link anyone can open (tell the user it is public).",
|
|
9106
|
+
inputSchema: obj({ pod: POD, port: n("the port, 1024+"), public: b("a public link anyone can open") }, ["pod", "port"]),
|
|
9107
|
+
async call(ctx, a) {
|
|
9108
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
9109
|
+
const port = Number(a.port);
|
|
9110
|
+
if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new UsageError("port: 1024\u201365535");
|
|
9111
|
+
const pv = await ctx.api.ensurePreview(p.id, { port, public: a.public === true });
|
|
9112
|
+
return pv.public ? { url: pv.url, public: true } : { url: await ctx.api.openPreview(pv.slug, "/"), public: false, note: "a private link: it signs the user in; give it to them, not to anyone else" };
|
|
9113
|
+
}
|
|
8712
9114
|
}
|
|
8713
9115
|
];
|
|
8714
9116
|
function errorText(e) {
|
|
@@ -8732,7 +9134,7 @@ async function handle(ctx, msg) {
|
|
|
8732
9134
|
protocolVersion: asked,
|
|
8733
9135
|
capabilities: { tools: { listChanged: false } },
|
|
8734
9136
|
serverInfo: { name: "airgate", title: "Airgate", version: VERSION },
|
|
8735
|
-
instructions: "Airgate pods are private cloud computers for coding agents.
|
|
9137
|
+
instructions: "Airgate pods are private cloud computers for coding agents. pod_run does one task in a fresh pod and brings the result back; pod_up / pod_down move the local project and conversation there and back; task_start hands an existing pod a task (run_status / run_log follow it). Sleep pods you are done with; delete only pods you launched (or the user asked). Outbound actions wait in the airlock for the user: never approve on your own initiative."
|
|
8736
9138
|
});
|
|
8737
9139
|
}
|
|
8738
9140
|
case "ping":
|
|
@@ -8795,6 +9197,9 @@ var mcp = {
|
|
|
8795
9197
|
};
|
|
8796
9198
|
|
|
8797
9199
|
// src/commands/misc.ts
|
|
9200
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync5 } from "node:fs";
|
|
9201
|
+
import { homedir as homedir3 } from "node:os";
|
|
9202
|
+
import { join as join5 } from "node:path";
|
|
8798
9203
|
var PAGES = ["pods", "airlock", "usage", "settings", "mask", "chat", "notes", "data", "status"];
|
|
8799
9204
|
var open = {
|
|
8800
9205
|
name: "open",
|
|
@@ -8853,6 +9258,106 @@ var version = {
|
|
|
8853
9258
|
ctx.ui.out(`airgate ${VERSION}`);
|
|
8854
9259
|
}
|
|
8855
9260
|
};
|
|
9261
|
+
var skill = {
|
|
9262
|
+
name: "skill",
|
|
9263
|
+
group: "other",
|
|
9264
|
+
usage: "[install [--project] | show]",
|
|
9265
|
+
summary: "teach Claude Code airgate: install the airgate skill (~/.claude/skills, or this project with --project)",
|
|
9266
|
+
example: "airgate skill install",
|
|
9267
|
+
public: true,
|
|
9268
|
+
flags: { project: { type: "boolean", help: "install into ./.claude/skills (shared with the project) instead of ~/.claude/skills" } },
|
|
9269
|
+
async run(ctx) {
|
|
9270
|
+
const action = ctx.args.positionals[0] ?? "show";
|
|
9271
|
+
const url = `${ctx.cfg.app}/skills/airgate/SKILL.md`;
|
|
9272
|
+
const res = await ctx.ui.spin("fetching the airgate skill", ctx.fetch(url));
|
|
9273
|
+
if (!res.ok) throw new CliError(`couldn't fetch ${url} (${res.status})`);
|
|
9274
|
+
const text2 = await res.text();
|
|
9275
|
+
if (!text2.startsWith("---")) throw new CliError(`${url} isn't a skill file`);
|
|
9276
|
+
if (action === "show") return void ctx.io.stdout.write(text2);
|
|
9277
|
+
if (action !== "install") throw new UsageError(`unknown action "${action}"`, "airgate skill install [--project] | airgate skill show");
|
|
9278
|
+
const base = bool(ctx.args, "project") ? join5(ctx.io.cwd, ".claude") : join5(ctx.io.env.HOME ?? homedir3(), ".claude");
|
|
9279
|
+
const dir = join5(base, "skills", "airgate");
|
|
9280
|
+
mkdirSync4(dir, { recursive: true });
|
|
9281
|
+
writeFileSync5(join5(dir, "SKILL.md"), text2);
|
|
9282
|
+
if (ctx.json) return ctx.ui.json({ installed: join5(dir, "SKILL.md") });
|
|
9283
|
+
ctx.ui.out(`${ctx.ui.c("ok", "installed")} the airgate skill: ${join5(dir, "SKILL.md")}`);
|
|
9284
|
+
ctx.ui.out(ctx.ui.c("dim", 'claude code picks it up in its next session. ask it to "move this to an airgate pod".'));
|
|
9285
|
+
}
|
|
9286
|
+
};
|
|
9287
|
+
|
|
9288
|
+
// src/commands/previews.ts
|
|
9289
|
+
var portArg = (v) => {
|
|
9290
|
+
const n2 = Number(v);
|
|
9291
|
+
if (!v || !Number.isInteger(n2) || n2 < 1 || n2 > 65535) throw new UsageError("a port, like 3000");
|
|
9292
|
+
return n2;
|
|
9293
|
+
};
|
|
9294
|
+
async function podAndPort(ctx) {
|
|
9295
|
+
const [ref, port] = ctx.args.positionals;
|
|
9296
|
+
if (!ref) throw new UsageError("which pod?");
|
|
9297
|
+
return { pod: await resolvePod(ctx, ref), port: portArg(port) };
|
|
9298
|
+
}
|
|
9299
|
+
var ports = {
|
|
9300
|
+
name: "ports",
|
|
9301
|
+
aliases: ["previews"],
|
|
9302
|
+
group: "pods",
|
|
9303
|
+
usage: "<pod>",
|
|
9304
|
+
summary: "what's listening in a pod, and its preview links",
|
|
9305
|
+
example: "airgate ports web",
|
|
9306
|
+
async run(ctx) {
|
|
9307
|
+
const ref = ctx.args.positionals[0];
|
|
9308
|
+
if (!ref) throw new UsageError("which pod? airgate ports <pod>");
|
|
9309
|
+
const pod = await resolvePod(ctx, ref);
|
|
9310
|
+
const r = await ctx.api.previews(pod.id);
|
|
9311
|
+
if (ctx.json) return ctx.ui.json(r);
|
|
9312
|
+
if (!r.enabled) return void ctx.ui.out("previews are not set up on this gateway.");
|
|
9313
|
+
const byPort = new Map(r.previews.map((p) => [p.port, p]));
|
|
9314
|
+
const all = [.../* @__PURE__ */ new Set([...r.ports, ...r.previews.map((p) => p.port)])].sort((a, b2) => a - b2);
|
|
9315
|
+
if (!all.length) return void ctx.ui.out(`nothing listening in ${pod.spec.name} yet. start a dev server (any port 1024+), then: airgate preview ${pod.spec.name} <port>`);
|
|
9316
|
+
ctx.ui.table(
|
|
9317
|
+
all.map((port) => {
|
|
9318
|
+
const p = byPort.get(port);
|
|
9319
|
+
return [`:${port}`, r.ports.includes(port) ? ctx.ui.c("ok", "listening") : ctx.ui.c("dim", "not listening"), p ? p.public ? ctx.ui.c("warn", "public") : "private" : ctx.ui.c("dim", "-"), p?.public ? p.url : ""];
|
|
9320
|
+
}),
|
|
9321
|
+
{ head: ["port", "", "preview", "link"] }
|
|
9322
|
+
);
|
|
9323
|
+
ctx.ui.out(ctx.ui.c("dim", `open one: airgate preview ${pod.spec.name} <port> \xB7 share it: airgate share ${pod.spec.name} <port>`));
|
|
9324
|
+
}
|
|
9325
|
+
};
|
|
9326
|
+
var preview = {
|
|
9327
|
+
name: "preview",
|
|
9328
|
+
group: "pods",
|
|
9329
|
+
usage: "<pod> <port> [/path] [--no-open]",
|
|
9330
|
+
summary: "open a port of a pod in your browser (private to you)",
|
|
9331
|
+
example: "airgate preview web 3000",
|
|
9332
|
+
flags: { "no-open": { type: "boolean", help: "print the link instead (it signs you in, once, within a minute)" } },
|
|
9333
|
+
async run(ctx) {
|
|
9334
|
+
const { pod, port } = await podAndPort(ctx);
|
|
9335
|
+
const path = ctx.args.positionals[2] ?? "/";
|
|
9336
|
+
const p = await ctx.api.ensurePreview(pod.id, { port });
|
|
9337
|
+
const url = await ctx.api.openPreview(p.slug, path.startsWith("/") ? path : `/${path}`);
|
|
9338
|
+
if (ctx.json) return ctx.ui.json({ preview: p, open: url });
|
|
9339
|
+
await openOrPrint(ctx, url, `${pod.spec.name}:${port}`);
|
|
9340
|
+
}
|
|
9341
|
+
};
|
|
9342
|
+
var share = {
|
|
9343
|
+
name: "share",
|
|
9344
|
+
group: "pods",
|
|
9345
|
+
usage: "<pod> <port> [--off]",
|
|
9346
|
+
summary: "a public link to a port of a pod (anyone with it can open it); --off makes it private again",
|
|
9347
|
+
example: "airgate share web 3000",
|
|
9348
|
+
flags: { off: { type: "boolean", help: "stop sharing: private to you again" } },
|
|
9349
|
+
async run(ctx) {
|
|
9350
|
+
const { pod, port } = await podAndPort(ctx);
|
|
9351
|
+
const off = bool(ctx.args, "off");
|
|
9352
|
+
const p = await ctx.api.ensurePreview(pod.id, { port, public: !off });
|
|
9353
|
+
if (ctx.json) return ctx.ui.json(p);
|
|
9354
|
+
if (off) ctx.ui.out(`${pod.spec.name}:${port} is private again: only you can open it (airgate preview ${pod.spec.name} ${port}).`);
|
|
9355
|
+
else {
|
|
9356
|
+
ctx.ui.out(p.url);
|
|
9357
|
+
ctx.ui.err(ctx.ui.ce("warn", `public: anyone with this link reaches ${pod.spec.name}:${port}. stop with: airgate share ${pod.spec.name} ${port} --off`));
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
};
|
|
8856
9361
|
|
|
8857
9362
|
// src/commands/undo.ts
|
|
8858
9363
|
var newestFirst = (cps) => [...cps].sort((a, b2) => b2.at - a.at);
|
|
@@ -9033,15 +9538,15 @@ var Canvas = class {
|
|
|
9033
9538
|
/** write text at (x, y), clipped to maxWidth and the canvas; returns columns written */
|
|
9034
9539
|
text(x, y, s2, st = {}, maxWidth = Infinity) {
|
|
9035
9540
|
if (y < 0 || y >= this.rows) return 0;
|
|
9036
|
-
let
|
|
9541
|
+
let n2 = 0;
|
|
9037
9542
|
for (const ch of s2.replace(/[\u0000-\u001f\u007f]/g, " ")) {
|
|
9038
|
-
if (
|
|
9039
|
-
const cx = x +
|
|
9543
|
+
if (n2 >= maxWidth) break;
|
|
9544
|
+
const cx = x + n2;
|
|
9040
9545
|
if (cx >= this.cols) break;
|
|
9041
9546
|
if (cx >= 0) this.cells[y][cx] = { ch, st };
|
|
9042
|
-
|
|
9547
|
+
n2++;
|
|
9043
9548
|
}
|
|
9044
|
-
return
|
|
9549
|
+
return n2;
|
|
9045
9550
|
}
|
|
9046
9551
|
fill(x, y, w, h, st = {}) {
|
|
9047
9552
|
for (let r = y; r < y + h; r++) for (let c = x; c < x + w; c++) if (r >= 0 && r < this.rows && c >= 0 && c < this.cols) this.cells[r][c] = { ch: " ", st };
|
|
@@ -9246,7 +9751,7 @@ function formKey(s2, form, key) {
|
|
|
9246
9751
|
}
|
|
9247
9752
|
return { state: { ...s2, modal: { kind: "form", form: next } }, effects: [] };
|
|
9248
9753
|
};
|
|
9249
|
-
const cycle2 = (
|
|
9754
|
+
const cycle2 = (n2, len, d2) => (n2 + d2 + len) % len;
|
|
9250
9755
|
switch (key) {
|
|
9251
9756
|
case "tab":
|
|
9252
9757
|
case "down":
|
|
@@ -9338,10 +9843,10 @@ function modalKey(s2, m, key) {
|
|
|
9338
9843
|
return none;
|
|
9339
9844
|
}
|
|
9340
9845
|
case "timeline": {
|
|
9341
|
-
const
|
|
9846
|
+
const n2 = m.checkpoints?.length ?? 0;
|
|
9342
9847
|
if (key === "esc" || key === "q" || key === "u") return close;
|
|
9343
|
-
if ((key === "down" || key === "j") &&
|
|
9344
|
-
if ((key === "up" || key === "k") &&
|
|
9848
|
+
if ((key === "down" || key === "j") && n2) return { state: { ...s2, modal: { ...m, sel: Math.min(n2 - 1, m.sel + 1) } }, effects: [] };
|
|
9849
|
+
if ((key === "up" || key === "k") && n2) return { state: { ...s2, modal: { ...m, sel: Math.max(0, m.sel - 1) } }, effects: [] };
|
|
9345
9850
|
const cp = m.checkpoints?.[m.sel];
|
|
9346
9851
|
if (key === "enter" && cp) {
|
|
9347
9852
|
return {
|
|
@@ -9649,16 +10154,16 @@ function draw(s2, cols, rows2) {
|
|
|
9649
10154
|
}
|
|
9650
10155
|
function header(c, s2) {
|
|
9651
10156
|
let x = c.text(0, 0, " \u22A0 airgate", { bold: true }) + 2;
|
|
9652
|
-
const
|
|
10157
|
+
const n2 = pendingApprovals(s2).length;
|
|
9653
10158
|
const tabs = [
|
|
9654
10159
|
["pods", "pods"],
|
|
9655
|
-
["airlock",
|
|
10160
|
+
["airlock", n2 ? `airlock [${n2}]` : "airlock"],
|
|
9656
10161
|
["usage", "usage"]
|
|
9657
10162
|
];
|
|
9658
10163
|
for (const [id, label2] of tabs) {
|
|
9659
10164
|
if (x + label2.length + 2 > c.cols) break;
|
|
9660
10165
|
const active = s2.screen === id;
|
|
9661
|
-
x += c.text(x, 0, ` ${label2} `, { inverse: active, bold: active, fg: id === "airlock" &&
|
|
10166
|
+
x += c.text(x, 0, ` ${label2} `, { inverse: active, bold: active, fg: id === "airlock" && n2 && !active ? "warn" : void 0 });
|
|
9662
10167
|
x += 1;
|
|
9663
10168
|
}
|
|
9664
10169
|
const right = s2.connected ? "\u25CF live" : s2.loaded ? "\u25CB offline" : "\u25CB connecting";
|
|
@@ -9704,9 +10209,9 @@ function splash(c, s2) {
|
|
|
9704
10209
|
}
|
|
9705
10210
|
function statusLine(c, s2) {
|
|
9706
10211
|
const y = c.rows - 1;
|
|
9707
|
-
const
|
|
10212
|
+
const n2 = pendingApprovals(s2).length;
|
|
9708
10213
|
const parts = [];
|
|
9709
|
-
if (
|
|
10214
|
+
if (n2) parts.push(`airlock [${n2}]`);
|
|
9710
10215
|
if (s2.me) parts.push(s2.me.plan === "none" ? "no plan" : s2.me.plan, `${hrs(s2.me.usedHours)} this month`);
|
|
9711
10216
|
const right = parts.join(" \xB7 ");
|
|
9712
10217
|
let left;
|
|
@@ -9725,8 +10230,8 @@ function statusLine(c, s2) {
|
|
|
9725
10230
|
c.text(0, y, " " + clip(left, Math.max(0, c.cols - rw - 2)), { fg: leftFg });
|
|
9726
10231
|
if (right && c.cols - rw > 10) {
|
|
9727
10232
|
const x = c.cols - rw;
|
|
9728
|
-
if (
|
|
9729
|
-
const badge = `airlock [${
|
|
10233
|
+
if (n2) {
|
|
10234
|
+
const badge = `airlock [${n2}]`;
|
|
9730
10235
|
c.text(x, y, badge, { fg: "warn", bold: true });
|
|
9731
10236
|
c.text(x + badge.length, y, right.slice(badge.length), { fg: "dim" });
|
|
9732
10237
|
} else c.text(x, y, right, { fg: "dim" });
|
|
@@ -10029,11 +10534,11 @@ function modal(c, s2, m) {
|
|
|
10029
10534
|
return;
|
|
10030
10535
|
}
|
|
10031
10536
|
case "timeline": {
|
|
10032
|
-
const
|
|
10033
|
-
const b2 = centered(c, 64, Math.max(6, Math.min(
|
|
10537
|
+
const n2 = m.checkpoints?.length ?? 0;
|
|
10538
|
+
const b2 = centered(c, 64, Math.max(6, Math.min(n2, 14) + 4));
|
|
10034
10539
|
c.box(b2.x, b2.y, b2.w, b2.h, { double: true, title: `undo \xB7 ${m.name}`, clear: true });
|
|
10035
10540
|
if (!m.checkpoints) c.text(b2.x + 2, b2.y + 1, "loading\u2026", { fg: "dim" });
|
|
10036
|
-
else if (!
|
|
10541
|
+
else if (!n2) c.text(b2.x + 2, b2.y + 1, "no checkpoints yet. c takes one.", { fg: "dim" }, b2.w - 4);
|
|
10037
10542
|
const rowsAvail = b2.h - 3;
|
|
10038
10543
|
const start = Math.max(0, m.sel - rowsAvail + 1);
|
|
10039
10544
|
(m.checkpoints ?? []).slice(start, start + rowsAvail).forEach((cp, i) => {
|
|
@@ -10236,7 +10741,7 @@ function startTui(d) {
|
|
|
10236
10741
|
scheduleDraw();
|
|
10237
10742
|
}
|
|
10238
10743
|
};
|
|
10239
|
-
const onSignal = () =>
|
|
10744
|
+
const onSignal = () => finish2(130);
|
|
10240
10745
|
const onCrash = (e) => crash(e);
|
|
10241
10746
|
function listenKeys(on) {
|
|
10242
10747
|
if (on) {
|
|
@@ -10256,7 +10761,7 @@ function startTui(d) {
|
|
|
10256
10761
|
d.stdin.pause();
|
|
10257
10762
|
d.stdout.write(LEAVE_ALT);
|
|
10258
10763
|
}
|
|
10259
|
-
function
|
|
10764
|
+
function finish2(code3, message) {
|
|
10260
10765
|
if (finished) return;
|
|
10261
10766
|
finished = true;
|
|
10262
10767
|
controller.abort();
|
|
@@ -10271,7 +10776,7 @@ function startTui(d) {
|
|
|
10271
10776
|
resolveDone(code3);
|
|
10272
10777
|
}
|
|
10273
10778
|
function crash(e) {
|
|
10274
|
-
|
|
10779
|
+
finish2(1, `airgate ui crashed: ${e instanceof Error ? e.message : String(e)}`);
|
|
10275
10780
|
}
|
|
10276
10781
|
const errText = (e) => {
|
|
10277
10782
|
if (e instanceof ApiError) {
|
|
@@ -10300,7 +10805,7 @@ function startTui(d) {
|
|
|
10300
10805
|
try {
|
|
10301
10806
|
switch (e.type) {
|
|
10302
10807
|
case "quit":
|
|
10303
|
-
return
|
|
10808
|
+
return finish2(0);
|
|
10304
10809
|
case "attach":
|
|
10305
10810
|
return await handoff(e.podId, e.name);
|
|
10306
10811
|
case "loadUsage": {
|
|
@@ -10538,6 +11043,9 @@ var COMMANDS = [
|
|
|
10538
11043
|
task,
|
|
10539
11044
|
runs,
|
|
10540
11045
|
log,
|
|
11046
|
+
ports,
|
|
11047
|
+
preview,
|
|
11048
|
+
share,
|
|
10541
11049
|
checkpoints,
|
|
10542
11050
|
checkpoint,
|
|
10543
11051
|
rewind,
|
|
@@ -10548,6 +11056,7 @@ var COMMANDS = [
|
|
|
10548
11056
|
mask,
|
|
10549
11057
|
up,
|
|
10550
11058
|
down2,
|
|
11059
|
+
runTask,
|
|
10551
11060
|
usage,
|
|
10552
11061
|
plan,
|
|
10553
11062
|
upgrade,
|
|
@@ -10556,6 +11065,7 @@ var COMMANDS = [
|
|
|
10556
11065
|
ui,
|
|
10557
11066
|
mcp,
|
|
10558
11067
|
open,
|
|
11068
|
+
skill,
|
|
10559
11069
|
status,
|
|
10560
11070
|
help,
|
|
10561
11071
|
version
|
|
@@ -10598,7 +11108,7 @@ function printCommandHelp(ui2, c) {
|
|
|
10598
11108
|
ui2.out(`usage: airgate ${c.name}${c.usage ? " " + c.usage : ""}`);
|
|
10599
11109
|
ui2.out();
|
|
10600
11110
|
ui2.out(c.summary + ".");
|
|
10601
|
-
for (const
|
|
11111
|
+
for (const n2 of c.notes ?? []) ui2.out(n2);
|
|
10602
11112
|
const flags = flagLines({ ...c.flags ?? {}, json: GLOBAL_FLAGS.json });
|
|
10603
11113
|
ui2.out();
|
|
10604
11114
|
for (const l of formatTable(flags, void 0, 2)) ui2.out(l);
|
|
@@ -10733,7 +11243,7 @@ async function run(argv, io, opts2 = {}) {
|
|
|
10733
11243
|
}
|
|
10734
11244
|
}
|
|
10735
11245
|
function suggest2(name) {
|
|
10736
|
-
const near = COMMANDS.filter((c) => [c.name, ...c.aliases ?? []].some((
|
|
11246
|
+
const near = COMMANDS.filter((c) => [c.name, ...c.aliases ?? []].some((n2) => n2.startsWith(name.slice(0, 2)))).map((c) => c.name);
|
|
10737
11247
|
return near.length ? `did you mean: ${near.slice(0, 3).join(", ")}? (airgate help)` : "see: airgate help";
|
|
10738
11248
|
}
|
|
10739
11249
|
|