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.
Files changed (2) hide show
  1. package/dist/airgate.mjs +500 -208
  2. 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 line2 = lines[i].trim();
272
- if (!line2 || line2.startsWith("#")) continue;
273
- const m = /^(?:export\s+)?([A-Za-z_][A-Za-z0-9_.-]*)\s*=\s*(.*)$/.exec(line2);
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 line2 of block.split("\n")) {
842
- if (line2.startsWith(":")) continue;
843
- if (line2.startsWith("event:")) event = line2.slice(6).trim();
844
- else if (line2.startsWith("data:")) data.push(line2.slice(5).replace(/^ /, ""));
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((line2, y) => {
1403
- for (let i = 0; i < line2.length; i++)
1404
- if (line2[i] === "#") {
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 line2 = "";
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
- line2 += " ";
1666
+ line3 += " ";
1493
1667
  continue;
1494
1668
  }
1495
1669
  if (cell !== null) {
1496
- line2 += cell;
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
- line2 += d < width / 2 ? core : d < width ? edge ?? core : fill[g];
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(line2);
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 line2 = text2.split("\n").find((l) => l.trim())?.trim() ?? "";
1727
- return line2 ? `"${line2.slice(0, 24)}"` : "text";
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 line2 = (v) => typeof v === "string" && !!v.trim() && !/[\u0000-\u001f\u007f-\u009f]/.test(v);
2299
- if (!line2(name))
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 && !line2(opts2.note))
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 line2 = forward ? pos : along - 1 - pos;
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 === line2)
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 < line2 : q > line2)) {
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 line2 = (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));
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 (!line2(x, y))
3286
+ if (!line3(x, y))
3113
3287
  continue;
