airgate 0.1.7 → 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 +537 -210
- 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,9 +931,49 @@ 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
|
}
|
|
974
|
+
reportBug(text2, expected, context) {
|
|
975
|
+
return this.request("POST", "/v1/bugs", { body: { text: text2, ...expected ? { expected } : {}, ...context ? { context } : {} } });
|
|
976
|
+
}
|
|
800
977
|
terminalUrl(id, cols, rows2) {
|
|
801
978
|
const q = new URLSearchParams({ token: this.tokenForUrl(), cols: String(cols), rows: String(rows2), client: this.client });
|
|
802
979
|
return `${this.gateway.replace(/^http/, "ws")}/v1/pods/${enc(id)}/terminal?${q}`;
|
|
@@ -835,10 +1012,10 @@ async function readSse(body, onEvent) {
|
|
|
835
1012
|
buf = buf.slice(i + 2);
|
|
836
1013
|
let event = "message";
|
|
837
1014
|
const data = [];
|
|
838
|
-
for (const
|
|
839
|
-
if (
|
|
840
|
-
if (
|
|
841
|
-
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(/^ /, ""));
|
|
842
1019
|
}
|
|
843
1020
|
if (data.length) onEvent(event, data.join("\n"));
|
|
844
1021
|
}
|
|
@@ -1396,9 +1573,9 @@ function banner(text2, options = {}) {
|
|
|
1396
1573
|
const ink = new Uint8Array(cols * rows2);
|
|
1397
1574
|
let x = x0;
|
|
1398
1575
|
for (const g of glyphs) {
|
|
1399
|
-
g.forEach((
|
|
1400
|
-
for (let i = 0; i <
|
|
1401
|
-
if (
|
|
1576
|
+
g.forEach((line3, y) => {
|
|
1577
|
+
for (let i = 0; i < line3.length; i++)
|
|
1578
|
+
if (line3[i] === "#") {
|
|
1402
1579
|
for (let k = 0; k < s2; k++)
|
|
1403
1580
|
if (x + i * s2 + k < cols && y0 + y < rows2)
|
|
1404
1581
|
ink[(y0 + y) * cols + x + i * s2 + k] = 1;
|
|
@@ -1481,26 +1658,26 @@ function banner(text2, options = {}) {
|
|
|
1481
1658
|
const [core, edge] = [...shine[g]];
|
|
1482
1659
|
const lines = [];
|
|
1483
1660
|
for (let r = 0; r < rows2; r++) {
|
|
1484
|
-
let
|
|
1661
|
+
let line3 = "";
|
|
1485
1662
|
for (let c = 0; c < cols; c++) {
|
|
1486
1663
|
const k = r * cols + c;
|
|
1487
1664
|
const cell = grid2[k];
|
|
1488
1665
|
if (c >= upTo) {
|
|
1489
|
-
|
|
1666
|
+
line3 += " ";
|
|
1490
1667
|
continue;
|
|
1491
1668
|
}
|
|
1492
1669
|
if (cell !== null) {
|
|
1493
|
-
|
|
1670
|
+
line3 += cell;
|
|
1494
1671
|
if (color && palette && cell !== " ")
|
|
1495
1672
|
color[k] = (paper ? 0 : half) + 0;
|
|
1496
1673
|
continue;
|
|
1497
1674
|
}
|
|
1498
1675
|
const d = Math.abs(c + slant * r - p);
|
|
1499
|
-
|
|
1676
|
+
line3 += d < width / 2 ? core : d < width ? edge ?? core : fill[g];
|
|
1500
1677
|
if (color && palette)
|
|
1501
1678
|
color[k] = (paper ? 0 : half) + 1 + bucket(c);
|
|
1502
1679
|
}
|
|
1503
|
-
lines.push(
|
|
1680
|
+
lines.push(line3);
|
|
1504
1681
|
}
|
|
1505
1682
|
return lines.join("\n");
|
|
1506
1683
|
};
|
|
@@ -1720,8 +1897,8 @@ var isPiece = (v) => v !== null && typeof v === "object" && "meta" in v && typeo
|
|
|
1720
1897
|
var isClip = (v) => v !== null && typeof v === "object" && !(v instanceof Surface) && "src" in v && !("meta" in v);
|
|
1721
1898
|
var unwrap = (v) => v !== null && typeof v === "object" && !("meta" in v) && !("src" in v) && isPiece(v.default) ? v.default : v;
|
|
1722
1899
|
var textName = (text2) => {
|
|
1723
|
-
const
|
|
1724
|
-
return
|
|
1900
|
+
const line3 = text2.split("\n").find((l) => l.trim())?.trim() ?? "";
|
|
1901
|
+
return line3 ? `"${line3.slice(0, 24)}"` : "text";
|
|
1725
1902
|
};
|
|
1726
1903
|
function prepare(fn, v, keys = CLIP) {
|
|
1727
1904
|
v = unwrap(v);
|
|
@@ -2292,10 +2469,10 @@ function freeze(src, at3) {
|
|
|
2292
2469
|
}
|
|
2293
2470
|
function named(src, name, o = {}) {
|
|
2294
2471
|
const opts2 = object(o, "named()", ["note", "category"]);
|
|
2295
|
-
const
|
|
2296
|
-
if (!
|
|
2472
|
+
const line3 = (v) => typeof v === "string" && !!v.trim() && !/[\u0000-\u001f\u007f-\u009f]/.test(v);
|
|
2473
|
+
if (!line3(name))
|
|
2297
2474
|
fail2(`named() takes a name, one line such as "hello", not ${shown(name)}`);
|
|
2298
|
-
if (opts2.note !== void 0 && !
|
|
2475
|
+
if (opts2.note !== void 0 && !line3(opts2.note))
|
|
2299
2476
|
fail2(`named()'s note takes one line saying what you see, not ${shown(opts2.note)}`);
|
|
2300
2477
|
const p = prepare("named", src);
|
|
2301
2478
|
const asItWas = p.speed === 1 && !p.offset && !p.tint && !Object.keys(p.options).length;
|
|
@@ -2866,13 +3043,13 @@ function scan(src, options) {
|
|
|
2866
3043
|
const pos = lineAt(t);
|
|
2867
3044
|
if (pos === null)
|
|
2868
3045
|
return copy(s2, g);
|
|
2869
|
-
const
|
|
3046
|
+
const line3 = forward ? pos : along - 1 - pos;
|
|
2870
3047
|
for (let y = 0, i = 0; y < g.rows; y++)
|
|
2871
3048
|
for (let x = 0; x < g.cols; x++, i++) {
|
|
2872
3049
|
const q = across2 ? y : x;
|
|
2873
|
-
if (q ===
|
|
3050
|
+
if (q === line3)
|
|
2874
3051
|
s2.put(i, char, ink >= 0 ? ink : g.chars[i] === EMPTY ? NONE : g.colors[i]);
|
|
2875
|
-
else if (!reveal || (forward ? q <
|
|
3052
|
+
else if (!reveal || (forward ? q < line3 : q > line3)) {
|
|
2876
3053
|
s2.chars[i] = g.chars[i];
|
|
2877
3054
|
s2.colors[i] = g.colors[i];
|
|
2878
3055
|
}
|
|
@@ -3086,7 +3263,7 @@ function outline(src, options) {
|
|
|
3086
3263
|
setup: () => {
|
|
3087
3264
|
const solid = new Uint8Array(cols * rows2), out = new Uint8Array(cols * rows2), queue = new Int32Array(cols * rows2);
|
|
3088
3265
|
const is = (a, x, y) => x >= 0 && x < cols && y >= 0 && y < rows2 && a[y * cols + x] === 1;
|
|
3089
|
-
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));
|
|
3090
3267
|
const over2 = { mask: null };
|
|
3091
3268
|
return (t, s2, g) => {
|
|
3092
3269
|
solid.fill(0);
|
|
@@ -3106,12 +3283,12 @@ function outline(src, options) {
|
|
|
3106
3283
|
flood(solid, out, queue, cols, rows2);
|
|
3107
3284
|
for (let y = 0; y < rows2; y++)
|
|
3108
3285
|
for (let x = 0; x < cols; x++) {
|
|
3109
|
-
if (!
|
|
3286
|
+
if (!line3(x, y))
|
|
3110
3287
|
continue;
|
|
3111
|
-
const up2 =
|
|
3112
|
-
const down4 =
|
|
3113
|
-
const left =
|
|
3114
|
-
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));
|
|
3115
3292
|
const bits = (up2 ? 1 : 0) | (down4 ? 2 : 0) | (left ? 4 : 0) | (right ? 8 : 0);
|
|
3116
3293
|
if (!bits)
|
|
3117
3294
|
continue;
|
|
@@ -3753,12 +3930,12 @@ var Surface = class _Surface {
|
|
|
3753
3930
|
for (let i = 0; i < n2; i++)
|
|
3754
3931
|
color[i] = colors[i] === NONE ? ink : colors[i];
|
|
3755
3932
|
}
|
|
3756
|
-
const
|
|
3933
|
+
const line3 = this.#line;
|
|
3757
3934
|
let out = "";
|
|
3758
3935
|
for (let r = 0; r < rows2; r++) {
|
|
3759
3936
|
for (let c = 0, i = r * cols; c < cols; c++, i++)
|
|
3760
|
-
|
|
3761
|
-
out += (r ? "\n" : "") + String.fromCharCode.apply(null,
|
|
3937
|
+
line3[c] = chars3[i] || 32;
|
|
3938
|
+
out += (r ? "\n" : "") + String.fromCharCode.apply(null, line3);
|
|
3762
3939
|
}
|
|
3763
3940
|
return out;
|
|
3764
3941
|
}
|
|
@@ -4653,12 +4830,12 @@ function words(str2, what) {
|
|
|
4653
4830
|
}
|
|
4654
4831
|
function writeLines(s2, lines, lefts, top, k, transparent) {
|
|
4655
4832
|
let left = Infinity, right = -Infinity;
|
|
4656
|
-
lines.forEach((
|
|
4657
|
-
const lx = lefts[r], len =
|
|
4833
|
+
lines.forEach((line3, r) => {
|
|
4834
|
+
const lx = lefts[r], len = line3.length;
|
|
4658
4835
|
if (len)
|
|
4659
4836
|
left = Math.min(left, lx), right = Math.max(right, lx + len);
|
|
4660
4837
|
for (let i = 0; i < len; i++) {
|
|
4661
|
-
const c =
|
|
4838
|
+
const c = line3.charCodeAt(i);
|
|
4662
4839
|
if (transparent && c === 32)
|
|
4663
4840
|
continue;
|
|
4664
4841
|
const j = s2.index(lx + i, top + r);
|
|
@@ -4675,25 +4852,25 @@ function layout(str2, width, wrap2) {
|
|
|
4675
4852
|
out.push(para.slice(0, width));
|
|
4676
4853
|
continue;
|
|
4677
4854
|
}
|
|
4678
|
-
let
|
|
4855
|
+
let line3 = "";
|
|
4679
4856
|
for (let word of para.split(" ").filter(Boolean)) {
|
|
4680
4857
|
while (word.length > width) {
|
|
4681
|
-
if (
|
|
4682
|
-
out.push(
|
|
4683
|
-
|
|
4858
|
+
if (line3)
|
|
4859
|
+
out.push(line3);
|
|
4860
|
+
line3 = "";
|
|
4684
4861
|
out.push(word.slice(0, width));
|
|
4685
4862
|
word = word.slice(width);
|
|
4686
4863
|
}
|
|
4687
|
-
if (!
|
|
4688
|
-
|
|
4689
|
-
else if (
|
|
4690
|
-
|
|
4864
|
+
if (!line3)
|
|
4865
|
+
line3 = word;
|
|
4866
|
+
else if (line3.length + 1 + word.length <= width)
|
|
4867
|
+
line3 += " " + word;
|
|
4691
4868
|
else {
|
|
4692
|
-
out.push(
|
|
4693
|
-
|
|
4869
|
+
out.push(line3);
|
|
4870
|
+
line3 = word;
|
|
4694
4871
|
}
|
|
4695
4872
|
}
|
|
4696
|
-
out.push(
|
|
4873
|
+
out.push(line3);
|
|
4697
4874
|
}
|
|
4698
4875
|
return out;
|
|
4699
4876
|
}
|
|
@@ -5403,7 +5580,7 @@ function cat() {
|
|
|
5403
5580
|
const put = (r, c, s2) => [...s2].forEach((ch, i) => ch !== " " && c + i < cols && (g[r][c + i] = ch));
|
|
5404
5581
|
const p = (u + 0.4) % BREATH / BREATH;
|
|
5405
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;
|
|
5406
|
-
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));
|
|
5407
5584
|
const fl = FLICK.find(([a, b2]) => u >= a && u < b2);
|
|
5408
5585
|
const up2 = fl ? Math.sin(Math.PI * (u - fl[0]) / (fl[1] - fl[0])) : 0;
|
|
5409
5586
|
TIP[up2 > 0.7 ? 2 : up2 > 0.25 ? 1 : 0].forEach(([r, c, s2]) => put(r, c, s2));
|
|
@@ -5511,9 +5688,9 @@ function spinners2({ names = meta2.options.names, labels = meta2.options.labels,
|
|
|
5511
5688
|
const x0 = n2 % per * slot2, y0 = 1 + Math.floor(n2 / per) * (block + 1);
|
|
5512
5689
|
const art = draw2(Math.floor(t * meta2.fps * speed2 / frames + 1e-3));
|
|
5513
5690
|
const top = y0 + Math.floor((3 - art.length) / 2);
|
|
5514
|
-
art.forEach((
|
|
5515
|
-
const left = x0 + Math.floor((slot2 - [...
|
|
5516
|
-
[...
|
|
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);
|
|
5517
5694
|
});
|
|
5518
5695
|
if (labels)
|
|
5519
5696
|
[...name].forEach((g, i) => grid2[y0 + 4][x0 + Math.floor((slot2 - name.length) / 2) + i] = g);
|
|
@@ -5552,8 +5729,8 @@ function inkBox(frame, seconds, step) {
|
|
|
5552
5729
|
let bottom = -1;
|
|
5553
5730
|
let right = -1;
|
|
5554
5731
|
for (let t = 0; t < seconds; t += step) {
|
|
5555
|
-
frame(t).split("\n").forEach((
|
|
5556
|
-
[...
|
|
5732
|
+
frame(t).split("\n").forEach((line3, y) => {
|
|
5733
|
+
[...line3].forEach((ch, x) => {
|
|
5557
5734
|
if (ch === " ") return;
|
|
5558
5735
|
top = Math.min(top, y);
|
|
5559
5736
|
bottom = Math.max(bottom, y);
|
|
@@ -5905,9 +6082,9 @@ function formatTable(rows2, head, indent = 0) {
|
|
|
5905
6082
|
);
|
|
5906
6083
|
}
|
|
5907
6084
|
function paintArt(on, a) {
|
|
5908
|
-
return a.lines.map((
|
|
6085
|
+
return a.lines.map((line3, y) => {
|
|
5909
6086
|
const tones2 = a.tones?.[y];
|
|
5910
|
-
if (!on || !tones2) return
|
|
6087
|
+
if (!on || !tones2) return line3.trimEnd();
|
|
5911
6088
|
let out = "";
|
|
5912
6089
|
let run2 = "";
|
|
5913
6090
|
let tone2;
|
|
@@ -5915,7 +6092,7 @@ function paintArt(on, a) {
|
|
|
5915
6092
|
if (run2) out += tone2 ? paint2(true, tone2, run2) : run2;
|
|
5916
6093
|
run2 = "";
|
|
5917
6094
|
};
|
|
5918
|
-
[...
|
|
6095
|
+
[...line3].forEach((ch, x) => {
|
|
5919
6096
|
const t = ch === " " ? tone2 : tones2[x];
|
|
5920
6097
|
if (t !== tone2) {
|
|
5921
6098
|
flush();
|
|
@@ -5932,8 +6109,8 @@ function createUi(io) {
|
|
|
5932
6109
|
const errColor = colorOn(io.stderr, io.env);
|
|
5933
6110
|
const ui2 = {
|
|
5934
6111
|
tty: Boolean(io.stdout.isTTY),
|
|
5935
|
-
out: (
|
|
5936
|
-
err: (
|
|
6112
|
+
out: (line3 = "") => void io.stdout.write(line3 + "\n"),
|
|
6113
|
+
err: (line3 = "") => void io.stderr.write(line3 + "\n"),
|
|
5937
6114
|
c: (tone2, s2) => paint2(outColor, tone2, s2),
|
|
5938
6115
|
ce: (tone2, s2) => paint2(errColor, tone2, s2),
|
|
5939
6116
|
json: (v) => void io.stdout.write(JSON.stringify(v, null, 2) + "\n"),
|
|
@@ -6021,9 +6198,9 @@ function billingOff(e) {
|
|
|
6021
6198
|
function hoursMeter(ui2, used, cap2) {
|
|
6022
6199
|
if (!ui2.tty) return null;
|
|
6023
6200
|
const scale2 = cap2 && cap2 > 0 ? cap2 : PLANS.pro.includedHours;
|
|
6024
|
-
const [
|
|
6201
|
+
const [line3] = ui2.art(bar(used / scale2, 24));
|
|
6025
6202
|
const of = cap2 && cap2 > 0 ? `of ${cap2}h` : `of ${scale2}h (what pro includes)`;
|
|
6026
|
-
return `${
|
|
6203
|
+
return `${line3} ${hours(used)} ${ui2.c("dim", of)}`;
|
|
6027
6204
|
}
|
|
6028
6205
|
var usage = {
|
|
6029
6206
|
name: "usage",
|
|
@@ -7380,6 +7557,11 @@ var secrets = {
|
|
|
7380
7557
|
import { spawnSync } from "node:child_process";
|
|
7381
7558
|
import { rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
7382
7559
|
import { resolve as resolve4 } from "node:path";
|
|
7560
|
+
|
|
7561
|
+
// src/version.ts
|
|
7562
|
+
var VERSION = "0.2.0";
|
|
7563
|
+
|
|
7564
|
+
// src/commands/team.ts
|
|
7383
7565
|
async function checkSimilar(ctx, task2) {
|
|
7384
7566
|
const similar = await ctx.api.similar(task2).catch(() => []);
|
|
7385
7567
|
if (!similar.length || ctx.json) return { go: true, similar };
|
|
@@ -7796,6 +7978,35 @@ var verify = {
|
|
|
7796
7978
|
ctx.ui.out(r.sent ? `sent to ${me.user.email}. open the link in it.` : "this gateway can't send email; ask whoever runs it");
|
|
7797
7979
|
}
|
|
7798
7980
|
};
|
|
7981
|
+
var bug = {
|
|
7982
|
+
name: "bug",
|
|
7983
|
+
aliases: ["report"],
|
|
7984
|
+
group: "account",
|
|
7985
|
+
usage: '"<what happened>" [--expected "<what you expected>"]',
|
|
7986
|
+
summary: "report a bug: a person reads every report, and you get an email when it changes",
|
|
7987
|
+
example: 'airgate bug "airgate down said the patch was empty after a crew"',
|
|
7988
|
+
notes: [
|
|
7989
|
+
"it sends your words plus the cli version, your OS and node version, and the folder name (never its contents). --no-context sends only your words.",
|
|
7990
|
+
"or pipe a longer report in: airgate bug < report.txt"
|
|
7991
|
+
],
|
|
7992
|
+
flags: {
|
|
7993
|
+
expected: { type: "string", value: "text", help: "what you expected instead" },
|
|
7994
|
+
"no-context": { type: "boolean", help: "don't send the cli version, OS and folder name" }
|
|
7995
|
+
},
|
|
7996
|
+
async run(ctx) {
|
|
7997
|
+
let text2 = ctx.args.positionals.join(" ").trim();
|
|
7998
|
+
if (!text2 && !ctx.io.stdin.isTTY) {
|
|
7999
|
+
const chunks = [];
|
|
8000
|
+
for await (const c of ctx.io.stdin) chunks.push(Buffer.from(c));
|
|
8001
|
+
text2 = Buffer.concat(chunks).toString("utf8").trim();
|
|
8002
|
+
}
|
|
8003
|
+
if (text2.length < 5) throw new UsageError('airgate bug "<what happened>"', 'a sentence is enough; add --expected "<what you expected>"');
|
|
8004
|
+
const context = bool(ctx.args, "no-context") ? void 0 : { cli: VERSION, os: `${process.platform} ${process.arch}`, node: process.version, folder: ctx.io.cwd.split("/").pop() ?? "" };
|
|
8005
|
+
const r = await ctx.api.reportBug(text2.slice(0, 8e3), str(ctx.args, "expected"), context);
|
|
8006
|
+
if (ctx.json) return ctx.ui.json(r);
|
|
8007
|
+
ctx.ui.out(`${ctx.ui.c("ok", "reported")} ${r.bug.id}. a person reads it; you get an email when it changes.`);
|
|
8008
|
+
}
|
|
8009
|
+
};
|
|
7799
8010
|
|
|
7800
8011
|
// src/commands/pods.ts
|
|
7801
8012
|
import { execFileSync } from "node:child_process";
|
|
@@ -7924,7 +8135,7 @@ var launch = {
|
|
|
7924
8135
|
network: { type: "string", value: "policy", choices: NETWORK_NAMES, default: "allowlist", help: "allowlist | open | off" },
|
|
7925
8136
|
allow: { type: "list", value: "host", help: "extra hosts for the allowlist (repeatable, or comma-separated)" },
|
|
7926
8137
|
"mask-term": { type: "list", value: "word", help: "always mask this word: a client, a codename (repeatable)" },
|
|
7927
|
-
"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 "..."' }
|
|
7928
8139
|
},
|
|
7929
8140
|
async run(ctx) {
|
|
7930
8141
|
if (ctx.args.positionals.length) throw new UsageError(`unexpected "${ctx.args.positionals[0]}"`, "name the pod with --name");
|
|
@@ -7989,10 +8200,11 @@ function specFromFlags(ctx, defaults = {}) {
|
|
|
7989
8200
|
const network2 = str(a, "network") ?? "allowlist";
|
|
7990
8201
|
const autoStop = str(a, "auto-stop");
|
|
7991
8202
|
let autoStopMinutes = null;
|
|
7992
|
-
if (autoStop === "keep-awake" || autoStop === "never"
|
|
7993
|
-
|
|
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) {
|
|
7994
8206
|
autoStopMinutes = Number(autoStop);
|
|
7995
|
-
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");
|
|
7996
8208
|
}
|
|
7997
8209
|
const repo = repoArg(ctx, str(a, "repo"));
|
|
7998
8210
|
const template = str(a, "template") ?? "code";
|
|
@@ -8061,7 +8273,7 @@ var info = {
|
|
|
8061
8273
|
["network", s2.network + (s2.network === "allowlist" && s2.allow?.length ? ` \xB7 ${s2.allow.join(", ")}` : "")],
|
|
8062
8274
|
["mask", s2.masking ? `on${s2.maskTerms?.length ? ` \xB7 ${s2.maskTerms.length} terms` : ""}` : ctx.ui.c("warn", "off")],
|
|
8063
8275
|
["undo", s2.autoCheckpoint ? "auto checkpoints" : "manual only"],
|
|
8064
|
-
["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`]
|
|
8065
8277
|
];
|
|
8066
8278
|
if (s2.repo) rows2.push(["repo", s2.repo]);
|
|
8067
8279
|
for (const [i, j] of (s2.jobs ?? []).entries()) {
|
|
@@ -8169,7 +8381,7 @@ var templates = {
|
|
|
8169
8381
|
const a = info2 ? info2.templates[id] : id === "code" ? { available: true } : { available: false, reason: "not on this gateway" };
|
|
8170
8382
|
const avail = a?.available ? ui2.c("ok", "yes") : ui2.c("dim", a?.reason ?? "no");
|
|
8171
8383
|
const rate = t.proOnly ? `pro ${price(PLANS.pro.hourlyUsd)}` : `${price(PLANS.payg.hourlyUsd)} \xB7 pro ${price(PLANS.pro.hourlyUsd)}`;
|
|
8172
|
-
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];
|
|
8173
8385
|
});
|
|
8174
8386
|
ui2.table(rows2, { head: ["template", "name", "", "available", "per active hour"] });
|
|
8175
8387
|
ui2.out();
|
|
@@ -8352,118 +8564,15 @@ var log = {
|
|
|
8352
8564
|
// src/commands/mcp.ts
|
|
8353
8565
|
import { createInterface } from "node:readline";
|
|
8354
8566
|
|
|
8355
|
-
// src/version.ts
|
|
8356
|
-
var VERSION = "0.1.7";
|
|
8357
|
-
|
|
8358
8567
|
// src/commands/updown.ts
|
|
8359
8568
|
import { appendFileSync, existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, realpathSync as realpathSync2, statSync as statSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
8360
8569
|
import { homedir as homedir2 } from "node:os";
|
|
8361
8570
|
import { basename, dirname as dirname2, isAbsolute, join as join4, relative as relative2, resolve as resolve5 } from "node:path";
|
|
8362
|
-
|
|
8363
|
-
// src/pack.ts
|
|
8364
|
-
import { spawn, spawnSync as spawnSync2 } from "node:child_process";
|
|
8365
|
-
import { existsSync, lstatSync, readdirSync } from "node:fs";
|
|
8366
|
-
import { join as join2 } from "node:path";
|
|
8367
|
-
var UPLOAD_LIMIT = 32 * 6 * 1024 * 1024;
|
|
8368
|
-
var TooLargeError = class extends Error {
|
|
8369
|
-
limit;
|
|
8370
|
-
constructor(limit) {
|
|
8371
|
-
super(`over ${Math.round(limit / 1024 / 1024)} MiB packed`);
|
|
8372
|
-
this.limit = limit;
|
|
8373
|
-
}
|
|
8374
|
-
};
|
|
8375
|
-
function isEnvFile(path) {
|
|
8376
|
-
const name = path.split("/").pop() ?? "";
|
|
8377
|
-
return /^\.env($|\.)/.test(name) && !/\.(example|sample|template|dist|defaults?|schema)$/.test(name);
|
|
8378
|
-
}
|
|
8379
|
-
var ENV_EXCLUDES = [".env", ".env.local", ".env.*.local", ".env.development", ".env.production", ".env.test", ".env.staging", ".envrc"].map((p) => `--exclude=${p}`);
|
|
8380
|
-
function git(cwd, args, input) {
|
|
8381
|
-
const r = spawnSync2("git", args, { cwd, input, maxBuffer: 256 * 1024 * 1024 });
|
|
8382
|
-
return { status: r.status ?? (r.error ? 127 : 1), stdout: r.stdout?.toString("utf8") ?? "", stderr: r.stderr?.toString("utf8") ?? "", error: r.error };
|
|
8383
|
-
}
|
|
8384
|
-
function isGitRepo(dir) {
|
|
8385
|
-
return git(dir, ["rev-parse", "--is-inside-work-tree"]).stdout.trim() === "true";
|
|
8386
|
-
}
|
|
8387
|
-
function gitTopLevel(dir) {
|
|
8388
|
-
const r = git(dir, ["rev-parse", "--show-toplevel"]);
|
|
8389
|
-
return r.status === 0 ? r.stdout.trim() : null;
|
|
8390
|
-
}
|
|
8391
|
-
function gitFiles(dir) {
|
|
8392
|
-
const r = git(dir, ["ls-files", "-co", "--exclude-standard", "-z"]);
|
|
8393
|
-
if (r.status !== 0) throw new Error(`git ls-files: ${r.stderr.trim()}`);
|
|
8394
|
-
const seen = /* @__PURE__ */ new Set();
|
|
8395
|
-
const out = [];
|
|
8396
|
-
for (const f of r.stdout.split("\0")) {
|
|
8397
|
-
if (!f || seen.has(f)) continue;
|
|
8398
|
-
seen.add(f);
|
|
8399
|
-
try {
|
|
8400
|
-
lstatSync(join2(dir, f));
|
|
8401
|
-
out.push(f);
|
|
8402
|
-
} catch {
|
|
8403
|
-
}
|
|
8404
|
-
}
|
|
8405
|
-
return out;
|
|
8406
|
-
}
|
|
8407
|
-
function runTar(cwd, args, input, limit = UPLOAD_LIMIT) {
|
|
8408
|
-
return new Promise((resolve9, reject) => {
|
|
8409
|
-
const child = spawn("tar", args, { cwd, env: { ...process.env, COPYFILE_DISABLE: "1" }, stdio: ["pipe", "pipe", "pipe"] });
|
|
8410
|
-
const chunks = [];
|
|
8411
|
-
let size2 = 0;
|
|
8412
|
-
let err = "";
|
|
8413
|
-
let over2 = false;
|
|
8414
|
-
child.stdout.on("data", (d) => {
|
|
8415
|
-
size2 += d.length;
|
|
8416
|
-
if (size2 > limit) {
|
|
8417
|
-
over2 = true;
|
|
8418
|
-
child.kill();
|
|
8419
|
-
return;
|
|
8420
|
-
}
|
|
8421
|
-
chunks.push(d);
|
|
8422
|
-
});
|
|
8423
|
-
child.stderr.on("data", (d) => err += d.toString());
|
|
8424
|
-
child.on("error", (e) => reject(new Error(`tar: ${e.message}`)));
|
|
8425
|
-
child.on("close", (code3) => {
|
|
8426
|
-
if (over2) return reject(new TooLargeError(limit));
|
|
8427
|
-
if (code3 !== 0) return reject(new Error(`tar failed: ${err.trim().split("\n").slice(-3).join(" ")}`));
|
|
8428
|
-
resolve9(Buffer.concat(chunks));
|
|
8429
|
-
});
|
|
8430
|
-
child.stdin.on("error", () => void 0);
|
|
8431
|
-
child.stdin.end(input ?? "");
|
|
8432
|
-
});
|
|
8433
|
-
}
|
|
8434
|
-
async function packProject(dir, limit = UPLOAD_LIMIT) {
|
|
8435
|
-
if (isGitRepo(dir)) {
|
|
8436
|
-
const tracked = new Set(git(dir, ["ls-files", "-c", "-z"]).stdout.split("\0"));
|
|
8437
|
-
const all = gitFiles(dir).filter((f) => !f.startsWith(".airgate/"));
|
|
8438
|
-
const skipped2 = all.filter((f) => (isEnvFile(f) || f.split("/").pop() === ".envrc") && !tracked.has(f));
|
|
8439
|
-
const files2 = all.filter((f) => !skipped2.includes(f));
|
|
8440
|
-
if (!files2.length) throw new Error("no files to send (git sees nothing tracked or untracked here)");
|
|
8441
|
-
const archive2 = await runTar(dir, ["--null", "-T", "-", "-czf", "-"], files2.join("\0") + "\0", limit);
|
|
8442
|
-
return { archive: archive2, files: files2.length, git: true, skipped: skipped2 };
|
|
8443
|
-
}
|
|
8444
|
-
const skipped = readdirSync(dir).filter((f) => isEnvFile(f) || f === ".envrc");
|
|
8445
|
-
const archive = await runTar(dir, ["--exclude=node_modules", "--exclude=.git", "--exclude=.airgate", ...ENV_EXCLUDES, "-czf", "-", "."], null, limit);
|
|
8446
|
-
return { archive, files: -1, git: false, skipped };
|
|
8447
|
-
}
|
|
8448
|
-
var CLAUDE_CONFIG = ["settings.json", "CLAUDE.md", "skills", "agents", "commands"];
|
|
8449
|
-
async function packClaudeConfig(home, limit = UPLOAD_LIMIT) {
|
|
8450
|
-
const root = join2(home, ".claude");
|
|
8451
|
-
if (!existsSync(root)) return null;
|
|
8452
|
-
const parts = CLAUDE_CONFIG.filter((p) => existsSync(join2(root, p)));
|
|
8453
|
-
if (!parts.length) return null;
|
|
8454
|
-
const archive = await runTar(
|
|
8455
|
-
root,
|
|
8456
|
-
["--exclude=*credentials*", "--exclude=*.credentials*", "--exclude=.env*", "--exclude=node_modules", "-czf", "-", ...parts],
|
|
8457
|
-
null,
|
|
8458
|
-
limit
|
|
8459
|
-
);
|
|
8460
|
-
return { archive, parts };
|
|
8461
|
-
}
|
|
8462
|
-
|
|
8463
|
-
// src/commands/updown.ts
|
|
8571
|
+
init_pack();
|
|
8464
8572
|
import { gunzipSync } from "node:zlib";
|
|
8465
8573
|
|
|
8466
8574
|
// src/sessions.ts
|
|
8575
|
+
init_pack();
|
|
8467
8576
|
import { createHash, randomUUID } from "node:crypto";
|
|
8468
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";
|
|
8469
8578
|
import { tmpdir } from "node:os";
|
|
@@ -9039,14 +9148,14 @@ function printSessions(ctx, list2) {
|
|
|
9039
9148
|
}
|
|
9040
9149
|
}
|
|
9041
9150
|
function printFiles(ctx, blocks, toErr) {
|
|
9042
|
-
const
|
|
9151
|
+
const line3 = toErr ? ctx.ui.err : ctx.ui.out;
|
|
9043
9152
|
const c = toErr ? ctx.ui.ce : ctx.ui.c;
|
|
9044
9153
|
const files2 = orderFiles(blocks.map((b2) => b2.file));
|
|
9045
9154
|
const width = Math.max(...files2.map((f) => f.path.length + (f.from ? f.from.length + 4 : 0)));
|
|
9046
9155
|
for (const f of files2) {
|
|
9047
9156
|
const name = f.from ? `${f.from} \u2192 ${f.path}` : f.path;
|
|
9048
9157
|
const mark = f.risky ? c("warn", "!") : " ";
|
|
9049
|
-
|
|
9158
|
+
line3(` ${mark} ${STATUS_MARK[f.status]} ${f.risky ? name.padEnd(width) + " " + c("warn", f.reason ?? "") : name}`.trimEnd());
|
|
9050
9159
|
}
|
|
9051
9160
|
}
|
|
9052
9161
|
var down2 = {
|
|
@@ -9213,7 +9322,7 @@ var LineReader = class {
|
|
|
9213
9322
|
}
|
|
9214
9323
|
};
|
|
9215
9324
|
function ttyPrompt(stdin, out) {
|
|
9216
|
-
return (question, opts2 = {}) => new Promise((
|
|
9325
|
+
return (question, opts2 = {}) => new Promise((resolve10, reject) => {
|
|
9217
9326
|
out.write(question);
|
|
9218
9327
|
const decoder = new StringDecoder2("utf8");
|
|
9219
9328
|
let answer = "";
|
|
@@ -9226,7 +9335,7 @@ function ttyPrompt(stdin, out) {
|
|
|
9226
9335
|
stdin.pause();
|
|
9227
9336
|
out.write("\n");
|
|
9228
9337
|
if (err) reject(err);
|
|
9229
|
-
else
|
|
9338
|
+
else resolve10(answer);
|
|
9230
9339
|
};
|
|
9231
9340
|
const onData = (d) => {
|
|
9232
9341
|
for (const ch of decoder.write(d)) {
|
|
@@ -9253,16 +9362,16 @@ function ttyPrompt(stdin, out) {
|
|
|
9253
9362
|
}
|
|
9254
9363
|
function openInBrowser(url) {
|
|
9255
9364
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : ["xdg-open", [url]];
|
|
9256
|
-
return new Promise((
|
|
9365
|
+
return new Promise((resolve10) => {
|
|
9257
9366
|
try {
|
|
9258
9367
|
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
9259
|
-
child.on("error", () =>
|
|
9368
|
+
child.on("error", () => resolve10(false));
|
|
9260
9369
|
child.on("spawn", () => {
|
|
9261
9370
|
child.unref();
|
|
9262
|
-
|
|
9371
|
+
resolve10(true);
|
|
9263
9372
|
});
|
|
9264
9373
|
} catch {
|
|
9265
|
-
|
|
9374
|
+
resolve10(false);
|
|
9266
9375
|
}
|
|
9267
9376
|
});
|
|
9268
9377
|
}
|
|
@@ -9359,6 +9468,7 @@ async function runCommand(ctx, cmd, argv, cwd) {
|
|
|
9359
9468
|
import { existsSync as existsSync5, readFileSync as readFileSync8, rmSync as rmSync5, statSync as statSync3, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9360
9469
|
import { basename as basename2, resolve as resolve6 } from "node:path";
|
|
9361
9470
|
import { randomBytes } from "node:crypto";
|
|
9471
|
+
init_pack();
|
|
9362
9472
|
|
|
9363
9473
|
// src/relay.ts
|
|
9364
9474
|
import { chmodSync as chmodSync2, existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync6, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "node:fs";
|
|
@@ -9863,6 +9973,7 @@ When you are done, leave your changes in the files there (no need to commit or p
|
|
|
9863
9973
|
// src/commands/crews.ts
|
|
9864
9974
|
import { existsSync as existsSync6, readFileSync as readFileSync9, realpathSync as realpathSync3, rmSync as rmSync6, statSync as statSync4, writeFileSync as writeFileSync7 } from "node:fs";
|
|
9865
9975
|
import { relative as relative3, resolve as resolve7 } from "node:path";
|
|
9976
|
+
init_pack();
|
|
9866
9977
|
var KINDS = ["race", "contest", "council", "squad", "split"];
|
|
9867
9978
|
var ACTIONS = ["show", "say", "stop", "down", "rm", "list"];
|
|
9868
9979
|
var AGENT_ALIAS = { claude: "claude-code", "claude-code": "claude-code", codex: "codex", opencode: "opencode", pi: "pi" };
|
|
@@ -10508,6 +10619,67 @@ var TOOLS = [
|
|
|
10508
10619
|
return ctx.api.note(need(a, "text"));
|
|
10509
10620
|
}
|
|
10510
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
|
+
},
|
|
10511
10683
|
{
|
|
10512
10684
|
name: "pod_share",
|
|
10513
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.",
|
|
@@ -10651,11 +10823,11 @@ var mcp = {
|
|
|
10651
10823
|
`));
|
|
10652
10824
|
if (relayer) ctx.io.stderr.write(`airgate mcp: signing pods' ${relayer.vendors.join(" / ")} calls from this laptop while this runs
|
|
10653
10825
|
`);
|
|
10654
|
-
for await (const
|
|
10655
|
-
if (!
|
|
10826
|
+
for await (const line3 of rl) {
|
|
10827
|
+
if (!line3.trim()) continue;
|
|
10656
10828
|
let msg;
|
|
10657
10829
|
try {
|
|
10658
|
-
msg = JSON.parse(
|
|
10830
|
+
msg = JSON.parse(line3);
|
|
10659
10831
|
} catch {
|
|
10660
10832
|
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "parse error" } });
|
|
10661
10833
|
continue;
|
|
@@ -11100,21 +11272,21 @@ function pad2(s2, w) {
|
|
|
11100
11272
|
function wrap(s2, w) {
|
|
11101
11273
|
const out = [];
|
|
11102
11274
|
for (const para of s2.split("\n")) {
|
|
11103
|
-
let
|
|
11275
|
+
let line3 = "";
|
|
11104
11276
|
for (const word of para.split(/(\s+)/)) {
|
|
11105
|
-
if ([...
|
|
11106
|
-
|
|
11277
|
+
if ([...line3 + word].length <= w) {
|
|
11278
|
+
line3 += word;
|
|
11107
11279
|
continue;
|
|
11108
11280
|
}
|
|
11109
|
-
if (
|
|
11281
|
+
if (line3.trim()) out.push(line3.trimEnd());
|
|
11110
11282
|
let rest = word.trimStart();
|
|
11111
11283
|
while ([...rest].length > w) {
|
|
11112
11284
|
out.push([...rest].slice(0, w).join(""));
|
|
11113
11285
|
rest = [...rest].slice(w).join("");
|
|
11114
11286
|
}
|
|
11115
|
-
|
|
11287
|
+
line3 = rest;
|
|
11116
11288
|
}
|
|
11117
|
-
out.push(
|
|
11289
|
+
out.push(line3.trimEnd());
|
|
11118
11290
|
}
|
|
11119
11291
|
return out;
|
|
11120
11292
|
}
|
|
@@ -11661,8 +11833,8 @@ function header(c, s2) {
|
|
|
11661
11833
|
if (x + right.length + 1 < c.cols) c.text(c.cols - right.length - 1, 0, right, { fg: s2.connected ? "ok" : "warn" });
|
|
11662
11834
|
}
|
|
11663
11835
|
function drawArt(c, x, y, a, w = Infinity, h = Infinity) {
|
|
11664
|
-
a.lines.slice(0, Math.max(0, h)).forEach((
|
|
11665
|
-
const chars3 = [...
|
|
11836
|
+
a.lines.slice(0, Math.max(0, h)).forEach((line3, r) => {
|
|
11837
|
+
const chars3 = [...line3].slice(0, Math.max(0, w));
|
|
11666
11838
|
const tones2 = a.tones?.[r];
|
|
11667
11839
|
let k = 0;
|
|
11668
11840
|
while (k < chars3.length) {
|
|
@@ -11691,8 +11863,8 @@ function splash(c, s2) {
|
|
|
11691
11863
|
y += 2;
|
|
11692
11864
|
}
|
|
11693
11865
|
const mark = "\u22A0 ";
|
|
11694
|
-
const
|
|
11695
|
-
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));
|
|
11696
11868
|
c.text(lx, y, mark, { bold: true });
|
|
11697
11869
|
c.text(lx + mark.length, y, TAGLINE, { fg: "dim" }, c.cols - lx - mark.length);
|
|
11698
11870
|
const skip = "any key skips";
|
|
@@ -12035,8 +12207,8 @@ function modal(c, s2, m) {
|
|
|
12035
12207
|
(m.checkpoints ?? []).slice(start, start + rowsAvail).forEach((cp, i) => {
|
|
12036
12208
|
const isSel = start + i === m.sel;
|
|
12037
12209
|
c.text(b2.x + 2, b2.y + 1 + i, isSel ? "> " : " ", { bold: isSel });
|
|
12038
|
-
const
|
|
12039
|
-
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 });
|
|
12040
12212
|
});
|
|
12041
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);
|
|
12042
12214
|
return;
|
|
@@ -12103,7 +12275,7 @@ function formModal(c, s2, f) {
|
|
|
12103
12275
|
v = text2(f.task, "what the agent should do each time");
|
|
12104
12276
|
break;
|
|
12105
12277
|
case "template":
|
|
12106
|
-
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}`;
|
|
12107
12279
|
break;
|
|
12108
12280
|
case "agent": {
|
|
12109
12281
|
const a = AGENT_LIST[f.agent];
|
|
@@ -12659,6 +12831,158 @@ var snapshot2 = {
|
|
|
12659
12831
|
}
|
|
12660
12832
|
};
|
|
12661
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
|
+
|
|
12662
12986
|
// src/cli.ts
|
|
12663
12987
|
var help = {
|
|
12664
12988
|
name: "help",
|
|
@@ -12684,6 +13008,9 @@ var COMMANDS = [
|
|
|
12684
13008
|
reset,
|
|
12685
13009
|
deleteAccount,
|
|
12686
13010
|
verify,
|
|
13011
|
+
bug,
|
|
13012
|
+
server,
|
|
13013
|
+
alwaysOn,
|
|
12687
13014
|
team,
|
|
12688
13015
|
github,
|
|
12689
13016
|
pods,
|
|
@@ -12785,7 +13112,7 @@ function printCommandHelp(ui2, c) {
|
|
|
12785
13112
|
ui2.out(`example: ${c.example}`);
|
|
12786
13113
|
}
|
|
12787
13114
|
function report(ui2, err, cmd, env) {
|
|
12788
|
-
const
|
|
13115
|
+
const line3 = (msg, hint) => {
|
|
12789
13116
|
ui2.err(`${ui2.ce("bad", "error")}: ${msg}`);
|
|
12790
13117
|
if (hint) ui2.err(ui2.ce("dim", hint));
|
|
12791
13118
|
};
|
|
@@ -12797,35 +13124,35 @@ function report(ui2, err, cmd, env) {
|
|
|
12797
13124
|
return 2;
|
|
12798
13125
|
}
|
|
12799
13126
|
if (err instanceof CliError) {
|
|
12800
|
-
|
|
13127
|
+
line3(err.message, err.hint);
|
|
12801
13128
|
return err.exitCode;
|
|
12802
13129
|
}
|
|
12803
13130
|
if (err instanceof NetworkError) {
|
|
12804
|
-
|
|
13131
|
+
line3(err.message, "is it up? airgate status \xB7 another gateway: airgate login --gateway <url> or AIRGATE_GATEWAY");
|
|
12805
13132
|
return 1;
|
|
12806
13133
|
}
|
|
12807
13134
|
if (err instanceof ApiError) {
|
|
12808
13135
|
if (err.status === 401 && err.code !== "bad_credentials") {
|
|
12809
|
-
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");
|
|
12810
13137
|
else ui2.err(`not signed in. run: airgate login`);
|
|
12811
13138
|
return 1;
|
|
12812
13139
|
}
|
|
12813
13140
|
if (err.code === "plan_required") {
|
|
12814
|
-
|
|
13141
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1));
|
|
12815
13142
|
const url = typeof err.extra.checkoutUrl === "string" ? err.extra.checkoutUrl : void 0;
|
|
12816
13143
|
ui2.err(url ? `upgrade: ${url}` : `upgrade: airgate upgrade pro ($${PLANS.pro.monthlyUsd}/month, ${PLANS.pro.includedHours}h included)`);
|
|
12817
13144
|
return 1;
|
|
12818
13145
|
}
|
|
12819
13146
|
if (err.code === "template_unavailable") {
|
|
12820
|
-
|
|
13147
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1), "see what this gateway offers: airgate templates");
|
|
12821
13148
|
return 1;
|
|
12822
13149
|
}
|
|
12823
13150
|
if (err.code === "team_plan_required" || err.code === "no_seat") {
|
|
12824
|
-
|
|
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>");
|
|
12825
13152
|
return 1;
|
|
12826
13153
|
}
|
|
12827
13154
|
if (err.status === 402 || err.code === "billing_required") {
|
|
12828
|
-
|
|
13155
|
+
line3(`${err.message}`);
|
|
12829
13156
|
const p = PLANS;
|
|
12830
13157
|
ui2.err(` payg no monthly fee, $${p.payg.hourlyUsd.toFixed(2)}/active hour`);
|
|
12831
13158
|
ui2.err(` pro $${p.pro.monthlyUsd}/month, ${p.pro.includedHours}h included, then $${p.pro.hourlyUsd.toFixed(2)}/h`);
|
|
@@ -12834,30 +13161,30 @@ function report(ui2, err, cmd, env) {
|
|
|
12834
13161
|
return 1;
|
|
12835
13162
|
}
|
|
12836
13163
|
if (err.code === "signups_closed") {
|
|
12837
|
-
|
|
13164
|
+
line3("sign-ups aren't open yet", "have an invite token? airgate login --token <token>");
|
|
12838
13165
|
return 1;
|
|
12839
13166
|
}
|
|
12840
13167
|
if (err.code === "signup_code_required") {
|
|
12841
|
-
|
|
13168
|
+
line3("an early access code is needed to sign up", "airgate signup --code <code>");
|
|
12842
13169
|
return 1;
|
|
12843
13170
|
}
|
|
12844
13171
|
if (err.code === "email_taken") {
|
|
12845
|
-
|
|
13172
|
+
line3("an account with this email exists", "sign in: airgate login \xB7 forgot the password? airgate forgot");
|
|
12846
13173
|
return 1;
|
|
12847
13174
|
}
|
|
12848
13175
|
if (err.status === 429) {
|
|
12849
|
-
|
|
13176
|
+
line3(err.message.toLowerCase(), "wait a few minutes and try again");
|
|
12850
13177
|
return 1;
|
|
12851
13178
|
}
|
|
12852
13179
|
if (err.code === "capacity") {
|
|
12853
|
-
|
|
13180
|
+
line3(err.message.toLowerCase(), "every machine is busy; try again in a minute");
|
|
12854
13181
|
return 1;
|
|
12855
13182
|
}
|
|
12856
13183
|
if (err.code === "not_ready") {
|
|
12857
|
-
|
|
13184
|
+
line3(err.message.toLowerCase(), "wait for it: airgate info <pod>");
|
|
12858
13185
|
return 1;
|
|
12859
13186
|
}
|
|
12860
|
-
|
|
13187
|
+
line3(err.message.charAt(0).toLowerCase() + err.message.slice(1));
|
|
12861
13188
|
return 1;
|
|
12862
13189
|
}
|
|
12863
13190
|
const e = err;
|
|
@@ -12865,7 +13192,7 @@ function report(ui2, err, cmd, env) {
|
|
|
12865
13192
|
ui2.err("cancelled.");
|
|
12866
13193
|
return 1;
|
|
12867
13194
|
}
|
|
12868
|
-
|
|
13195
|
+
line3(e?.message ?? String(err));
|
|
12869
13196
|
if (env.AIRGATE_DEBUG && e?.stack) ui2.err(e.stack);
|
|
12870
13197
|
return 1;
|
|
12871
13198
|
}
|