airgate 0.2.3 → 0.2.4

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 +70 -26
  2. package/package.json +1 -1
package/dist/airgate.mjs CHANGED
@@ -130,6 +130,12 @@ var init_pack = __esm({
130
130
  });
131
131
 
132
132
  // ../sandbox/src/types.ts
133
+ var AIRLOCK_MODES = ["ask", "branches", "off"];
134
+ var AIRLOCK_MODE_INFO = {
135
+ ask: { name: "ask", line: "everything waits for you: pushes, pull requests, email, deploys, outside calls" },
136
+ branches: { name: "work branches go through", line: "pushes to work branches go through on their own; main, force pushes, pull requests, email and deploys still wait" },
137
+ off: { name: "off", line: "everything goes through on its own. it's all still recorded in the airlock, but nothing waits for you" }
138
+ };
133
139
  var SIZES = {
134
140
  small: { cpu: 8, memoryGb: 4, diskGb: 100 },
135
141
  standard: { cpu: 8, memoryGb: 8, diskGb: 100 },
@@ -2914,12 +2920,12 @@ function typeIn(src, options) {
2914
2920
  };
2915
2921
  });
2916
2922
  }
2917
- function level(t, period2, mode) {
2923
+ function level(t, period2, mode2) {
2918
2924
  const u = mod(t, period2) / period2;
2919
2925
  const ease2 = (k) => smoothstep(0, 1, k);
2920
- if (mode === "in")
2926
+ if (mode2 === "in")
2921
2927
  return u < 0.4 ? ease2(u / 0.4) : 1;
2922
- if (mode === "out")
2928
+ if (mode2 === "out")
2923
2929
  return u < 0.6 ? 1 : 1 - ease2((u - 0.6) / 0.4);
2924
2930
  return u < 0.3 ? ease2(u / 0.3) : u < 0.6 ? 1 : u < 0.9 ? 1 - ease2((u - 0.6) / 0.3) : 0;
2925
2931
  }
@@ -2929,7 +2935,7 @@ var MODE_WHAT = { in: "in", out: "out", inout: "in and out" };
2929
2935
  function dissolve(src, options) {
2930
2936
  const o = optionsOf2(options, "dissolve", ["period", "mode", "blob", "edge", "seed"]);
2931
2937
  const period2 = num(o.period, "dissolve.period", 6, "seconds");
2932
- const mode = choice(o.mode, "dissolve.mode", MODES, "inout");
2938
+ const mode2 = choice(o.mode, "dissolve.mode", MODES, "inout");
2933
2939
  const scale2 = 1 / num(o.blob, "dissolve.blob", 7, "atLeast1");
2934
2940
  const auto = o.edge === void 0;
2935
2941
  const edge = o.edge === "" ? "" : chars2(o.edge, "dissolve.edge", ".:", 1);
@@ -2947,10 +2953,10 @@ function dissolve(src, options) {
2947
2953
  return {
2948
2954
  moves: true,
2949
2955
  period: period2,
2950
- still: HELD[mode] * period2,
2951
- what: `dissolving ${MODE_WHAT[mode]}`,
2956
+ still: HELD[mode2] * period2,
2957
+ what: `dissolving ${MODE_WHAT[mode2]}`,
2952
2958
  setup: () => (t, s2, g) => {
2953
- const k = level(t, period2, mode);
2959
+ const k = level(t, period2, mode2);
2954
2960
  if (k >= 1)
2955
2961
  return copy(s2, g);
2956
2962
  const front = k * (1 + band);
@@ -2971,7 +2977,7 @@ function dissolve(src, options) {
2971
2977
  function fade2(src, options) {
2972
2978
  const o = optionsOf2(options, "fade", ["period", "mode", "ramp"]);
2973
2979
  const period2 = num(o.period, "fade.period", 6, "seconds");
2974
- const mode = choice(o.mode, "fade.mode", MODES, "inout");
2980
+ const mode2 = choice(o.mode, "fade.mode", MODES, "inout");
2975
2981
  const auto = o.ramp === void 0 || o.ramp === "auto";
2976
2982
  const chars3 = ramp(auto ? "standard" : o.ramp);
2977
2983
  if (chars3[0] !== " ")
@@ -2982,8 +2988,8 @@ function fade2(src, options) {
2982
2988
  return build2("fade", src, o, () => ({
2983
2989
  moves: true,
2984
2990
  period: period2,
2985
- still: HELD[mode] * period2,
2986
- what: `fading ${MODE_WHAT[mode]}`,
2991
+ still: HELD[mode2] * period2,
2992
+ what: `fading ${MODE_WHAT[mode2]}`,
2987
2993
  setup: () => {
2988
2994
  const steps = /* @__PURE__ */ new Map();
2989
2995
  const stepsOf = (c) => {
@@ -2996,7 +3002,7 @@ function fade2(src, options) {
2996
3002
  return v;
2997
3003
  };
2998
3004
  return (t, s2, g) => {
2999
- const k = level(t, period2, mode);
3005
+ const k = level(t, period2, mode2);
3000
3006
  if (k >= 1)
3001
3007
  return copy(s2, g);
3002
3008
  for (let y = 0, i = 0; y < g.rows; y++)
@@ -7070,7 +7076,7 @@ function approvalRows(ctx, items, names) {
7070
7076
  ctx.ui.c(a.state === "pending" ? "warn" : a.state === "denied" || a.state === "failed" ? "bad" : "ok", a.state),
7071
7077
  a.kind,
7072
7078
  names.get(a.podId) ?? (a.requestedBy ? `${a.requestedBy}'s pod` : a.podId),
7073
- a.summary + (a.requestedBy ? ctx.ui.c("dim", ` \xB7 from ${a.requestedBy}`) : "") + (a.needsApprover && a.state === "pending" && !a.requestedBy ? ctx.ui.c("warn", " \xB7 waits for an approver") : ""),
7079
+ a.summary + (a.auto ? ctx.ui.c("dim", ` \xB7 went through on its own (${a.auto})`) : "") + (a.requestedBy ? ctx.ui.c("dim", ` \xB7 from ${a.requestedBy}`) : "") + (a.needsApprover && a.state === "pending" && !a.requestedBy ? ctx.ui.c("warn", " \xB7 waits for an approver") : ""),
7074
7080
  ago(a.createdAt),
7075
7081
  ctx.ui.c("dim", a.id)
7076
7082
  ]);
@@ -7083,12 +7089,21 @@ var airlock = {
7083
7089
  name: "airlock",
7084
7090
  aliases: ["approvals"],
7085
7091
  group: "airlock \xB7 mask",
7086
- usage: "[--watch]",
7087
- summary: "what your pods want to send out: pending first, then recent",
7088
- example: "airgate airlock --watch",
7089
- notes: ["--watch follows the live stream and prints each new request as it arrives; ctrl-c to stop."],
7090
- flags: { watch: { type: "boolean", short: "w", help: "follow new requests" } },
7092
+ usage: "[--watch] | mode [ask|branches|off|account] [--pod p]",
7093
+ summary: "what your pods want to send out: pending first, then recent \xB7 how strict it is",
7094
+ example: "airgate airlock mode branches",
7095
+ notes: [
7096
+ "--watch follows the live stream and prints each new request as it arrives; ctrl-c to stop.",
7097
+ "mode: how much waits for you, like Claude Code's permission modes. ask (the default): everything waits. branches: pushes to work branches go through on their own; main, force pushes, pull requests, email and deploys still wait. off: everything goes through, still recorded.",
7098
+ "mode with --pod sets one pod's own mode (account: back to the account's). a team's approver rules always win."
7099
+ ],
7100
+ flags: {
7101
+ watch: { type: "boolean", short: "w", help: "follow new requests" },
7102
+ pod: { type: "string", value: "pod", help: "with mode: this pod only" }
7103
+ },
7091
7104
  async run(ctx) {
7105
+ if (ctx.args.positionals[0] === "mode") return mode(ctx);
7106
+ if (ctx.args.positionals.length) throw new UsageError("airgate airlock [--watch] | airgate airlock mode [ask|branches|off]");
7092
7107
  if (bool(ctx.args, "watch")) return watch(ctx);
7093
7108
  const [items, names] = await Promise.all([ctx.api.approvals(), podNames(ctx)]);
7094
7109
  const pending = items.filter((a) => a.state === "pending").sort((a, b2) => a.createdAt - b2.createdAt);
@@ -7107,6 +7122,30 @@ var airlock = {
7107
7122
  }
7108
7123
  }
7109
7124
  };
7125
+ async function mode(ctx) {
7126
+ const want = ctx.args.positionals[1];
7127
+ const podRef = str(ctx.args, "pod");
7128
+ if (want && !AIRLOCK_MODES.includes(want) && !(podRef && want === "account")) {
7129
+ throw new UsageError(`${AIRLOCK_MODES.join(", ")}${podRef ? " or account" : ""}`, "airgate airlock mode branches");
7130
+ }
7131
+ const account = (await ctx.api.me()).user.airlockMode ?? "ask";
7132
+ const say = (who, m2, own) => {
7133
+ ctx.ui.out(`${who}: ${m2 === "off" ? ctx.ui.c("warn", AIRLOCK_MODE_INFO[m2].name) : AIRLOCK_MODE_INFO[m2].name}${own ? "" : ctx.ui.c("dim", " (the account's)")}`);
7134
+ ctx.ui.out(ctx.ui.c("dim", ` ${AIRLOCK_MODE_INFO[m2].line}`));
7135
+ if (m2 === "off") ctx.ui.err(ctx.ui.ce("warn", " nothing waits: an agent's push to main, a pull request or an email goes out on its own"));
7136
+ };
7137
+ if (podRef) {
7138
+ let pod = await resolvePod(ctx, podRef);
7139
+ if (want) pod = await ctx.api.updatePod(pod.id, { airlock: want === "account" ? null : want });
7140
+ const m2 = pod.spec.airlock ?? account;
7141
+ if (ctx.json) return ctx.ui.json({ pod: pod.spec.name, mode: m2, own: Boolean(pod.spec.airlock) });
7142
+ return say(pod.spec.name, m2, Boolean(pod.spec.airlock));
7143
+ }
7144
+ const m = want ? (await ctx.api.updateMe({ airlockMode: want })).user.airlockMode ?? want : account;
7145
+ if (ctx.json) return ctx.ui.json({ mode: m });
7146
+ say("airlock", m, true);
7147
+ if (!want) ctx.ui.out(ctx.ui.c("dim", ` change it: airgate airlock mode ${AIRLOCK_MODES.join("|")} \xB7 one pod: --pod <p>`));
7148
+ }
7110
7149
  async function watch(ctx) {
7111
7150
  const seen = /* @__PURE__ */ new Set();
7112
7151
  let first = true;
@@ -7562,7 +7601,7 @@ import { rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
7562
7601
  import { resolve as resolve4 } from "node:path";
7563
7602
 
7564
7603
  // src/version.ts
7565
- var VERSION = "0.2.3";
7604
+ var VERSION = "0.2.4";
7566
7605
 
7567
7606
  // src/commands/team.ts
7568
7607
  async function checkSimilar(ctx, task2) {
@@ -8136,6 +8175,7 @@ var launch = {
8136
8175
  env: { type: "string", value: "file", help: "a .env file: its keys become this pod's secrets before it boots" },
8137
8176
  "public-repo": { type: "boolean", help: "the repo is public: don't check it against your GitHub App" },
8138
8177
  mask: { type: "boolean", default: true, help: "mask model traffic (--no-mask to turn off)" },
8178
+ airlock: { type: "string", value: "mode", choices: AIRLOCK_MODES, help: "this pod's airlock: ask | branches | off (default: your account's, see airgate airlock mode)" },
8139
8179
  undo: { type: "boolean", default: true, help: "checkpoint after every agent step (--no-undo to turn off)" },
8140
8180
  network: { type: "string", value: "policy", choices: NETWORK_NAMES, default: "allowlist", help: "allowlist | open | off" },
8141
8181
  allow: { type: "list", value: "host", help: "extra hosts for the allowlist (repeatable, or comma-separated)" },
@@ -8255,6 +8295,8 @@ function specFromFlags(ctx, defaults = {}) {
8255
8295
  if (jobs2.length) spec.jobs = jobs2;
8256
8296
  if (builtin) spec.builtinModel = true;
8257
8297
  if (builtin && model) spec.model = model;
8298
+ const airlockMode = str(a, "airlock");
8299
+ if (airlockMode) spec.airlock = airlockMode;
8258
8300
  return spec;
8259
8301
  }
8260
8302
  var info = {
@@ -8278,6 +8320,7 @@ var info = {
8278
8320
  ["network", s2.network + (s2.network === "allowlist" && s2.allow?.length ? ` \xB7 ${s2.allow.join(", ")}` : "")],
8279
8321
  ["mask", s2.masking ? `on${s2.maskTerms?.length ? ` \xB7 ${s2.maskTerms.length} terms` : ""}` : ctx.ui.c("warn", "off")],
8280
8322
  ["undo", s2.autoCheckpoint ? "auto checkpoints" : "manual only"],
8323
+ ["airlock", s2.airlock ? s2.airlock === "off" ? ctx.ui.c("warn", AIRLOCK_MODE_INFO.off.name) : AIRLOCK_MODE_INFO[s2.airlock].name : "as the account"],
8281
8324
  ["auto-stop", s2.autoStopMinutes === 0 ? "keep awake (never sleeps: every hour counts, ~720 a month)" : `sleeps after ${s2.autoStopMinutes ?? 10} min idle`]
8282
8325
  ];
8283
8326
  if (s2.repo) rows2.push(["repo", s2.repo]);
@@ -10675,17 +10718,18 @@ var TOOLS = [
10675
10718
  },
10676
10719
  {
10677
10720
  name: "pod_settings",
10678
- description: "Change a pod's network after launch (open | allowlist | off, extra allowlist hosts) or switch its AI off/on (aiOff: no model calls at all). Applies immediately. Only when the user asked for it.",
10679
- inputSchema: obj({ pod: POD, network: s("open | allowlist | off", { enum: ["open", "allowlist", "off"] }), allow: { type: "array", items: { type: "string" }, description: "extra allowlist hosts (replaces the list)" }, aiOff: b("true switches the pod's AI off; false back on") }, ["pod"]),
10721
+ description: "Change a pod's network after launch (open | allowlist | off, extra allowlist hosts), switch its AI off/on (aiOff: no model calls at all), or set its airlock (ask | branches | off | account). Applies immediately. Only when the user asked for it; never loosen the airlock on your own.",
10722
+ inputSchema: obj({ pod: POD, network: s("open | allowlist | off", { enum: ["open", "allowlist", "off"] }), allow: { type: "array", items: { type: "string" }, description: "extra allowlist hosts (replaces the list)" }, aiOff: b("true switches the pod's AI off; false back on"), airlock: s("ask (everything waits) | branches (pushes to work branches go through) | off (nothing waits) | account (the account's mode)", { enum: ["ask", "branches", "off", "account"] }) }, ["pod"]),
10680
10723
  async call(ctx, a) {
10681
10724
  const p = await resolvePod(ctx, need(a, "pod"));
10682
10725
  const patch = {};
10683
10726
  if (typeof a.network === "string") patch.network = a.network;
10684
10727
  if (Array.isArray(a.allow)) patch.allow = a.allow.map(String);
10685
10728
  if (typeof a.aiOff === "boolean") patch.aiOff = a.aiOff;
10686
- if (!Object.keys(patch).length) throw new UsageError("nothing to change: network, allow or aiOff");
10729
+ if (typeof a.airlock === "string") patch.airlock = a.airlock === "account" ? null : a.airlock;
10730
+ if (!Object.keys(patch).length) throw new UsageError("nothing to change: network, allow, aiOff or airlock");
10687
10731
  const pod = await ctx.api.updatePod(p.id, patch);
10688
- return { name: pod.spec.name, network: pod.spec.network, allow: pod.spec.allow ?? [], aiOff: Boolean(pod.spec.aiOff) };
10732
+ return { name: pod.spec.name, network: pod.spec.network, allow: pod.spec.allow ?? [], aiOff: Boolean(pod.spec.aiOff), airlock: pod.spec.airlock ?? "account" };
10689
10733
  }
10690
10734
  },
10691
10735
  {
@@ -12890,16 +12934,16 @@ var network = {
12890
12934
  remove: { type: "string", value: "host", help: "remove hosts (comma separated)" }
12891
12935
  },
12892
12936
  async run(ctx) {
12893
- const [ref, mode] = ctx.args.positionals;
12937
+ const [ref, mode2] = ctx.args.positionals;
12894
12938
  if (!ref) throw new UsageError("airgate network <pod> [open|allowlist|off]");
12895
- if (mode && !["open", "allowlist", "off"].includes(mode)) throw new UsageError("open, allowlist or off");
12939
+ if (mode2 && !["open", "allowlist", "off"].includes(mode2)) throw new UsageError("open, allowlist or off");
12896
12940
  const pod = await resolvePod(ctx, ref);
12897
12941
  const list2 = (v) => (v ?? "").split(",").map((h) => h.trim().toLowerCase()).filter(Boolean);
12898
12942
  let allow = pod.spec.allow ?? [];
12899
12943
  if (str(ctx.args, "allow") !== void 0) allow = list2(str(ctx.args, "allow"));
12900
12944
  allow = [.../* @__PURE__ */ new Set([...allow, ...list2(str(ctx.args, "add"))])].filter((h) => !list2(str(ctx.args, "remove")).includes(h));
12901
- const changed = mode || ["allow", "add", "remove"].some((k) => str(ctx.args, k) !== void 0);
12902
- const p = changed ? await ctx.api.updatePod(pod.id, { ...mode ? { network: mode } : {}, allow }) : pod;
12945
+ const changed = mode2 || ["allow", "add", "remove"].some((k) => str(ctx.args, k) !== void 0);
12946
+ const p = changed ? await ctx.api.updatePod(pod.id, { ...mode2 ? { network: mode2 } : {}, allow }) : pod;
12903
12947
  if (ctx.json) return ctx.ui.json({ network: p.spec.network, allow: p.spec.allow ?? [], aiOff: Boolean(p.spec.aiOff) });
12904
12948
  ctx.ui.out(`${p.spec.name}: ${p.spec.network}${p.spec.network === "allowlist" && (p.spec.allow ?? []).length ? ` + ${(p.spec.allow ?? []).join(", ")}` : ""}${p.spec.aiOff ? ctx.ui.c("warn", " \xB7 AI off") : ""}${changed ? ctx.ui.c("dim", " (applied now)") : ""}`);
12905
12949
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airgate",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Give your AI its own computer: the airgate command line.",
5
5
  "type": "module",
6
6
  "bin": {