3114
- const up2 = line2(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));
3115
- const down4 = line2(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));
3116
- const left = line2(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));
3117
- const right = line2(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));
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 line2 = this.#line;
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
- line2[c] = chars3[i] || 32;
3764
- out += (r ? "\n" : "") + String.fromCharCode.apply(null, line2);
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((line2, r) => {
4660
- const lx = lefts[r], len = line2.length;
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 = line2.charCodeAt(i);
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 line2 = "";
4855
+ let line3 = "";
4682
4856
  for (let word of para.split(" ").filter(Boolean)) {
4683
4857
  while (word.length > width) {
4684
- if (line2)
4685
- out.push(line2);
4686
- line2 = "";
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 (!line2)
4691
- line2 = word;
4692
- else if (line2.length + 1 + word.length <= width)
4693
- line2 += " " + word;
4864
+ if (!line3)
4865
+ line3 = word;
4866
+ else if (line3.length + 1 + word.length <= width)
4867
+ line3 += " " + word;
4694
4868
  else {
4695
- out.push(line2);
4696
- line2 = word;
4869
+ out.push(line3);
4870
+ line3 = word;
4697
4871
  }
4698
4872
  }
4699
- out.push(line2);
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((line2, i) => put(3 + i, 0, line2));
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((line2, j) => {
5518
- const left = x0 + Math.floor((slot2 - [...line2].length) / 2);
5519
- [...line2].forEach((g, i) => grid2[top + j][left + i] = g);
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((line2, y) => {
5559
- [...line2].forEach((ch, x) => {
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((line2, y) => {
6085
+ return a.lines.map((line3, y) => {
5912
6086
  const tones2 = a.tones?.[y];
5913
- if (!on || !tones2) return line2.trimEnd();
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
- [...line2].forEach((ch, x) => {
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: (line2 = "") => void io.stdout.write(line2 + "\n"),
5939
- err: (line2 = "") => void io.stderr.write(line2 + "\n"),
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 [line2] = ui2.art(bar(used / scale2, 24));
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 `${line2} ${hours(used)} ${ui2.c("dim", of)}`;
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.1.8";
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: "sleep after this many idle minutes (default 10), or keep-awake (pro)" }
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") autoStopMinutes = 0;
8030
- else if (autoStop !== void 0) {
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, or keep-awake");
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 (billed while awake)" : `sleeps after ${s2.autoStopMinutes ?? 10} min idle`]
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 on, idle is free") : ""), avail, rate];
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 line2 = toErr ? ctx.ui.err : ctx.ui.out;
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
- line2(` ${mark} ${STATUS_MARK[f.status]} ${f.risky ? name.padEnd(width) + " " + c("warn", f.reason ?? "") : name}`.trimEnd());
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((resolve9, reject) => {
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 resolve9(answer);
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((resolve9) => {
9365
+ return new Promise((resolve10) => {
9291
9366
  try {
9292
9367
  const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
9293
- child.on("error", () => resolve9(false));
9368
+ child.on("error", () => resolve10(false));
9294
9369
  child.on("spawn", () => {
9295
9370
  child.unref();
9296
- resolve9(true);
9371
+ resolve10(true);
9297
9372
  });
9298
9373
  } catch {
9299
- resolve9(false);
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 line2 of rl) {
10689
- if (!line2.trim()) continue;
10826
+ for await (const line3 of rl) {
10827
+ if (!line3.trim()) continue;
10690
10828
  let msg;
10691
10829
  try {
10692
- msg = JSON.parse(line2);
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 line2 = "";
11275
+ let line3 = "";
11138
11276
  for (const word of para.split(/(\s+)/)) {
11139
- if ([...line2 + word].length <= w) {
11140
- line2 += word;
11277
+ if ([...line3 + word].length <= w) {
11278
+ line3 += word;
11141
11279
  continue;
11142
11280
  }
11143
- if (line2.trim()) out.push(line2.trimEnd());
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
- line2 = rest;
11287
+ line3 = rest;
11150
11288
  }
11151
- out.push(line2.trimEnd());
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((line2, r) => {
11699
- const chars3 = [...line2].slice(0, Math.max(0, w));
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 line2 = mark + TAGLINE;
11729
- const lx = Math.max(0, Math.floor((c.cols - line2.length) / 2));
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 line2 = `${pad2(`#${start + i + 1}`, 4)} ${pad2(ago2(cp.at, s2.now), 4)} ${pad2(cp.source, 6)} ${cp.label}`;
12073
- c.text(b2.x + 4, b2.y + 1 + i, pad2(line2, b2.w - 6), { inverse: isSel, fg: cp.source === "auto" && !isSel ? "dim" : void 0 });
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 on \xB7 " : ""}${t.tagline}`;
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 line2 = (msg, hint) => {
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
- line2(err.message, err.hint);
13127
+ line3(err.message, err.hint);
12836
13128
  return err.exitCode;
12837
13129
  }
12838
13130
  if (err instanceof NetworkError) {
12839
- line2(err.message, "is it up? airgate status \xB7 another gateway: airgate login --gateway <url> or AIRGATE_GATEWAY");
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) line2("AIRGATE_TOKEN was not accepted (expired, revoked, or for another gateway)", "unset it, or run: airgate login");
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
- line2(err.message.charAt(0).toLowerCase() + err.message.slice(1));
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
- line2(err.message.charAt(0).toLowerCase() + err.message.slice(1), "see what this gateway offers: airgate templates");
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
- line2(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>");
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
- line2(`${err.message}`);
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
- line2("sign-ups aren't open yet", "have an invite token? airgate login --token <token>");
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
- line2("an early access code is needed to sign up", "airgate signup --code <code>");
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
- line2("an account with this email exists", "sign in: airgate login \xB7 forgot the password? airgate forgot");
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
- line2(err.message.toLowerCase(), "wait a few minutes and try again");
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
- line2(err.message.toLowerCase(), "every machine is busy; try again in a minute");
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
- line2(err.message.toLowerCase(), "wait for it: airgate info <pod>");
13184
+ line3(err.message.toLowerCase(), "wait for it: airgate info <pod>");
12893
13185
  return 1;
12894
13186
  }
12895
- line2(err.message.charAt(0).toLowerCase() + err.message.slice(1));
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
- line2(e?.message ?? String(err));
13195
+ line3(e?.message ?? String(err));
12904
13196
  if (env.AIRGATE_DEBUG && e?.stack) ui2.err(e.stack);
12905
13197
  return 1;
12906
13198
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgate",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "Give your AI its own computer: the airgate command line.",
5
5
  "type": "module",
6
6
  "bin": {