airgate 0.1.8 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/airgate.mjs +500 -208
- package/package.json +1 -1
package/dist/airgate.mjs
CHANGED
|
@@ -1,4 +1,133 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/pack.ts
|
|
13
|
+
var pack_exports = {};
|
|
14
|
+
__export(pack_exports, {
|
|
15
|
+
CLAUDE_CONFIG: () => CLAUDE_CONFIG,
|
|
16
|
+
TooLargeError: () => TooLargeError,
|
|
17
|
+
UPLOAD_LIMIT: () => UPLOAD_LIMIT,
|
|
18
|
+
git: () => git,
|
|
19
|
+
gitFiles: () => gitFiles,
|
|
20
|
+
gitTopLevel: () => gitTopLevel,
|
|
21
|
+
isEnvFile: () => isEnvFile,
|
|
22
|
+
isGitRepo: () => isGitRepo,
|
|
23
|
+
packClaudeConfig: () => packClaudeConfig,
|
|
24
|
+
packProject: () => packProject,
|
|
25
|
+
runTar: () => runTar
|
|
26
|
+
});
|
|
27
|
+
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
28
|
+
import { existsSync, lstatSync, readdirSync } from "node:fs";
|
|
29
|
+
import { join as join2 } from "node:path";
|
|
30
|
+
function isEnvFile(path) {
|
|
31
|
+
const name = path.split("/").pop() ?? "";
|
|
32
|
+
return /^\.env($|\.)/.test(name) && !/\.(example|sample|template|dist|defaults?|schema)$/.test(name);
|
|
33
|
+
}
|
|
34
|
+
function git(cwd, args, input) {
|
|
35
|
+
const r = spawnSync2("git", args, { cwd, input, maxBuffer: 256 * 1024 * 1024 });
|
|
36
|
+
return { status: r.status ?? (r.error ? 127 : 1), stdout: r.stdout?.toString("utf8") ?? "", stderr: r.stderr?.toString("utf8") ?? "", error: r.error };
|
|
37
|
+
}
|
|
38
|
+
function isGitRepo(dir) {
|
|
39
|
+
return git(dir, ["rev-parse", "--is-inside-work-tree"]).stdout.trim() === "true";
|
|
40
|
+
}
|
|
41
|
+
function gitTopLevel(dir) {
|
|
42
|
+
const r = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
43
|
+
return r.status === 0 ? r.stdout.trim() : null;
|
|
44
|
+
}
|
|
45
|
+
function gitFiles(dir) {
|
|
46
|
+
const r = git(dir, ["ls-files", "-co", "--exclude-standard", "-z"]);
|
|
47
|
+
if (r.status !== 0) throw new Error(`git ls-files: ${r.stderr.trim()}`);
|
|
48
|
+
const seen = /* @__PURE__ */ new Set();
|
|
49
|
+
const out = [];
|
|
50
|
+
for (const f of r.stdout.split("\0")) {
|
|
51
|
+
if (!f || seen.has(f)) continue;
|
|
52
|
+
seen.add(f);
|
|
53
|
+
try {
|
|
54
|
+
lstatSync(join2(dir, f));
|
|
55
|
+
out.push(f);
|
|
56
|
+
} catch {
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
function runTar(cwd, args, input, limit = UPLOAD_LIMIT) {
|
|
62
|
+
return new Promise((resolve10, reject) => {
|
|
63
|
+
const child = spawn("tar", args, { cwd, env: { ...process.env, COPYFILE_DISABLE: "1" }, stdio: ["pipe", "pipe", "pipe"] });
|
|
64
|
+
const chunks = [];
|
|
65
|
+
let size2 = 0;
|
|
66
|
+
let err = "";
|
|
67
|
+
let over2 = false;
|
|
68
|
+
child.stdout.on("data", (d) => {
|
|
69
|
+
size2 += d.length;
|
|
70
|
+
if (size2 > limit) {
|
|
71
|
+
over2 = true;
|
|
72
|
+
child.kill();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
chunks.push(d);
|
|
76
|
+
});
|
|
77
|
+
child.stderr.on("data", (d) => err += d.toString());
|
|
78
|
+
child.on("error", (e) => reject(new Error(`tar: ${e.message}`)));
|
|
79
|
+
child.on("close", (code3) => {
|
|
80
|
+
if (over2) return reject(new TooLargeError(limit));
|
|
81
|
+
if (code3 !== 0) return reject(new Error(`tar failed: ${err.trim().split("\n").slice(-3).join(" ")}`));
|
|
82
|
+
resolve10(Buffer.concat(chunks));
|
|
83
|
+
});
|
|
84
|
+
child.stdin.on("error", () => void 0);
|
|
85
|
+
child.stdin.end(input ?? "");
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async function packProject(dir, limit = UPLOAD_LIMIT) {
|
|
89
|
+
if (isGitRepo(dir)) {
|
|
90
|
+
const tracked = new Set(git(dir, ["ls-files", "-c", "-z"]).stdout.split("\0"));
|
|
91
|
+
const all = gitFiles(dir).filter((f) => !f.startsWith(".airgate/"));
|
|
92
|
+
const skipped2 = all.filter((f) => (isEnvFile(f) || f.split("/").pop() === ".envrc") && !tracked.has(f));
|
|
93
|
+
const files2 = all.filter((f) => !skipped2.includes(f));
|
|
94
|
+
if (!files2.length) throw new Error("no files to send (git sees nothing tracked or untracked here)");
|
|
95
|
+
const archive2 = await runTar(dir, ["--null", "-T", "-", "-czf", "-"], files2.join("\0") + "\0", limit);
|
|
96
|
+
return { archive: archive2, files: files2.length, git: true, skipped: skipped2 };
|
|
97
|
+
}
|
|
98
|
+
const skipped = readdirSync(dir).filter((f) => isEnvFile(f) || f === ".envrc");
|
|
99
|
+
const archive = await runTar(dir, ["--exclude=node_modules", "--exclude=.git", "--exclude=.airgate", ...ENV_EXCLUDES, "-czf", "-", "."], null, limit);
|
|
100
|
+
return { archive, files: -1, git: false, skipped };
|
|
101
|
+
}
|
|
102
|
+
async function packClaudeConfig(home, limit = UPLOAD_LIMIT) {
|
|
103
|
+
const root = join2(home, ".claude");
|
|
104
|
+
if (!existsSync(root)) return null;
|
|
105
|
+
const parts = CLAUDE_CONFIG.filter((p) => existsSync(join2(root, p)));
|
|
106
|
+
if (!parts.length) return null;
|
|
107
|
+
const archive = await runTar(
|
|
108
|
+
root,
|
|
109
|
+
["--exclude=*credentials*", "--exclude=*.credentials*", "--exclude=.env*", "--exclude=node_modules", "-czf", "-", ...parts],
|
|
110
|
+
null,
|
|
111
|
+
limit
|
|
112
|
+
);
|
|
113
|
+
return { archive, parts };
|
|
114
|
+
}
|
|
115
|
+
var UPLOAD_LIMIT, TooLargeError, ENV_EXCLUDES, CLAUDE_CONFIG;
|
|
116
|
+
var init_pack = __esm({
|
|
117
|
+
"src/pack.ts"() {
|
|
118
|
+
"use strict";
|
|
119
|
+
UPLOAD_LIMIT = 32 * 6 * 1024 * 1024;
|
|
120
|
+
TooLargeError = class extends Error {
|
|
121
|
+
limit;
|
|
122
|
+
constructor(limit) {
|
|
123
|
+
super(`over ${Math.round(limit / 1024 / 1024)} MiB packed`);
|
|
124
|
+
this.limit = limit;
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
ENV_EXCLUDES = [".env", ".env.local", ".env.*.local", ".env.development", ".env.production", ".env.test", ".env.staging", ".envrc"].map((p) => `--exclude=${p}`);
|
|
128
|
+
CLAUDE_CONFIG = ["settings.json", "CLAUDE.md", "skills", "agents", "commands"];
|
|
129
|
+
}
|
|
130
|
+
});
|
|
2
131
|
|
|
3
132
|
// ../sandbox/src/types.ts
|
|
4
133
|
var SIZES = {
|
|
@@ -268,9 +397,9 @@ function parseDotenv(input) {
|
|
|
268
397
|
const unparsed = [];
|
|
269
398
|
for (let i = 0; i < lines.length; i++) {
|
|
270
399
|
const lineNo = i + 1;
|
|
271
|
-
const
|
|
272
|
-
if (!
|
|
273
|
-
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(
|
|
400
|
+
const line3 = lines[i].trim();
|
|
401
|
+
if (!line3 || line3.startsWith("#")) continue;
|
|
402
|
+
const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line3);
|
|
274
403
|
if (!m) {
|
|
275
404
|
unparsed.push(lineNo);
|
|
276
405
|
continue;
|
|
@@ -387,6 +516,14 @@ function signChatgpt(t, headers) {
|
|
|
387
516
|
headers.originator ??= "codex_cli_rs";
|
|
388
517
|
}
|
|
389
518
|
|
|
519
|
+
// ../sandbox/src/servers.ts
|
|
520
|
+
var SERVER_SIZES = {
|
|
521
|
+
tiny: { id: "tiny", name: "tiny", cpuKind: "shared", cpus: 1, memoryMb: 512, diskGb: 1, monthlyUsd: 6, costUsd: 3.84, blurb: "a bot, a webhook, a cron" },
|
|
522
|
+
small: { id: "small", name: "small", cpuKind: "shared", cpus: 1, memoryMb: 1024, diskGb: 3, monthlyUsd: 10, costUsd: 7.15, blurb: "a small api or an assistant" },
|
|
523
|
+
medium: { id: "medium", name: "medium", cpuKind: "shared", cpus: 2, memoryMb: 2048, diskGb: 10, monthlyUsd: 20, costUsd: 14.89, blurb: "a busier service with a database" }
|
|
524
|
+
};
|
|
525
|
+
var SERVER_SIZE_IDS = Object.keys(SERVER_SIZES);
|
|
526
|
+
|
|
390
527
|
// src/api.ts
|
|
391
528
|
var UPLOAD_PART = 6 * 1024 * 1024;
|
|
392
529
|
var ApiError = class extends Error {
|
|
@@ -794,6 +931,43 @@ var Api = class {
|
|
|
794
931
|
removeTrigger(id) {
|
|
795
932
|
return this.request("DELETE", `/v1/github/triggers/${enc(id)}`);
|
|
796
933
|
}
|
|
934
|
+
/* always-on servers (docs/SERVERS.md) */
|
|
935
|
+
servers() {
|
|
936
|
+
return this.request("GET", "/v1/servers").then((r) => r.servers);
|
|
937
|
+
}
|
|
938
|
+
serverSizes() {
|
|
939
|
+
return this.request("GET", "/v1/servers/sizes");
|
|
940
|
+
}
|
|
941
|
+
createServer(name, size2) {
|
|
942
|
+
return this.request("POST", "/v1/servers", { body: { name, size: size2 } }).then((r) => r.server);
|
|
943
|
+
}
|
|
944
|
+
server(ref) {
|
|
945
|
+
return this.request("GET", `/v1/servers/${enc(ref)}`).then((r) => r.server);
|
|
946
|
+
}
|
|
947
|
+
serverCheckout(ref) {
|
|
948
|
+
return this.request("POST", `/v1/servers/${enc(ref)}/checkout`, { body: {} });
|
|
949
|
+
}
|
|
950
|
+
deployServer(ref, archive, cmd) {
|
|
951
|
+
return this.request("POST", `/v1/servers/${enc(ref)}/deploy${cmd ? `?cmd=${encodeURIComponent(cmd)}` : ""}`, { raw: archive, contentType: "application/gzip" }).then((r) => r.server);
|
|
952
|
+
}
|
|
953
|
+
deployServerFromPod(ref, podId, dir, command) {
|
|
954
|
+
return this.request("POST", `/v1/servers/${enc(ref)}/deploy`, { body: { podId, dir, ...command ? { command } : {} } }).then((r) => r.server);
|
|
955
|
+
}
|
|
956
|
+
restartServer(ref) {
|
|
957
|
+
return this.request("POST", `/v1/servers/${enc(ref)}/restart`, { body: {} });
|
|
958
|
+
}
|
|
959
|
+
serverEnv(ref, body) {
|
|
960
|
+
return this.request("PUT", `/v1/servers/${enc(ref)}/env`, { body }).then((r) => r.server);
|
|
961
|
+
}
|
|
962
|
+
podAlwaysOn(podId, body) {
|
|
963
|
+
return this.request("POST", `/v1/pods/${enc(podId)}/always-on`, { body }).then((r) => r.server);
|
|
964
|
+
}
|
|
965
|
+
serverLogs(ref) {
|
|
966
|
+
return this.request("GET", `/v1/servers/${enc(ref)}/logs`).then((r) => r.lines);
|
|
967
|
+
}
|
|
968
|
+
deleteServer(ref) {
|
|
969
|
+
return this.request("DELETE", `/v1/servers/${enc(ref)}`);
|
|
970
|
+
}
|
|
797
971
|
sendVerification() {
|
|
798
972
|
return this.request("POST", "/v1/auth/verify/send", { body: {} });
|
|
799
973
|
}
|
|
@@ -838,10 +1012,10 @@ async function readSse(body, onEvent) {
|
|
|
838
1012
|
buf = buf.slice(i + 2);
|
|
839
1013
|
let event = "message";
|
|
840
1014
|
const data = [];
|
|
841
|
-
for (const
|
|
842
|
-
if (
|
|
843
|
-
if (
|
|
844
|
-
else if (
|
|
1015
|
+
for (const line3 of block.split("\n")) {
|
|
1016
|
+
if (line3.startsWith(":")) continue;
|
|
1017
|
+
if (line3.startsWith("event:")) event = line3.slice(6).trim();
|
|
1018
|
+
else if (line3.startsWith("data:")) data.push(line3.slice(5).replace(/^ /, ""));
|
|
845
1019
|
}
|
|
846
1020
|
if (data.length) onEvent(event, data.join("\n"));
|
|
847
1021
|
}
|
|
@@ -1399,9 +1573,9 @@ function banner(text2, options = {}) {
|
|
|
1399
1573
|
const ink = new Uint8Array(cols * rows2);
|
|
1400
1574
|
let x = x0;
|
|
1401
1575
|
for (const g of glyphs) {
|
|
1402
|
-
g.forEach((
|
|
1403
|
-
for (let i = 0; i <
|
|
1404
|
-
if (
|
|
1576
|
+
g.forEach((line3, y) => {
|
|
1577
|
+
for (let i = 0; i < line3.length; i++)
|
|
1578
|
+
if (line3[i] === "#") {
|
|
1405
1579
|
for (let k = 0; k < s2; k++)
|
|
1406
1580
|
if (x + i * s2 + k < cols && y0 + y < rows2)
|
|
1407
1581
|
ink[(y0 + y) * cols + x + i * s2 + k] = 1;
|
|
@@ -1484,26 +1658,26 @@ function banner(text2, options = {}) {
|
|
|
1484
1658
|
const [core, edge] = [...shine[g]];
|
|
1485
1659
|
const lines = [];
|
|
1486
1660
|
for (let r = 0; r < rows2; r++) {
|
|
1487
|
-
let
|
|
1661
|
+
let line3 = "";
|
|
1488
1662
|
for (let c = 0; c < cols; c++) {
|
|
1489
1663
|
const k = r * cols + c;
|
|
1490
1664
|
const cell = grid2[k];
|
|
1491
1665
|
if (c >= upTo) {
|
|
1492
|
-
|
|
1666
|
+
line3 += " ";
|
|
1493
1667
|
continue;
|
|
1494
1668
|
}
|
|
1495
1669
|
if (cell !== null) {
|
|
1496
|
-
|
|
1670
|
+
line3 += cell;
|
|
1497
1671
|
if (color && palette && cell !== " ")
|
|
1498
1672
|
color[k] = (paper ? 0 : half) + 0;
|
|
1499
1673
|
continue;
|
|
1500
1674
|
}
|
|
1501
1675
|
const d = Math.abs(c + slant * r - p);
|
|
1502
|
-
|
|
1676
|
+
line3 += d < width / 2 ? core : d < width ? edge ?? core : fill[g];
|
|
1503
1677
|
if (color && palette)
|
|
1504
1678
|
color[k] = (paper ? 0 : half) + 1 + bucket(c);
|
|
1505
1679
|
}
|
|
1506
|
-
lines.push(
|
|
1680
|
+
lines.push(line3);
|
|
1507
1681
|
}
|
|
1508
1682
|
return lines.join("\n");
|
|
1509
1683
|
};
|
|
@@ -1723,8 +1897,8 @@ var isPiece = (v) => v !== null && typeof v === "object" && "meta" in v && typeo
|
|
|
1723
1897
|
var isClip = (v) => v !== null && typeof v === "object" && !(v instanceof Surface) && "src" in v && !("meta" in v);
|
|
1724
1898
|
var unwrap = (v) => v !== null && typeof v === "object" && !("meta" in v) && !("src" in v) && isPiece(v.default) ? v.default : v;
|
|
1725
1899
|
var textName = (text2) => {
|
|
1726
|
-
const
|
|
1727
|
-
return
|
|
1900
|
+
const line3 = text2.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
1901
|
+
return line3 ? `"${line3.slice(0, 24)}"` : "text";
|
|
1728
1902
|
};
|
|
1729
1903
|
function prepare(fn, v, keys = CLIP) {
|
|
1730
1904
|
v = unwrap(v);
|
|
@@ -2295,10 +2469,10 @@ function freeze(src, at3) {
|
|
|
2295
2469
|
}
|
|
2296
2470
|
function named(src, name, o = {}) {
|
|
2297
2471
|
const opts2 = object(o, "named()", ["note", "category"]);
|
|
2298
|
-
const
|
|
2299
|
-
if (!
|
|
2472
|
+
const line3 = (v) => typeof v === "string" && !!v.trim() && !/[\u0000-\u001f\u007f-\u009f]/.test(v);
|
|
2473
|
+
if (!line3(name))
|
|
2300
2474
|
fail2(`named() takes a name, one line such as "hello", not ${shown(name)}`);
|
|
2301
|
-
if (opts2.note !== void 0 && !
|
|
2475
|
+
if (opts2.note !== void 0 && !line3(opts2.note))
|
|
2302
2476
|
fail2(`named()'s note takes one line saying what you see, not ${shown(opts2.note)}`);
|
|
2303
2477
|
const p = prepare("named", src);
|
|
2304
2478
|
const asItWas = p.speed === 1 && !p.offset && !p.tint && !Object.keys(p.options).length;
|
|
@@ -2869,13 +3043,13 @@ function scan(src, options) {
|
|
|
2869
3043
|
const pos = lineAt(t);
|
|
2870
3044
|
if (pos === null)
|
|
2871
3045
|
return copy(s2, g);
|
|
2872
|
-
const
|
|
3046
|
+
const line3 = forward ? pos : along - 1 - pos;
|
|
2873
3047
|
for (let y = 0, i = 0; y < g.rows; y++)
|
|
2874
3048
|
for (let x = 0; x < g.cols; x++, i++) {
|
|
2875
3049
|
const q = across2 ? y : x;
|
|
2876
|
-
if (q ===
|
|
3050
|
+
if (q === line3)
|
|
2877
3051
|
s2.put(i, char, ink >= 0 ? ink : g.chars[i] === EMPTY ? NONE : g.colors[i]);
|
|
2878
|
-
else if (!reveal || (forward ? q <
|
|
3052
|
+
else if (!reveal || (forward ? q < line3 : q > line3)) {
|
|
2879
3053
|
s2.chars[i] = g.chars[i];
|
|
2880
3054
|
s2.colors[i] = g.colors[i];
|
|
2881
3055
|
}
|
|
@@ -3089,7 +3263,7 @@ function outline(src, options) {
|
|
|
3089
3263
|
setup: () => {
|
|
3090
3264
|
const solid = new Uint8Array(cols * rows2), out = new Uint8Array(cols * rows2), queue = new Int32Array(cols * rows2);
|
|
3091
3265
|
const is = (a, x, y) => x >= 0 && x < cols && y >= 0 && y < rows2 && a[y * cols + x] === 1;
|
|
3092
|
-
const
|
|
3266
|
+
const line3 = (x, y) => is(out, x, y) && (is(solid, x - 1, y - 1) || is(solid, x, y - 1) || is(solid, x + 1, y - 1) || is(solid, x - 1, y) || is(solid, x + 1, y) || is(solid, x - 1, y + 1) || is(solid, x, y + 1) || is(solid, x + 1, y + 1));
|
|
3093
3267
|
const over2 = { mask: null };
|
|
3094
3268
|
return (t, s2, g) => {
|
|
3095
3269
|
solid.fill(0);
|
|
@@ -3109,12 +3283,12 @@ function outline(src, options) {
|
|
|
3109
3283
|
flood(solid, out, queue, cols, rows2);
|
|
3110
3284
|
for (let y = 0; y < rows2; y++)
|
|
3111
3285
|
for (let x = 0; x < cols; x++) {
|
|
3112
|
-
if (!
|
|
3286
|
+
if (!line3(x, y))
|
|
3113
3287
|
continue;
|
|
3114
|
-
const up2 =
|
|
3115
|
-
const down4 =
|
|
3116
|
-
const left =
|
|
3117
|
-
const right =
|
|
3288
|
+
const up2 = line3(x, y - 1) && (is(solid, x - 1, y) || is(solid, x - 1, y - 1) || is(solid, x + 1, y) || is(solid, x + 1, y - 1));
|
|
3289
|
+
const down4 = line3(x, y + 1) && (is(solid, x - 1, y) || is(solid, x - 1, y + 1) || is(solid, x + 1, y) || is(solid, x + 1, y + 1));
|
|
3290
|
+
const left = line3(x - 1, y) && (is(solid, x, y - 1) || is(solid, x - 1, y - 1) || is(solid, x, y + 1) || is(solid, x - 1, y + 1));
|
|
3291
|
+
const right = line3(x + 1, y) && (is(solid, x, y - 1) || is(solid, x + 1, y - 1) || is(solid, x, y + 1) || is(solid, x + 1, y + 1));
|
|
3118
3292
|
const bits = (up2 ? 1 : 0) | (down4 ? 2 : 0) | (left ? 4 : 0) | (right ? 8 : 0);
|
|
3119
3293
|
if (!bits)
|
|
3120
3294
|
continue;
|
|
@@ -3756,12 +3930,12 @@ var Surface = class _Surface {
|
|
|
3756
3930
|
for (let i = 0; i < n2; i++)
|
|
3757
3931
|
color[i] = colors[i] === NONE ? ink : colors[i];
|
|
3758
3932
|
}
|
|
3759
|
-
const
|
|
3933
|
+
const line3 = this.#line;
|
|
3760
3934
|
let out = "";
|
|
3761
3935
|
for (let r = 0; r < rows2; r++) {
|
|
3762
3936
|
for (let c = 0, i = r * cols; c < cols; c++, i++)
|
|
3763
|
-
|
|
3764
|
-
out += (r ? "\n" : "") + String.fromCharCode.apply(null,
|
|
3937
|
+
line3[c] = chars3[i] || 32;
|
|
3938
|
+
out += (r ? "\n" : "") + String.fromCharCode.apply(null, line3);
|
|
3765
3939
|
}
|
|
3766
3940
|
return out;
|
|
3767
3941
|
}
|
|
@@ -4656,12 +4830,12 @@ function words(str2, what) {
|
|
|
4656
4830
|
}
|
|
4657
4831
|
function writeLines(s2, lines, lefts, top, k, transparent) {
|
|
4658
4832
|
let left = Infinity, right = -Infinity;
|
|
4659
|
-
lines.forEach((
|
|
4660
|
-
const lx = lefts[r], len =
|
|
4833
|
+
lines.forEach((line3, r) => {
|
|
4834
|
+
const lx = lefts[r], len = line3.length;
|
|
4661
4835
|
if (len)
|
|
4662
4836
|
left = Math.min(left, lx), right = Math.max(right, lx + len);
|
|
4663
4837
|
for (let i = 0; i < len; i++) {
|
|
4664
|
-
const c =
|
|
4838
|
+
const c = line3.charCodeAt(i);
|
|
4665
4839
|
if (transparent && c === 32)
|
|
4666
4840
|
continue;
|
|
4667
4841
|
const j = s2.index(lx + i, top + r);
|
|
@@ -4678,25 +4852,25 @@ function layout(str2, width, wrap2) {
|
|
|
4678
4852
|
out.push(para.slice(0, width));
|
|
4679
4853
|
continue;
|
|
4680
4854
|
}
|
|
4681
|
-
let
|
|
4855
|
+
let line3 = "";
|
|
4682
4856
|
for (let word of para.split(" ").filter(Boolean)) {
|
|
4683
4857
|
while (word.length > width) {
|
|
4684
|
-
if (
|
|
4685
|
-
out.push(
|
|
4686
|
-
|
|
4858
|
+
if (line3)
|
|
4859
|
+
out.push(line3);
|
|
4860
|
+
line3 = "";
|
|
4687
4861
|
out.push(word.slice(0, width));
|
|
4688
4862
|
word = word.slice(width);
|
|
4689
4863
|
}
|
|
4690
|
-
if (!
|
|
4691
|
-
|
|
4692
|
-
else if (
|
|
4693
|
-
|
|
4864
|
+
if (!line3)
|
|
4865
|
+
line3 = word;
|
|
4866
|
+
else if (line3.length + 1 + word.length <= width)
|
|
4867
|
+
line3 += " " + word;
|
|
4694
4868
|
else {
|
|
4695
|
-
out.push(
|
|
4696
|
-
|
|
4869
|
+
out.push(line3);
|
|
4870
|
+
line3 = word;
|
|
4697
4871
|
}
|
|
4698
4872
|
}
|
|
4699
|
-
out.push(
|
|
4873
|
+
out.push(line3);
|
|
4700
4874
|
}
|
|
4701
4875
|
return out;
|
|
4702
4876
|
}
|
|
@@ -5406,7 +5580,7 @@ function cat() {
|
|
|
5406
5580
|
const put = (r, c, s2) => [...s2].forEach((ch, i) => ch !== " " && c + i < cols && (g[r][c + i] = ch));
|
|
5407
5581
|
const p = (u + 0.4) % BREATH / BREATH;
|
|
5408
5582
|
const rise = p < 0.35 ? Math.sin(Math.PI / 2 * (p / 0.35)) ** 2 : p < 0.8 ? Math.cos(Math.PI / 2 * ((p - 0.35) / 0.45)) ** 2 : 0;
|
|
5409
|
-
BACK[rise < 0.3 ? 0 : rise < 0.75 ? 1 : 2].forEach((
|
|
5583
|
+
BACK[rise < 0.3 ? 0 : rise < 0.75 ? 1 : 2].forEach((line3, i) => put(3 + i, 0, line3));
|
|
5410
5584
|
const fl = FLICK.find(([a, b2]) => u >= a && u < b2);
|
|
5411
5585
|
const up2 = fl ? Math.sin(Math.PI * (u - fl[0]) / (fl[1] - fl[0])) : 0;
|
|
5412
5586
|
TIP[up2 > 0.7 ? 2 : up2 > 0.25 ? 1 : 0].forEach(([r, c, s2]) => put(r, c, s2));
|
|
@@ -5514,9 +5688,9 @@ function spinners2({ names = meta2.options.names, labels = meta2.options.labels,
|
|
|
5514
5688
|
const x0 = n2 % per * slot2, y0 = 1 + Math.floor(n2 / per) * (block + 1);
|
|
5515
5689
|
const art = draw2(Math.floor(t * meta2.fps * speed2 / frames + 1e-3));
|
|
5516
5690
|
const top = y0 + Math.floor((3 - art.length) / 2);
|
|
5517
|
-
art.forEach((
|
|
5518
|
-
const left = x0 + Math.floor((slot2 - [...
|
|
5519
|
-
[...
|
|
5691
|
+
art.forEach((line3, j) => {
|
|
5692
|
+
const left = x0 + Math.floor((slot2 - [...line3].length) / 2);
|
|
5693
|
+
[...line3].forEach((g, i) => grid2[top + j][left + i] = g);
|
|
5520
5694
|
});
|
|
5521
5695
|
if (labels)
|
|
5522
5696
|
[...name].forEach((g, i) => grid2[y0 + 4][x0 + Math.floor((slot2 - name.length) / 2) + i] = g);
|
|
@@ -5555,8 +5729,8 @@ function inkBox(frame, seconds, step) {
|
|
|
5555
5729
|
let bottom = -1;
|
|
5556
5730
|
let right = -1;
|
|
5557
5731
|
for (let t = 0; t < seconds; t += step) {
|
|
5558
|
-
frame(t).split("\n").forEach((
|
|
5559
|
-
[...
|
|
5732
|
+
frame(t).split("\n").forEach((line3, y) => {
|
|
5733
|
+
[...line3].forEach((ch, x) => {
|
|
5560
5734
|
if (ch === " ") return;
|
|
5561
5735
|
top = Math.min(top, y);
|
|
5562
5736
|
bottom = Math.max(bottom, y);
|
|
@@ -5908,9 +6082,9 @@ function formatTable(rows2, head, indent = 0) {
|
|
|
5908
6082
|
);
|
|
5909
6083
|
}
|
|
5910
6084
|
function paintArt(on, a) {
|
|
5911
|
-
return a.lines.map((
|
|
6085
|
+
return a.lines.map((line3, y) => {
|
|
5912
6086
|
const tones2 = a.tones?.[y];
|
|
5913
|
-
if (!on || !tones2) return
|
|
6087
|
+
if (!on || !tones2) return line3.trimEnd();
|
|
5914
6088
|
let out = "";
|
|
5915
6089
|
let run2 = "";
|
|
5916
6090
|
let tone2;
|
|
@@ -5918,7 +6092,7 @@ function paintArt(on, a) {
|
|
|
5918
6092
|
if (run2) out += tone2 ? paint2(true, tone2, run2) : run2;
|
|
5919
6093
|
run2 = "";
|
|
5920
6094
|
};
|
|
5921
|
-
[...
|
|
6095
|
+
[...line3].forEach((ch, x) => {
|
|
5922
6096
|
const t = ch === " " ? tone2 : tones2[x];
|
|
5923
6097
|
if (t !== tone2) {
|
|
5924
6098
|
flush();
|
|
@@ -5935,8 +6109,8 @@ function createUi(io) {
|
|
|
5935
6109
|
const errColor = colorOn(io.stderr, io.env);
|
|
5936
6110
|
const ui2 = {
|
|
5937
6111
|
tty: Boolean(io.stdout.isTTY),
|
|
5938
|
-
out: (
|
|
5939
|
-
err: (
|
|
6112
|
+
out: (line3 = "") => void io.stdout.write(line3 + "\n"),
|
|
6113
|
+
err: (line3 = "") => void io.stderr.write(line3 + "\n"),
|
|
5940
6114
|
c: (tone2, s2) => paint2(outColor, tone2, s2),
|
|
5941
6115
|
ce: (tone2, s2) => paint2(errColor, tone2, s2),
|
|
5942
6116
|
json: (v) => void io.stdout.write(JSON.stringify(v, null, 2) + "\n"),
|
|
@@ -6024,9 +6198,9 @@ function billingOff(e) {
|
|
|
6024
6198
|
function hoursMeter(ui2, used, cap2) {
|
|
6025
6199
|
if (!ui2.tty) return null;
|
|
6026
6200
|
const scale2 = cap2 && cap2 > 0 ? cap2 : PLANS.pro.includedHours;
|
|
6027
|
-
const [
|
|
6201
|
+
const [line3] = ui2.art(bar(used / scale2, 24));
|
|
6028
6202
|
const of = cap2 && cap2 > 0 ? `of ${cap2}h` : `of ${scale2}h (what pro includes)`;
|
|
6029
|
-
return `${
|
|
6203
|
+
return `${line3} ${hours(used)} ${ui2.c("dim", of)}`;
|
|
6030
6204
|
}
|
|
6031
6205
|
var usage = {
|
|
6032
6206
|
name: "usage",
|
|
@@ -7385,7 +7559,7 @@ import { rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
|
7385
7559
|
import { resolve as resolve4 } from "node:path";
|
|
7386
7560
|
|
|
7387
7561
|
// src/version.ts
|
|
7388
|
-
var VERSION = "0.
|
|
7562
|
+
var VERSION = "0.2.0";
|
|
7389
7563
|
|
|
7390
7564
|
// src/commands/team.ts
|
|
7391
7565
|
async function checkSimilar(ctx, task2) {
|
|
@@ -7961,7 +8135,7 @@ var launch = {
|
|
|
7961
8135
|
network: { type: "string", value: "policy", choices: NETWORK_NAMES, default: "allowlist", help: "allowlist | open | off" },
|
|
7962
8136
|
allow: { type: "list", value: "host", help: "extra hosts for the allowlist (repeatable, or comma-separated)" },
|
|
7963
8137
|
"mask-term": { type: "list", value: "word", help: "always mask this word: a client, a codename (repeatable)" },
|
|
7964
|
-
"auto-stop": { type: "string", value: "minutes", help:
|
|
8138
|
+
"auto-stop": { type: "string", value: "minutes", help: 'sleep after this many idle minutes (default 10). for 24/7: airgate always-on <pod> --cmd "..."' }
|
|
7965
8139
|
},
|
|
7966
8140
|
async run(ctx) {
|
|
7967
8141
|
if (ctx.args.positionals.length) throw new UsageError(`unexpected "${ctx.args.positionals[0]}"`, "name the pod with --name");
|
|
@@ -8026,10 +8200,11 @@ function specFromFlags(ctx, defaults = {}) {
|
|
|
8026
8200
|
const network2 = str(a, "network") ?? "allowlist";
|
|
8027
8201
|
const autoStop = str(a, "auto-stop");
|
|
8028
8202
|
let autoStopMinutes = null;
|
|
8029
|
-
if (autoStop === "keep-awake" || autoStop === "never"
|
|
8030
|
-
|
|
8203
|
+
if (autoStop === "keep-awake" || autoStop === "never" || autoStop === "0") {
|
|
8204
|
+
throw new UsageError("keep awake is gone: a pod sleeps when idle", 'for 24/7, make it always on once it is up: airgate always-on <pod> --cmd "node bot.js" (from $6 a month per server, on any plan, never counts against your hours)');
|
|
8205
|
+
} else if (autoStop !== void 0) {
|
|
8031
8206
|
autoStopMinutes = Number(autoStop);
|
|
8032
|
-
if (!Number.isInteger(autoStopMinutes) || autoStopMinutes < 1) throw new UsageError("--auto-stop: whole idle minutes \u2265 1
|
|
8207
|
+
if (!Number.isInteger(autoStopMinutes) || autoStopMinutes < 1) throw new UsageError("--auto-stop: whole idle minutes \u2265 1");
|
|
8033
8208
|
}
|
|
8034
8209
|
const repo = repoArg(ctx, str(a, "repo"));
|
|
8035
8210
|
const template = str(a, "template") ?? "code";
|
|
@@ -8098,7 +8273,7 @@ var info = {
|
|
|
8098
8273
|
["network", s2.network + (s2.network === "allowlist" && s2.allow?.length ? ` \xB7 ${s2.allow.join(", ")}` : "")],
|
|
8099
8274
|
["mask", s2.masking ? `on${s2.maskTerms?.length ? ` \xB7 ${s2.maskTerms.length} terms` : ""}` : ctx.ui.c("warn", "off")],
|
|
8100
8275
|
["undo", s2.autoCheckpoint ? "auto checkpoints" : "manual only"],
|
|
8101
|
-
["auto-stop", s2.autoStopMinutes === 0 ? "keep awake (
|
|
8276
|
+
["auto-stop", s2.autoStopMinutes === 0 ? "keep awake (never sleeps: every hour counts, ~720 a month)" : `sleeps after ${s2.autoStopMinutes ?? 10} min idle`]
|
|
8102
8277
|
];
|
|
8103
8278
|
if (s2.repo) rows2.push(["repo", s2.repo]);
|
|
8104
8279
|
for (const [i, j] of (s2.jobs ?? []).entries()) {
|
|
@@ -8206,7 +8381,7 @@ var templates = {
|
|
|
8206
8381
|
const a = info2 ? info2.templates[id] : id === "code" ? { available: true } : { available: false, reason: "not on this gateway" };
|
|
8207
8382
|
const avail = a?.available ? ui2.c("ok", "yes") : ui2.c("dim", a?.reason ?? "no");
|
|
8208
8383
|
const rate = t.proOnly ? `pro ${price(PLANS.pro.hourlyUsd)}` : `${price(PLANS.payg.hourlyUsd)} \xB7 pro ${price(PLANS.pro.hourlyUsd)}`;
|
|
8209
|
-
return [id, t.name, t.tagline + (t.alwaysOn ? ui2.c("dim", " \xB7 always
|
|
8384
|
+
return [id, t.name, t.tagline + (t.alwaysOn ? ui2.c("dim", " \xB7 always reachable, asleep is free") : ""), avail, rate];
|
|
8210
8385
|
});
|
|
8211
8386
|
ui2.table(rows2, { head: ["template", "name", "", "available", "per active hour"] });
|
|
8212
8387
|
ui2.out();
|
|
@@ -8393,111 +8568,11 @@ import { createInterface } from "node:readline";
|
|
|
8393
8568
|
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8394
8569
|
import { homedir as homedir2 } from "node:os";
|
|
8395
8570
|
import { basename, dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve5 } from "node:path";
|
|
8396
|
-
|
|
8397
|
-
// src/pack.ts
|
|
8398
|
-
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
8399
|
-
import { existsSync, lstatSync, readdirSync } from "node:fs";
|
|
8400
|
-
import { join as join2 } from "node:path";
|
|
8401
|
-
var UPLOAD_LIMIT = 32 * 6 * 1024 * 1024;
|
|
8402
|
-
var TooLargeError = class extends Error {
|
|
8403
|
-
limit;
|
|
8404
|
-
constructor(limit) {
|
|
8405
|
-
super(`over ${Math.round(limit / 1024 / 1024)} MiB packed`);
|
|
8406
|
-
this.limit = limit;
|
|
8407
|
-
}
|
|
8408
|
-
};
|
|
8409
|
-
function isEnvFile(path) {
|
|
8410
|
-
const name = path.split("/").pop() ?? "";
|
|
8411
|
-
return /^\.env($|\.)/.test(name) && !/\.(example|sample|template|dist|defaults?|schema)$/.test(name);
|
|
8412
|
-
}
|
|
8413
|
-
var ENV_EXCLUDES = [".env", ".env.local", ".env.*.local", ".env.development", ".env.production", ".env.test", ".env.staging", ".envrc"].map((p) => `--exclude=${p}`);
|
|
8414
|
-
function git(cwd, args, input) {
|
|
8415
|
-
const r = spawnSync2("git", args, { cwd, input, maxBuffer: 256 * 1024 * 1024 });
|
|
8416
|
-
return { status: r.status ?? (r.error ? 127 : 1), stdout: r.stdout?.toString("utf8") ?? "", stderr: r.stderr?.toString("utf8") ?? "", error: r.error };
|
|
8417
|
-
}
|
|
8418
|
-
function isGitRepo(dir) {
|
|
8419
|
-
return git(dir, ["rev-parse", "--is-inside-work-tree"]).stdout.trim() === "true";
|
|
8420
|
-
}
|
|
8421
|
-
function gitTopLevel(dir) {
|
|
8422
|
-
const r = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
8423
|
-
return r.status === 0 ? r.stdout.trim() : null;
|
|
8424
|
-
}
|
|
8425
|
-
function gitFiles(dir) {
|
|
8426
|
-
const r = git(dir, ["ls-files", "-co", "--exclude-standard", "-z"]);
|
|
8427
|
-
if (r.status !== 0) throw new Error(`git ls-files: ${r.stderr.trim()}`);
|
|
8428
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8429
|
-
const out = [];
|
|
8430
|
-
for (const f of r.stdout.split("\0")) {
|
|
8431
|
-
if (!f || seen.has(f)) continue;
|
|
8432
|
-
seen.add(f);
|
|
8433
|
-
try {
|
|
8434
|
-
lstatSync(join2(dir, f));
|
|
8435
|
-
out.push(f);
|
|
8436
|
-
} catch {
|
|
8437
|
-
}
|
|
8438
|
-
}
|
|
8439
|
-
return out;
|
|
8440
|
-
}
|
|
8441
|
-
function runTar(cwd, args, input, limit = UPLOAD_LIMIT) {
|
|
8442
|
-
return new Promise((resolve9, reject) => {
|
|
8443
|
-
const child = spawn("tar", args, { cwd, env: { ...process.env, COPYFILE_DISABLE: "1" }, stdio: ["pipe", "pipe", "pipe"] });
|
|
8444
|
-
const chunks = [];
|
|
8445
|
-
let size2 = 0;
|
|
8446
|
-
let err = "";
|
|
8447
|
-
let over2 = false;
|
|
8448
|
-
child.stdout.on("data", (d) => {
|
|
8449
|
-
size2 += d.length;
|
|
8450
|
-
if (size2 > limit) {
|
|
8451
|
-
over2 = true;
|
|
8452
|
-
child.kill();
|
|
8453
|
-
return;
|
|
8454
|
-
}
|
|
8455
|
-
chunks.push(d);
|
|
8456
|
-
});
|
|
8457
|
-
child.stderr.on("data", (d) => err += d.toString());
|
|
8458
|
-
child.on("error", (e) => reject(new Error(`tar: ${e.message}`)));
|
|
8459
|
-
child.on("close", (code3) => {
|
|
8460
|
-
if (over2) return reject(new TooLargeError(limit));
|
|
8461
|
-
if (code3 !== 0) return reject(new Error(`tar failed: ${err.trim().split("\n").slice(-3).join(" ")}`));
|
|
8462
|
-
resolve9(Buffer.concat(chunks));
|
|
8463
|
-
});
|
|
8464
|
-
child.stdin.on("error", () => void 0);
|
|
8465
|
-
child.stdin.end(input ?? "");
|
|
8466
|
-
});
|
|
8467
|
-
}
|
|
8468
|
-
async function packProject(dir, limit = UPLOAD_LIMIT) {
|
|
8469
|
-
if (isGitRepo(dir)) {
|
|
8470
|
-
const tracked = new Set(git(dir, ["ls-files", "-c", "-z"]).stdout.split("\0"));
|
|
8471
|
-
const all = gitFiles(dir).filter((f) => !f.startsWith(".airgate/"));
|
|
8472
|
-
const skipped2 = all.filter((f) => (isEnvFile(f) || f.split("/").pop() === ".envrc") && !tracked.has(f));
|
|
8473
|
-
const files2 = all.filter((f) => !skipped2.includes(f));
|
|
8474
|
-
if (!files2.length) throw new Error("no files to send (git sees nothing tracked or untracked here)");
|
|
8475
|
-
const archive2 = await runTar(dir, ["--null", "-T", "-", "-czf", "-"], files2.join("\0") + "\0", limit);
|
|
8476
|
-
return { archive: archive2, files: files2.length, git: true, skipped: skipped2 };
|
|
8477
|
-
}
|
|
8478
|
-
const skipped = readdirSync(dir).filter((f) => isEnvFile(f) || f === ".envrc");
|
|
8479
|
-
const archive = await runTar(dir, ["--exclude=node_modules", "--exclude=.git", "--exclude=.airgate", ...ENV_EXCLUDES, "-czf", "-", "."], null, limit);
|
|
8480
|
-
return { archive, files: -1, git: false, skipped };
|
|
8481
|
-
}
|
|
8482
|
-
var CLAUDE_CONFIG = ["settings.json", "CLAUDE.md", "skills", "agents", "commands"];
|
|
8483
|
-
async function packClaudeConfig(home, limit = UPLOAD_LIMIT) {
|
|
8484
|
-
const root = join2(home, ".claude");
|
|
8485
|
-
if (!existsSync(root)) return null;
|
|
8486
|
-
const parts = CLAUDE_CONFIG.filter((p) => existsSync(join2(root, p)));
|
|
8487
|
-
if (!parts.length) return null;
|
|
8488
|
-
const archive = await runTar(
|
|
8489
|
-
root,
|
|
8490
|
-
["--exclude=*credentials*", "--exclude=*.credentials*", "--exclude=.env*", "--exclude=node_modules", "-czf", "-", ...parts],
|
|
8491
|
-
null,
|
|
8492
|
-
limit
|
|
8493
|
-
);
|
|
8494
|
-
return { archive, parts };
|
|
8495
|
-
}
|
|
8496
|
-
|
|
8497
|
-
// src/commands/updown.ts
|
|
8571
|
+
init_pack();
|
|
8498
8572
|
import { gunzipSync } from "node:zlib";
|
|
8499
8573
|
|
|
8500
8574
|
// src/sessions.ts
|
|
8575
|
+
init_pack();
|
|
8501
8576
|
import { createHash, randomUUID } from "node:crypto";
|
|
8502
8577
|
import { closeSync, existsSync as existsSync2, mkdirSync as mkdirSync2, mkdtempSync, openSync, readdirSync as readdirSync2, readFileSync as readFileSync4, readSync, realpathSync, renameSync as renameSync2, rmSync as rmSync3, statSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
8503
8578
|
import { tmpdir } from "node:os";
|
|
@@ -9073,14 +9148,14 @@ function printSessions(ctx, list2) {
|
|
|
9073
9148
|
}
|
|
9074
9149
|
}
|
|
9075
9150
|
function printFiles(ctx, blocks, toErr) {
|
|
9076
|
-
const
|
|
9151
|
+
const line3 = toErr ? ctx.ui.err : ctx.ui.out;
|
|
9077
9152
|
const c = toErr ? ctx.ui.ce : ctx.ui.c;
|
|
9078
9153
|
const files2 = orderFiles(blocks.map((b2) => b2.file));
|
|
9079
9154
|
const width = Math.max(...files2.map((f) => f.path.length + (f.from ? f.from.length + 4 : 0)));
|
|
9080
9155
|
for (const f of files2) {
|
|
9081
9156
|
const name = f.from ? `${f.from} \u2192 ${f.path}` : f.path;
|
|
9082
9157
|
const mark = f.risky ? c("warn", "!") : " ";
|
|
9083
|
-
|
|
9158
|
+
line3(` ${mark} ${STATUS_MARK[f.status]} ${f.risky ? name.padEnd(width) + " " + c("warn", f.reason ?? "") : name}`.trimEnd());
|
|
9084
9159
|
}
|
|
9085
9160
|
}
|
|
9086
9161
|
var down2 = {
|
|
@@ -9247,7 +9322,7 @@ var LineReader = class {
|
|
|
9247
9322
|
}
|
|
9248
9323
|
};
|
|
9249
9324
|
function ttyPrompt(stdin, out) {
|
|
9250
|
-
return (question, opts2 = {}) => new Promise((
|
|
9325
|
+
return (question, opts2 = {}) => new Promise((resolve10, reject) => {
|
|
9251
9326
|
out.write(question);
|
|
9252
9327
|
const decoder = new StringDecoder2("utf8");
|
|
9253
9328
|
let answer = "";
|
|
@@ -9260,7 +9335,7 @@ function ttyPrompt(stdin, out) {
|
|
|
9260
9335
|
stdin.pause();
|
|
9261
9336
|
out.write("\n");
|
|
9262
9337
|
if (err) reject(err);
|
|
9263
|
-
else
|
|
9338
|
+
else resolve10(answer);
|
|
9264
9339
|
};
|
|
9265
9340
|
const onData = (d) => {
|
|
9266
9341
|
for (const ch of decoder.write(d)) {
|
|
@@ -9287,16 +9362,16 @@ function ttyPrompt(stdin, out) {
|
|
|
9287
9362
|
}
|
|
9288
9363
|
function openInBrowser(url) {
|
|
9289
9364
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : ["xdg-open", [url]];
|
|
9290
|
-
return new Promise((
|
|
9365
|
+
return new Promise((resolve10) => {
|
|
9291
9366
|
try {
|
|
9292
9367
|
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
9293
|
-
child.on("error", () =>
|
|
9368
|
+
child.on("error", () => resolve10(false));
|
|
9294
9369
|
child.on("spawn", () => {
|
|
9295
9370
|
child.unref();
|
|
9296
|
-
|
|
9371
|
+
resolve10(true);
|
|
9297
9372
|
});
|
|
9298
9373
|
} catch {
|
|
9299
|
-
|
|
9374
|
+
resolve10(false);
|
|
9300
9375
|
}
|
|
9301
9376
|
});
|
|
9302
9377
|
}
|
|
@@ -9393,6 +9468,7 @@ async function runCommand(ctx, cmd, argv, cwd) {
|
|
|
9393
9468
|
import { existsSync as existsSync5, readFileSync as readFileSync8, rmSync as rmSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9394
9469
|
import { basename as basename2, resolve as resolve6 } from "node:path";
|
|
9395
9470
|
import { randomBytes } from "node:crypto";
|
|
9471
|
+
init_pack();
|
|
9396
9472
|
|
|
9397
9473
|
// src/relay.ts
|
|
9398
9474
|
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
@@ -9897,6 +9973,7 @@ When you are done, leave your changes in the files there (no need to commit or p
|
|
|
9897
9973
|
// src/commands/crews.ts
|
|
9898
9974
|
import { existsSync as existsSync6, readFileSync as readFileSync9, realpathSync as realpathSync3, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9899
9975
|
import { relative as relative3, resolve as resolve7 } from "node:path";
|
|
9976
|
+
init_pack();
|
|
9900
9977
|
var KINDS = ["race", "contest", "council", "squad", "split"];
|
|
9901
9978
|
var ACTIONS = ["show", "say", "stop", "down", "rm", "list"];
|
|
9902
9979
|
var AGENT_ALIAS = { claude: "claude-code", "claude-code": "claude-code", codex: "codex", opencode: "opencode", pi: "pi" };
|
|
@@ -10542,6 +10619,67 @@ var TOOLS = [
|
|
|
10542
10619
|
return ctx.api.note(need(a, "text"));
|
|
10543
10620
|
}
|
|
10544
10621
|
},
|
|
10622
|
+
{
|
|
10623
|
+
name: "pod_always_on",
|
|
10624
|
+
description: "Keep something from one of the user's pods running 24/7 (a bot, a webhook, a small api): it runs the pod's folder with a start command on a small server of its own that never sleeps, with its own https address. A flat monthly price per server on any plan (from $6), never counted against hours: only when the user asked for it. Running it again redeploys. When billing is on the result has a checkoutUrl the user must open to pay.",
|
|
10625
|
+
inputSchema: obj(
|
|
10626
|
+
{
|
|
10627
|
+
pod: POD,
|
|
10628
|
+
command: s('the start command, e.g. "node bot.js"'),
|
|
10629
|
+
dir: s("a folder in the pod's workspace (default: all of it)"),
|
|
10630
|
+
size: s("tiny ($6/month, default), small ($10) or medium ($20)")
|
|
10631
|
+
},
|
|
10632
|
+
["pod", "command"]
|
|
10633
|
+
),
|
|
10634
|
+
async call(ctx, a) {
|
|
10635
|
+
const p = await resolvePod(ctx, need(a, "pod"));
|
|
10636
|
+
const out = await ctx.api.podAlwaysOn(p.id, { command: need(a, "command"), dir: typeof a.dir === "string" ? a.dir : "", size: typeof a.size === "string" ? a.size : "tiny" });
|
|
10637
|
+
return { server: out.name, state: out.state, url: out.url, command: out.command, ...out.checkoutUrl ? { checkoutUrl: out.checkoutUrl } : {}, ...out.error ? { error: out.error } : {} };
|
|
10638
|
+
}
|
|
10639
|
+
},
|
|
10640
|
+
{
|
|
10641
|
+
name: "server_list",
|
|
10642
|
+
description: "List the user's always-on servers (small machines that never sleep, for a bot, a webhook or a small api): name, size, state, https address, what they run.",
|
|
10643
|
+
inputSchema: obj({}, []),
|
|
10644
|
+
async call(ctx) {
|
|
10645
|
+
return (await ctx.api.servers()).map((x) => ({ name: x.name, size: x.size, state: x.state, url: x.url, command: x.command, pod: x.podId ?? null }));
|
|
10646
|
+
}
|
|
10647
|
+
},
|
|
10648
|
+
{
|
|
10649
|
+
name: "server_deploy",
|
|
10650
|
+
description: "Deploy code to one of the user's always-on servers: a folder on this machine (packed like airgate up, .env files left out) or a folder in one of their pods, plus the command that starts it. The code gets PORT=8080 (served at the server's https address) and DATA_DIR=/data. It replaces what the server runs: only when the user asked for it.",
|
|
10651
|
+
inputSchema: obj(
|
|
10652
|
+
{
|
|
10653
|
+
server: s("the server name"),
|
|
10654
|
+
command: s('the start command, e.g. "node bot.js" (kept from the last deploy if left out)'),
|
|
10655
|
+
dir: s("a folder on this machine (default: the current one); with pod: a folder in the pod's workspace"),
|
|
10656
|
+
pod: s("deploy from this pod instead (name or id)")
|
|
10657
|
+
},
|
|
10658
|
+
["server"]
|
|
10659
|
+
),
|
|
10660
|
+
async call(ctx, a) {
|
|
10661
|
+
const name = need(a, "server");
|
|
10662
|
+
const command = typeof a.command === "string" ? a.command : void 0;
|
|
10663
|
+
if (typeof a.pod === "string" && a.pod) {
|
|
10664
|
+
const p = await resolvePod(ctx, a.pod);
|
|
10665
|
+
const out2 = await ctx.api.deployServerFromPod(name, p.id, typeof a.dir === "string" ? a.dir : "", command);
|
|
10666
|
+
return { server: out2.name, url: out2.url, command: out2.command };
|
|
10667
|
+
}
|
|
10668
|
+
const { packProject: packProject2 } = await Promise.resolve().then(() => (init_pack(), pack_exports));
|
|
10669
|
+
const { resolve: resolve10 } = await import("node:path");
|
|
10670
|
+
const packed = await packProject2(resolve10(ctx.io.cwd, typeof a.dir === "string" && a.dir ? a.dir : "."), 50 * 1024 * 1024);
|
|
10671
|
+
const out = await ctx.api.deployServer(name, packed.archive, command);
|
|
10672
|
+
return { server: out.name, url: out.url, command: out.command, leftOut: packed.skipped };
|
|
10673
|
+
}
|
|
10674
|
+
},
|
|
10675
|
+
{
|
|
10676
|
+
name: "server_logs",
|
|
10677
|
+
description: "Recent log lines of one of the user's always-on servers (what its command printed).",
|
|
10678
|
+
inputSchema: obj({ server: s("the server name") }, ["server"]),
|
|
10679
|
+
async call(ctx, a) {
|
|
10680
|
+
return (await ctx.api.serverLogs(need(a, "server"))).slice(-100);
|
|
10681
|
+
}
|
|
10682
|
+
},
|
|
10545
10683
|
{
|
|
10546
10684
|
name: "pod_share",
|
|
10547
10685
|
description: "Share one of the user's pods with their team workspace (teammates can open its terminal, files and checkpoints and fork it; never delete it), or stop sharing. Only when the user asks for it.",
|
|
@@ -10685,11 +10823,11 @@ var mcp = {
|
|
|
10685
10823
|
`));
|
|
10686
10824
|
if (relayer) ctx.io.stderr.write(`airgate mcp: signing pods' ${relayer.vendors.join(" / ")} calls from this laptop while this runs
|
|
10687
10825
|
`);
|
|
10688
|
-
for await (const
|
|
10689
|
-
if (!
|
|
10826
|
+
for await (const line3 of rl) {
|
|
10827
|
+
if (!line3.trim()) continue;
|
|
10690
10828
|
let msg;
|
|
10691
10829
|
try {
|
|
10692
|
-
msg = JSON.parse(
|
|
10830
|
+
msg = JSON.parse(line3);
|
|
10693
10831
|
} catch {
|
|
10694
10832
|
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
|
|
10695
10833
|
continue;
|
|
@@ -11134,21 +11272,21 @@ function pad2(s2, w) {
|
|
|
11134
11272
|
function wrap(s2, w) {
|
|
11135
11273
|
const out = [];
|
|
11136
11274
|
for (const para of s2.split("\n")) {
|
|
11137
|
-
let
|
|
11275
|
+
let line3 = "";
|
|
11138
11276
|
for (const word of para.split(/(\s+)/)) {
|
|
11139
|
-
if ([...
|
|
11140
|
-
|
|
11277
|
+
if ([...line3 + word].length <= w) {
|
|
11278
|
+
line3 += word;
|
|
11141
11279
|
continue;
|
|
11142
11280
|
}
|
|
11143
|
-
if (
|
|
11281
|
+
if (line3.trim()) out.push(line3.trimEnd());
|
|
11144
11282
|
let rest = word.trimStart();
|
|
11145
11283
|
while ([...rest].length > w) {
|
|
11146
11284
|
out.push([...rest].slice(0, w).join(""));
|
|
11147
11285
|
rest = [...rest].slice(w).join("");
|
|
11148
11286
|
}
|
|
11149
|
-
|
|
11287
|
+
line3 = rest;
|
|
11150
11288
|
}
|
|
11151
|
-
out.push(
|
|
11289
|
+
out.push(line3.trimEnd());
|
|
11152
11290
|
}
|
|
11153
11291
|
return out;
|
|
11154
11292
|
}
|
|
@@ -11695,8 +11833,8 @@ function header(c, s2) {
|
|
|
11695
11833
|
if (x + right.length + 1 < c.cols) c.text(c.cols - right.length - 1, 0, right, { fg: s2.connected ? "ok" : "warn" });
|
|
11696
11834
|
}
|
|
11697
11835
|
function drawArt(c, x, y, a, w = Infinity, h = Infinity) {
|
|
11698
|
-
a.lines.slice(0, Math.max(0, h)).forEach((
|
|
11699
|
-
const chars3 = [...
|
|
11836
|
+
a.lines.slice(0, Math.max(0, h)).forEach((line3, r) => {
|
|
11837
|
+
const chars3 = [...line3].slice(0, Math.max(0, w));
|
|
11700
11838
|
const tones2 = a.tones?.[r];
|
|
11701
11839
|
let k = 0;
|
|
11702
11840
|
while (k < chars3.length) {
|
|
@@ -11725,8 +11863,8 @@ function splash(c, s2) {
|
|
|
11725
11863
|
y += 2;
|
|
11726
11864
|
}
|
|
11727
11865
|
const mark = "\u22A0 ";
|
|
11728
|
-
const
|
|
11729
|
-
const lx = Math.max(0, Math.floor((c.cols -
|
|
11866
|
+
const line3 = mark + TAGLINE;
|
|
11867
|
+
const lx = Math.max(0, Math.floor((c.cols - line3.length) / 2));
|
|
11730
11868
|
c.text(lx, y, mark, { bold: true });
|
|
11731
11869
|
c.text(lx + mark.length, y, TAGLINE, { fg: "dim" }, c.cols - lx - mark.length);
|
|
11732
11870
|
const skip = "any key skips";
|
|
@@ -12069,8 +12207,8 @@ function modal(c, s2, m) {
|
|
|
12069
12207
|
(m.checkpoints ?? []).slice(start, start + rowsAvail).forEach((cp, i) => {
|
|
12070
12208
|
const isSel = start + i === m.sel;
|
|
12071
12209
|
c.text(b2.x + 2, b2.y + 1 + i, isSel ? "> " : " ", { bold: isSel });
|
|
12072
|
-
const
|
|
12073
|
-
c.text(b2.x + 4, b2.y + 1 + i, pad2(
|
|
12210
|
+
const line3 = `${pad2(`#${start + i + 1}`, 4)} ${pad2(ago2(cp.at, s2.now), 4)} ${pad2(cp.source, 6)} ${cp.label}`;
|
|
12211
|
+
c.text(b2.x + 4, b2.y + 1 + i, pad2(line3, b2.w - 6), { inverse: isSel, fg: cp.source === "auto" && !isSel ? "dim" : void 0 });
|
|
12074
12212
|
});
|
|
12075
12213
|
c.text(b2.x + 2, b2.y + b2.h - 2, "enter rewind \xB7 r race from here \xB7 esc close", { fg: "dim" }, b2.w - 4);
|
|
12076
12214
|
return;
|
|
@@ -12137,7 +12275,7 @@ function formModal(c, s2, f) {
|
|
|
12137
12275
|
v = text2(f.task, "what the agent should do each time");
|
|
12138
12276
|
break;
|
|
12139
12277
|
case "template":
|
|
12140
|
-
v = `\u2039 ${t.name} \u203A ${t.proOnly ? "pro \xB7 " : ""}${t.alwaysOn ? "always
|
|
12278
|
+
v = `\u2039 ${t.name} \u203A ${t.proOnly ? "pro \xB7 " : ""}${t.alwaysOn ? "always reachable \xB7 " : ""}${t.tagline}`;
|
|
12141
12279
|
break;
|
|
12142
12280
|
case "agent": {
|
|
12143
12281
|
const a = AGENT_LIST[f.agent];
|
|
@@ -12693,6 +12831,158 @@ var snapshot2 = {
|
|
|
12693
12831
|
}
|
|
12694
12832
|
};
|
|
12695
12833
|
|
|
12834
|
+
// src/commands/servers.ts
|
|
12835
|
+
import { resolve as resolve9 } from "node:path";
|
|
12836
|
+
init_pack();
|
|
12837
|
+
var memory = (mb) => mb >= 1024 ? `${mb / 1024} GB` : `${mb} MB`;
|
|
12838
|
+
function line2(ctx, s2) {
|
|
12839
|
+
const state = s2.state === "running" ? ctx.ui.c("ok", "running") : s2.state === "failed" ? ctx.ui.c("bad", "failed") : ctx.ui.c("warn", s2.state.replace("_", " "));
|
|
12840
|
+
return [s2.name, SERVER_SIZES[s2.size].name, state, s2.url ?? ctx.ui.c("dim", "(no address yet)"), s2.command ? ctx.ui.c("dim", s2.command) : ctx.ui.c("dim", "nothing deployed")];
|
|
12841
|
+
}
|
|
12842
|
+
var server = {
|
|
12843
|
+
name: "server",
|
|
12844
|
+
aliases: ["servers"],
|
|
12845
|
+
group: "pods",
|
|
12846
|
+
usage: "[ls|create|deploy|logs|env|restart|rm] \u2026",
|
|
12847
|
+
summary: "always-on servers: a small machine that never sleeps, for a bot, a webhook or a small api",
|
|
12848
|
+
example: 'airgate server deploy chat-bot --cmd "node bot.js"',
|
|
12849
|
+
notes: [
|
|
12850
|
+
"airgate server [ls] your servers",
|
|
12851
|
+
`airgate server create <name> [--size ${SERVER_SIZE_IDS.join("|")}] ${SERVER_SIZE_IDS.map((id) => `${id} $${SERVER_SIZES[id].monthlyUsd}`).join(", ")} a month; opens the checkout`,
|
|
12852
|
+
`airgate server deploy <name> [dir] --cmd "<start command>" this folder (no .env files) runs on it; --pod <pod> [--dir d] deploys a pod's folder`,
|
|
12853
|
+
"airgate server logs <name> what it printed",
|
|
12854
|
+
"airgate server env <name> --secrets A,B which of your account secrets it gets",
|
|
12855
|
+
"airgate server restart <name> \xB7 rm <name> rm deletes it now and ends its subscription",
|
|
12856
|
+
"what it runs gets PORT=8080 (served at its https address) and DATA_DIR=/data (kept). node and python are installed."
|
|
12857
|
+
],
|
|
12858
|
+
flags: {
|
|
12859
|
+
size: { type: "string", value: "size", help: `create: ${SERVER_SIZE_IDS.join(", ")} (default tiny)` },
|
|
12860
|
+
cmd: { type: "string", value: "command", help: "deploy: the command that starts it" },
|
|
12861
|
+
pod: { type: "string", value: "pod", help: "deploy: from this pod instead of this folder" },
|
|
12862
|
+
dir: { type: "string", value: "dir", help: "deploy --pod: the folder in its workspace" },
|
|
12863
|
+
secrets: { type: "string", value: "names", help: 'env: account secrets it gets (comma separated; "" for none)' },
|
|
12864
|
+
yes: { type: "boolean", short: "y", help: "don't ask" }
|
|
12865
|
+
},
|
|
12866
|
+
async run(ctx) {
|
|
12867
|
+
const { ui: ui2 } = ctx;
|
|
12868
|
+
const [sub = "ls", name, extra] = ctx.args.positionals;
|
|
12869
|
+
if (sub === "ls" || sub === "list") {
|
|
12870
|
+
const list2 = await ctx.api.servers();
|
|
12871
|
+
if (ctx.json) return ui2.json(list2);
|
|
12872
|
+
if (!list2.length) {
|
|
12873
|
+
ui2.out(ui2.c("dim", "no servers yet."));
|
|
12874
|
+
return void ui2.out(ui2.c("dim", `make one: airgate server create chat-bot (${SERVER_SIZE_IDS.map((id) => `${id} $${SERVER_SIZES[id].monthlyUsd}`).join(", ")} a month)`));
|
|
12875
|
+
}
|
|
12876
|
+
return void ui2.table(list2.map((s2) => line2(ctx, s2)), { head: ["server", "size", "", "address", "runs"] });
|
|
12877
|
+
}
|
|
12878
|
+
if (!name) throw new UsageError(`airgate server ${sub} <name>`);
|
|
12879
|
+
if (sub === "create") {
|
|
12880
|
+
const size2 = str(ctx.args, "size") ?? "tiny";
|
|
12881
|
+
if (!SERVER_SIZE_IDS.includes(size2)) throw new UsageError(`--size: ${SERVER_SIZE_IDS.join(", ")}`);
|
|
12882
|
+
const z = SERVER_SIZES[size2];
|
|
12883
|
+
const s2 = await ui2.spin(`making ${name}`, ctx.api.createServer(name, size2));
|
|
12884
|
+
if (ctx.json) return ui2.json(s2);
|
|
12885
|
+
if (s2.checkoutUrl) {
|
|
12886
|
+
ui2.out(`${name}: ${z.cpus} cpu, ${memory(z.memoryMb)}, ${z.diskGb} GB disk, $${z.monthlyUsd} a month. pay for it and it starts:`);
|
|
12887
|
+
return void await openOrPrint(ctx, s2.checkoutUrl, `${name} checkout`);
|
|
12888
|
+
}
|
|
12889
|
+
return void ui2.out(`${ui2.c("ok", "up")} ${name} at ${s2.url}. put code on it: airgate server deploy ${name} --cmd "node bot.js"`);
|
|
12890
|
+
}
|
|
12891
|
+
if (sub === "deploy") {
|
|
12892
|
+
const cmd = str(ctx.args, "cmd");
|
|
12893
|
+
const podRef = str(ctx.args, "pod");
|
|
12894
|
+
let s2;
|
|
12895
|
+
if (podRef) {
|
|
12896
|
+
const pod = await resolvePod(ctx, podRef);
|
|
12897
|
+
s2 = await ui2.spin(`deploying ${pod.spec.name}:~/workspace/${str(ctx.args, "dir") ?? ""} to ${name}`, ctx.api.deployServerFromPod(name, pod.id, str(ctx.args, "dir") ?? "", cmd));
|
|
12898
|
+
} else {
|
|
12899
|
+
const dir = resolve9(ctx.io.cwd, extra ?? ".");
|
|
12900
|
+
let archive;
|
|
12901
|
+
try {
|
|
12902
|
+
const packed = await ui2.spin(`packing ${dir}`, packProject(dir, 50 * 1024 * 1024));
|
|
12903
|
+
archive = packed.archive;
|
|
12904
|
+
if (packed.skipped.length && !ctx.json) ui2.err(ui2.ce("dim", `left out: ${packed.skipped.join(", ")} (env files never travel; give it secrets: airgate server env ${name} --secrets \u2026)`));
|
|
12905
|
+
} catch (e) {
|
|
12906
|
+
if (e instanceof TooLargeError) throw new CliError("the folder is over 50 MB packed", "leave out build output and dependencies (they install on the server), or deploy from a pod");
|
|
12907
|
+
throw e;
|
|
12908
|
+
}
|
|
12909
|
+
s2 = await ui2.spin(`deploying to ${name}`, ctx.api.deployServer(name, archive, cmd));
|
|
12910
|
+
}
|
|
12911
|
+
if (ctx.json) return ui2.json(s2);
|
|
12912
|
+
return void ui2.out(`${ui2.c("ok", "deployed")} ${name}: ${s2.command}. it restarts into it now; ${s2.url ?? ""}`);
|
|
12913
|
+
}
|
|
12914
|
+
if (sub === "logs") {
|
|
12915
|
+
const lines = await ctx.api.serverLogs(name);
|
|
12916
|
+
if (ctx.json) return ui2.json(lines);
|
|
12917
|
+
if (!lines.length) return void ui2.out(ui2.c("dim", "nothing yet"));
|
|
12918
|
+
for (const l of lines.slice(-200)) ui2.out(`${ui2.c("dim", l.at.slice(11, 19))} ${l.message}`);
|
|
12919
|
+
return;
|
|
12920
|
+
}
|
|
12921
|
+
if (sub === "env") {
|
|
12922
|
+
const raw = str(ctx.args, "secrets");
|
|
12923
|
+
const cmd = str(ctx.args, "cmd");
|
|
12924
|
+
if (raw === void 0 && !cmd) {
|
|
12925
|
+
const s3 = await ctx.api.server(name);
|
|
12926
|
+
if (ctx.json) return ui2.json({ secrets: s3.secrets, command: s3.command });
|
|
12927
|
+
return void ui2.out(`${name} gets: ${s3.secrets.length ? s3.secrets.join(", ") : ui2.c("dim", "no secrets")}${s3.command ? ` \xB7 runs ${s3.command}` : ""}`);
|
|
12928
|
+
}
|
|
12929
|
+
const names = raw === void 0 ? void 0 : raw.split(",").map((x) => x.trim()).filter(Boolean);
|
|
12930
|
+
const s2 = await ctx.api.serverEnv(name, { ...names ? { secrets: names } : {}, ...cmd ? { command: cmd } : {} });
|
|
12931
|
+
if (ctx.json) return ui2.json(s2);
|
|
12932
|
+
return void ui2.out(`${name} gets: ${s2.secrets.length ? s2.secrets.join(", ") : "no secrets"}. it restarts with them.`);
|
|
12933
|
+
}
|
|
12934
|
+
if (sub === "restart") {
|
|
12935
|
+
await ctx.api.restartServer(name);
|
|
12936
|
+
return ctx.json ? ui2.json({ ok: true }) : void ui2.out(`${name} is restarting.`);
|
|
12937
|
+
}
|
|
12938
|
+
if (sub === "pay" || sub === "checkout") {
|
|
12939
|
+
const r = await ctx.api.serverCheckout(name);
|
|
12940
|
+
return ctx.json ? ui2.json(r) : void await openOrPrint(ctx, r.url, `${name} checkout`);
|
|
12941
|
+
}
|
|
12942
|
+
if (sub === "rm" || sub === "delete") {
|
|
12943
|
+
if (!await confirm(ctx, `delete ${name}? its machine, disk and address go now, and its subscription ends today.`)) throw new CliError("nothing deleted");
|
|
12944
|
+
await ctx.api.deleteServer(name);
|
|
12945
|
+
return ctx.json ? ui2.json({ ok: true }) : void ui2.out(`deleted ${name}.`);
|
|
12946
|
+
}
|
|
12947
|
+
throw new UsageError(`unknown "${sub}"`, "airgate server ls|create|deploy|logs|env|restart|rm");
|
|
12948
|
+
}
|
|
12949
|
+
};
|
|
12950
|
+
var alwaysOn = {
|
|
12951
|
+
name: "always-on",
|
|
12952
|
+
group: "pods",
|
|
12953
|
+
usage: '<pod> --cmd "<start command>" [--dir <folder>] [--size tiny|small|medium]',
|
|
12954
|
+
summary: "keep something from a pod running 24/7: from $6 a month per server, on any plan, never counts against your hours",
|
|
12955
|
+
example: 'airgate always-on web --cmd "node bot.js" --dir bot',
|
|
12956
|
+
notes: [
|
|
12957
|
+
"the pod still sleeps when idle: its folder runs on a small server of its own that never sleeps, with its own https address. run it again to redeploy.",
|
|
12958
|
+
`sizes: ${SERVER_SIZE_IDS.map((id) => `${id} $${SERVER_SIZES[id].monthlyUsd}`).join(", ")} a month. it gets PORT=8080 (served at its address) and DATA_DIR=/data (kept).`,
|
|
12959
|
+
"see and manage them: airgate server"
|
|
12960
|
+
],
|
|
12961
|
+
flags: {
|
|
12962
|
+
cmd: { type: "string", value: "command", help: "the command that starts it" },
|
|
12963
|
+
dir: { type: "string", value: "folder", help: "a folder in the pod's workspace (default: all of it)" },
|
|
12964
|
+
size: { type: "string", value: "size", help: `${SERVER_SIZE_IDS.join(", ")} (default tiny)` }
|
|
12965
|
+
},
|
|
12966
|
+
async run(ctx) {
|
|
12967
|
+
const { ui: ui2 } = ctx;
|
|
12968
|
+
const [ref, extra] = ctx.args.positionals;
|
|
12969
|
+
if (!ref || extra) throw new UsageError('airgate always-on <pod> --cmd "<start command>"');
|
|
12970
|
+
const cmd = str(ctx.args, "cmd");
|
|
12971
|
+
if (!cmd) throw new UsageError('--cmd: what starts it, e.g. --cmd "node bot.js"');
|
|
12972
|
+
const size2 = str(ctx.args, "size") ?? "tiny";
|
|
12973
|
+
if (!SERVER_SIZE_IDS.includes(size2)) throw new UsageError(`--size: ${SERVER_SIZE_IDS.join(", ")}`);
|
|
12974
|
+
const pod = await resolvePod(ctx, ref);
|
|
12975
|
+
const s2 = await ui2.spin(`making ${pod.spec.name} always on`, ctx.api.podAlwaysOn(pod.id, { command: cmd, dir: str(ctx.args, "dir") ?? "", size: size2 }));
|
|
12976
|
+
if (ctx.json) return ui2.json(s2);
|
|
12977
|
+
if (s2.checkoutUrl) {
|
|
12978
|
+
ui2.out(`${pod.spec.name} always on: a ${size2} server, $${SERVER_SIZES[size2].monthlyUsd} a month. pay for it and it starts with ${cmd}:`);
|
|
12979
|
+
return void await openOrPrint(ctx, s2.checkoutUrl, `${s2.name} checkout`);
|
|
12980
|
+
}
|
|
12981
|
+
if (s2.error) ui2.err(ui2.ce("warn", s2.error));
|
|
12982
|
+
ui2.out(`${ui2.c("ok", "always on")} ${pod.spec.name}: ${s2.command ?? cmd} at ${s2.url}. manage it: airgate server ${s2.name}`);
|
|
12983
|
+
}
|
|
12984
|
+
};
|
|
12985
|
+
|
|
12696
12986
|
// src/cli.ts
|
|
12697
12987
|
var help = {
|
|
12698
12988
|
name: "help",
|
|
@@ -12719,6 +13009,8 @@ var COMMANDS = [
|
|
|
12719
13009
|
deleteAccount,
|
|
12720
13010
|
verify,
|
|
12721
13011
|
bug,
|
|
13012
|
+
server,
|
|
13013
|
+
alwaysOn,
|
|
12722
13014
|
team,
|
|
12723
13015
|
github,
|
|
12724
13016
|
pods,
|
|
@@ -12820,7 +13112,7 @@ function printCommandHelp(ui2, c) {
|
|
|
12820
13112
|
ui2.out(`example: ${c.example}`);
|
|
12821
13113
|
}
|
|
12822
13114
|
function report(ui2, err, cmd, env) {
|
|
12823
|
-
const
|
|
13115
|
+
const line3 = (msg, hint) => {
|
|
12824
13116
|
ui2.err(`${ui2.ce("bad", "error")}: ${msg}`);
|
|
12825
13117
|
if (hint) ui2.err(ui2.ce("dim", hint));
|
|
12826
13118
|
};
|
|
@@ -12832,35 +13124,35 @@ function report(ui2, err, cmd, env) {
|
|
|
12832
13124
|
return 2;
|
|
12833
13125
|
}
|
|
12834
13126
|
if (err instanceof CliError) {
|
|
12835
|
-
|
|
13127
|
+
line3(err.message, err.hint);
|
|
12836
13128
|
return err.exitCode;
|
|
12837
13129
|
}
|
|
12838
13130
|
if (err instanceof NetworkError) {
|
|
12839
|
-
|
|
13131
|
+
line3(err.message, "is it up? airgate status \xB7 another gateway: airgate login --gateway <url> or AIRGATE_GATEWAY");
|
|
12840
13132
|
return 1;
|
|
12841
13133
|
}
|
|
12842
13134
|
if (err instanceof ApiError) {
|
|
12843
13135
|
if (err.status === 401 && err.code !== "bad_credentials") {
|
|
12844
|
-
if (env.AIRGATE_TOKEN)
|
|
13136
|
+
if (env.AIRGATE_TOKEN) line3("AIRGATE_TOKEN was not accepted (expired, revoked, or for another gateway)", "unset it, or run: airgate login");
|
|
12845
13137
|
else ui2.err(`not signed in. run: airgate login`);
|
|
12846
13138
|
return 1;
|
|
12847
13139
|
}
|
|
12848
13140
|
if (err.code === "plan_required") {
|
|
12849
|
-
|
|
13141
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1));
|
|
12850
13142
|
const url = typeof err.extra.checkoutUrl === "string" ? err.extra.checkoutUrl : void 0;
|
|
12851
13143
|
ui2.err(url ? `upgrade: ${url}` : `upgrade: airgate upgrade pro ($${PLANS.pro.monthlyUsd}/month, ${PLANS.pro.includedHours}h included)`);
|
|
12852
13144
|
return 1;
|
|
12853
13145
|
}
|
|
12854
13146
|
if (err.code === "template_unavailable") {
|
|
12855
|
-
|
|
13147
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1), "see what this gateway offers: airgate templates");
|
|
12856
13148
|
return 1;
|
|
12857
13149
|
}
|
|
12858
13150
|
if (err.code === "team_plan_required" || err.code === "no_seat") {
|
|
12859
|
-
|
|
13151
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1), err.code === "no_seat" ? "add one: airgate team seats <n>" : "the owner starts it: airgate team upgrade --seats <n>");
|
|
12860
13152
|
return 1;
|
|
12861
13153
|
}
|
|
12862
13154
|
if (err.status === 402 || err.code === "billing_required") {
|
|
12863
|
-
|
|
13155
|
+
line3(`${err.message}`);
|
|
12864
13156
|
const p = PLANS;
|
|
12865
13157
|
ui2.err(` payg no monthly fee, $${p.payg.hourlyUsd.toFixed(2)}/active hour`);
|
|
12866
13158
|
ui2.err(` pro $${p.pro.monthlyUsd}/month, ${p.pro.includedHours}h included, then $${p.pro.hourlyUsd.toFixed(2)}/h`);
|
|
@@ -12869,30 +13161,30 @@ function report(ui2, err, cmd, env) {
|
|
|
12869
13161
|
return 1;
|
|
12870
13162
|
}
|
|
12871
13163
|
if (err.code === "signups_closed") {
|
|
12872
|
-
|
|
13164
|
+
line3("sign-ups aren't open yet", "have an invite token? airgate login --token <token>");
|
|
12873
13165
|
return 1;
|
|
12874
13166
|
}
|
|
12875
13167
|
if (err.code === "signup_code_required") {
|
|
12876
|
-
|
|
13168
|
+
line3("an early access code is needed to sign up", "airgate signup --code <code>");
|
|
12877
13169
|
return 1;
|
|
12878
13170
|
}
|
|
12879
13171
|
if (err.code === "email_taken") {
|
|
12880
|
-
|
|
13172
|
+
line3("an account with this email exists", "sign in: airgate login \xB7 forgot the password? airgate forgot");
|
|
12881
13173
|
return 1;
|
|
12882
13174
|
}
|
|
12883
13175
|
if (err.status === 429) {
|
|
12884
|
-
|
|
13176
|
+
line3(err.message.toLowerCase(), "wait a few minutes and try again");
|
|
12885
13177
|
return 1;
|
|
12886
13178
|
}
|
|
12887
13179
|
if (err.code === "capacity") {
|
|
12888
|
-
|
|
13180
|
+
line3(err.message.toLowerCase(), "every machine is busy; try again in a minute");
|
|
12889
13181
|
return 1;
|
|
12890
13182
|
}
|
|
12891
13183
|
if (err.code === "not_ready") {
|
|
12892
|
-
|
|
13184
|
+
line3(err.message.toLowerCase(), "wait for it: airgate info <pod>");
|
|
12893
13185
|
return 1;
|
|
12894
13186
|
}
|
|
12895
|
-
|
|
13187
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1));
|
|
12896
13188
|
return 1;
|
|
12897
13189
|
}
|
|
12898
13190
|
const e = err;
|
|
@@ -12900,7 +13192,7 @@ function report(ui2, err, cmd, env) {
|
|
|
12900
13192
|
ui2.err("cancelled.");
|
|
12901
13193
|
return 1;
|
|
12902
13194
|
}
|
|
12903
|
-
|
|
13195
|
+
line3(e?.message ?? String(err));
|
|
12904
13196
|
if (env.AIRGATE_DEBUG && e?.stack) ui2.err(e.stack);
|
|
12905
13197
|
return 1;
|
|
12906
13198
|
}
|