@openape/apes 1.24.1 → 1.25.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 (26) hide show
  1. package/dist/{auth-lock-BBSP72GH.js → auth-lock-4IRWI3ED.js} +1 -2
  2. package/dist/{auth-lock-BBSP72GH.js.map → auth-lock-4IRWI3ED.js.map} +1 -1
  3. package/dist/{chunk-QJJ7DG5C.js → chunk-4KPKANZT.js} +5 -3
  4. package/dist/{chunk-QJJ7DG5C.js.map → chunk-4KPKANZT.js.map} +1 -1
  5. package/dist/{chunk-FRCNYDTR.js → chunk-L2V3CW5B.js} +3 -1
  6. package/dist/{chunk-FRCNYDTR.js.map → chunk-L2V3CW5B.js.map} +1 -1
  7. package/dist/cli.js +1095 -1384
  8. package/dist/cli.js.map +1 -1
  9. package/dist/{config-DA2L3XOV.js → config-MOB5DJ6H.js} +1 -2
  10. package/dist/{http-JWS5LV2U.js → http-6OKWT52Z.js} +2 -3
  11. package/dist/index.js +2 -3
  12. package/dist/{orchestrator-2QS5KXFL.js → orchestrator-CIDV7OGM.js} +2 -3
  13. package/dist/{orchestrator-2QS5KXFL.js.map → orchestrator-CIDV7OGM.js.map} +1 -1
  14. package/dist/{server-KR6GVKRI.js → server-V2V5YHK3.js} +3 -4
  15. package/dist/{server-KR6GVKRI.js.map → server-V2V5YHK3.js.map} +1 -1
  16. package/dist/{ssh-key-6X3YZXSD.js → ssh-key-YBNNG5K5.js} +1 -2
  17. package/package.json +6 -5
  18. package/dist/chunk-7OCVIDC7.js +0 -12
  19. package/dist/chunk-WGF3SPIH.js +0 -3598
  20. package/dist/chunk-WGF3SPIH.js.map +0 -1
  21. package/dist/multipart-parser-FVZBRBXW.js +0 -182
  22. package/dist/multipart-parser-FVZBRBXW.js.map +0 -1
  23. package/dist/ssh-key-6X3YZXSD.js.map +0 -1
  24. /package/dist/{chunk-7OCVIDC7.js.map → config-MOB5DJ6H.js.map} +0 -0
  25. /package/dist/{config-DA2L3XOV.js.map → http-6OKWT52Z.js.map} +0 -0
  26. /package/dist/{http-JWS5LV2U.js.map → ssh-key-YBNNG5K5.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,9 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import {
3
- checkSudoRejection,
4
- isApesSelfDispatch,
5
- notifyGrantPending
6
- } from "./chunk-3COOEDPF.js";
7
2
  import {
8
3
  CliError,
9
4
  CliExit,
@@ -11,21 +6,16 @@ import {
11
6
  parseDuration,
12
7
  runLoop,
13
8
  taskTools
14
- } from "./chunk-FRCNYDTR.js";
9
+ } from "./chunk-L2V3CW5B.js";
15
10
  import {
16
11
  loadEd25519PrivateKey,
17
12
  readPublicKeyComment
18
13
  } from "./chunk-ION3CWD5.js";
19
14
  import {
20
- Mi,
21
- Mn,
22
- br,
23
- dt,
24
- le,
25
- qn,
26
- ut,
27
- ye
28
- } from "./chunk-WGF3SPIH.js";
15
+ checkSudoRejection,
16
+ isApesSelfDispatch,
17
+ notifyGrantPending
18
+ } from "./chunk-3COOEDPF.js";
29
19
  import {
30
20
  GENERIC_OPERATION_ID,
31
21
  buildGenericResolved,
@@ -61,7 +51,7 @@ import {
61
51
  getAgentChallengeEndpoint,
62
52
  getDelegationsEndpoint,
63
53
  getGrantsEndpoint
64
- } from "./chunk-QJJ7DG5C.js";
54
+ } from "./chunk-4KPKANZT.js";
65
55
  import {
66
56
  AUTH_FILE,
67
57
  CONFIG_DIR,
@@ -74,10 +64,9 @@ import {
74
64
  saveAuth,
75
65
  saveConfig
76
66
  } from "./chunk-OBF7IMQ2.js";
77
- import "./chunk-7OCVIDC7.js";
78
67
 
79
68
  // src/cli.ts
80
- import consola49 from "consola";
69
+ import consola51 from "consola";
81
70
 
82
71
  // src/ape-shell.ts
83
72
  import path from "path";
@@ -107,7 +96,7 @@ function rewriteApeShellArgs(argv, argv0) {
107
96
  }
108
97
 
109
98
  // src/cli.ts
110
- import { defineCommand as defineCommand60, runMain } from "citty";
99
+ import { defineCommand as defineCommand63, runMain } from "citty";
111
100
 
112
101
  // src/commands/auth/login.ts
113
102
  import { Buffer as Buffer2 } from "buffer";
@@ -393,9 +382,9 @@ async function loginWithPKCE(idp) {
393
382
  consola2.success(`Logged in as ${payload.email || payload.sub}`);
394
383
  }
395
384
  async function loginWithKey(idp, keyPath, agentEmail) {
396
- const { readFileSync: readFileSync15 } = await import("fs");
385
+ const { readFileSync: readFileSync17 } = await import("fs");
397
386
  const { sign: sign3 } = await import("crypto");
398
- const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-6X3YZXSD.js");
387
+ const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
399
388
  const challengeUrl = await getAgentChallengeEndpoint(idp);
400
389
  const challengeResp = await fetch(challengeUrl, {
401
390
  method: "POST",
@@ -406,7 +395,7 @@ async function loginWithKey(idp, keyPath, agentEmail) {
406
395
  throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
407
396
  }
408
397
  const { challenge } = await challengeResp.json();
409
- const keyContent = readFileSync15(keyPath, "utf-8");
398
+ const keyContent = readFileSync17(keyPath, "utf-8");
410
399
  const privateKey = loadEd25519PrivateKey2(keyContent);
411
400
  const signature = sign3(null, Buffer2.from(challenge), privateKey).toString("base64");
412
401
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -774,7 +763,7 @@ async function waitForApproval(grantsUrl, grantId) {
774
763
  if (grant.status === "revoked") {
775
764
  throw new CliError("Grant revoked.");
776
765
  }
777
- await new Promise((r3) => setTimeout(r3, interval));
766
+ await new Promise((r) => setTimeout(r, interval));
778
767
  }
779
768
  throw new CliError("Timed out waiting for approval.");
780
769
  }
@@ -1122,12 +1111,12 @@ var revokeCommand = defineCommand11({
1122
1111
  { method: "POST", body: { operations }, token }
1123
1112
  );
1124
1113
  let succeeded = 0;
1125
- for (const r3 of results) {
1126
- if (r3.success) {
1127
- consola11.success(`Grant ${r3.id} revoked.`);
1114
+ for (const r of results) {
1115
+ if (r.success) {
1116
+ consola11.success(`Grant ${r.id} revoked.`);
1128
1117
  succeeded++;
1129
1118
  } else {
1130
- consola11.error(`Grant ${r3.id}: ${r3.error?.title || "Failed"}`);
1119
+ consola11.error(`Grant ${r.id}: ${r.error?.title || "Failed"}`);
1131
1120
  }
1132
1121
  }
1133
1122
  if (succeeded < results.length) {
@@ -1147,32 +1136,32 @@ import consola12 from "consola";
1147
1136
  function getPollIntervalSeconds() {
1148
1137
  const envValue = process.env.APES_GRANT_POLL_INTERVAL;
1149
1138
  if (envValue) {
1150
- const n2 = Number(envValue);
1151
- if (Number.isFinite(n2) && n2 > 0)
1152
- return Math.floor(n2);
1139
+ const n = Number(envValue);
1140
+ if (Number.isFinite(n) && n > 0)
1141
+ return Math.floor(n);
1153
1142
  }
1154
1143
  const cfg = loadConfig();
1155
1144
  const cfgValue = cfg.defaults?.grant_poll_interval_seconds;
1156
1145
  if (cfgValue) {
1157
- const n2 = Number(cfgValue);
1158
- if (Number.isFinite(n2) && n2 > 0)
1159
- return Math.floor(n2);
1146
+ const n = Number(cfgValue);
1147
+ if (Number.isFinite(n) && n > 0)
1148
+ return Math.floor(n);
1160
1149
  }
1161
1150
  return 10;
1162
1151
  }
1163
1152
  function getPollMaxMinutes() {
1164
1153
  const envValue = process.env.APES_GRANT_POLL_MAX_MINUTES;
1165
1154
  if (envValue) {
1166
- const n2 = Number(envValue);
1167
- if (Number.isFinite(n2) && n2 > 0)
1168
- return Math.floor(n2);
1155
+ const n = Number(envValue);
1156
+ if (Number.isFinite(n) && n > 0)
1157
+ return Math.floor(n);
1169
1158
  }
1170
1159
  const cfg = loadConfig();
1171
1160
  const cfgValue = cfg.defaults?.grant_poll_max_minutes;
1172
1161
  if (cfgValue) {
1173
- const n2 = Number(cfgValue);
1174
- if (Number.isFinite(n2) && n2 > 0)
1175
- return Math.floor(n2);
1162
+ const n = Number(cfgValue);
1163
+ if (Number.isFinite(n) && n > 0)
1164
+ return Math.floor(n);
1176
1165
  }
1177
1166
  return 5;
1178
1167
  }
@@ -1189,7 +1178,7 @@ async function pollGrantUntilResolved(idp, grantId) {
1189
1178
  return { kind: "approved" };
1190
1179
  if (grant.status === "denied" || grant.status === "revoked" || grant.status === "used")
1191
1180
  return { kind: "terminal", status: grant.status };
1192
- await new Promise((r3) => setTimeout(r3, intervalMs));
1181
+ await new Promise((r) => setTimeout(r, intervalMs));
1193
1182
  }
1194
1183
  return { kind: "timeout" };
1195
1184
  }
@@ -1769,742 +1758,216 @@ var adminCommand = defineCommand19({
1769
1758
  }
1770
1759
  });
1771
1760
 
1772
- // src/commands/agents/index.ts
1773
- import { defineCommand as defineCommand28 } from "citty";
1761
+ // src/commands/agent/index.ts
1762
+ import { defineCommand as defineCommand21 } from "citty";
1774
1763
 
1775
- // src/commands/agents/allow.ts
1776
- import { execFileSync as execFileSync4 } from "child_process";
1764
+ // src/commands/agent/deploy.ts
1765
+ import { ensureFreshIdpAuth } from "@openape/cli-auth";
1777
1766
  import { defineCommand as defineCommand20 } from "citty";
1778
1767
  import consola18 from "consola";
1779
1768
 
1780
- // src/lib/agent-bootstrap.ts
1781
- import { Buffer as Buffer3 } from "buffer";
1782
- import { execFileSync as execFileSync2 } from "child_process";
1783
- import { createPrivateKey, sign } from "crypto";
1784
- import { rmSync } from "fs";
1785
-
1786
- // ../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/node.mjs
1787
- import http from "http";
1788
- import https from "https";
1789
-
1790
- // ../../node_modules/.pnpm/node-fetch-native@1.6.7/node_modules/node-fetch-native/dist/index.mjs
1791
- import "http";
1792
- import "https";
1793
- import "zlib";
1794
- import "stream";
1795
- import "buffer";
1796
- import "util";
1797
- import "url";
1798
- import "net";
1799
- import "fs";
1800
- import "path";
1801
- var o = !!globalThis.process?.env?.FORCE_NODE_FETCH;
1802
- var r = !o && globalThis.fetch || Mi;
1803
- var p = !o && globalThis.Blob || ut;
1804
- var F = !o && globalThis.File || qn;
1805
- var h = !o && globalThis.FormData || br;
1806
- var n = !o && globalThis.Headers || ye;
1807
- var c = !o && globalThis.Request || dt;
1808
- var R = !o && globalThis.Response || le;
1809
- var T = !o && globalThis.AbortController || Mn;
1810
-
1811
- // ../../node_modules/.pnpm/destr@2.0.5/node_modules/destr/dist/index.mjs
1812
- var suspectProtoRx = /"(?:_|\\u0{2}5[Ff]){2}(?:p|\\u0{2}70)(?:r|\\u0{2}72)(?:o|\\u0{2}6[Ff])(?:t|\\u0{2}74)(?:o|\\u0{2}6[Ff])(?:_|\\u0{2}5[Ff]){2}"\s*:/;
1813
- var suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
1814
- var JsonSigRx = /^\s*["[{]|^\s*-?\d{1,16}(\.\d{1,17})?([Ee][+-]?\d+)?\s*$/;
1815
- function jsonParseTransform(key, value) {
1816
- if (key === "__proto__" || key === "constructor" && value && typeof value === "object" && "prototype" in value) {
1817
- warnKeyDropped(key);
1818
- return;
1819
- }
1820
- return value;
1821
- }
1822
- function warnKeyDropped(key) {
1823
- console.warn(`[destr] Dropping "${key}" key to prevent prototype pollution.`);
1824
- }
1825
- function destr(value, options = {}) {
1826
- if (typeof value !== "string") {
1827
- return value;
1828
- }
1829
- if (value[0] === '"' && value[value.length - 1] === '"' && value.indexOf("\\") === -1) {
1830
- return value.slice(1, -1);
1831
- }
1832
- const _value = value.trim();
1833
- if (_value.length <= 9) {
1834
- switch (_value.toLowerCase()) {
1835
- case "true": {
1836
- return true;
1837
- }
1838
- case "false": {
1839
- return false;
1840
- }
1841
- case "undefined": {
1842
- return void 0;
1843
- }
1844
- case "null": {
1845
- return null;
1846
- }
1847
- case "nan": {
1848
- return Number.NaN;
1849
- }
1850
- case "infinity": {
1851
- return Number.POSITIVE_INFINITY;
1852
- }
1853
- case "-infinity": {
1854
- return Number.NEGATIVE_INFINITY;
1855
- }
1856
- }
1857
- }
1858
- if (!JsonSigRx.test(value)) {
1859
- if (options.strict) {
1860
- throw new SyntaxError("[destr] Invalid JSON");
1861
- }
1862
- return value;
1769
+ // src/lib/troop-client.ts
1770
+ var DEFAULT_TROOP_URL = "https://troop.openape.ai";
1771
+ var TroopClient = class {
1772
+ constructor(troopUrl, agentJwt) {
1773
+ this.troopUrl = troopUrl;
1774
+ this.agentJwt = agentJwt;
1863
1775
  }
1864
- try {
1865
- if (suspectProtoRx.test(value) || suspectConstructorRx.test(value)) {
1866
- if (options.strict) {
1867
- throw new Error("[destr] Possible prototype pollution");
1776
+ troopUrl;
1777
+ agentJwt;
1778
+ async request(path2, init) {
1779
+ const res = await fetch(`${this.troopUrl}${path2}`, {
1780
+ ...init,
1781
+ headers: {
1782
+ ...init?.headers ?? {},
1783
+ "Authorization": `Bearer ${this.agentJwt}`,
1784
+ "Content-Type": "application/json"
1868
1785
  }
1869
- return JSON.parse(value, jsonParseTransform);
1870
- }
1871
- return JSON.parse(value);
1872
- } catch (error) {
1873
- if (options.strict) {
1874
- throw error;
1875
- }
1876
- return value;
1877
- }
1878
- }
1879
-
1880
- // ../../node_modules/.pnpm/ufo@1.6.3/node_modules/ufo/dist/index.mjs
1881
- var r2 = String.fromCharCode;
1882
- var HASH_RE = /#/g;
1883
- var AMPERSAND_RE = /&/g;
1884
- var SLASH_RE = /\//g;
1885
- var EQUAL_RE = /=/g;
1886
- var PLUS_RE = /\+/g;
1887
- var ENC_CARET_RE = /%5e/gi;
1888
- var ENC_BACKTICK_RE = /%60/gi;
1889
- var ENC_PIPE_RE = /%7c/gi;
1890
- var ENC_SPACE_RE = /%20/gi;
1891
- function encode(text) {
1892
- return encodeURI("" + text).replace(ENC_PIPE_RE, "|");
1893
- }
1894
- function encodeQueryValue(input) {
1895
- return encode(typeof input === "string" ? input : JSON.stringify(input)).replace(PLUS_RE, "%2B").replace(ENC_SPACE_RE, "+").replace(HASH_RE, "%23").replace(AMPERSAND_RE, "%26").replace(ENC_BACKTICK_RE, "`").replace(ENC_CARET_RE, "^").replace(SLASH_RE, "%2F");
1896
- }
1897
- function encodeQueryKey(text) {
1898
- return encodeQueryValue(text).replace(EQUAL_RE, "%3D");
1899
- }
1900
- function decode(text = "") {
1901
- try {
1902
- return decodeURIComponent("" + text);
1903
- } catch {
1904
- return "" + text;
1905
- }
1906
- }
1907
- function decodeQueryKey(text) {
1908
- return decode(text.replace(PLUS_RE, " "));
1909
- }
1910
- function decodeQueryValue(text) {
1911
- return decode(text.replace(PLUS_RE, " "));
1912
- }
1913
- function parseQuery(parametersString = "") {
1914
- const object = /* @__PURE__ */ Object.create(null);
1915
- if (parametersString[0] === "?") {
1916
- parametersString = parametersString.slice(1);
1917
- }
1918
- for (const parameter of parametersString.split("&")) {
1919
- const s = parameter.match(/([^=]+)=?(.*)/) || [];
1920
- if (s.length < 2) {
1921
- continue;
1922
- }
1923
- const key = decodeQueryKey(s[1]);
1924
- if (key === "__proto__" || key === "constructor") {
1925
- continue;
1926
- }
1927
- const value = decodeQueryValue(s[2] || "");
1928
- if (object[key] === void 0) {
1929
- object[key] = value;
1930
- } else if (Array.isArray(object[key])) {
1931
- object[key].push(value);
1932
- } else {
1933
- object[key] = [object[key], value];
1934
- }
1935
- }
1936
- return object;
1937
- }
1938
- function encodeQueryItem(key, value) {
1939
- if (typeof value === "number" || typeof value === "boolean") {
1940
- value = String(value);
1941
- }
1942
- if (!value) {
1943
- return encodeQueryKey(key);
1944
- }
1945
- if (Array.isArray(value)) {
1946
- return value.map(
1947
- (_value) => `${encodeQueryKey(key)}=${encodeQueryValue(_value)}`
1948
- ).join("&");
1949
- }
1950
- return `${encodeQueryKey(key)}=${encodeQueryValue(value)}`;
1951
- }
1952
- function stringifyQuery(query) {
1953
- return Object.keys(query).filter((k) => query[k] !== void 0).map((k) => encodeQueryItem(k, query[k])).filter(Boolean).join("&");
1954
- }
1955
- var PROTOCOL_STRICT_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{1,2})/;
1956
- var PROTOCOL_REGEX = /^[\s\w\0+.-]{2,}:([/\\]{2})?/;
1957
- var PROTOCOL_RELATIVE_REGEX = /^([/\\]\s*){2,}[^/\\]/;
1958
- var TRAILING_SLASH_RE = /\/$|\/\?|\/#/;
1959
- var JOIN_LEADING_SLASH_RE = /^\.?\//;
1960
- function hasProtocol(inputString, opts = {}) {
1961
- if (typeof opts === "boolean") {
1962
- opts = { acceptRelative: opts };
1963
- }
1964
- if (opts.strict) {
1965
- return PROTOCOL_STRICT_REGEX.test(inputString);
1966
- }
1967
- return PROTOCOL_REGEX.test(inputString) || (opts.acceptRelative ? PROTOCOL_RELATIVE_REGEX.test(inputString) : false);
1968
- }
1969
- function hasTrailingSlash(input = "", respectQueryAndFragment) {
1970
- if (!respectQueryAndFragment) {
1971
- return input.endsWith("/");
1972
- }
1973
- return TRAILING_SLASH_RE.test(input);
1974
- }
1975
- function withoutTrailingSlash(input = "", respectQueryAndFragment) {
1976
- if (!respectQueryAndFragment) {
1977
- return (hasTrailingSlash(input) ? input.slice(0, -1) : input) || "/";
1978
- }
1979
- if (!hasTrailingSlash(input, true)) {
1980
- return input || "/";
1981
- }
1982
- let path2 = input;
1983
- let fragment = "";
1984
- const fragmentIndex = input.indexOf("#");
1985
- if (fragmentIndex !== -1) {
1986
- path2 = input.slice(0, fragmentIndex);
1987
- fragment = input.slice(fragmentIndex);
1988
- }
1989
- const [s0, ...s] = path2.split("?");
1990
- const cleanPath = s0.endsWith("/") ? s0.slice(0, -1) : s0;
1991
- return (cleanPath || "/") + (s.length > 0 ? `?${s.join("?")}` : "") + fragment;
1992
- }
1993
- function withTrailingSlash(input = "", respectQueryAndFragment) {
1994
- if (!respectQueryAndFragment) {
1995
- return input.endsWith("/") ? input : input + "/";
1996
- }
1997
- if (hasTrailingSlash(input, true)) {
1998
- return input || "/";
1999
- }
2000
- let path2 = input;
2001
- let fragment = "";
2002
- const fragmentIndex = input.indexOf("#");
2003
- if (fragmentIndex !== -1) {
2004
- path2 = input.slice(0, fragmentIndex);
2005
- fragment = input.slice(fragmentIndex);
2006
- if (!path2) {
2007
- return fragment;
2008
- }
2009
- }
2010
- const [s0, ...s] = path2.split("?");
2011
- return s0 + "/" + (s.length > 0 ? `?${s.join("?")}` : "") + fragment;
2012
- }
2013
- function withBase(input, base) {
2014
- if (isEmptyURL(base) || hasProtocol(input)) {
2015
- return input;
2016
- }
2017
- const _base = withoutTrailingSlash(base);
2018
- if (input.startsWith(_base)) {
2019
- const nextChar = input[_base.length];
2020
- if (!nextChar || nextChar === "/" || nextChar === "?") {
2021
- return input;
2022
- }
2023
- }
2024
- return joinURL(_base, input);
2025
- }
2026
- function withQuery(input, query) {
2027
- const parsed = parseURL(input);
2028
- const mergedQuery = { ...parseQuery(parsed.search), ...query };
2029
- parsed.search = stringifyQuery(mergedQuery);
2030
- return stringifyParsedURL(parsed);
2031
- }
2032
- function isEmptyURL(url) {
2033
- return !url || url === "/";
2034
- }
2035
- function isNonEmptyURL(url) {
2036
- return url && url !== "/";
2037
- }
2038
- function joinURL(base, ...input) {
2039
- let url = base || "";
2040
- for (const segment of input.filter((url2) => isNonEmptyURL(url2))) {
2041
- if (url) {
2042
- const _segment = segment.replace(JOIN_LEADING_SLASH_RE, "");
2043
- url = withTrailingSlash(url) + _segment;
2044
- } else {
2045
- url = segment;
1786
+ });
1787
+ if (!res.ok) {
1788
+ const text = await res.text().catch(() => "");
1789
+ throw new Error(`troop ${init?.method ?? "GET"} ${path2} failed: ${res.status} ${text}`);
2046
1790
  }
1791
+ if (res.status === 204) return void 0;
1792
+ return await res.json();
2047
1793
  }
2048
- return url;
2049
- }
2050
- var protocolRelative = /* @__PURE__ */ Symbol.for("ufo:protocolRelative");
2051
- function parseURL(input = "", defaultProto) {
2052
- const _specialProtoMatch = input.match(
2053
- /^[\s\0]*(blob:|data:|javascript:|vbscript:)(.*)/i
2054
- );
2055
- if (_specialProtoMatch) {
2056
- const [, _proto, _pathname = ""] = _specialProtoMatch;
2057
- return {
2058
- protocol: _proto.toLowerCase(),
2059
- pathname: _pathname,
2060
- href: _proto + _pathname,
2061
- auth: "",
2062
- host: "",
2063
- search: "",
2064
- hash: ""
2065
- };
2066
- }
2067
- if (!hasProtocol(input, { acceptRelative: true })) {
2068
- return defaultProto ? parseURL(defaultProto + input) : parsePath(input);
2069
- }
2070
- const [, protocol = "", auth, hostAndPath = ""] = input.replace(/\\/g, "/").match(/^[\s\0]*([\w+.-]{2,}:)?\/\/([^/@]+@)?(.*)/) || [];
2071
- let [, host = "", path2 = ""] = hostAndPath.match(/([^#/?]*)(.*)?/) || [];
2072
- if (protocol === "file:") {
2073
- path2 = path2.replace(/\/(?=[A-Za-z]:)/, "");
1794
+ sync(input) {
1795
+ return this.request("/api/agents/me/sync", {
1796
+ method: "POST",
1797
+ body: JSON.stringify({
1798
+ hostname: input.hostname,
1799
+ host_id: input.hostId,
1800
+ owner_email: input.ownerEmail,
1801
+ ...input.pubkeySsh ? { pubkey_ssh: input.pubkeySsh } : {}
1802
+ })
1803
+ });
2074
1804
  }
2075
- const { pathname, search, hash } = parsePath(path2);
2076
- return {
2077
- protocol: protocol.toLowerCase(),
2078
- auth: auth ? auth.slice(0, Math.max(0, auth.length - 1)) : "",
2079
- host,
2080
- pathname,
2081
- search,
2082
- hash,
2083
- [protocolRelative]: !protocol
2084
- };
2085
- }
2086
- function parsePath(input = "") {
2087
- const [pathname = "", search = "", hash = ""] = (input.match(/([^#?]*)(\?[^#]*)?(#.*)?/) || []).splice(1);
2088
- return {
2089
- pathname,
2090
- search,
2091
- hash
2092
- };
2093
- }
2094
- function stringifyParsedURL(parsed) {
2095
- const pathname = parsed.pathname || "";
2096
- const search = parsed.search ? (parsed.search.startsWith("?") ? "" : "?") + parsed.search : "";
2097
- const hash = parsed.hash || "";
2098
- const auth = parsed.auth ? parsed.auth + "@" : "";
2099
- const host = parsed.host || "";
2100
- const proto = parsed.protocol || parsed[protocolRelative] ? (parsed.protocol || "") + "//" : "";
2101
- return proto + auth + host + pathname + search + hash;
2102
- }
2103
-
2104
- // ../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/shared/ofetch.CWycOUEr.mjs
2105
- var FetchError = class extends Error {
2106
- constructor(message, opts) {
2107
- super(message, opts);
2108
- this.name = "FetchError";
2109
- if (opts?.cause && !this.cause) {
2110
- this.cause = opts.cause;
2111
- }
1805
+ listTasks() {
1806
+ return this.request("/api/agents/me/tasks");
2112
1807
  }
2113
- };
2114
- function createFetchError(ctx) {
2115
- const errorMessage = ctx.error?.message || ctx.error?.toString() || "";
2116
- const method = ctx.request?.method || ctx.options?.method || "GET";
2117
- const url = ctx.request?.url || String(ctx.request) || "/";
2118
- const requestStr = `[${method}] ${JSON.stringify(url)}`;
2119
- const statusStr = ctx.response ? `${ctx.response.status} ${ctx.response.statusText}` : "<no response>";
2120
- const message = `${requestStr}: ${statusStr}${errorMessage ? ` ${errorMessage}` : ""}`;
2121
- const fetchError = new FetchError(
2122
- message,
2123
- ctx.error ? { cause: ctx.error } : void 0
2124
- );
2125
- for (const key of ["request", "options", "response"]) {
2126
- Object.defineProperty(fetchError, key, {
2127
- get() {
2128
- return ctx[key];
2129
- }
1808
+ startRun(taskId) {
1809
+ return this.request("/api/agents/me/runs", {
1810
+ method: "POST",
1811
+ body: JSON.stringify({ task_id: taskId })
2130
1812
  });
2131
1813
  }
2132
- for (const [key, refKey] of [
2133
- ["data", "_data"],
2134
- ["status", "status"],
2135
- ["statusCode", "status"],
2136
- ["statusText", "statusText"],
2137
- ["statusMessage", "statusText"]
2138
- ]) {
2139
- Object.defineProperty(fetchError, key, {
2140
- get() {
2141
- return ctx.response && ctx.response[refKey];
2142
- }
1814
+ finaliseRun(id, payload) {
1815
+ return this.request(`/api/agents/me/runs/${id}`, {
1816
+ method: "PATCH",
1817
+ body: JSON.stringify(payload)
2143
1818
  });
2144
1819
  }
2145
- return fetchError;
2146
- }
2147
- var payloadMethods = new Set(
2148
- Object.freeze(["PATCH", "POST", "PUT", "DELETE"])
2149
- );
2150
- function isPayloadMethod(method = "GET") {
2151
- return payloadMethods.has(method.toUpperCase());
1820
+ };
1821
+ function resolveTroopUrl(override) {
1822
+ if (override) return override.replace(/\/$/, "");
1823
+ const fromEnv = process.env.OPENAPE_TROOP_URL;
1824
+ if (fromEnv) return fromEnv.replace(/\/$/, "");
1825
+ return DEFAULT_TROOP_URL;
2152
1826
  }
2153
- function isJSONSerializable(value) {
2154
- if (value === void 0) {
2155
- return false;
2156
- }
2157
- const t = typeof value;
2158
- if (t === "string" || t === "number" || t === "boolean" || t === null) {
2159
- return true;
2160
- }
2161
- if (t !== "object") {
2162
- return false;
2163
- }
2164
- if (Array.isArray(value)) {
2165
- return true;
2166
- }
2167
- if (value.buffer) {
2168
- return false;
2169
- }
2170
- if (value instanceof FormData || value instanceof URLSearchParams) {
2171
- return false;
1827
+
1828
+ // src/commands/agent/deploy.ts
1829
+ function parseKeyValues(pairs) {
1830
+ const out = {};
1831
+ for (const p of pairs) {
1832
+ const eq = p.indexOf("=");
1833
+ if (eq <= 0) throw new CliError(`bad key=value pair: "${p}" (expected KEY=value)`);
1834
+ out[p.slice(0, eq)] = p.slice(eq + 1);
2172
1835
  }
2173
- return value.constructor && value.constructor.name === "Object" || typeof value.toJSON === "function";
1836
+ return out;
2174
1837
  }
2175
- var textTypes = /* @__PURE__ */ new Set([
2176
- "image/svg",
2177
- "application/xml",
2178
- "application/xhtml",
2179
- "application/html"
2180
- ]);
2181
- var JSON_RE = /^application\/(?:[\w!#$%&*.^`~-]*\+)?json(;.+)?$/i;
2182
- function detectResponseType(_contentType = "") {
2183
- if (!_contentType) {
2184
- return "json";
2185
- }
2186
- const contentType = _contentType.split(";").shift() || "";
2187
- if (JSON_RE.test(contentType)) {
2188
- return "json";
2189
- }
2190
- if (contentType === "text/event-stream") {
2191
- return "stream";
2192
- }
2193
- if (textTypes.has(contentType) || contentType.startsWith("text/")) {
2194
- return "text";
2195
- }
2196
- return "blob";
1838
+ function missingCapabilities(required, provided) {
1839
+ return required.filter((env) => !(env in provided));
2197
1840
  }
2198
- function resolveFetchOptions(request, input, defaults, Headers2) {
2199
- const headers = mergeHeaders(
2200
- input?.headers ?? request?.headers,
2201
- defaults?.headers,
2202
- Headers2
2203
- );
2204
- let query;
2205
- if (defaults?.query || defaults?.params || input?.params || input?.query) {
2206
- query = {
2207
- ...defaults?.params,
2208
- ...defaults?.query,
2209
- ...input?.params,
2210
- ...input?.query
2211
- };
2212
- }
2213
- return {
2214
- ...defaults,
2215
- ...input,
2216
- query,
2217
- params: query,
2218
- headers
2219
- };
1841
+ function asArray(v) {
1842
+ if (Array.isArray(v)) return v;
1843
+ if (typeof v === "string" && v.length > 0) return [v];
1844
+ return [];
2220
1845
  }
2221
- function mergeHeaders(input, defaults, Headers2) {
2222
- if (!defaults) {
2223
- return new Headers2(input);
2224
- }
2225
- const headers = new Headers2(defaults);
2226
- if (input) {
2227
- for (const [key, value] of Symbol.iterator in input || Array.isArray(input) ? input : new Headers2(input)) {
2228
- headers.set(key, value);
2229
- }
1846
+ async function api(url, token, init) {
1847
+ const res = await fetch(url, {
1848
+ ...init,
1849
+ headers: { "Authorization": `Bearer ${token}`, "Content-Type": "application/json", ...init?.headers }
1850
+ });
1851
+ if (!res.ok) {
1852
+ const body = await res.text().catch(() => "");
1853
+ throw new CliError(`${init?.method ?? "GET"} ${url} \u2192 HTTP ${res.status}${body ? `: ${body.slice(0, 300)}` : ""}`);
2230
1854
  }
2231
- return headers;
1855
+ return res.json();
2232
1856
  }
2233
- async function callHooks(context, hooks) {
2234
- if (hooks) {
2235
- if (Array.isArray(hooks)) {
2236
- for (const hook of hooks) {
2237
- await hook(context);
2238
- }
2239
- } else {
2240
- await hooks(context);
2241
- }
2242
- }
2243
- }
2244
- var retryStatusCodes = /* @__PURE__ */ new Set([
2245
- 408,
2246
- // Request Timeout
2247
- 409,
2248
- // Conflict
2249
- 425,
2250
- // Too Early (Experimental)
2251
- 429,
2252
- // Too Many Requests
2253
- 500,
2254
- // Internal Server Error
2255
- 502,
2256
- // Bad Gateway
2257
- 503,
2258
- // Service Unavailable
2259
- 504
2260
- // Gateway Timeout
2261
- ]);
2262
- var nullBodyResponses = /* @__PURE__ */ new Set([101, 204, 205, 304]);
2263
- function createFetch(globalOptions = {}) {
2264
- const {
2265
- fetch: fetch3 = globalThis.fetch,
2266
- Headers: Headers2 = globalThis.Headers,
2267
- AbortController: AbortController3 = globalThis.AbortController
2268
- } = globalOptions;
2269
- async function onError(context) {
2270
- const isAbort = context.error && context.error.name === "AbortError" && !context.options.timeout || false;
2271
- if (context.options.retry !== false && !isAbort) {
2272
- let retries;
2273
- if (typeof context.options.retry === "number") {
2274
- retries = context.options.retry;
2275
- } else {
2276
- retries = isPayloadMethod(context.options.method) ? 0 : 1;
2277
- }
2278
- const responseCode = context.response && context.response.status || 500;
2279
- if (retries > 0 && (Array.isArray(context.options.retryStatusCodes) ? context.options.retryStatusCodes.includes(responseCode) : retryStatusCodes.has(responseCode))) {
2280
- const retryDelay = typeof context.options.retryDelay === "function" ? context.options.retryDelay(context) : context.options.retryDelay || 0;
2281
- if (retryDelay > 0) {
2282
- await new Promise((resolve4) => setTimeout(resolve4, retryDelay));
2283
- }
2284
- return $fetchRaw(context.request, {
2285
- ...context.options,
2286
- retry: retries - 1
2287
- });
2288
- }
2289
- }
2290
- const error = createFetchError(context);
2291
- if (Error.captureStackTrace) {
2292
- Error.captureStackTrace(error, $fetchRaw);
2293
- }
2294
- throw error;
2295
- }
2296
- const $fetchRaw = async function $fetchRaw2(_request, _options = {}) {
2297
- const context = {
2298
- request: _request,
2299
- options: resolveFetchOptions(
2300
- _request,
2301
- _options,
2302
- globalOptions.defaults,
2303
- Headers2
2304
- ),
2305
- response: void 0,
2306
- error: void 0
2307
- };
2308
- if (context.options.method) {
2309
- context.options.method = context.options.method.toUpperCase();
2310
- }
2311
- if (context.options.onRequest) {
2312
- await callHooks(context, context.options.onRequest);
2313
- if (!(context.options.headers instanceof Headers2)) {
2314
- context.options.headers = new Headers2(
2315
- context.options.headers || {}
2316
- /* compat */
2317
- );
2318
- }
2319
- }
2320
- if (typeof context.request === "string") {
2321
- if (context.options.baseURL) {
2322
- context.request = withBase(context.request, context.options.baseURL);
2323
- }
2324
- if (context.options.query) {
2325
- context.request = withQuery(context.request, context.options.query);
2326
- delete context.options.query;
2327
- }
2328
- if ("query" in context.options) {
2329
- delete context.options.query;
1857
+ var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
1858
+ var deployAgentCommand = defineCommand20({
1859
+ meta: { name: "deploy", description: "Deploy an Agent Recipe (<repo>@<ref>) in one step" },
1860
+ args: {
1861
+ repo: { type: "positional", description: "Recipe repo + pinned ref, e.g. github.com/owner/name@v1.0.0" },
1862
+ param: { type: "string", description: "Recipe param, KEY=value (repeatable)" },
1863
+ secret: { type: "string", description: "Capability secret, ENV=value (repeatable; else prompted)" },
1864
+ "host-id": { type: "string", description: "Target nest host_id (default: first connected)" },
1865
+ json: { type: "boolean", description: "Machine-readable output, no prompts" }
1866
+ },
1867
+ async run({ args }) {
1868
+ const repoRef = args.repo;
1869
+ if (!repoRef) throw new CliError("usage: apes agent deploy <repo>@<ref> [--param k=v] [--secret ENV=val]");
1870
+ const params = parseKeyValues(asArray(args.param));
1871
+ const secrets = parseKeyValues(asArray(args.secret));
1872
+ const json = !!args.json;
1873
+ const token = (await ensureFreshIdpAuth()).access_token;
1874
+ const troop = resolveTroopUrl();
1875
+ const deploy = await api(`${troop}/api/agents/recipe-deploy`, token, {
1876
+ method: "POST",
1877
+ body: JSON.stringify({
1878
+ repo_ref: repoRef,
1879
+ params,
1880
+ ...args["host-id"] ? { host_id: args["host-id"] } : {}
1881
+ })
1882
+ });
1883
+ if (!json) {
1884
+ consola18.success(`Deploying ${deploy.agent_name} from ${repoRef} (ref ${deploy.ref})`);
1885
+ consola18.info(`Schedules: ${deploy.schedules.map((s) => `${s.task_id}=${s.cron}`).join(", ") || "none"}`);
1886
+ }
1887
+ const missing = missingCapabilities(deploy.required_capabilities, secrets);
1888
+ if (missing.length > 0) {
1889
+ if (json) {
1890
+ throw new CliError(`missing required capability secrets: ${missing.join(", ")} (pass via --secret in --json mode)`);
2330
1891
  }
2331
- if ("params" in context.options) {
2332
- delete context.options.params;
1892
+ for (const env of missing) {
1893
+ const val = await consola18.prompt(`Secret value for ${env}:`, { type: "text" });
1894
+ if (typeof val !== "string" || val.length === 0) throw new CliError(`no value provided for ${env} \u2014 aborting`);
1895
+ secrets[env] = val;
2333
1896
  }
2334
1897
  }
2335
- if (context.options.body && isPayloadMethod(context.options.method)) {
2336
- if (isJSONSerializable(context.options.body)) {
2337
- const contentType = context.options.headers.get("content-type");
2338
- if (typeof context.options.body !== "string") {
2339
- context.options.body = contentType === "application/x-www-form-urlencoded" ? new URLSearchParams(
2340
- context.options.body
2341
- ).toString() : JSON.stringify(context.options.body);
2342
- }
2343
- if (!contentType) {
2344
- context.options.headers.set("content-type", "application/json");
2345
- }
2346
- if (!context.options.headers.has("accept")) {
2347
- context.options.headers.set("accept", "application/json");
2348
- }
2349
- } else if (
2350
- // ReadableStream Body
2351
- "pipeTo" in context.options.body && typeof context.options.body.pipeTo === "function" || // Node.js Stream Body
2352
- typeof context.options.body.pipe === "function"
2353
- ) {
2354
- if (!("duplex" in context.options)) {
2355
- context.options.duplex = "half";
2356
- }
2357
- }
2358
- }
2359
- let abortTimeout;
2360
- if (!context.options.signal && context.options.timeout) {
2361
- const controller = new AbortController3();
2362
- abortTimeout = setTimeout(() => {
2363
- const error = new Error(
2364
- "[TimeoutError]: The operation was aborted due to timeout"
2365
- );
2366
- error.name = "TimeoutError";
2367
- error.code = 23;
2368
- controller.abort(error);
2369
- }, context.options.timeout);
2370
- context.options.signal = controller.signal;
2371
- }
2372
- try {
2373
- context.response = await fetch3(
2374
- context.request,
2375
- context.options
2376
- );
2377
- } catch (error) {
2378
- context.error = error;
2379
- if (context.options.onRequestError) {
2380
- await callHooks(
2381
- context,
2382
- context.options.onRequestError
1898
+ if (deploy.required_capabilities.length > 0) {
1899
+ if (!json) consola18.start("Waiting for the agent to come online\u2026");
1900
+ let online = false;
1901
+ for (let i = 0; i < 90 && !online; i++) {
1902
+ const st = await api(
1903
+ `${troop}/api/agents/spawn-intent/${deploy.intent_id}`,
1904
+ token
2383
1905
  );
2384
- }
2385
- return await onError(context);
2386
- } finally {
2387
- if (abortTimeout) {
2388
- clearTimeout(abortTimeout);
2389
- }
2390
- }
2391
- const hasBody = (context.response.body || // https://github.com/unjs/ofetch/issues/324
2392
- // https://github.com/unjs/ofetch/issues/294
2393
- // https://github.com/JakeChampion/fetch/issues/1454
2394
- context.response._bodyInit) && !nullBodyResponses.has(context.response.status) && context.options.method !== "HEAD";
2395
- if (hasBody) {
2396
- const responseType = (context.options.parseResponse ? "json" : context.options.responseType) || detectResponseType(context.response.headers.get("content-type") || "");
2397
- switch (responseType) {
2398
- case "json": {
2399
- const data = await context.response.text();
2400
- const parseFunction = context.options.parseResponse || destr;
2401
- context.response._data = parseFunction(data);
2402
- break;
2403
- }
2404
- case "stream": {
2405
- context.response._data = context.response.body || context.response._bodyInit;
1906
+ if (!st.pending) {
1907
+ if (!st.ok) throw new CliError(`spawn failed: ${st.error ?? "unknown error"}`);
1908
+ online = true;
2406
1909
  break;
2407
1910
  }
2408
- default: {
2409
- context.response._data = await context.response[responseType]();
2410
- }
1911
+ await sleep(2e3);
2411
1912
  }
2412
- }
2413
- if (context.options.onResponse) {
2414
- await callHooks(
2415
- context,
2416
- context.options.onResponse
2417
- );
2418
- }
2419
- if (!context.options.ignoreResponseError && context.response.status >= 400 && context.response.status < 600) {
2420
- if (context.options.onResponseError) {
2421
- await callHooks(
2422
- context,
2423
- context.options.onResponseError
2424
- );
1913
+ if (!online) throw new CliError("timed out waiting for the agent to spawn \u2014 bind secrets later with `apes agent secret`");
1914
+ for (const [env, value] of Object.entries(secrets)) {
1915
+ let bound = false;
1916
+ for (let i = 0; i < 60 && !bound; i++) {
1917
+ try {
1918
+ await api(`${troop}/api/agents/${deploy.agent_name}/secrets/${env}`, token, {
1919
+ method: "PUT",
1920
+ body: JSON.stringify({ value })
1921
+ });
1922
+ bound = true;
1923
+ } catch (e) {
1924
+ if (i === 59) throw e;
1925
+ await sleep(3e3);
1926
+ }
1927
+ }
1928
+ if (!json) consola18.success(`Bound ${env}`);
2425
1929
  }
2426
- return await onError(context);
2427
1930
  }
2428
- return context.response;
2429
- };
2430
- const $fetch = async function $fetch2(request, options) {
2431
- const r3 = await $fetchRaw(request, options);
2432
- return r3._data;
2433
- };
2434
- $fetch.raw = $fetchRaw;
2435
- $fetch.native = (...args) => fetch3(...args);
2436
- $fetch.create = (defaultOptions = {}, customGlobalOptions = {}) => createFetch({
2437
- ...globalOptions,
2438
- ...customGlobalOptions,
2439
- defaults: {
2440
- ...globalOptions.defaults,
2441
- ...customGlobalOptions.defaults,
2442
- ...defaultOptions
2443
- }
2444
- });
2445
- return $fetch;
2446
- }
2447
-
2448
- // ../../node_modules/.pnpm/ofetch@1.5.1/node_modules/ofetch/dist/node.mjs
2449
- function createNodeFetch() {
2450
- const useKeepAlive = JSON.parse(process.env.FETCH_KEEP_ALIVE || "false");
2451
- if (!useKeepAlive) {
2452
- return r;
2453
- }
2454
- const agentOptions = { keepAlive: true };
2455
- const httpAgent = new http.Agent(agentOptions);
2456
- const httpsAgent = new https.Agent(agentOptions);
2457
- const nodeFetchOptions = {
2458
- agent(parsedURL) {
2459
- return parsedURL.protocol === "http:" ? httpAgent : httpsAgent;
1931
+ if (json) {
1932
+ consola18.log(JSON.stringify({
1933
+ ok: true,
1934
+ agent_name: deploy.agent_name,
1935
+ ref: deploy.ref,
1936
+ intent_id: deploy.intent_id,
1937
+ schedules: deploy.schedules,
1938
+ bound: Object.keys(secrets)
1939
+ }));
1940
+ } else {
1941
+ consola18.success(`${deploy.agent_name} deployed. Schedules are live; secrets sealed to the agent.`);
2460
1942
  }
2461
- };
2462
- return function nodeFetchWithKeepAlive(input, init) {
2463
- return r(input, { ...nodeFetchOptions, ...init });
2464
- };
2465
- }
2466
- var fetch2 = globalThis.fetch ? (...args) => globalThis.fetch(...args) : createNodeFetch();
2467
- var Headers = globalThis.Headers || n;
2468
- var AbortController2 = globalThis.AbortController || T;
2469
- var ofetch = createFetch({ fetch: fetch2, Headers, AbortController: AbortController2 });
2470
-
2471
- // ../cli-auth/dist/index.js
2472
- var AuthError = class extends Error {
2473
- status;
2474
- hint;
2475
- constructor(status, message, hint) {
2476
- super(hint ? `${message}
2477
- ${hint}` : message);
2478
- this.name = "AuthError";
2479
- this.status = status;
2480
- this.hint = hint;
2481
1943
  }
2482
- };
2483
- var TOKEN_EXCHANGE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:token-exchange";
2484
- var ACCESS_TOKEN_TYPE = "urn:ietf:params:oauth:token-type:access_token";
2485
- async function exchangeWithDelegation(req) {
2486
- const url = `${req.idp.replace(/\/$/, "")}/api/oauth/token-exchange`;
2487
- try {
2488
- return await ofetch(url, {
2489
- method: "POST",
2490
- body: {
2491
- grant_type: TOKEN_EXCHANGE_GRANT_TYPE,
2492
- actor_token: req.actorToken,
2493
- actor_token_type: ACCESS_TOKEN_TYPE,
2494
- ...req.subjectToken ? { subject_token: req.subjectToken, subject_token_type: ACCESS_TOKEN_TYPE } : {},
2495
- ...req.audience ? { audience: req.audience } : {},
2496
- ...req.delegationGrantId ? { delegation_grant_id: req.delegationGrantId } : {}
2497
- }
2498
- });
2499
- } catch (err) {
2500
- const status = err.status ?? err.statusCode ?? 0;
2501
- const data = err.data;
2502
- const title = data?.title ?? `Delegation token-exchange failed (HTTP ${status})`;
2503
- throw new AuthError(status, title, data?.detail);
1944
+ });
1945
+
1946
+ // src/commands/agent/index.ts
1947
+ var agentCommand = defineCommand21({
1948
+ meta: {
1949
+ name: "agent",
1950
+ description: "Agent Recipe operations (deploy)"
1951
+ },
1952
+ subCommands: {
1953
+ deploy: deployAgentCommand
2504
1954
  }
2505
- }
1955
+ });
1956
+
1957
+ // src/commands/agents/index.ts
1958
+ import { defineCommand as defineCommand31 } from "citty";
1959
+
1960
+ // src/commands/agents/allow.ts
1961
+ import { execFileSync as execFileSync4 } from "child_process";
1962
+ import { defineCommand as defineCommand22 } from "citty";
1963
+ import consola19 from "consola";
2506
1964
 
2507
1965
  // src/lib/agent-bootstrap.ts
1966
+ import { Buffer as Buffer3 } from "buffer";
1967
+ import { execFileSync as execFileSync2 } from "child_process";
1968
+ import { createPrivateKey, sign } from "crypto";
1969
+ import { rmSync } from "fs";
1970
+ import { exchangeWithDelegation } from "@openape/cli-auth";
2508
1971
  var AGENT_NAME_REGEX = /^[a-z][a-z0-9-]{0,23}$/;
2509
1972
  var SSH_ED25519_PREFIX = "ssh-ed25519 ";
2510
1973
  var SSH_ED25519_REGEX = /^ssh-ed25519 [A-Za-z0-9+/=]+(\s.*)?$/;
@@ -2611,7 +2074,7 @@ ${content}
2611
2074
  ${SH_HEREDOC_DELIMITER}`;
2612
2075
  }
2613
2076
  function buildSpawnSetupScript(input) {
2614
- const { name, homeDir, shellPath } = input;
2077
+ const { name, macOSUsername, homeDir, shellPath } = input;
2615
2078
  const privatePemForHeredoc = input.privateKeyPem.endsWith("\n") ? input.privateKeyPem : `${input.privateKeyPem}
2616
2079
  `;
2617
2080
  const claudeBlock = input.claudeSettingsJson && input.hookScriptSource ? `
@@ -2649,6 +2112,7 @@ set -euo pipefail
2649
2112
  export PATH="/usr/sbin:/usr/bin:/bin:/sbin:/opt/homebrew/bin:/usr/local/bin"
2650
2113
 
2651
2114
  NAME=${shQuote(name)}
2115
+ MACOS_USER=${shQuote(macOSUsername)}
2652
2116
  HOME_DIR=${shQuote(homeDir)}
2653
2117
  SHELL_PATH=${shQuote(shellPath)}
2654
2118
 
@@ -2663,18 +2127,18 @@ SHELL_PATH=${shQuote(shellPath)}
2663
2127
  # overwrite the home. If the home is still on disk it's a real,
2664
2128
  # live agent \u2014 refuse.
2665
2129
  TOMBSTONE_REUSE=0
2666
- if dscl . -read "/Users/$NAME" >/dev/null 2>&1; then
2667
- EXISTING_HOME=$(dscl . -read "/Users/$NAME" NFSHomeDirectory 2>/dev/null | awk '/NFSHomeDirectory:/ {print $2}')
2130
+ if dscl . -read "/Users/$MACOS_USER" >/dev/null 2>&1; then
2131
+ EXISTING_HOME=$(dscl . -read "/Users/$MACOS_USER" NFSHomeDirectory 2>/dev/null | awk '/NFSHomeDirectory:/ {print $2}')
2668
2132
  if [ -n "$EXISTING_HOME" ] && [ -d "$EXISTING_HOME" ]; then
2669
- echo "User $NAME already exists with a live home at $EXISTING_HOME; refusing to overwrite." >&2
2133
+ echo "User $MACOS_USER already exists with a live home at $EXISTING_HOME; refusing to overwrite." >&2
2670
2134
  exit 1
2671
2135
  fi
2672
- NEXT_UID=$(dscl . -read "/Users/$NAME" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
2136
+ NEXT_UID=$(dscl . -read "/Users/$MACOS_USER" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
2673
2137
  if [ -z "$NEXT_UID" ]; then
2674
- echo "User $NAME exists but has no UniqueID; record is malformed, refusing to recycle." >&2
2138
+ echo "User $MACOS_USER exists but has no UniqueID; record is malformed, refusing to recycle." >&2
2675
2139
  exit 1
2676
2140
  fi
2677
- echo "Recycling tombstone dscl record for $NAME (uid=$NEXT_UID, prior home $EXISTING_HOME \u2014 gone)"
2141
+ echo "Recycling tombstone dscl record for $MACOS_USER (uid=$NEXT_UID, prior home $EXISTING_HOME \u2014 gone)"
2678
2142
  TOMBSTONE_REUSE=1
2679
2143
  fi
2680
2144
 
@@ -2685,54 +2149,65 @@ mkdir -p /var/openape/homes
2685
2149
  chmod 755 /var/openape/homes
2686
2150
 
2687
2151
  if [ "$TOMBSTONE_REUSE" = "0" ]; then
2688
- # Pick the next free UID in the [200, 500) hidden service-account range.
2689
- # Starts the running max at 199 so an empty range yields 200 after the
2690
- # floor check; otherwise NEXT_UID = max(existing in-range UIDs) + 1.
2691
- NEXT_UID=199
2692
- for uid in $(dscl . -list /Users UniqueID | awk '$2 >= 200 && $2 < 500 {print $2}'); do
2693
- if [ "$uid" -ge "$NEXT_UID" ]; then
2694
- NEXT_UID=$((uid + 1))
2152
+ # Pick the LOWEST free UID in the [200, 500) hidden service-account
2153
+ # range. We must skip every occupied UID \u2014 agent users AND the many
2154
+ # macOS system _* accounts \u2014 and reuse gaps. The old code took
2155
+ # max(existing)+1, so a single agent landing on 499 wedged every
2156
+ # future spawn ("No free UID") even with 100+ UIDs free. Now we scan
2157
+ # for the first actually-unused UID.
2158
+ OCCUPIED_UIDS=$(dscl . -list /Users UniqueID | awk '$2 >= 200 && $2 < 500 {print $2}' | sort -n -u)
2159
+ NEXT_UID=""
2160
+ candidate=200
2161
+ while [ "$candidate" -lt 500 ]; do
2162
+ if ! printf '%s
2163
+ ' "$OCCUPIED_UIDS" | grep -qx "$candidate"; then
2164
+ NEXT_UID=$candidate
2165
+ break
2695
2166
  fi
2167
+ candidate=$((candidate + 1))
2696
2168
  done
2697
- if [ "$NEXT_UID" -lt 200 ]; then
2698
- NEXT_UID=200
2699
- fi
2700
- if [ "$NEXT_UID" -ge 500 ]; then
2169
+ if [ -z "$NEXT_UID" ]; then
2701
2170
  echo "No free UID in [200, 500) \u2014 refusing to clobber a real user." >&2
2702
2171
  exit 1
2703
2172
  fi
2704
2173
 
2705
- dscl . -create "/Users/$NAME"
2174
+ dscl . -create "/Users/$MACOS_USER"
2706
2175
  fi
2707
2176
 
2708
2177
  # Idempotent attribute writes \u2014 \`dscl . -create\` on an existing
2709
2178
  # property overwrites in place, so the tombstone-reuse path lands
2710
2179
  # the same end-state as a fresh create.
2711
- dscl . -create "/Users/$NAME" UserShell "$SHELL_PATH"
2712
- dscl . -create "/Users/$NAME" RealName "OpenApe Agent $NAME"
2713
- dscl . -create "/Users/$NAME" UniqueID "$NEXT_UID"
2714
- dscl . -create "/Users/$NAME" PrimaryGroupID 20
2715
- dscl . -create "/Users/$NAME" NFSHomeDirectory "$HOME_DIR"
2716
- dscl . -create "/Users/$NAME" IsHidden 1
2180
+ dscl . -create "/Users/$MACOS_USER" UserShell "$SHELL_PATH"
2181
+ dscl . -create "/Users/$MACOS_USER" RealName "OpenApe Agent $NAME"
2182
+ dscl . -create "/Users/$MACOS_USER" UniqueID "$NEXT_UID"
2183
+ dscl . -create "/Users/$MACOS_USER" PrimaryGroupID 20
2184
+ dscl . -create "/Users/$MACOS_USER" NFSHomeDirectory "$HOME_DIR"
2185
+ dscl . -create "/Users/$MACOS_USER" IsHidden 1
2717
2186
 
2718
2187
  mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
2719
2188
 
2720
2189
  cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
2721
2190
  cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
2722
2191
  cat > "$HOME_DIR/.config/apes/auth.json" ${shHeredoc(input.authJson)}
2192
+ mkdir -p "$HOME_DIR/.config/openape"
2193
+ cat > "$HOME_DIR/.config/openape/agent-x25519.key" ${shHeredoc(input.x25519PrivateKey)}
2194
+ cat > "$HOME_DIR/.config/openape/agent-x25519.key.pub" ${shHeredoc(input.x25519PublicKey)}
2723
2195
  ${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}${buildTroopBlock(input.troop)}
2724
- chown -R "$NAME:staff" "$HOME_DIR"
2196
+ chown -R "$MACOS_USER:staff" "$HOME_DIR"
2725
2197
  chmod 700 "$HOME_DIR/.ssh"
2726
2198
  chmod 700 "$HOME_DIR/.config"
2199
+ chmod 700 "$HOME_DIR/.config/openape"
2727
2200
  chmod 600 "$HOME_DIR/.ssh/id_ed25519"
2728
2201
  chmod 644 "$HOME_DIR/.ssh/id_ed25519.pub"
2729
2202
  chmod 600 "$HOME_DIR/.config/apes/auth.json"
2203
+ chmod 600 "$HOME_DIR/.config/openape/agent-x25519.key"
2204
+ chmod 644 "$HOME_DIR/.config/openape/agent-x25519.key.pub"
2730
2205
  if [ -f "$HOME_DIR/.config/openape/claude-token.env" ]; then
2731
2206
  chmod 700 "$HOME_DIR/.config/openape"
2732
2207
  chmod 600 "$HOME_DIR/.config/openape/claude-token.env"
2733
2208
  fi
2734
2209
 
2735
- echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
2210
+ echo "OK $NAME (macOS user $MACOS_USER) uid=$NEXT_UID home=$HOME_DIR"
2736
2211
  ${buildBridgeBootstrapBlock(input.bridge, name)}${buildTroopBootstrapBlock(input.troop, name)}`;
2737
2212
  }
2738
2213
  function buildBridgeBlock(bridge) {
@@ -2756,9 +2231,10 @@ function buildTroopBootstrapBlock(_troop, _name) {
2756
2231
  }
2757
2232
  function runPhaseGTeardownInProcess(input) {
2758
2233
  const { name, homeDir } = input;
2234
+ const macOSUser = input.macOSUsername ?? name;
2759
2235
  let uid = null;
2760
2236
  try {
2761
- const out = execFileSync2("/usr/bin/dscl", [".", "-read", `/Users/${name}`, "UniqueID"], { encoding: "utf8" });
2237
+ const out = execFileSync2("/usr/bin/dscl", [".", "-read", `/Users/${macOSUser}`, "UniqueID"], { encoding: "utf8" });
2762
2238
  const m = out.match(/UniqueID:\s*(\d+)/);
2763
2239
  if (m) uid = m[1];
2764
2240
  } catch {
@@ -2784,7 +2260,7 @@ function runPhaseGTeardownInProcess(input) {
2784
2260
  } catch {
2785
2261
  }
2786
2262
  }
2787
- console.log(`OK Phase-G teardown done for ${name} (dscl record kept as tombstone)`);
2263
+ console.log(`OK Phase-G teardown done for ${name} (dscl record /Users/${macOSUser} kept as tombstone)`);
2788
2264
  }
2789
2265
  function buildDestroyTeardownScript(input) {
2790
2266
  const { name, homeDir, adminUser } = input;
@@ -2913,6 +2389,38 @@ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2913
2389
  function isDarwin() {
2914
2390
  return process.platform === "darwin";
2915
2391
  }
2392
+ var MACOS_USER_PREFIX = "openape-agent-";
2393
+ function macOSUsernameForAgent(agentName) {
2394
+ return `${MACOS_USER_PREFIX}${agentName}`;
2395
+ }
2396
+ function lookupMacOSUserForAgent(agentName) {
2397
+ return readMacOSUser(macOSUsernameForAgent(agentName)) ?? readMacOSUser(agentName);
2398
+ }
2399
+ function listOrphanedAgentRecords() {
2400
+ if (!isDarwin()) return [];
2401
+ let output;
2402
+ try {
2403
+ output = execFileSync3("dscl", [".", "-list", "/Users", "NFSHomeDirectory"], {
2404
+ encoding: "utf-8",
2405
+ stdio: ["ignore", "pipe", "pipe"]
2406
+ });
2407
+ } catch {
2408
+ return [];
2409
+ }
2410
+ const orphans = [];
2411
+ for (const line of output.split("\n")) {
2412
+ const parts = line.trim().split(/\s+/);
2413
+ if (parts.length < 2) continue;
2414
+ const name = parts[0];
2415
+ const homeDir = parts.slice(1).join(" ");
2416
+ const looksLikeAgent = name.startsWith(MACOS_USER_PREFIX) || homeDir.startsWith("/var/openape/homes/");
2417
+ if (!looksLikeAgent) continue;
2418
+ if (existsSync3(homeDir)) continue;
2419
+ const record = readMacOSUser(name);
2420
+ orphans.push({ name, uid: record?.uid ?? null, homeDir });
2421
+ }
2422
+ return orphans;
2423
+ }
2916
2424
  function readMacOSUser(name) {
2917
2425
  let output;
2918
2426
  try {
@@ -2965,7 +2473,7 @@ function isShellRegistered(shellPath) {
2965
2473
  }
2966
2474
 
2967
2475
  // src/commands/agents/allow.ts
2968
- var allowAgentCommand = defineCommand20({
2476
+ var allowAgentCommand = defineCommand22({
2969
2477
  meta: {
2970
2478
  name: "allow",
2971
2479
  description: "Add a peer to the agent's contact-allowlist so the bridge auto-accepts that peer's contact request."
@@ -2994,8 +2502,8 @@ var allowAgentCommand = defineCommand20({
2994
2502
  if (!isDarwin()) {
2995
2503
  throw new CliError("`apes agents allow` is currently macOS-only.");
2996
2504
  }
2997
- if (!readMacOSUser(agent)) {
2998
- throw new CliError(`No macOS user "${agent}" \u2014 has the agent been spawned?`);
2505
+ if (!lookupMacOSUserForAgent(agent)) {
2506
+ throw new CliError(`No macOS user for agent "${agent}" \u2014 has it been spawned?`);
2999
2507
  }
3000
2508
  const apes = whichBinary("apes");
3001
2509
  if (!apes) throw new CliError("`apes` not found on PATH.");
@@ -3025,22 +2533,99 @@ print("ok")
3025
2533
  PY
3026
2534
  chmod 600 "$F"
3027
2535
  `;
3028
- consola18.start(`Adding ${email} to ${agent}'s allowlist\u2026`);
2536
+ consola19.start(`Adding ${email} to ${agent}'s allowlist\u2026`);
3029
2537
  execFileSync4(apes, ["run", "--as", agent, "--wait", "--", "bash", "-c", script], { stdio: "inherit" });
3030
- consola18.success(`${agent} will auto-accept future contact requests from ${email} (within ~30s of next bridge connect).`);
2538
+ consola19.success(`${agent} will auto-accept future contact requests from ${email} (within ~30s of next bridge connect).`);
3031
2539
  }
3032
2540
  });
3033
2541
  function shQuote2(s) {
3034
2542
  return `'${s.replace(/'/g, `'\\''`)}'`;
3035
2543
  }
3036
2544
 
3037
- // src/commands/agents/destroy.ts
2545
+ // src/commands/agents/cleanup-orphans.ts
3038
2546
  import { execFileSync as execFileSync5 } from "child_process";
2547
+ import { defineCommand as defineCommand23 } from "citty";
2548
+ import consola20 from "consola";
2549
+ var cleanupOrphansCommand = defineCommand23({
2550
+ meta: {
2551
+ name: "cleanup-orphans",
2552
+ description: "Delete tombstoned macOS user records left behind by `apes agents destroy` (run with sudo)."
2553
+ },
2554
+ args: {
2555
+ "dry-run": {
2556
+ type: "boolean",
2557
+ description: "List orphans without deleting."
2558
+ },
2559
+ "force": {
2560
+ type: "boolean",
2561
+ description: "Skip the interactive confirmation. Required when stdin is not a TTY."
2562
+ }
2563
+ },
2564
+ async run({ args }) {
2565
+ if (!isDarwin()) {
2566
+ throw new CliError(`\`apes agents cleanup-orphans\` is macOS-only. Detected platform: ${process.platform}.`);
2567
+ }
2568
+ const orphans = listOrphanedAgentRecords();
2569
+ if (orphans.length === 0) {
2570
+ consola20.success("No agent tombstones found \u2014 dscl is clean.");
2571
+ return;
2572
+ }
2573
+ consola20.info(`Found ${orphans.length} agent tombstone${orphans.length === 1 ? "" : "s"}:`);
2574
+ for (const o of orphans) {
2575
+ console.log(` \u2022 ${o.name}${o.uid !== null ? ` (uid=${o.uid})` : ""} \u2014 was ${o.homeDir}`);
2576
+ }
2577
+ if (args["dry-run"]) {
2578
+ consola20.info("Dry-run \u2014 no records deleted. Re-run without --dry-run to clean up.");
2579
+ return;
2580
+ }
2581
+ if (process.geteuid?.() !== 0) {
2582
+ throw new CliError(
2583
+ "Must run as root so opendirectoryd accepts the sysadminctl-deleteUser calls. Re-run with `sudo apes agents cleanup-orphans` from a shell login (the sudo session inherits your admin audit-session, which opendirectoryd verifies)."
2584
+ );
2585
+ }
2586
+ if (!args.force) {
2587
+ if (!process.stdin.isTTY) {
2588
+ throw new CliError(
2589
+ "No TTY available for the interactive confirmation. Re-run with --force (this is the same flag CI / scripted callers use)."
2590
+ );
2591
+ }
2592
+ const confirmed = await consola20.prompt(`Delete ${orphans.length} tombstone${orphans.length === 1 ? "" : "s"}?`, {
2593
+ type: "confirm",
2594
+ initial: false
2595
+ });
2596
+ if (typeof confirmed === "symbol" || !confirmed) {
2597
+ consola20.info("Aborted \u2014 no records deleted.");
2598
+ return;
2599
+ }
2600
+ }
2601
+ let deleted = 0;
2602
+ let failed = 0;
2603
+ for (const o of orphans) {
2604
+ try {
2605
+ execFileSync5("/usr/sbin/sysadminctl", ["-deleteUser", o.name], {
2606
+ stdio: ["ignore", "inherit", "inherit"]
2607
+ });
2608
+ consola20.success(`Deleted ${o.name}`);
2609
+ deleted++;
2610
+ } catch (err) {
2611
+ consola20.warn(`Failed to delete ${o.name}: ${err instanceof Error ? err.message : String(err)}`);
2612
+ failed++;
2613
+ }
2614
+ }
2615
+ if (failed > 0) {
2616
+ throw new CliError(`Cleanup finished with errors: ${deleted} deleted, ${failed} failed.`);
2617
+ }
2618
+ consola20.success(`Cleanup complete \u2014 ${deleted} tombstone${deleted === 1 ? "" : "s"} removed.`);
2619
+ }
2620
+ });
2621
+
2622
+ // src/commands/agents/destroy.ts
2623
+ import { execFileSync as execFileSync6 } from "child_process";
3039
2624
  import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
3040
2625
  import { tmpdir, userInfo } from "os";
3041
2626
  import { join as join3 } from "path";
3042
- import { defineCommand as defineCommand21 } from "citty";
3043
- import consola19 from "consola";
2627
+ import { defineCommand as defineCommand24 } from "citty";
2628
+ import consola21 from "consola";
3044
2629
 
3045
2630
  // src/lib/nest-registry.ts
3046
2631
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
@@ -3146,7 +2731,7 @@ function readPasswordSilent(prompt) {
3146
2731
  }
3147
2732
 
3148
2733
  // src/commands/agents/destroy.ts
3149
- var destroyAgentCommand = defineCommand21({
2734
+ var destroyAgentCommand = defineCommand24({
3150
2735
  meta: {
3151
2736
  name: "destroy",
3152
2737
  description: "Tear down an agent: remove macOS user, hard-delete IdP agent, drop all SSH keys"
@@ -3185,9 +2770,11 @@ var destroyAgentCommand = defineCommand21({
3185
2770
  if (process.geteuid?.() !== 0) {
3186
2771
  throw new CliError("--root-stage was passed but this process is not running as root. Refusing to continue.");
3187
2772
  }
3188
- const homeDir = readMacOSUser(name)?.homeDir ?? `/var/openape/homes/${name}`;
3189
- consola19.start(`Running teardown for ${name} (Phase-G, root-stage)\u2026`);
3190
- runPhaseGTeardownInProcess({ name, homeDir });
2773
+ const resolved = lookupMacOSUserForAgent(name);
2774
+ const macOSUsername = resolved?.name ?? macOSUsernameForAgent(name);
2775
+ const homeDir = resolved?.homeDir ?? `/var/openape/homes/${macOSUsername}`;
2776
+ consola21.start(`Running teardown for ${name} (Phase-G, root-stage)\u2026`);
2777
+ runPhaseGTeardownInProcess({ name, homeDir, macOSUsername });
3191
2778
  return;
3192
2779
  }
3193
2780
  const auth = loadAuth();
@@ -3201,29 +2788,29 @@ var destroyAgentCommand = defineCommand21({
3201
2788
  const owned = await apiFetch("/api/my-agents", { idp });
3202
2789
  const idpAgent = owned.find((u) => u.name === name);
3203
2790
  const idpExists = idpAgent !== void 0;
3204
- const osUser = isDarwin() ? readMacOSUser(name) : null;
2791
+ const osUser = isDarwin() ? lookupMacOSUserForAgent(name) : null;
3205
2792
  const osUserExists = !args["keep-os-user"] && osUser !== null;
3206
2793
  if (!idpExists && !osUserExists) {
3207
- consola19.info(`Nothing to destroy: no IdP agent and no OS user named "${name}".`);
2794
+ consola21.info(`Nothing to destroy: no IdP agent and no OS user for "${name}".`);
3208
2795
  return;
3209
2796
  }
3210
2797
  if (!args.force) {
3211
2798
  const consequences = [];
3212
2799
  if (osUserExists) {
3213
2800
  const home = osUser?.homeDir ?? `/Users/${name}`;
3214
- consequences.push(`\u2022 Remove macOS user ${name} and rm -rf ${home}`);
2801
+ consequences.push(`\u2022 Remove macOS user ${osUser?.name ?? name} and rm -rf ${home}`);
3215
2802
  }
3216
2803
  if (idpExists) {
3217
2804
  consequences.push(args.soft ? `\u2022 Deactivate IdP agent ${idpAgent.email} (PATCH isActive=false)` : `\u2022 Hard-delete IdP agent ${idpAgent.email} and all its SSH keys`);
3218
2805
  }
3219
- consola19.warn(`About to destroy "${name}":
2806
+ consola21.warn(`About to destroy "${name}":
3220
2807
  ${consequences.join("\n")}`);
3221
2808
  if (!process.stdin.isTTY) {
3222
2809
  throw new CliError(
3223
2810
  "No TTY available for the interactive confirmation. Re-run with --force to skip the prompt (this is the same flag CI uses)."
3224
2811
  );
3225
2812
  }
3226
- const confirmed = await consola19.prompt("Proceed?", { type: "confirm", initial: false });
2813
+ const confirmed = await consola21.prompt("Proceed?", { type: "confirm", initial: false });
3227
2814
  if (typeof confirmed === "symbol" || !confirmed) {
3228
2815
  throw new CliExit(0);
3229
2816
  }
@@ -3232,24 +2819,26 @@ ${consequences.join("\n")}`);
3232
2819
  const id = encodeURIComponent(idpAgent.email);
3233
2820
  if (args.soft) {
3234
2821
  await apiFetch(`/api/my-agents/${id}`, { method: "PATCH", body: { isActive: false }, idp });
3235
- consola19.success(`Deactivated IdP agent ${idpAgent.email}`);
2822
+ consola21.success(`Deactivated IdP agent ${idpAgent.email}`);
3236
2823
  } else {
3237
2824
  await apiFetch(`/api/my-agents/${id}`, { method: "DELETE", idp });
3238
- consola19.success(`Deleted IdP agent ${idpAgent.email}`);
2825
+ consola21.success(`Deleted IdP agent ${idpAgent.email}`);
3239
2826
  }
3240
2827
  } else {
3241
- consola19.info("No IdP agent to remove (skipped).");
2828
+ consola21.info("No IdP agent to remove (skipped).");
3242
2829
  }
3243
2830
  if (osUserExists) {
3244
- const homeDir = osUser?.homeDir ?? `/Users/${name}`;
2831
+ const macOSUsername = osUser?.name ?? macOSUsernameForAgent(name);
2832
+ const fallbackHome = macOSUsername.startsWith("openape-agent-") ? `/var/openape/homes/${macOSUsername}` : `/Users/${macOSUsername}`;
2833
+ const homeDir = osUser?.homeDir ?? fallbackHome;
3245
2834
  const isPhaseG = homeDir.startsWith("/var/openape/homes/");
3246
2835
  if (isPhaseG) {
3247
2836
  if (process.geteuid?.() === 0) {
3248
- consola19.start("Running teardown (Phase G \u2014 already root, no grant needed)\u2026");
3249
- runPhaseGTeardownInProcess({ name, homeDir });
2837
+ consola21.start("Running teardown (Phase G \u2014 already root, no grant needed)\u2026");
2838
+ runPhaseGTeardownInProcess({ name, homeDir, macOSUsername });
3250
2839
  } else {
3251
- consola19.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
3252
- execFileSync5("apes", [
2840
+ consola21.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
2841
+ execFileSync6("apes", [
3253
2842
  "run",
3254
2843
  "--as",
3255
2844
  "root",
@@ -3263,7 +2852,7 @@ ${consequences.join("\n")}`);
3263
2852
  "--root-stage"
3264
2853
  ], { stdio: "inherit" });
3265
2854
  }
3266
- consola19.info(`dscl record /Users/${name} kept as tombstone (hidden, no home). Run \`sudo sysadminctl -deleteUser ${name}\` to fully remove.`);
2855
+ consola21.info(`dscl record /Users/${macOSUsername} kept as tombstone (hidden, no home). Run \`sudo apes agents cleanup-orphans\` to sweep accumulated tombstones.`);
3267
2856
  } else {
3268
2857
  const sudo = whichBinary("sudo");
3269
2858
  if (!sudo) {
@@ -3276,7 +2865,7 @@ ${consequences.join("\n")}`);
3276
2865
  } catch (err) {
3277
2866
  const headless = !process.stdin.isTTY && !process.env.APES_ADMIN_PASSWORD;
3278
2867
  if (headless) {
3279
- consola19.warn(`Legacy OS teardown for ${name} requires a TTY or APES_ADMIN_PASSWORD; skipping. Run \`apes agents destroy ${name}\` from a shell later to fully clean up /Users/${name} + dscl record.`);
2868
+ consola21.warn(`Legacy OS teardown for ${name} requires a TTY or APES_ADMIN_PASSWORD; skipping. Run \`apes agents destroy ${name}\` from a shell later to fully clean up /Users/${name} + dscl record.`);
3280
2869
  adminPassword = "";
3281
2870
  } else {
3282
2871
  throw err;
@@ -3288,8 +2877,8 @@ ${consequences.join("\n")}`);
3288
2877
  try {
3289
2878
  const script = buildDestroyTeardownScript({ name, homeDir, adminUser });
3290
2879
  writeFileSync2(scriptPath, script, { mode: 448 });
3291
- consola19.start("Running teardown via sudo\u2026");
3292
- execFileSync5(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
2880
+ consola21.start("Running teardown via sudo\u2026");
2881
+ execFileSync6(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
3293
2882
  input: `${adminPassword}
3294
2883
  ${adminPassword}
3295
2884
  `,
@@ -3301,14 +2890,14 @@ ${adminPassword}
3301
2890
  }
3302
2891
  }
3303
2892
  } else if (!args["keep-os-user"] && isDarwin()) {
3304
- consola19.info("No macOS user to remove (skipped).");
2893
+ consola21.info("No macOS user to remove (skipped).");
3305
2894
  }
3306
2895
  try {
3307
2896
  removeNestAgent(name);
3308
2897
  } catch (err) {
3309
- consola19.warn(`Could not update nest registry: ${err instanceof Error ? err.message : String(err)}`);
2898
+ consola21.warn(`Could not update nest registry: ${err instanceof Error ? err.message : String(err)}`);
3310
2899
  }
3311
- consola19.success(`Destroyed ${name}.`);
2900
+ consola21.success(`Destroyed ${name}.`);
3312
2901
  }
3313
2902
  });
3314
2903
  async function collectAdminPassword(opts) {
@@ -3322,9 +2911,9 @@ async function collectAdminPassword(opts) {
3322
2911
  }
3323
2912
 
3324
2913
  // src/commands/agents/list.ts
3325
- import { defineCommand as defineCommand22 } from "citty";
3326
- import consola20 from "consola";
3327
- var listAgentsCommand = defineCommand22({
2914
+ import { defineCommand as defineCommand25 } from "citty";
2915
+ import consola22 from "consola";
2916
+ var listAgentsCommand = defineCommand25({
3328
2917
  meta: {
3329
2918
  name: "list",
3330
2919
  description: "List agents owned by the current user, with local OS-user status"
@@ -3351,46 +2940,52 @@ var listAgentsCommand = defineCommand22({
3351
2940
  const all = await apiFetch("/api/my-agents", { idp });
3352
2941
  const filtered = args["include-inactive"] ? all : all.filter((u) => u.isActive !== false);
3353
2942
  const osUsers = isDarwin() ? listMacOSUserNames() : /* @__PURE__ */ new Set();
3354
- const homeOf = (name) => {
3355
- if (!osUsers.has(name)) return null;
3356
- const u = readMacOSUser(name);
3357
- return u?.homeDir ?? `/Users/${name}`;
2943
+ const osStateOf = (agentName) => {
2944
+ const u = lookupMacOSUserForAgent(agentName);
2945
+ if (u) return { osUser: true, home: u.homeDir };
2946
+ if (osUsers.has(macOSUsernameForAgent(agentName)) || osUsers.has(agentName)) {
2947
+ return { osUser: true, home: null };
2948
+ }
2949
+ return { osUser: false, home: null };
3358
2950
  };
3359
- const rows = filtered.map((u) => ({
3360
- name: u.name,
3361
- email: u.email,
3362
- isActive: u.isActive !== false,
3363
- osUser: osUsers.has(u.name),
3364
- home: homeOf(u.name)
3365
- }));
2951
+ const rows = filtered.map((u) => {
2952
+ const os = osStateOf(u.name);
2953
+ return {
2954
+ name: u.name,
2955
+ email: u.email,
2956
+ isActive: u.isActive !== false,
2957
+ osUser: os.osUser,
2958
+ home: os.home
2959
+ };
2960
+ });
3366
2961
  if (args.json) {
3367
2962
  process.stdout.write(`${JSON.stringify(rows, null, 2)}
3368
2963
  `);
3369
2964
  return;
3370
2965
  }
3371
2966
  if (rows.length === 0) {
3372
- consola20.info(args["include-inactive"] ? "No agents found." : "No active agents found. Use --include-inactive to show deactivated.");
2967
+ consola22.info(args["include-inactive"] ? "No agents found." : "No active agents found. Use --include-inactive to show deactivated.");
3373
2968
  return;
3374
2969
  }
3375
- const nameW = Math.max(4, ...rows.map((r3) => r3.name.length));
3376
- const emailW = Math.max(5, ...rows.map((r3) => r3.email.length));
2970
+ const nameW = Math.max(4, ...rows.map((r) => r.name.length));
2971
+ const emailW = Math.max(5, ...rows.map((r) => r.email.length));
3377
2972
  const header = `${"NAME".padEnd(nameW)} ${"EMAIL".padEnd(emailW)} ACTIVE OS-USER HOME`;
3378
2973
  console.log(header);
3379
2974
  console.log("-".repeat(header.length));
3380
- for (const r3 of rows) {
3381
- const active = r3.isActive ? "\u2713" : "\u2717";
3382
- const os = r3.osUser ? "\u2713" : "\u2717";
3383
- const homeCol = r3.home ?? (isDarwin() ? "(missing)" : "(non-darwin)");
3384
- console.log(`${r3.name.padEnd(nameW)} ${r3.email.padEnd(emailW)} ${active.padEnd(6)} ${os.padEnd(7)} ${homeCol}`);
2975
+ for (const r of rows) {
2976
+ const active = r.isActive ? "\u2713" : "\u2717";
2977
+ const os = r.osUser ? "\u2713" : "\u2717";
2978
+ const homeCol = r.home ?? (isDarwin() ? "(missing)" : "(non-darwin)");
2979
+ console.log(`${r.name.padEnd(nameW)} ${r.email.padEnd(emailW)} ${active.padEnd(6)} ${os.padEnd(7)} ${homeCol}`);
3385
2980
  }
3386
2981
  }
3387
2982
  });
3388
2983
 
3389
2984
  // src/commands/agents/register.ts
3390
2985
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3391
- import { defineCommand as defineCommand23 } from "citty";
3392
- import consola21 from "consola";
3393
- var registerAgentCommand = defineCommand23({
2986
+ import { defineCommand as defineCommand26 } from "citty";
2987
+ import consola23 from "consola";
2988
+ var registerAgentCommand = defineCommand26({
3394
2989
  meta: {
3395
2990
  name: "register",
3396
2991
  description: "Register an agent at the IdP using a supplied public key"
@@ -3460,7 +3055,7 @@ var registerAgentCommand = defineCommand23({
3460
3055
  `);
3461
3056
  return;
3462
3057
  }
3463
- consola21.success("Agent registered.");
3058
+ consola23.success("Agent registered.");
3464
3059
  console.log(` Name: ${result.name}`);
3465
3060
  console.log(` Email: ${result.email}`);
3466
3061
  console.log(` IdP: ${idp}`);
@@ -3472,78 +3067,93 @@ var registerAgentCommand = defineCommand23({
3472
3067
  }
3473
3068
  });
3474
3069
 
3475
- // src/commands/agents/run.ts
3476
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
3070
+ // src/commands/agents/run.ts
3071
+ import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
3072
+ import { homedir as homedir6 } from "os";
3073
+ import { join as join5 } from "path";
3074
+ import { defineCommand as defineCommand27 } from "citty";
3075
+ import consola24 from "consola";
3076
+
3077
+ // src/lib/agent-secrets-runtime.ts
3078
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync5, watch } from "fs";
3477
3079
  import { homedir as homedir5 } from "os";
3478
3080
  import { join as join4 } from "path";
3479
- import { defineCommand as defineCommand24 } from "citty";
3480
- import consola22 from "consola";
3481
-
3482
- // src/lib/troop-client.ts
3483
- var DEFAULT_TROOP_URL = "https://troop.openape.ai";
3484
- var TroopClient = class {
3485
- constructor(troopUrl, agentJwt) {
3486
- this.troopUrl = troopUrl;
3487
- this.agentJwt = agentJwt;
3488
- }
3489
- async request(path2, init) {
3490
- const res = await fetch(`${this.troopUrl}${path2}`, {
3491
- ...init,
3492
- headers: {
3493
- ...init?.headers ?? {},
3494
- "Authorization": `Bearer ${this.agentJwt}`,
3495
- "Content-Type": "application/json"
3496
- }
3497
- });
3498
- if (!res.ok) {
3499
- const text = await res.text().catch(() => "");
3500
- throw new Error(`troop ${init?.method ?? "GET"} ${path2} failed: ${res.status} ${text}`);
3081
+ import { openString } from "@openape/core";
3082
+ var CONFIG_DIR2 = join4(homedir5(), ".config", "openape");
3083
+ var SECRETS_DIR = join4(CONFIG_DIR2, "secrets.d");
3084
+ var X25519_KEY_PATH = join4(CONFIG_DIR2, "agent-x25519.key");
3085
+ function envNameFromFile(file) {
3086
+ if (!file.endsWith(".blob")) return null;
3087
+ const env = file.slice(0, -".blob".length);
3088
+ return /^[A-Z][A-Z0-9_]*$/.test(env) ? env : null;
3089
+ }
3090
+ function readAgentEncryptionKey(keyPath = X25519_KEY_PATH) {
3091
+ if (!existsSync6(keyPath)) return null;
3092
+ const k = readFileSync5(keyPath, "utf8").trim();
3093
+ return k.length > 0 ? k : null;
3094
+ }
3095
+ function materializeSecrets(opts = {}) {
3096
+ const dir = opts.dir ?? SECRETS_DIR;
3097
+ const env = opts.env ?? process.env;
3098
+ const log = opts.log ?? (() => {
3099
+ });
3100
+ const applied = [];
3101
+ const failed = [];
3102
+ const key = readAgentEncryptionKey(opts.keyPath);
3103
+ const files = key && existsSync6(dir) ? readdirSync(dir) : [];
3104
+ for (const file of files) {
3105
+ const name = envNameFromFile(file);
3106
+ if (!name) continue;
3107
+ try {
3108
+ const box = JSON.parse(readFileSync5(join4(dir, file), "utf8"));
3109
+ env[name] = openString(box, key);
3110
+ applied.push(name);
3111
+ } catch (e) {
3112
+ failed.push(file);
3113
+ log(`secrets: failed to open ${file}: ${e.message}`);
3501
3114
  }
3502
- if (res.status === 204) return void 0;
3503
- return await res.json();
3504
- }
3505
- sync(input) {
3506
- return this.request("/api/agents/me/sync", {
3507
- method: "POST",
3508
- body: JSON.stringify({
3509
- hostname: input.hostname,
3510
- host_id: input.hostId,
3511
- owner_email: input.ownerEmail,
3512
- ...input.pubkeySsh ? { pubkey_ssh: input.pubkeySsh } : {}
3513
- })
3514
- });
3515
- }
3516
- listTasks() {
3517
- return this.request("/api/agents/me/tasks");
3518
- }
3519
- startRun(taskId) {
3520
- return this.request("/api/agents/me/runs", {
3521
- method: "POST",
3522
- body: JSON.stringify({ task_id: taskId })
3523
- });
3524
3115
  }
3525
- finaliseRun(id, payload) {
3526
- return this.request(`/api/agents/me/runs/${id}`, {
3527
- method: "PATCH",
3528
- body: JSON.stringify(payload)
3529
- });
3116
+ const live = new Set(applied);
3117
+ for (const prev of opts.previouslyApplied ?? []) {
3118
+ if (!live.has(prev)) {
3119
+ delete env[prev];
3120
+ log(`secrets: revoked ${prev}`);
3121
+ }
3530
3122
  }
3531
- };
3532
- function resolveTroopUrl(override) {
3533
- if (override) return override.replace(/\/$/, "");
3534
- const fromEnv = process.env.OPENAPE_TROOP_URL;
3535
- if (fromEnv) return fromEnv.replace(/\/$/, "");
3536
- return DEFAULT_TROOP_URL;
3123
+ return { applied, failed };
3124
+ }
3125
+ function startSecretsWatcher(opts = {}) {
3126
+ const dir = opts.dir ?? SECRETS_DIR;
3127
+ const log = opts.log ?? (() => {
3128
+ });
3129
+ let appliedNames = /* @__PURE__ */ new Set();
3130
+ const run = () => {
3131
+ const r = materializeSecrets({ ...opts, previouslyApplied: appliedNames });
3132
+ appliedNames = new Set(r.applied);
3133
+ };
3134
+ run();
3135
+ if (!existsSync6(dir)) return () => {
3136
+ };
3137
+ let timer = null;
3138
+ const watcher = watch(dir, () => {
3139
+ if (timer) clearTimeout(timer);
3140
+ timer = setTimeout(run, 150);
3141
+ });
3142
+ watcher.on("error", (err) => log(`secrets: watcher error: ${err.message}`));
3143
+ return () => {
3144
+ if (timer) clearTimeout(timer);
3145
+ watcher.close();
3146
+ };
3537
3147
  }
3538
3148
 
3539
3149
  // src/commands/agents/run.ts
3540
- var AUTH_PATH = join4(homedir5(), ".config", "apes", "auth.json");
3541
- var TASK_CACHE_DIR = join4(homedir5(), ".openape", "agent", "tasks");
3150
+ var AUTH_PATH = join5(homedir6(), ".config", "apes", "auth.json");
3151
+ var TASK_CACHE_DIR = join5(homedir6(), ".openape", "agent", "tasks");
3542
3152
  function readAuth() {
3543
- if (!existsSync6(AUTH_PATH)) {
3153
+ if (!existsSync7(AUTH_PATH)) {
3544
3154
  throw new CliError(`No agent auth found at ${AUTH_PATH}. Run \`apes agents spawn <name>\` first.`);
3545
3155
  }
3546
- const parsed = JSON.parse(readFileSync5(AUTH_PATH, "utf8"));
3156
+ const parsed = JSON.parse(readFileSync6(AUTH_PATH, "utf8"));
3547
3157
  if (!parsed.access_token) throw new CliError("auth.json missing access_token");
3548
3158
  return parsed;
3549
3159
  }
@@ -3556,9 +3166,9 @@ async function postRunResultToChat(opts) {
3556
3166
  if (!contactsRes.ok) return;
3557
3167
  const contacts = await contactsRes.json();
3558
3168
  const ownerLower = opts.ownerEmail.toLowerCase();
3559
- const ownerRow = contacts.find((c2) => c2.peerEmail.toLowerCase() === ownerLower && c2.connected && c2.roomId);
3169
+ const ownerRow = contacts.find((c) => c.peerEmail.toLowerCase() === ownerLower && c.connected && c.roomId);
3560
3170
  if (!ownerRow?.roomId) {
3561
- consola22.info("chat DM skipped \u2014 no active room with owner (accept the contact request in chat to enable)");
3171
+ consola24.info("chat DM skipped \u2014 no active room with owner (accept the contact request in chat to enable)");
3562
3172
  return;
3563
3173
  }
3564
3174
  const prefix = opts.status === "ok" ? "\u2705" : "\u274C";
@@ -3572,33 +3182,33 @@ ${msg}`.slice(0, 9e3);
3572
3182
  body: JSON.stringify({ body })
3573
3183
  });
3574
3184
  if (!postRes.ok) {
3575
- consola22.warn(`chat DM post failed: ${postRes.status}`);
3185
+ consola24.warn(`chat DM post failed: ${postRes.status}`);
3576
3186
  }
3577
3187
  } catch (err) {
3578
- consola22.warn(`chat DM error: ${err.message}`);
3188
+ consola24.warn(`chat DM error: ${err.message}`);
3579
3189
  }
3580
3190
  }
3581
3191
  function readTaskSpec(taskId) {
3582
- const path2 = join4(TASK_CACHE_DIR, `${taskId}.json`);
3583
- if (!existsSync6(path2)) {
3192
+ const path2 = join5(TASK_CACHE_DIR, `${taskId}.json`);
3193
+ if (!existsSync7(path2)) {
3584
3194
  throw new CliError(`No cached task spec at ${path2}. Run \`apes agents sync\` first to pull the task list from troop.`);
3585
3195
  }
3586
- return JSON.parse(readFileSync5(path2, "utf8"));
3196
+ return JSON.parse(readFileSync6(path2, "utf8"));
3587
3197
  }
3588
- var AGENT_CONFIG_PATH = join4(homedir5(), ".openape", "agent", "agent.json");
3198
+ var AGENT_CONFIG_PATH = join5(homedir6(), ".openape", "agent", "agent.json");
3589
3199
  function readAgentConfig() {
3590
- if (!existsSync6(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
3200
+ if (!existsSync7(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
3591
3201
  try {
3592
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf8"));
3202
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf8"));
3593
3203
  } catch {
3594
3204
  return { systemPrompt: "" };
3595
3205
  }
3596
3206
  }
3597
3207
  function readLitellmConfig(model) {
3598
- const envPath = join4(homedir5(), "litellm", ".env");
3208
+ const envPath = join5(homedir6(), "litellm", ".env");
3599
3209
  const env = {};
3600
- if (existsSync6(envPath)) {
3601
- for (const line of readFileSync5(envPath, "utf8").split(/\r?\n/)) {
3210
+ if (existsSync7(envPath)) {
3211
+ for (const line of readFileSync6(envPath, "utf8").split(/\r?\n/)) {
3602
3212
  const m = line.match(/^([A-Z_]+)=(.*)$/);
3603
3213
  if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3604
3214
  }
@@ -3613,7 +3223,7 @@ function readLitellmConfig(model) {
3613
3223
  }
3614
3224
  return { apiBase, apiKey, model: model || "claude-haiku-4-5" };
3615
3225
  }
3616
- var runAgentCommand = defineCommand24({
3226
+ var runAgentCommand = defineCommand27({
3617
3227
  meta: {
3618
3228
  name: "run",
3619
3229
  description: "Execute one task (typically launchd-invoked). Reports the run record to troop."
@@ -3636,6 +3246,7 @@ var runAgentCommand = defineCommand24({
3636
3246
  async run({ args }) {
3637
3247
  const taskId = args["task-id"];
3638
3248
  const auth = readAuth();
3249
+ materializeSecrets({ log: (l) => consola24.info(l) });
3639
3250
  const spec = readTaskSpec(taskId);
3640
3251
  const agentCfg = readAgentConfig();
3641
3252
  const config = readLitellmConfig(args.model);
@@ -3647,7 +3258,7 @@ var runAgentCommand = defineCommand24({
3647
3258
  }
3648
3259
  const troop = new TroopClient(resolveTroopUrl(args["troop-url"]), auth.access_token);
3649
3260
  const { id: runId } = await troop.startRun(taskId);
3650
- consola22.info(`Run ${runId} started for task ${taskId}`);
3261
+ consola24.info(`Run ${runId} started for task ${taskId}`);
3651
3262
  try {
3652
3263
  const result = await runLoop({
3653
3264
  config,
@@ -3666,7 +3277,7 @@ var runAgentCommand = defineCommand24({
3666
3277
  step_count: result.stepCount,
3667
3278
  trace: result.trace
3668
3279
  });
3669
- consola22.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3280
+ consola24.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3670
3281
  if (auth.owner_email) {
3671
3282
  await postRunResultToChat({
3672
3283
  authToken: auth.access_token,
@@ -3703,17 +3314,17 @@ var runAgentCommand = defineCommand24({
3703
3314
  });
3704
3315
 
3705
3316
  // src/commands/agents/serve.ts
3706
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
3707
- import { homedir as homedir6 } from "os";
3708
- import { join as join5 } from "path";
3317
+ import { existsSync as existsSync8, readFileSync as readFileSync7 } from "fs";
3318
+ import { homedir as homedir7 } from "os";
3319
+ import { join as join6 } from "path";
3709
3320
  import { createInterface } from "readline";
3710
- import { defineCommand as defineCommand25 } from "citty";
3711
- var AUTH_PATH2 = join5(homedir6(), ".config", "apes", "auth.json");
3321
+ import { defineCommand as defineCommand28 } from "citty";
3322
+ var AUTH_PATH2 = join6(homedir7(), ".config", "apes", "auth.json");
3712
3323
  function readLitellmConfig2(model) {
3713
- const envPath = join5(homedir6(), "litellm", ".env");
3324
+ const envPath = join6(homedir7(), "litellm", ".env");
3714
3325
  const env = {};
3715
- if (existsSync7(envPath)) {
3716
- for (const line of readFileSync6(envPath, "utf8").split(/\r?\n/)) {
3326
+ if (existsSync8(envPath)) {
3327
+ for (const line of readFileSync7(envPath, "utf8").split(/\r?\n/)) {
3717
3328
  const m = line.match(/^([A-Z_]+)=(.*)$/);
3718
3329
  if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3719
3330
  }
@@ -3730,7 +3341,7 @@ function emit(event) {
3730
3341
  process.stdout.write(`${JSON.stringify(event)}
3731
3342
  `);
3732
3343
  }
3733
- var serveAgentCommand = defineCommand25({
3344
+ var serveAgentCommand = defineCommand28({
3734
3345
  meta: {
3735
3346
  name: "serve",
3736
3347
  description: "Long-running stdio RPC server for chat-bridge subprocess use."
@@ -3745,15 +3356,20 @@ var serveAgentCommand = defineCommand25({
3745
3356
  if (!args.rpc) {
3746
3357
  throw new CliError("apes agents serve currently only supports --rpc mode");
3747
3358
  }
3748
- if (existsSync7(AUTH_PATH2)) {
3359
+ if (existsSync8(AUTH_PATH2)) {
3749
3360
  try {
3750
- JSON.parse(readFileSync6(AUTH_PATH2, "utf8"));
3361
+ JSON.parse(readFileSync7(AUTH_PATH2, "utf8"));
3751
3362
  } catch {
3752
3363
  }
3753
3364
  }
3365
+ const stopSecrets = startSecretsWatcher({ log: (l) => process.stderr.write(`${l}
3366
+ `) });
3754
3367
  const sessions = new RpcSessionMap();
3755
3368
  const evictTimer = setInterval(() => sessions.evictStale(), 5 * 60 * 1e3);
3756
- process.on("exit", () => clearInterval(evictTimer));
3369
+ process.on("exit", () => {
3370
+ clearInterval(evictTimer);
3371
+ stopSecrets();
3372
+ });
3757
3373
  const rl = createInterface({ input: process.stdin, terminal: false });
3758
3374
  rl.on("line", async (line) => {
3759
3375
  const trimmed = line.trim();
@@ -3822,12 +3438,12 @@ async function handleInbound(msg, sessions) {
3822
3438
  }
3823
3439
 
3824
3440
  // src/commands/agents/spawn.ts
3825
- import { execFileSync as execFileSync7 } from "child_process";
3441
+ import { execFileSync as execFileSync8 } from "child_process";
3826
3442
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3827
3443
  import { tmpdir as tmpdir2 } from "os";
3828
- import { join as join7 } from "path";
3829
- import { defineCommand as defineCommand26 } from "citty";
3830
- import consola23 from "consola";
3444
+ import { join as join8 } from "path";
3445
+ import { defineCommand as defineCommand29 } from "citty";
3446
+ import consola25 from "consola";
3831
3447
 
3832
3448
  // src/lib/troop-bootstrap.ts
3833
3449
  var SYNC_LABEL_PREFIX = "openape.troop.sync";
@@ -3883,12 +3499,13 @@ ${envBlock} <key>StartInterval</key>
3883
3499
 
3884
3500
  // src/lib/keygen.ts
3885
3501
  import { Buffer as Buffer4 } from "buffer";
3886
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
3502
+ import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
3887
3503
  import { generateKeyPairSync } from "crypto";
3888
- import { homedir as homedir7 } from "os";
3504
+ import { homedir as homedir8 } from "os";
3889
3505
  import { dirname, resolve as resolve2 } from "path";
3890
- function resolveKeyPath(p2) {
3891
- return resolve2(p2.replace(/^~/, homedir7()));
3506
+ import { generateX25519KeyPair } from "@openape/core";
3507
+ function resolveKeyPath(p) {
3508
+ return resolve2(p.replace(/^~/, homedir8()));
3892
3509
  }
3893
3510
  function buildSshEd25519Line(rawPub) {
3894
3511
  const keyTypeStr = "ssh-ed25519";
@@ -3901,10 +3518,10 @@ function buildSshEd25519Line(rawPub) {
3901
3518
  }
3902
3519
  function readPublicKey(keyPath) {
3903
3520
  const pubPath = `${keyPath}.pub`;
3904
- if (existsSync8(pubPath)) {
3905
- return readFileSync7(pubPath, "utf-8").trim();
3521
+ if (existsSync9(pubPath)) {
3522
+ return readFileSync8(pubPath, "utf-8").trim();
3906
3523
  }
3907
- const keyContent = readFileSync7(keyPath, "utf-8");
3524
+ const keyContent = readFileSync8(keyPath, "utf-8");
3908
3525
  const privateKey = loadEd25519PrivateKey(keyContent);
3909
3526
  const jwk = privateKey.export({ format: "jwk" });
3910
3527
  const pubBytes = Buffer4.from(jwk.x, "base64url");
@@ -3913,7 +3530,7 @@ function readPublicKey(keyPath) {
3913
3530
  function generateAndSaveKey(keyPath) {
3914
3531
  const resolved = resolveKeyPath(keyPath);
3915
3532
  const dir = dirname(resolved);
3916
- if (!existsSync8(dir)) {
3533
+ if (!existsSync9(dir)) {
3917
3534
  mkdirSync2(dir, { recursive: true });
3918
3535
  }
3919
3536
  const { publicKey, privateKey } = generateKeyPairSync("ed25519");
@@ -3931,22 +3548,25 @@ function generateKeyPairInMemory() {
3931
3548
  const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
3932
3549
  const jwk = publicKey.export({ format: "jwk" });
3933
3550
  const pubBytes = Buffer4.from(jwk.x, "base64url");
3551
+ const enc = generateX25519KeyPair();
3934
3552
  return {
3935
3553
  privatePem,
3936
- publicSshLine: buildSshEd25519Line(pubBytes)
3554
+ publicSshLine: buildSshEd25519Line(pubBytes),
3555
+ x25519PrivateKey: enc.privateKey,
3556
+ x25519PublicKey: enc.publicKey
3937
3557
  };
3938
3558
  }
3939
3559
 
3940
3560
  // src/lib/llm-bridge.ts
3941
- import { execFileSync as execFileSync6 } from "child_process";
3942
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
3943
- import { homedir as homedir8 } from "os";
3944
- import { dirname as dirname2, join as join6 } from "path";
3561
+ import { execFileSync as execFileSync7 } from "child_process";
3562
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
3563
+ import { homedir as homedir9 } from "os";
3564
+ import { dirname as dirname2, join as join7 } from "path";
3945
3565
  var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
3946
- function readLitellmEnv(envPath = join6(homedir8(), "litellm", ".env")) {
3947
- if (!existsSync9(envPath)) return null;
3566
+ function readLitellmEnv(envPath = join7(homedir9(), "litellm", ".env")) {
3567
+ if (!existsSync10(envPath)) return null;
3948
3568
  try {
3949
- const text = readFileSync8(envPath, "utf8");
3569
+ const text = readFileSync9(envPath, "utf8");
3950
3570
  const out = {};
3951
3571
  for (const line of text.split("\n")) {
3952
3572
  const trimmed = line.trim();
@@ -3982,7 +3602,7 @@ function captureHostBinDirs() {
3982
3602
  for (const bin of ["node", "ape-agent", "apes"]) {
3983
3603
  let resolved;
3984
3604
  try {
3985
- resolved = execFileSync6("/usr/bin/which", [bin], { encoding: "utf8" }).trim();
3605
+ resolved = execFileSync7("/usr/bin/which", [bin], { encoding: "utf8" }).trim();
3986
3606
  } catch {
3987
3607
  const installCmd = bin === "ape-agent" ? "npm i -g @openape/ape-agent" : bin === "apes" ? "npm i -g @openape/apes" : "install Node.js (e.g. brew install node)";
3988
3608
  throw new Error(`'${bin}' not found on host PATH. ${installCmd} before spawning agents \u2014 the bridge runtime resolves these at spawn time and bakes the dir into the agent's launchd plist.`);
@@ -4083,7 +3703,7 @@ function readMacOSUidOrNull(name) {
4083
3703
  return null;
4084
3704
  }
4085
3705
  }
4086
- var spawnAgentCommand = defineCommand26({
3706
+ var spawnAgentCommand = defineCommand29({
4087
3707
  meta: {
4088
3708
  name: "spawn",
4089
3709
  description: "Provision a local macOS agent end-to-end (OS user, keypair, IdP agent, Claude hook)"
@@ -4165,20 +3785,21 @@ var spawnAgentCommand = defineCommand26({
4165
3785
  and try again.`
4166
3786
  );
4167
3787
  }
4168
- const existing = readMacOSUser(name);
3788
+ const macOSUsername = macOSUsernameForAgent(name);
3789
+ const existing = readMacOSUser(macOSUsername) ?? readMacOSUser(name);
4169
3790
  if (existing) {
4170
- throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
3791
+ throw new CliError(`macOS user "${existing.name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
4171
3792
  }
4172
- const homeDir = `/var/openape/homes/${name}`;
4173
- const scratch = mkdtempSync2(join7(tmpdir2(), `apes-spawn-${name}-`));
4174
- const scriptPath = join7(scratch, "setup.sh");
3793
+ const homeDir = `/var/openape/homes/${macOSUsername}`;
3794
+ const scratch = mkdtempSync2(join8(tmpdir2(), `apes-spawn-${name}-`));
3795
+ const scriptPath = join8(scratch, "setup.sh");
4175
3796
  try {
4176
- consola23.start(`Generating keypair for ${name}\u2026`);
4177
- const { privatePem, publicSshLine } = generateKeyPairInMemory();
4178
- consola23.start(`Registering agent at ${idp}\u2026`);
3797
+ consola25.start(`Generating keypair for ${name}\u2026`);
3798
+ const { privatePem, publicSshLine, x25519PrivateKey, x25519PublicKey } = generateKeyPairInMemory();
3799
+ consola25.start(`Registering agent at ${idp}\u2026`);
4179
3800
  const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
4180
- consola23.success(`Registered as ${registration.email}`);
4181
- consola23.start("Issuing agent access token\u2026");
3801
+ consola25.success(`Registered as ${registration.email}`);
3802
+ consola25.start("Issuing agent access token\u2026");
4182
3803
  const { token, expiresIn } = await issueAgentToken({
4183
3804
  idp,
4184
3805
  agentEmail: registration.email,
@@ -4231,10 +3852,13 @@ and try again.`
4231
3852
  };
4232
3853
  const script = buildSpawnSetupScript({
4233
3854
  name,
3855
+ macOSUsername,
4234
3856
  homeDir,
4235
3857
  shellPath: loginShell,
4236
3858
  privateKeyPem: privatePem,
4237
3859
  publicKeySshLine: publicSshLine,
3860
+ x25519PrivateKey,
3861
+ x25519PublicKey,
4238
3862
  authJson,
4239
3863
  claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
4240
3864
  hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null,
@@ -4245,15 +3869,15 @@ and try again.`
4245
3869
  writeFileSync4(scriptPath, script, { mode: 448 });
4246
3870
  const alreadyRoot = process.getuid?.() === 0;
4247
3871
  if (alreadyRoot) {
4248
- consola23.start("Running privileged setup directly (already root)\u2026");
4249
- execFileSync7("bash", [scriptPath], { stdio: "inherit" });
3872
+ consola25.start("Running privileged setup directly (already root)\u2026");
3873
+ execFileSync8("bash", [scriptPath], { stdio: "inherit" });
4250
3874
  } else {
4251
- consola23.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
4252
- consola23.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
4253
- execFileSync7(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
3875
+ consola25.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
3876
+ consola25.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
3877
+ execFileSync8(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
4254
3878
  }
4255
3879
  try {
4256
- const uid = readMacOSUidOrNull(name);
3880
+ const uid = readMacOSUidOrNull(macOSUsername) ?? readMacOSUidOrNull(name);
4257
3881
  upsertNestAgent({
4258
3882
  name,
4259
3883
  uid: uid ?? -1,
@@ -4267,13 +3891,13 @@ and try again.`
4267
3891
  } : void 0
4268
3892
  });
4269
3893
  } catch (err) {
4270
- consola23.warn(`Could not write to nest registry: ${err instanceof Error ? err.message : String(err)}`);
3894
+ consola25.warn(`Could not write to nest registry: ${err instanceof Error ? err.message : String(err)}`);
4271
3895
  }
4272
- consola23.success(`Agent ${name} spawned.`);
4273
- consola23.info(`\u{1F517} Troop: https://troop.openape.ai/agents/${name}`);
3896
+ consola25.success(`Agent ${name} spawned.`);
3897
+ consola25.info(`\u{1F517} Troop: https://troop.openape.ai/agents/${name}`);
4274
3898
  if (withBridge) {
4275
- consola23.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
4276
- consola23.info("Open chat.openape.ai and accept it to start chatting with the agent.");
3899
+ consola25.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
3900
+ consola25.info("Open chat.openape.ai and accept it to start chatting with the agent.");
4277
3901
  }
4278
3902
  console.log("");
4279
3903
  console.log("Run as the agent with:");
@@ -4307,18 +3931,18 @@ async function resolveClaudeToken(opts) {
4307
3931
  }
4308
3932
 
4309
3933
  // src/commands/agents/sync.ts
4310
- import { chownSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync9, rmSync as rmSync4, statSync, writeFileSync as writeFileSync5 } from "fs";
4311
- import { homedir as homedir9 } from "os";
4312
- import { join as join8 } from "path";
4313
- import { defineCommand as defineCommand27 } from "citty";
4314
- import consola24 from "consola";
3934
+ import { chownSync, existsSync as existsSync11, mkdirSync as mkdirSync3, readdirSync as readdirSync2, readFileSync as readFileSync10, rmSync as rmSync4, statSync, writeFileSync as writeFileSync5 } from "fs";
3935
+ import { homedir as homedir10 } from "os";
3936
+ import { join as join9 } from "path";
3937
+ import { defineCommand as defineCommand30 } from "citty";
3938
+ import consola26 from "consola";
4315
3939
 
4316
3940
  // src/lib/macos-host.ts
4317
- import { execFileSync as execFileSync8 } from "child_process";
3941
+ import { execFileSync as execFileSync9 } from "child_process";
4318
3942
  import { hostname as hostname3 } from "os";
4319
3943
  function getHostId() {
4320
3944
  try {
4321
- const output = execFileSync8(
3945
+ const output = execFileSync9(
4322
3946
  "/usr/sbin/ioreg",
4323
3947
  ["-d2", "-c", "IOPlatformExpertDevice"],
4324
3948
  { encoding: "utf8", timeout: 2e3 }
@@ -4338,15 +3962,15 @@ function getHostname() {
4338
3962
  }
4339
3963
 
4340
3964
  // src/commands/agents/sync.ts
4341
- var AUTH_PATH3 = join8(homedir9(), ".config", "apes", "auth.json");
4342
- var TASK_CACHE_DIR2 = join8(homedir9(), ".openape", "agent", "tasks");
3965
+ var AUTH_PATH3 = join9(homedir10(), ".config", "apes", "auth.json");
3966
+ var TASK_CACHE_DIR2 = join9(homedir10(), ".openape", "agent", "tasks");
4343
3967
  function readAuthJson() {
4344
- if (!existsSync10(AUTH_PATH3)) {
3968
+ if (!existsSync11(AUTH_PATH3)) {
4345
3969
  throw new CliError(
4346
3970
  `No agent auth found at ${AUTH_PATH3}. Run \`apes agents spawn <name>\` to provision an agent first.`
4347
3971
  );
4348
3972
  }
4349
- const raw = readFileSync9(AUTH_PATH3, "utf8");
3973
+ const raw = readFileSync10(AUTH_PATH3, "utf8");
4350
3974
  let parsed;
4351
3975
  try {
4352
3976
  parsed = JSON.parse(raw);
@@ -4370,7 +3994,7 @@ function agentNameFromEmail(email) {
4370
3994
  }
4371
3995
  return before.slice(0, dashIdx);
4372
3996
  }
4373
- var syncAgentCommand = defineCommand27({
3997
+ var syncAgentCommand = defineCommand30({
4374
3998
  meta: {
4375
3999
  name: "sync",
4376
4000
  description: "Pull this agent's task list from troop.openape.ai and reconcile launchd plists"
@@ -4394,22 +4018,22 @@ var syncAgentCommand = defineCommand27({
4394
4018
  if (!auth.owner_email) {
4395
4019
  throw new CliError(`${AUTH_PATH3} is missing owner_email \u2014 re-run \`apes agents spawn\` to update.`);
4396
4020
  }
4397
- consola24.start(`Syncing ${agentName} (${host}, hostId ${hostId.slice(0, 8)}\u2026) with ${troopUrl}`);
4021
+ consola26.start(`Syncing ${agentName} (${host}, hostId ${hostId.slice(0, 8)}\u2026) with ${troopUrl}`);
4398
4022
  const sync = await client.sync({
4399
4023
  hostname: host,
4400
4024
  hostId,
4401
4025
  ownerEmail: auth.owner_email
4402
4026
  });
4403
- consola24.info(sync.first_sync ? "\u2713 first sync \u2014 agent registered" : "\u2713 presence updated");
4027
+ consola26.info(sync.first_sync ? "\u2713 first sync \u2014 agent registered" : "\u2713 presence updated");
4404
4028
  const { system_prompt: systemPrompt, tools, skills, tasks } = await client.listTasks();
4405
- consola24.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
4406
- consola24.info(`Tools enabled: ${tools.length === 0 ? "(none)" : tools.join(", ")}`);
4407
- consola24.info(`Skills: ${skills.length === 0 ? "(none)" : skills.map((s) => s.name).join(", ")}`);
4029
+ consola26.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
4030
+ consola26.info(`Tools enabled: ${tools.length === 0 ? "(none)" : tools.join(", ")}`);
4031
+ consola26.info(`Skills: ${skills.length === 0 ? "(none)" : skills.map((s) => s.name).join(", ")}`);
4408
4032
  let agentUid = null;
4409
4033
  let agentGid = null;
4410
4034
  if (process.geteuid?.() === 0) {
4411
4035
  try {
4412
- const homeStat = statSync(homedir9());
4036
+ const homeStat = statSync(homedir10());
4413
4037
  agentUid = homeStat.uid;
4414
4038
  agentGid = homeStat.gid;
4415
4039
  } catch {
@@ -4423,11 +4047,11 @@ var syncAgentCommand = defineCommand27({
4423
4047
  }
4424
4048
  }
4425
4049
  }
4426
- const agentDir = join8(homedir9(), ".openape", "agent");
4050
+ const agentDir = join9(homedir10(), ".openape", "agent");
4427
4051
  mkdirSync3(agentDir, { recursive: true });
4428
- chownToAgent(join8(homedir9(), ".openape"));
4052
+ chownToAgent(join9(homedir10(), ".openape"));
4429
4053
  chownToAgent(agentDir);
4430
- const agentJsonPath = join8(agentDir, "agent.json");
4054
+ const agentJsonPath = join9(agentDir, "agent.json");
4431
4055
  writeFileSync5(
4432
4056
  agentJsonPath,
4433
4057
  `${JSON.stringify({ systemPrompt, tools }, null, 2)}
@@ -4438,43 +4062,43 @@ var syncAgentCommand = defineCommand27({
4438
4062
  mkdirSync3(TASK_CACHE_DIR2, { recursive: true });
4439
4063
  chownToAgent(TASK_CACHE_DIR2);
4440
4064
  for (const task of tasks) {
4441
- const path2 = join8(TASK_CACHE_DIR2, `${task.taskId}.json`);
4065
+ const path2 = join9(TASK_CACHE_DIR2, `${task.taskId}.json`);
4442
4066
  writeFileSync5(path2, `${JSON.stringify(task, null, 2)}
4443
4067
  `, { mode: 384 });
4444
4068
  chownToAgent(path2);
4445
4069
  }
4446
- const skillsDir = join8(agentDir, "skills");
4070
+ const skillsDir = join9(agentDir, "skills");
4447
4071
  mkdirSync3(skillsDir, { recursive: true });
4448
4072
  chownToAgent(skillsDir);
4449
4073
  const incomingNames = new Set(skills.map((s) => s.name));
4450
4074
  try {
4451
- for (const entry of readdirSync(skillsDir)) {
4075
+ for (const entry of readdirSync2(skillsDir)) {
4452
4076
  if (incomingNames.has(entry)) continue;
4453
4077
  try {
4454
- rmSync4(join8(skillsDir, entry), { recursive: true, force: true });
4078
+ rmSync4(join9(skillsDir, entry), { recursive: true, force: true });
4455
4079
  } catch {
4456
4080
  }
4457
4081
  }
4458
4082
  } catch {
4459
4083
  }
4460
4084
  for (const skill of skills) {
4461
- const skillDir = join8(skillsDir, skill.name);
4085
+ const skillDir = join9(skillsDir, skill.name);
4462
4086
  mkdirSync3(skillDir, { recursive: true });
4463
4087
  chownToAgent(skillDir);
4464
- const skillPath = join8(skillDir, "SKILL.md");
4088
+ const skillPath = join9(skillDir, "SKILL.md");
4465
4089
  writeFileSync5(skillPath, skill.body.endsWith("\n") ? skill.body : `${skill.body}
4466
4090
  `, { mode: 384 });
4467
4091
  chownToAgent(skillPath);
4468
4092
  }
4469
- consola24.success("Sync complete.");
4093
+ consola26.success("Sync complete.");
4470
4094
  }
4471
4095
  });
4472
4096
 
4473
4097
  // src/commands/agents/index.ts
4474
- var agentsCommand = defineCommand28({
4098
+ var agentsCommand = defineCommand31({
4475
4099
  meta: {
4476
4100
  name: "agents",
4477
- description: "Manage owned agents (register, spawn, list, destroy, allow, sync, run, serve)"
4101
+ description: "Manage owned agents (register, spawn, list, destroy, allow, sync, run, serve, cleanup-orphans)"
4478
4102
  },
4479
4103
  subCommands: {
4480
4104
  register: registerAgentCommand,
@@ -4484,27 +4108,28 @@ var agentsCommand = defineCommand28({
4484
4108
  allow: allowAgentCommand,
4485
4109
  sync: syncAgentCommand,
4486
4110
  run: runAgentCommand,
4487
- serve: serveAgentCommand
4111
+ serve: serveAgentCommand,
4112
+ "cleanup-orphans": cleanupOrphansCommand
4488
4113
  }
4489
4114
  });
4490
4115
 
4491
4116
  // src/commands/nest/index.ts
4492
- import { defineCommand as defineCommand36 } from "citty";
4117
+ import { defineCommand as defineCommand39 } from "citty";
4493
4118
 
4494
4119
  // src/commands/nest/authorize.ts
4495
- import { execFileSync as execFileSync9 } from "child_process";
4496
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4497
- import { join as join10 } from "path";
4498
- import { defineCommand as defineCommand30 } from "citty";
4499
- import consola26 from "consola";
4120
+ import { execFileSync as execFileSync10 } from "child_process";
4121
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "fs";
4122
+ import { join as join11 } from "path";
4123
+ import { defineCommand as defineCommand33 } from "citty";
4124
+ import consola28 from "consola";
4500
4125
 
4501
4126
  // src/commands/nest/enroll.ts
4502
- import { hostname as hostname4, homedir as homedir10 } from "os";
4503
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
4504
- import { join as join9 } from "path";
4505
- import { defineCommand as defineCommand29 } from "citty";
4506
- import consola25 from "consola";
4507
- var NEST_DATA_DIR = join9(homedir10(), ".openape", "nest");
4127
+ import { hostname as hostname4, homedir as homedir11 } from "os";
4128
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
4129
+ import { join as join10 } from "path";
4130
+ import { defineCommand as defineCommand32 } from "citty";
4131
+ import consola27 from "consola";
4132
+ var NEST_DATA_DIR = join10(homedir11(), ".openape", "nest");
4508
4133
  function nestAgentName() {
4509
4134
  const raw = hostname4().toLowerCase();
4510
4135
  const head = raw.split(".")[0] ?? raw;
@@ -4512,7 +4137,7 @@ function nestAgentName() {
4512
4137
  const trimmed = safe.slice(0, 16);
4513
4138
  return `nest-${trimmed || "host"}`;
4514
4139
  }
4515
- var enrollNestCommand = defineCommand29({
4140
+ var enrollNestCommand = defineCommand32({
4516
4141
  meta: {
4517
4142
  name: "enroll",
4518
4143
  description: "Register the local nest as a DDISA agent at the IdP. One-time per machine. Required before `apes nest authorize` so YOLO-policies have a target identity."
@@ -4537,25 +4162,25 @@ var enrollNestCommand = defineCommand29({
4537
4162
  throw new CliError("Run `apes login <email>` first \u2014 nest enroll attaches the new identity to your owner account.");
4538
4163
  }
4539
4164
  const name = args.name || nestAgentName();
4540
- const authPath = join9(NEST_DATA_DIR, ".config", "apes", "auth.json");
4541
- if (existsSync11(authPath) && !args.force) {
4165
+ const authPath = join10(NEST_DATA_DIR, ".config", "apes", "auth.json");
4166
+ if (existsSync12(authPath) && !args.force) {
4542
4167
  throw new CliError(`Nest already enrolled at ${authPath}. Pass --force to re-enroll.`);
4543
4168
  }
4544
- const sshDir = join9(NEST_DATA_DIR, ".ssh");
4545
- const configDir = join9(NEST_DATA_DIR, ".config", "apes");
4169
+ const sshDir = join10(NEST_DATA_DIR, ".ssh");
4170
+ const configDir = join10(NEST_DATA_DIR, ".config", "apes");
4546
4171
  mkdirSync4(sshDir, { recursive: true });
4547
4172
  mkdirSync4(configDir, { recursive: true });
4548
- consola25.start(`Generating keypair for ${name}\u2026`);
4173
+ consola27.start(`Generating keypair for ${name}\u2026`);
4549
4174
  const { privatePem, publicSshLine } = generateKeyPairInMemory();
4550
- writeFileSync6(join9(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
4175
+ writeFileSync6(join10(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
4551
4176
  `, { mode: 384 });
4552
- writeFileSync6(join9(sshDir, "id_ed25519.pub"), `${publicSshLine}
4177
+ writeFileSync6(join10(sshDir, "id_ed25519.pub"), `${publicSshLine}
4553
4178
  `, { mode: 420 });
4554
4179
  chmodSync(sshDir, 448);
4555
- consola25.start(`Registering nest at ${idp}\u2026`);
4180
+ consola27.start(`Registering nest at ${idp}\u2026`);
4556
4181
  const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
4557
- consola25.success(`Registered as ${registration.email}`);
4558
- consola25.start("Issuing nest access token\u2026");
4182
+ consola27.success(`Registered as ${registration.email}`);
4183
+ consola27.start("Issuing nest access token\u2026");
4559
4184
  const { token, expiresIn } = await issueAgentToken({
4560
4185
  idp,
4561
4186
  agentEmail: registration.email,
@@ -4566,16 +4191,16 @@ var enrollNestCommand = defineCommand29({
4566
4191
  accessToken: token,
4567
4192
  email: registration.email,
4568
4193
  expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
4569
- keyPath: join9(sshDir, "id_ed25519"),
4194
+ keyPath: join10(sshDir, "id_ed25519"),
4570
4195
  ownerEmail: ownerAuth.email
4571
4196
  });
4572
4197
  writeFileSync6(authPath, authJson, { mode: 384 });
4573
4198
  chmodSync(configDir, 448);
4574
- consola25.success(`Nest enrolled \u2014 auth.json at ${authPath}`);
4575
- consola25.info("");
4576
- consola25.info("Next: configure the YOLO-policy so the nest can spawn/destroy without prompts:");
4577
- consola25.info("");
4578
- consola25.info(" apes nest authorize");
4199
+ consola27.success(`Nest enrolled \u2014 auth.json at ${authPath}`);
4200
+ consola27.info("");
4201
+ consola27.info("Next: configure the YOLO-policy so the nest can spawn/destroy without prompts:");
4202
+ consola27.info("");
4203
+ consola27.info(" apes nest authorize");
4579
4204
  }
4580
4205
  });
4581
4206
 
@@ -4622,7 +4247,7 @@ var DEFAULT_ALLOW_PATTERNS = [
4622
4247
  "pm2 delete openape-bridge-*",
4623
4248
  "pm2 jlist"
4624
4249
  ];
4625
- var authorizeNestCommand = defineCommand30({
4250
+ var authorizeNestCommand = defineCommand33({
4626
4251
  meta: {
4627
4252
  name: "authorize",
4628
4253
  description: "Set the YOLO-policy that lets the local nest spawn/destroy without per-call DDISA prompts (wraps `apes yolo set`)"
@@ -4638,14 +4263,14 @@ var authorizeNestCommand = defineCommand30({
4638
4263
  }
4639
4264
  },
4640
4265
  async run({ args }) {
4641
- const nestAuthPath = join10(NEST_DATA_DIR, ".config", "apes", "auth.json");
4642
- if (!existsSync12(nestAuthPath)) {
4266
+ const nestAuthPath = join11(NEST_DATA_DIR, ".config", "apes", "auth.json");
4267
+ if (!existsSync13(nestAuthPath)) {
4643
4268
  throw new CliError("Nest not enrolled. Run `apes nest enroll` first.");
4644
4269
  }
4645
- const nestAuth = JSON.parse(readFileSync10(nestAuthPath, "utf8"));
4270
+ const nestAuth = JSON.parse(readFileSync11(nestAuthPath, "utf8"));
4646
4271
  if (!nestAuth.email) throw new CliError(`${nestAuthPath} has no email`);
4647
4272
  const allow = args.allow ?? DEFAULT_ALLOW_PATTERNS.join(",");
4648
- consola26.info(`Configuring YOLO-policy on ${nestAuth.email} via \`apes yolo set\`\u2026`);
4273
+ consola28.info(`Configuring YOLO-policy on ${nestAuth.email} via \`apes yolo set\`\u2026`);
4649
4274
  const cmdArgs = [
4650
4275
  "yolo",
4651
4276
  "set",
@@ -4659,20 +4284,20 @@ var authorizeNestCommand = defineCommand30({
4659
4284
  cmdArgs.push("--expires-in", args["expires-in"]);
4660
4285
  }
4661
4286
  try {
4662
- execFileSync9("apes", cmdArgs, { stdio: "inherit" });
4287
+ execFileSync10("apes", cmdArgs, { stdio: "inherit" });
4663
4288
  } catch (err) {
4664
4289
  throw new CliError(err instanceof Error ? err.message : String(err));
4665
4290
  }
4666
- consola26.success("Nest-driven agent lifecycle is now zero-prompt.");
4667
- consola26.info("Test: apes agents spawn <name> via the nest API \u2192 no DDISA prompt.");
4291
+ consola28.success("Nest-driven agent lifecycle is now zero-prompt.");
4292
+ consola28.info("Test: apes agents spawn <name> via the nest API \u2192 no DDISA prompt.");
4668
4293
  }
4669
4294
  });
4670
4295
 
4671
4296
  // src/commands/nest/destroy.ts
4672
- import { execFileSync as execFileSync10 } from "child_process";
4673
- import { defineCommand as defineCommand31 } from "citty";
4674
- import consola27 from "consola";
4675
- var destroyNestCommand = defineCommand31({
4297
+ import { execFileSync as execFileSync11 } from "child_process";
4298
+ import { defineCommand as defineCommand34 } from "citty";
4299
+ import consola29 from "consola";
4300
+ var destroyNestCommand = defineCommand34({
4676
4301
  meta: {
4677
4302
  name: "destroy",
4678
4303
  description: "Destroy a local agent. Wraps `apes run --as root -- apes agents destroy <name>`; the Nest watches its registry and pm2-deletes the bridge automatically."
@@ -4683,8 +4308,8 @@ var destroyNestCommand = defineCommand31({
4683
4308
  async run({ args }) {
4684
4309
  const name = String(args.name);
4685
4310
  try {
4686
- execFileSync10("apes", ["run", "--as", "root", "--wait", "--", "apes", "agents", "destroy", name, "--force"], { stdio: "inherit" });
4687
- consola27.success(`Nest will tear down ${name}'s pm2 process on its next reconcile (\u22642s).`);
4311
+ execFileSync11("apes", ["run", "--as", "root", "--wait", "--", "apes", "agents", "destroy", name, "--force"], { stdio: "inherit" });
4312
+ consola29.success(`Nest will tear down ${name}'s pm2 process on its next reconcile (\u22642s).`);
4688
4313
  } catch (err) {
4689
4314
  const status = err.status ?? 1;
4690
4315
  throw new CliExit(status);
@@ -4693,12 +4318,12 @@ var destroyNestCommand = defineCommand31({
4693
4318
  });
4694
4319
 
4695
4320
  // src/commands/nest/install.ts
4696
- import { execFileSync as execFileSync11 } from "child_process";
4697
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
4698
- import { homedir as homedir11, userInfo as userInfo2 } from "os";
4699
- import { dirname as dirname3, join as join11 } from "path";
4700
- import { defineCommand as defineCommand32 } from "citty";
4701
- import consola28 from "consola";
4321
+ import { execFileSync as execFileSync12 } from "child_process";
4322
+ import { existsSync as existsSync14, mkdirSync as mkdirSync5, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
4323
+ import { homedir as homedir12, userInfo as userInfo2 } from "os";
4324
+ import { dirname as dirname3, join as join12 } from "path";
4325
+ import { defineCommand as defineCommand35 } from "citty";
4326
+ import consola30 from "consola";
4702
4327
 
4703
4328
  // src/commands/nest/apes-agents-adapter.ts
4704
4329
  var APES_AGENTS_ADAPTER_TOML = `schema = "openape-shapes/v1"
@@ -4765,13 +4390,13 @@ resource_chain = ["agents:name={name}", "allowlist:email={peer_email}"]
4765
4390
  // src/commands/nest/install.ts
4766
4391
  var PLIST_LABEL = "ai.openape.nest";
4767
4392
  function plistPath() {
4768
- return join11(homedir11(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
4393
+ return join12(homedir12(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
4769
4394
  }
4770
4395
  function escape2(s) {
4771
4396
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4772
4397
  }
4773
4398
  function buildPlist(args) {
4774
- const logsDir = join11(args.userHome, "Library", "Logs");
4399
+ const logsDir = join12(args.userHome, "Library", "Logs");
4775
4400
  return `<?xml version="1.0" encoding="UTF-8"?>
4776
4401
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4777
4402
  <plist version="1.0">
@@ -4806,25 +4431,25 @@ function buildPlist(args) {
4806
4431
  `;
4807
4432
  }
4808
4433
  function installAdapter2() {
4809
- const target = join11(homedir11(), ".openape", "shapes", "adapters", "apes-agents.toml");
4434
+ const target = join12(homedir12(), ".openape", "shapes", "adapters", "apes-agents.toml");
4810
4435
  mkdirSync5(dirname3(target), { recursive: true });
4811
4436
  let existing = "";
4812
4437
  try {
4813
- existing = readFileSync11(target, "utf8");
4438
+ existing = readFileSync12(target, "utf8");
4814
4439
  } catch {
4815
4440
  }
4816
4441
  if (existing === APES_AGENTS_ADAPTER_TOML) return false;
4817
4442
  writeFileSync7(target, APES_AGENTS_ADAPTER_TOML, { mode: 420 });
4818
- consola28.success(`Wrote shapes adapter ${target}`);
4443
+ consola30.success(`Wrote shapes adapter ${target}`);
4819
4444
  return true;
4820
4445
  }
4821
4446
  function writeBridgeModelDefault(model) {
4822
- for (const envDir of [join11(homedir11(), "litellm"), join11(NEST_DATA_DIR, "litellm")]) {
4823
- const envFile = join11(envDir, ".env");
4447
+ for (const envDir of [join12(homedir12(), "litellm"), join12(NEST_DATA_DIR, "litellm")]) {
4448
+ const envFile = join12(envDir, ".env");
4824
4449
  mkdirSync5(envDir, { recursive: true });
4825
4450
  let lines = [];
4826
- if (existsSync13(envFile)) {
4827
- lines = readFileSync11(envFile, "utf8").split("\n").filter((l) => !l.startsWith("APE_CHAT_BRIDGE_MODEL="));
4451
+ if (existsSync14(envFile)) {
4452
+ lines = readFileSync12(envFile, "utf8").split("\n").filter((l) => !l.startsWith("APE_CHAT_BRIDGE_MODEL="));
4828
4453
  }
4829
4454
  lines.push(`APE_CHAT_BRIDGE_MODEL=${model}`);
4830
4455
  while (lines.length > 0 && lines.at(-1).trim() === "") lines.pop();
@@ -4834,17 +4459,17 @@ function writeBridgeModelDefault(model) {
4834
4459
  }
4835
4460
  function findBinary(name) {
4836
4461
  for (const dir of [
4837
- join11(homedir11(), ".bun", "bin"),
4462
+ join12(homedir12(), ".bun", "bin"),
4838
4463
  "/opt/homebrew/bin",
4839
4464
  "/usr/local/bin",
4840
4465
  "/usr/bin"
4841
4466
  ]) {
4842
- const p2 = join11(dir, name);
4843
- if (existsSync13(p2)) return p2;
4467
+ const p = join12(dir, name);
4468
+ if (existsSync14(p)) return p;
4844
4469
  }
4845
4470
  throw new Error(`could not locate ${name} on PATH; install it first`);
4846
4471
  }
4847
- var installNestCommand = defineCommand32({
4472
+ var installNestCommand = defineCommand35({
4848
4473
  meta: {
4849
4474
  name: "install",
4850
4475
  description: "Install + start the local nest-daemon (idempotent \u2014 re-running just restarts)"
@@ -4860,57 +4485,57 @@ var installNestCommand = defineCommand32({
4860
4485
  }
4861
4486
  },
4862
4487
  async run({ args }) {
4863
- const homeDir = homedir11();
4488
+ const homeDir = homedir12();
4864
4489
  const port = Number(args.port ?? 9091);
4865
4490
  if (!Number.isInteger(port) || port < 1024 || port > 65535) {
4866
4491
  throw new Error(`invalid port ${port}`);
4867
4492
  }
4868
4493
  const nestBin = findBinary("openape-nest");
4869
4494
  const apesBin = findBinary("apes");
4870
- consola28.info(`Installing nest at ${plistPath()}`);
4871
- consola28.info(` nest binary: ${nestBin}`);
4872
- consola28.info(` apes binary: ${apesBin}`);
4873
- consola28.info(` HTTP port: ${port}`);
4495
+ consola30.info(`Installing nest at ${plistPath()}`);
4496
+ consola30.info(` nest binary: ${nestBin}`);
4497
+ consola30.info(` apes binary: ${apesBin}`);
4498
+ consola30.info(` HTTP port: ${port}`);
4874
4499
  if (typeof args["bridge-model"] === "string" && args["bridge-model"]) {
4875
4500
  writeBridgeModelDefault(args["bridge-model"]);
4876
- consola28.success(`Default bridge model set to ${args["bridge-model"]} (in ~/litellm/.env)`);
4501
+ consola30.success(`Default bridge model set to ${args["bridge-model"]} (in ~/litellm/.env)`);
4877
4502
  }
4878
4503
  installAdapter2();
4879
- mkdirSync5(join11(homeDir, "Library", "LaunchAgents"), { recursive: true });
4504
+ mkdirSync5(join12(homeDir, "Library", "LaunchAgents"), { recursive: true });
4880
4505
  mkdirSync5(NEST_DATA_DIR, { recursive: true });
4881
4506
  const desired = buildPlist({ nestBin, apesBin, userHome: homeDir, nestHome: NEST_DATA_DIR, port });
4882
4507
  let existing = "";
4883
4508
  try {
4884
- existing = readFileSync11(plistPath(), "utf8");
4509
+ existing = readFileSync12(plistPath(), "utf8");
4885
4510
  } catch {
4886
4511
  }
4887
4512
  if (existing !== desired) {
4888
4513
  writeFileSync7(plistPath(), desired, { mode: 420 });
4889
- consola28.success("Wrote launchd plist");
4514
+ consola30.success("Wrote launchd plist");
4890
4515
  } else {
4891
- consola28.info("plist already up to date");
4516
+ consola30.info("plist already up to date");
4892
4517
  }
4893
4518
  const uid = userInfo2().uid;
4894
4519
  try {
4895
- execFileSync11("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore" });
4520
+ execFileSync12("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore" });
4896
4521
  } catch {
4897
4522
  }
4898
- execFileSync11("/bin/launchctl", ["bootstrap", `gui/${uid}`, plistPath()], { stdio: "inherit" });
4899
- consola28.success(`Nest daemon bootstrapped \u2014 http://127.0.0.1:${port}`);
4900
- consola28.info("");
4901
- consola28.info("Next steps for zero-prompt spawn \u2014 both one-time:");
4902
- consola28.info("");
4903
- consola28.info(" 1. apes nest enroll # register nest as DDISA agent (creates own auth.json)");
4904
- consola28.info(" 2. apes nest authorize # set YOLO-policy on the nest agent");
4905
- consola28.info("");
4906
- consola28.info("After that, every `POST http://127.0.0.1:9091/agents` runs without DDISA prompts.");
4523
+ execFileSync12("/bin/launchctl", ["bootstrap", `gui/${uid}`, plistPath()], { stdio: "inherit" });
4524
+ consola30.success(`Nest daemon bootstrapped \u2014 http://127.0.0.1:${port}`);
4525
+ consola30.info("");
4526
+ consola30.info("Next steps for zero-prompt spawn \u2014 both one-time:");
4527
+ consola30.info("");
4528
+ consola30.info(" 1. apes nest enroll # register nest as DDISA agent (creates own auth.json)");
4529
+ consola30.info(" 2. apes nest authorize # set YOLO-policy on the nest agent");
4530
+ consola30.info("");
4531
+ consola30.info("After that, every `POST http://127.0.0.1:9091/agents` runs without DDISA prompts.");
4907
4532
  }
4908
4533
  });
4909
4534
 
4910
4535
  // src/commands/nest/list.ts
4911
- import { defineCommand as defineCommand33 } from "citty";
4912
- import consola29 from "consola";
4913
- var listNestCommand = defineCommand33({
4536
+ import { defineCommand as defineCommand36 } from "citty";
4537
+ import consola31 from "consola";
4538
+ var listNestCommand = defineCommand36({
4914
4539
  meta: {
4915
4540
  name: "list",
4916
4541
  description: "List agents registered with the local nest. Reads /var/openape/nest/agents.json directly."
@@ -4925,22 +4550,22 @@ var listNestCommand = defineCommand33({
4925
4550
  return;
4926
4551
  }
4927
4552
  if (reg.agents.length === 0) {
4928
- consola29.info("(no agents registered with this nest)");
4553
+ consola31.info("(no agents registered with this nest)");
4929
4554
  return;
4930
4555
  }
4931
- consola29.info(`${reg.agents.length} agent(s) registered with this nest:`);
4556
+ consola31.info(`${reg.agents.length} agent(s) registered with this nest:`);
4932
4557
  for (const a of reg.agents) {
4933
4558
  const bridge = a.bridge ? " bridge=on" : "";
4934
- consola29.info(` ${a.name.padEnd(16)} uid=${String(a.uid).padEnd(5)} home=${a.home}${bridge}`);
4559
+ consola31.info(` ${a.name.padEnd(16)} uid=${String(a.uid).padEnd(5)} home=${a.home}${bridge}`);
4935
4560
  }
4936
4561
  }
4937
4562
  });
4938
4563
 
4939
4564
  // src/commands/nest/spawn.ts
4940
- import { execFileSync as execFileSync12 } from "child_process";
4941
- import { defineCommand as defineCommand34 } from "citty";
4942
- import consola30 from "consola";
4943
- var spawnNestCommand = defineCommand34({
4565
+ import { execFileSync as execFileSync13 } from "child_process";
4566
+ import { defineCommand as defineCommand37 } from "citty";
4567
+ import consola32 from "consola";
4568
+ var spawnNestCommand = defineCommand37({
4944
4569
  meta: {
4945
4570
  name: "spawn",
4946
4571
  description: "Spawn a new agent locally. Wraps `apes run --as root -- apes agents spawn <name>`; the Nest watches its registry and starts the bridge in pm2 automatically."
@@ -4970,8 +4595,8 @@ var spawnNestCommand = defineCommand34({
4970
4595
  if (typeof args["bridge-base-url"] === "string") apesArgs.push("--bridge-base-url", args["bridge-base-url"]);
4971
4596
  if (typeof args["bridge-model"] === "string") apesArgs.push("--bridge-model", args["bridge-model"]);
4972
4597
  try {
4973
- execFileSync12("apes", apesArgs, { stdio: "inherit" });
4974
- consola30.success(`Nest will pick up ${name} on its next reconcile (\u22642s).`);
4598
+ execFileSync13("apes", apesArgs, { stdio: "inherit" });
4599
+ consola32.success(`Nest will pick up ${name} on its next reconcile (\u22642s).`);
4975
4600
  } catch (err) {
4976
4601
  const status = err.status ?? 1;
4977
4602
  throw new CliExit(status);
@@ -4980,37 +4605,37 @@ var spawnNestCommand = defineCommand34({
4980
4605
  });
4981
4606
 
4982
4607
  // src/commands/nest/uninstall.ts
4983
- import { execFileSync as execFileSync13 } from "child_process";
4984
- import { existsSync as existsSync14, unlinkSync } from "fs";
4985
- import { homedir as homedir12, userInfo as userInfo3 } from "os";
4986
- import { join as join12 } from "path";
4987
- import { defineCommand as defineCommand35 } from "citty";
4988
- import consola31 from "consola";
4608
+ import { execFileSync as execFileSync14 } from "child_process";
4609
+ import { existsSync as existsSync15, unlinkSync } from "fs";
4610
+ import { homedir as homedir13, userInfo as userInfo3 } from "os";
4611
+ import { join as join13 } from "path";
4612
+ import { defineCommand as defineCommand38 } from "citty";
4613
+ import consola33 from "consola";
4989
4614
  var PLIST_LABEL2 = "ai.openape.nest";
4990
- var uninstallNestCommand = defineCommand35({
4615
+ var uninstallNestCommand = defineCommand38({
4991
4616
  meta: {
4992
4617
  name: "uninstall",
4993
4618
  description: "Stop + remove the local nest-daemon (registry + agents preserved)"
4994
4619
  },
4995
4620
  async run() {
4996
4621
  const uid = userInfo3().uid;
4997
- const path2 = join12(homedir12(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
4622
+ const path2 = join13(homedir13(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
4998
4623
  try {
4999
- execFileSync13("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
5000
- consola31.success("Nest daemon stopped");
4624
+ execFileSync14("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
4625
+ consola33.success("Nest daemon stopped");
5001
4626
  } catch {
5002
- consola31.info("Nest daemon was not loaded");
4627
+ consola33.info("Nest daemon was not loaded");
5003
4628
  }
5004
- if (existsSync14(path2)) {
4629
+ if (existsSync15(path2)) {
5005
4630
  unlinkSync(path2);
5006
- consola31.success(`Removed ${path2}`);
4631
+ consola33.success(`Removed ${path2}`);
5007
4632
  }
5008
- consola31.info("Registry at ~/.openape/nest/agents.json kept \u2014 re-run `apes nest install` to resume supervision.");
4633
+ consola33.info("Registry at ~/.openape/nest/agents.json kept \u2014 re-run `apes nest install` to resume supervision.");
5009
4634
  }
5010
4635
  });
5011
4636
 
5012
4637
  // src/commands/nest/index.ts
5013
- var nestCommand = defineCommand36({
4638
+ var nestCommand = defineCommand39({
5014
4639
  meta: {
5015
4640
  name: "nest",
5016
4641
  description: "Manage the local Nest control-plane daemon. One-time setup: `install` \u2192 `enroll` \u2192 `authorize`. Day-to-day: `list` / `spawn` / `destroy`. As of Phase D the Nest is a long-running CLIENT \u2014 commands talk to it via filesystem intent files in $NEST_HOME/intents (mode 770, group _openape_nest) instead of HTTP."
@@ -5027,12 +4652,12 @@ var nestCommand = defineCommand36({
5027
4652
  });
5028
4653
 
5029
4654
  // src/commands/yolo/index.ts
5030
- import { defineCommand as defineCommand40 } from "citty";
4655
+ import { defineCommand as defineCommand43 } from "citty";
5031
4656
 
5032
4657
  // src/commands/yolo/clear.ts
5033
- import { defineCommand as defineCommand37 } from "citty";
5034
- import consola32 from "consola";
5035
- var yoloClearCommand = defineCommand37({
4658
+ import { defineCommand as defineCommand40 } from "citty";
4659
+ import consola34 from "consola";
4660
+ var yoloClearCommand = defineCommand40({
5036
4661
  meta: {
5037
4662
  name: "clear",
5038
4663
  description: "Remove the YOLO-policy from a DDISA agent (subsequent grants need human approval)"
@@ -5061,15 +4686,15 @@ var yoloClearCommand = defineCommand37({
5061
4686
  const text = await res.text().catch(() => "");
5062
4687
  throw new CliError(`DELETE /yolo-policy failed (${res.status}): ${text}`);
5063
4688
  }
5064
- consola32.success(`YOLO-policy cleared on ${email}`);
4689
+ consola34.success(`YOLO-policy cleared on ${email}`);
5065
4690
  }
5066
4691
  });
5067
4692
 
5068
4693
  // src/commands/yolo/set.ts
5069
- import { defineCommand as defineCommand38 } from "citty";
5070
- import consola33 from "consola";
4694
+ import { defineCommand as defineCommand41 } from "citty";
4695
+ import consola35 from "consola";
5071
4696
  var VALID_MODES = ["allow-list", "deny-list"];
5072
- var yoloSetCommand = defineCommand38({
4697
+ var yoloSetCommand = defineCommand41({
5073
4698
  meta: {
5074
4699
  name: "set",
5075
4700
  description: "Write a YOLO-policy on a DDISA agent you own"
@@ -5117,12 +4742,12 @@ var yoloSetCommand = defineCommand38({
5117
4742
  const denyPatterns = parseList(args.deny);
5118
4743
  const denyRiskThreshold = args["deny-risk"] ?? null;
5119
4744
  const expiresAt = parseExpiresIn(args["expires-in"]);
5120
- consola33.info(`Setting YOLO-policy on ${email}`);
5121
- consola33.info(` mode: ${mode}`);
5122
- if (allowPatterns.length) consola33.info(` allow_patterns: ${allowPatterns.join(", ")}`);
5123
- if (denyPatterns.length) consola33.info(` deny_patterns: ${denyPatterns.join(", ")}`);
5124
- if (denyRiskThreshold) consola33.info(` deny_risk: ${denyRiskThreshold}`);
5125
- if (expiresAt) consola33.info(` expires_at: ${new Date(expiresAt * 1e3).toISOString()}`);
4745
+ consola35.info(`Setting YOLO-policy on ${email}`);
4746
+ consola35.info(` mode: ${mode}`);
4747
+ if (allowPatterns.length) consola35.info(` allow_patterns: ${allowPatterns.join(", ")}`);
4748
+ if (denyPatterns.length) consola35.info(` deny_patterns: ${denyPatterns.join(", ")}`);
4749
+ if (denyRiskThreshold) consola35.info(` deny_risk: ${denyRiskThreshold}`);
4750
+ if (expiresAt) consola35.info(` expires_at: ${new Date(expiresAt * 1e3).toISOString()}`);
5126
4751
  const url = `${idp}/api/users/${encodeURIComponent(email)}/yolo-policy`;
5127
4752
  const res = await fetch(url, {
5128
4753
  method: "PUT",
@@ -5142,27 +4767,27 @@ var yoloSetCommand = defineCommand38({
5142
4767
  const text = await res.text().catch(() => "");
5143
4768
  throw new CliError(`PUT /yolo-policy failed (${res.status}): ${text}`);
5144
4769
  }
5145
- consola33.success(`YOLO-policy applied to ${email}`);
4770
+ consola35.success(`YOLO-policy applied to ${email}`);
5146
4771
  }
5147
4772
  });
5148
4773
  function parseList(s) {
5149
4774
  if (!s) return [];
5150
- return s.split(",").map((p2) => p2.trim()).filter(Boolean);
4775
+ return s.split(",").map((p) => p.trim()).filter(Boolean);
5151
4776
  }
5152
4777
  function parseExpiresIn(s) {
5153
4778
  if (!s) return null;
5154
4779
  const m = s.match(/^(\d+)([hdw])$/);
5155
4780
  if (!m) throw new CliError(`Invalid --expires-in "${s}" \u2014 expected forms like 30d, 6h, 2w`);
5156
- const n2 = Number(m[1]);
4781
+ const n = Number(m[1]);
5157
4782
  const unit = m[2];
5158
4783
  const seconds = unit === "h" ? 3600 : unit === "d" ? 86400 : 7 * 86400;
5159
- return Math.floor(Date.now() / 1e3) + n2 * seconds;
4784
+ return Math.floor(Date.now() / 1e3) + n * seconds;
5160
4785
  }
5161
4786
 
5162
4787
  // src/commands/yolo/show.ts
5163
- import { defineCommand as defineCommand39 } from "citty";
5164
- import consola34 from "consola";
5165
- var yoloShowCommand = defineCommand39({
4788
+ import { defineCommand as defineCommand42 } from "citty";
4789
+ import consola36 from "consola";
4790
+ var yoloShowCommand = defineCommand42({
5166
4791
  meta: {
5167
4792
  name: "show",
5168
4793
  description: "Print the YOLO-policy currently set on a DDISA agent"
@@ -5199,17 +4824,17 @@ var yoloShowCommand = defineCommand39({
5199
4824
  console.log(JSON.stringify(policy, null, 2));
5200
4825
  return;
5201
4826
  }
5202
- consola34.info(`YOLO-policy for ${email}`);
5203
- consola34.info(` mode: ${policy.mode}`);
5204
- consola34.info(` allow_patterns: ${policy.allowPatterns.length ? policy.allowPatterns.join(", ") : "(none)"}`);
5205
- consola34.info(` deny_patterns: ${policy.denyPatterns.length ? policy.denyPatterns.join(", ") : "(none)"}`);
5206
- consola34.info(` deny_risk: ${policy.denyRiskThreshold ?? "(none)"}`);
5207
- consola34.info(` expires_at: ${policy.expiresAt ? new Date(policy.expiresAt * 1e3).toISOString() : "(never)"}`);
4827
+ consola36.info(`YOLO-policy for ${email}`);
4828
+ consola36.info(` mode: ${policy.mode}`);
4829
+ consola36.info(` allow_patterns: ${policy.allowPatterns.length ? policy.allowPatterns.join(", ") : "(none)"}`);
4830
+ consola36.info(` deny_patterns: ${policy.denyPatterns.length ? policy.denyPatterns.join(", ") : "(none)"}`);
4831
+ consola36.info(` deny_risk: ${policy.denyRiskThreshold ?? "(none)"}`);
4832
+ consola36.info(` expires_at: ${policy.expiresAt ? new Date(policy.expiresAt * 1e3).toISOString() : "(never)"}`);
5208
4833
  }
5209
4834
  });
5210
4835
 
5211
4836
  // src/commands/yolo/index.ts
5212
- var yoloCommand = defineCommand40({
4837
+ var yoloCommand = defineCommand43({
5213
4838
  meta: {
5214
4839
  name: "yolo",
5215
4840
  description: "Manage YOLO-policies on DDISA agents you own \u2014 auto-approve grant patterns at the IdP layer (allow-list) or block dangerous ones outright (deny-list)."
@@ -5222,15 +4847,15 @@ var yoloCommand = defineCommand40({
5222
4847
  });
5223
4848
 
5224
4849
  // src/commands/adapter/index.ts
5225
- import { defineCommand as defineCommand41 } from "citty";
5226
- import consola35 from "consola";
5227
- var adapterCommand = defineCommand41({
4850
+ import { defineCommand as defineCommand44 } from "citty";
4851
+ import consola37 from "consola";
4852
+ var adapterCommand = defineCommand44({
5228
4853
  meta: {
5229
4854
  name: "adapter",
5230
4855
  description: "Manage CLI adapters"
5231
4856
  },
5232
4857
  subCommands: {
5233
- list: defineCommand41({
4858
+ list: defineCommand44({
5234
4859
  meta: {
5235
4860
  name: "list",
5236
4861
  description: "List available adapters"
@@ -5261,7 +4886,7 @@ var adapterCommand = defineCommand41({
5261
4886
  `);
5262
4887
  return;
5263
4888
  }
5264
- consola35.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
4889
+ consola37.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
5265
4890
  for (const a of index2.adapters) {
5266
4891
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
5267
4892
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -5283,7 +4908,7 @@ var adapterCommand = defineCommand41({
5283
4908
  return;
5284
4909
  }
5285
4910
  if (local.length === 0) {
5286
- consola35.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
4911
+ consola37.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
5287
4912
  return;
5288
4913
  }
5289
4914
  for (const a of local) {
@@ -5291,7 +4916,7 @@ var adapterCommand = defineCommand41({
5291
4916
  }
5292
4917
  }
5293
4918
  }),
5294
- install: defineCommand41({
4919
+ install: defineCommand44({
5295
4920
  meta: {
5296
4921
  name: "install",
5297
4922
  description: "Install an adapter from the registry"
@@ -5320,24 +4945,24 @@ var adapterCommand = defineCommand41({
5320
4945
  for (const id of ids) {
5321
4946
  const entry = findAdapter(index, id);
5322
4947
  if (!entry) {
5323
- consola35.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
4948
+ consola37.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
5324
4949
  continue;
5325
4950
  }
5326
4951
  const conflicts = findConflictingAdapters(entry.executable, id);
5327
4952
  if (conflicts.length > 0) {
5328
- for (const c2 of conflicts) {
5329
- consola35.warn(`Conflicting adapter found: ${c2.path} (id: ${c2.adapterId}, executable: ${c2.executable})`);
5330
- consola35.warn(` Remove it with: apes adapter remove ${c2.adapterId}`);
4953
+ for (const c of conflicts) {
4954
+ consola37.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
4955
+ consola37.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
5331
4956
  }
5332
4957
  }
5333
4958
  const result = await installAdapter(entry, { local });
5334
4959
  const verb = result.updated ? "Updated" : "Installed";
5335
- consola35.success(`${verb} ${result.id} \u2192 ${result.path}`);
5336
- consola35.info(`Digest: ${result.digest}`);
4960
+ consola37.success(`${verb} ${result.id} \u2192 ${result.path}`);
4961
+ consola37.info(`Digest: ${result.digest}`);
5337
4962
  }
5338
4963
  }
5339
4964
  }),
5340
- remove: defineCommand41({
4965
+ remove: defineCommand44({
5341
4966
  meta: {
5342
4967
  name: "remove",
5343
4968
  description: "Remove an installed adapter"
@@ -5360,9 +4985,9 @@ var adapterCommand = defineCommand41({
5360
4985
  let failed = false;
5361
4986
  for (const id of ids) {
5362
4987
  if (removeAdapter(id, local)) {
5363
- consola35.success(`Removed adapter: ${id}`);
4988
+ consola37.success(`Removed adapter: ${id}`);
5364
4989
  } else {
5365
- consola35.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
4990
+ consola37.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
5366
4991
  failed = true;
5367
4992
  }
5368
4993
  }
@@ -5370,7 +4995,7 @@ var adapterCommand = defineCommand41({
5370
4995
  throw new CliError("Some adapters could not be removed");
5371
4996
  }
5372
4997
  }),
5373
- info: defineCommand41({
4998
+ info: defineCommand44({
5374
4999
  meta: {
5375
5000
  name: "info",
5376
5001
  description: "Show detailed adapter information"
@@ -5412,7 +5037,7 @@ var adapterCommand = defineCommand41({
5412
5037
  }
5413
5038
  }
5414
5039
  }),
5415
- search: defineCommand41({
5040
+ search: defineCommand44({
5416
5041
  meta: {
5417
5042
  name: "search",
5418
5043
  description: "Search adapters in the registry"
@@ -5444,7 +5069,7 @@ var adapterCommand = defineCommand41({
5444
5069
  return;
5445
5070
  }
5446
5071
  if (results.length === 0) {
5447
- consola35.info(`No adapters matching "${query}"`);
5072
+ consola37.info(`No adapters matching "${query}"`);
5448
5073
  return;
5449
5074
  }
5450
5075
  for (const a of results) {
@@ -5453,7 +5078,7 @@ var adapterCommand = defineCommand41({
5453
5078
  }
5454
5079
  }
5455
5080
  }),
5456
- update: defineCommand41({
5081
+ update: defineCommand44({
5457
5082
  meta: {
5458
5083
  name: "update",
5459
5084
  description: "Update installed adapters"
@@ -5479,33 +5104,33 @@ var adapterCommand = defineCommand41({
5479
5104
  const targetId = args.id ? String(args.id) : void 0;
5480
5105
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
5481
5106
  if (targets.length === 0) {
5482
- consola35.info("No adapters installed to update.");
5107
+ consola37.info("No adapters installed to update.");
5483
5108
  return;
5484
5109
  }
5485
5110
  for (const id of targets) {
5486
5111
  const entry = findAdapter(index, id);
5487
5112
  if (!entry) {
5488
- consola35.warn(`${id}: not found in registry, skipping`);
5113
+ consola37.warn(`${id}: not found in registry, skipping`);
5489
5114
  continue;
5490
5115
  }
5491
5116
  const localDigest = getInstalledDigest(id, false);
5492
5117
  if (localDigest === entry.digest) {
5493
- consola35.info(`${id}: already up to date`);
5118
+ consola37.info(`${id}: already up to date`);
5494
5119
  continue;
5495
5120
  }
5496
5121
  if (localDigest && !args.yes) {
5497
- consola35.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
5498
- consola35.info(` Old: ${localDigest}`);
5499
- consola35.info(` New: ${entry.digest}`);
5500
- consola35.info(" Use --yes to confirm");
5122
+ consola37.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
5123
+ consola37.info(` Old: ${localDigest}`);
5124
+ consola37.info(` New: ${entry.digest}`);
5125
+ consola37.info(" Use --yes to confirm");
5501
5126
  continue;
5502
5127
  }
5503
5128
  const result = await installAdapter(entry);
5504
- consola35.success(`Updated ${result.id} \u2192 ${result.path}`);
5129
+ consola37.success(`Updated ${result.id} \u2192 ${result.path}`);
5505
5130
  }
5506
5131
  }
5507
5132
  }),
5508
- verify: defineCommand41({
5133
+ verify: defineCommand44({
5509
5134
  meta: {
5510
5135
  name: "verify",
5511
5136
  description: "Verify installed adapter against registry digest"
@@ -5538,7 +5163,7 @@ var adapterCommand = defineCommand41({
5538
5163
  if (!localDigest)
5539
5164
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
5540
5165
  if (localDigest === entry.digest) {
5541
- consola35.success(`${id}: digest matches registry`);
5166
+ consola37.success(`${id}: digest matches registry`);
5542
5167
  } else {
5543
5168
  console.log(` Local: ${localDigest}`);
5544
5169
  console.log(` Registry: ${entry.digest}`);
@@ -5550,11 +5175,20 @@ var adapterCommand = defineCommand41({
5550
5175
  });
5551
5176
 
5552
5177
  // src/commands/run.ts
5553
- import { execFileSync as execFileSync14 } from "child_process";
5178
+ import { execFileSync as execFileSync15 } from "child_process";
5554
5179
  import { hostname as hostname5 } from "os";
5555
5180
  import { basename } from "path";
5556
- import { defineCommand as defineCommand42 } from "citty";
5557
- import consola36 from "consola";
5181
+ import { defineCommand as defineCommand45 } from "citty";
5182
+ import consola38 from "consola";
5183
+ function resolveRunAsTarget(runAs) {
5184
+ if (!runAs) return runAs;
5185
+ if (!isDarwin()) return runAs;
5186
+ if (!AGENT_NAME_REGEX.test(runAs)) return runAs;
5187
+ if (runAs.startsWith("openape-agent-")) return runAs;
5188
+ const prefixed = macOSUsernameForAgent(runAs);
5189
+ if (readMacOSUser(prefixed)) return prefixed;
5190
+ return runAs;
5191
+ }
5558
5192
  function shouldWaitForGrant(args) {
5559
5193
  return args.wait === true || process.env.APE_WAIT === "1";
5560
5194
  }
@@ -5572,16 +5206,16 @@ function getUserMode() {
5572
5206
  function getAsyncExitCode() {
5573
5207
  const envValue = process.env.APES_ASYNC_EXIT_CODE;
5574
5208
  if (envValue !== void 0 && envValue !== "") {
5575
- const n2 = Number(envValue);
5576
- if (Number.isFinite(n2) && n2 >= 0 && n2 <= 255)
5577
- return Math.floor(n2);
5209
+ const n = Number(envValue);
5210
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
5211
+ return Math.floor(n);
5578
5212
  }
5579
5213
  const cfg = loadConfig();
5580
5214
  const cfgValue = cfg.defaults?.async_exit_code;
5581
5215
  if (cfgValue !== void 0 && cfgValue !== "") {
5582
- const n2 = Number(cfgValue);
5583
- if (Number.isFinite(n2) && n2 >= 0 && n2 <= 255)
5584
- return Math.floor(n2);
5216
+ const n = Number(cfgValue);
5217
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
5218
+ return Math.floor(n);
5585
5219
  }
5586
5220
  return 75;
5587
5221
  }
@@ -5591,7 +5225,7 @@ function printPendingGrantInfo(grant, idp) {
5591
5225
  const statusCmd = `apes grants status ${grant.id}`;
5592
5226
  const executeCmd = `apes grants run ${grant.id}`;
5593
5227
  if (mode === "human") {
5594
- consola36.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
5228
+ consola38.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
5595
5229
  console.log(` Approve in browser: ${approveUrl}`);
5596
5230
  console.log(` Check status: ${statusCmd}`);
5597
5231
  console.log(` Run after approval: ${executeCmd}`);
@@ -5601,7 +5235,7 @@ function printPendingGrantInfo(grant, idp) {
5601
5235
  return;
5602
5236
  }
5603
5237
  const maxMin = getPollMaxMinutes();
5604
- consola36.success(`Grant ${grant.id} created (pending approval)`);
5238
+ consola38.success(`Grant ${grant.id} created (pending approval)`);
5605
5239
  console.log(` Approve: ${approveUrl}`);
5606
5240
  console.log(` Status: ${statusCmd} [--json]`);
5607
5241
  console.log(` Execute: ${executeCmd} --wait`);
@@ -5623,7 +5257,7 @@ function printPendingGrantInfo(grant, idp) {
5623
5257
  console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
5624
5258
  console.log(" grant be reused on subsequent invocations without re-approval.");
5625
5259
  }
5626
- var runCommand = defineCommand42({
5260
+ var runCommand = defineCommand45({
5627
5261
  meta: {
5628
5262
  name: "run",
5629
5263
  description: "Execute a grant-secured command"
@@ -5726,7 +5360,7 @@ async function runShellMode(command, args) {
5726
5360
  }
5727
5361
  } catch {
5728
5362
  }
5729
- consola36.info(`Requesting ape-shell session grant on ${targetHost}`);
5363
+ consola38.info(`Requesting ape-shell session grant on ${targetHost}`);
5730
5364
  const grant = await apiFetch(grantsUrl, {
5731
5365
  method: "POST",
5732
5366
  body: {
@@ -5746,8 +5380,8 @@ async function runShellMode(command, args) {
5746
5380
  host: targetHost
5747
5381
  });
5748
5382
  if (shouldWaitForGrant(args)) {
5749
- consola36.info(`Grant requested: ${grant.id}`);
5750
- consola36.info("Waiting for approval...");
5383
+ consola38.info(`Grant requested: ${grant.id}`);
5384
+ consola38.info("Waiting for approval...");
5751
5385
  const maxWait = 3e5;
5752
5386
  const interval = 3e3;
5753
5387
  const start = Date.now();
@@ -5757,7 +5391,7 @@ async function runShellMode(command, args) {
5757
5391
  break;
5758
5392
  if (status.status === "denied" || status.status === "revoked")
5759
5393
  throw new CliError(`Grant ${status.status}.`);
5760
- await new Promise((r3) => setTimeout(r3, interval));
5394
+ await new Promise((r) => setTimeout(r, interval));
5761
5395
  }
5762
5396
  execShellCommand(command);
5763
5397
  return;
@@ -5778,13 +5412,13 @@ async function tryAdapterModeFromShell(command, idp, args) {
5778
5412
  try {
5779
5413
  resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
5780
5414
  } catch (err) {
5781
- consola36.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
5415
+ consola38.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
5782
5416
  return false;
5783
5417
  }
5784
5418
  try {
5785
5419
  const existingGrantId = await findExistingGrant(resolved, idp);
5786
5420
  if (existingGrantId) {
5787
- consola36.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
5421
+ consola38.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
5788
5422
  const token = await fetchGrantToken(idp, existingGrantId);
5789
5423
  await verifyAndExecute(token, resolved, existingGrantId);
5790
5424
  return true;
@@ -5792,16 +5426,16 @@ async function tryAdapterModeFromShell(command, idp, args) {
5792
5426
  } catch {
5793
5427
  }
5794
5428
  const approval = args.approval ?? "once";
5795
- consola36.info(`Requesting grant for: ${resolved.detail.display}`);
5429
+ consola38.info(`Requesting grant for: ${resolved.detail.display}`);
5796
5430
  const grant = await createShapesGrant(resolved, {
5797
5431
  idp,
5798
5432
  approval,
5799
5433
  reason: args.reason || `ape-shell: ${resolved.detail.display}`
5800
5434
  });
5801
5435
  if (grant.similar_grants?.similar_grants?.length) {
5802
- const n2 = grant.similar_grants.similar_grants.length;
5803
- consola36.info("");
5804
- consola36.info(` Similar grant(s) found (${n2}). Your approver can extend an existing grant to cover this request.`);
5436
+ const n = grant.similar_grants.similar_grants.length;
5437
+ consola38.info("");
5438
+ consola38.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
5805
5439
  }
5806
5440
  notifyGrantPending({
5807
5441
  grantId: grant.id,
@@ -5811,8 +5445,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
5811
5445
  host: args.host || hostname5()
5812
5446
  });
5813
5447
  if (shouldWaitForGrant(args)) {
5814
- consola36.info(`Grant requested: ${grant.id}`);
5815
- consola36.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5448
+ consola38.info(`Grant requested: ${grant.id}`);
5449
+ consola38.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5816
5450
  const status = await waitForGrantStatus(idp, grant.id);
5817
5451
  if (status !== "approved")
5818
5452
  throw new CliError(`Grant ${status}`);
@@ -5828,7 +5462,7 @@ function execShellCommand(command) {
5828
5462
  throw new CliError("No command to execute");
5829
5463
  try {
5830
5464
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
5831
- execFileSync14(command[0], command.slice(1), {
5465
+ execFileSync15(command[0], command.slice(1), {
5832
5466
  stdio: "inherit",
5833
5467
  env: inheritedEnv
5834
5468
  });
@@ -5886,7 +5520,7 @@ async function runAdapterMode(command, rawArgs, args) {
5886
5520
  try {
5887
5521
  const existingGrantId = await findExistingGrant(resolved, idp);
5888
5522
  if (existingGrantId) {
5889
- consola36.info(`Reusing existing grant: ${existingGrantId}`);
5523
+ consola38.info(`Reusing existing grant: ${existingGrantId}`);
5890
5524
  const token = await fetchGrantToken(idp, existingGrantId);
5891
5525
  await verifyAndExecute(token, resolved, existingGrantId);
5892
5526
  return;
@@ -5899,18 +5533,18 @@ async function runAdapterMode(command, rawArgs, args) {
5899
5533
  ...args.reason ? { reason: args.reason } : {}
5900
5534
  });
5901
5535
  if (grant.similar_grants?.similar_grants?.length) {
5902
- const n2 = grant.similar_grants.similar_grants.length;
5903
- consola36.info("");
5904
- consola36.info(` Similar grant(s) found (${n2}). Your approver can extend an existing grant to cover this request.`);
5536
+ const n = grant.similar_grants.similar_grants.length;
5537
+ consola38.info("");
5538
+ consola38.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
5905
5539
  if (grant.similar_grants.widened_details?.length) {
5906
5540
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
5907
- consola36.info(` Broader scope: ${wider}`);
5541
+ consola38.info(` Broader scope: ${wider}`);
5908
5542
  }
5909
- consola36.info("");
5543
+ consola38.info("");
5910
5544
  }
5911
5545
  if (shouldWaitForGrant(args)) {
5912
- consola36.info(`Grant requested: ${grant.id}`);
5913
- consola36.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5546
+ consola38.info(`Grant requested: ${grant.id}`);
5547
+ consola38.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5914
5548
  const status = await waitForGrantStatus(idp, grant.id);
5915
5549
  if (status !== "approved")
5916
5550
  throw new Error(`Grant ${status}`);
@@ -5930,7 +5564,7 @@ async function runAudienceMode(audience, action, args) {
5930
5564
  const grantsUrl = await getGrantsEndpoint(idp);
5931
5565
  const command = action.split(" ");
5932
5566
  const targetHost = args.host || hostname5();
5933
- const runAs = args.as ?? void 0;
5567
+ const runAs = resolveRunAsTarget(args.as ?? void 0);
5934
5568
  const reusableId = await findReusableAudienceGrant({
5935
5569
  grantsUrl,
5936
5570
  requester: auth.email,
@@ -5943,7 +5577,7 @@ async function runAudienceMode(audience, action, args) {
5943
5577
  const { authz_jwt: authz_jwt2 } = await apiFetch(`${grantsUrl}/${reusableId}/token`, { method: "POST" });
5944
5578
  return executeWithGrantToken({ audience, command, args, token: authz_jwt2 });
5945
5579
  }
5946
- consola36.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
5580
+ consola38.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
5947
5581
  const grant = await apiFetch(grantsUrl, {
5948
5582
  method: "POST",
5949
5583
  body: {
@@ -5960,9 +5594,9 @@ async function runAudienceMode(audience, action, args) {
5960
5594
  printPendingGrantInfo(grant, idp);
5961
5595
  throw new CliExit(getAsyncExitCode());
5962
5596
  }
5963
- consola36.success(`Grant requested: ${grant.id}`);
5964
- consola36.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5965
- consola36.info("Waiting for approval...");
5597
+ consola38.success(`Grant requested: ${grant.id}`);
5598
+ consola38.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5599
+ consola38.info("Waiting for approval...");
5966
5600
  const maxWait = 15 * 60 * 1e3;
5967
5601
  const interval = 3e3;
5968
5602
  const start = Date.now();
@@ -5970,14 +5604,14 @@ async function runAudienceMode(audience, action, args) {
5970
5604
  while (Date.now() - start < maxWait) {
5971
5605
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
5972
5606
  if (status.status === "approved") {
5973
- consola36.success("Grant approved!");
5607
+ consola38.success("Grant approved!");
5974
5608
  approved = true;
5975
5609
  break;
5976
5610
  }
5977
5611
  if (status.status === "denied" || status.status === "revoked") {
5978
5612
  throw new CliError(`Grant ${status.status}.`);
5979
5613
  }
5980
- await new Promise((r3) => setTimeout(r3, interval));
5614
+ await new Promise((r) => setTimeout(r, interval));
5981
5615
  }
5982
5616
  if (!approved) {
5983
5617
  const minutes = Math.round(maxWait / 6e4);
@@ -5985,7 +5619,7 @@ async function runAudienceMode(audience, action, args) {
5985
5619
  `Grant approval timed out after ${minutes} min (still pending). Check your DDISA inbox at ${idp}/grant-approval?grant_id=${grant.id} \u2014 if approved later, re-run the same \`apes run\` command and it will reuse the grant.`
5986
5620
  );
5987
5621
  }
5988
- consola36.info("Fetching grant token...");
5622
+ consola38.info("Fetching grant token...");
5989
5623
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
5990
5624
  method: "POST"
5991
5625
  });
@@ -5994,10 +5628,10 @@ async function runAudienceMode(audience, action, args) {
5994
5628
  function executeWithGrantToken(opts) {
5995
5629
  const { audience, command, args, token } = opts;
5996
5630
  if (audience === "escapes") {
5997
- consola36.info(`Executing: ${command.join(" ")}`);
5631
+ consola38.info(`Executing: ${command.join(" ")}`);
5998
5632
  try {
5999
5633
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
6000
- execFileSync14(args["escapes-path"] || "escapes", ["--grant", token, "--", ...command], {
5634
+ execFileSync15(args["escapes-path"] || "escapes", ["--grant", token, "--", ...command], {
6001
5635
  stdio: "inherit",
6002
5636
  env: inheritedEnv
6003
5637
  });
@@ -6014,15 +5648,15 @@ async function findReusableAudienceGrant(opts) {
6014
5648
  const grants = await apiFetch(`${opts.grantsUrl}?requester=${encodeURIComponent(opts.requester)}&status=approved&limit=50`);
6015
5649
  const now = Math.floor(Date.now() / 1e3);
6016
5650
  const match = grants.data.find((g) => {
6017
- const r3 = g.request;
6018
- if (r3.audience !== opts.audience) return false;
6019
- if (r3.target_host !== opts.targetHost) return false;
6020
- if (r3.grant_type === "once") return false;
6021
- if (r3.grant_type === "timed" && g.expires_at && g.expires_at <= now) return false;
6022
- const cmd = r3.command ?? [];
5651
+ const r = g.request;
5652
+ if (r.audience !== opts.audience) return false;
5653
+ if (r.target_host !== opts.targetHost) return false;
5654
+ if (r.grant_type === "once") return false;
5655
+ if (r.grant_type === "timed" && g.expires_at && g.expires_at <= now) return false;
5656
+ const cmd = r.command ?? [];
6023
5657
  if (cmd.length !== opts.command.length) return false;
6024
- if (!cmd.every((c2, i) => c2 === opts.command[i])) return false;
6025
- if ((r3.run_as ?? void 0) !== opts.runAs) return false;
5658
+ if (!cmd.every((c, i) => c === opts.command[i])) return false;
5659
+ if ((r.run_as ?? void 0) !== opts.runAs) return false;
6026
5660
  return true;
6027
5661
  });
6028
5662
  return match?.id ?? null;
@@ -6033,8 +5667,11 @@ async function findReusableAudienceGrant(opts) {
6033
5667
 
6034
5668
  // src/commands/proxy.ts
6035
5669
  import { spawn as spawn2 } from "child_process";
6036
- import { defineCommand as defineCommand43 } from "citty";
6037
- import consola37 from "consola";
5670
+ import { existsSync as existsSync17 } from "fs";
5671
+ import { homedir as homedir14 } from "os";
5672
+ import { join as join16 } from "path";
5673
+ import { defineCommand as defineCommand46 } from "citty";
5674
+ import consola39 from "consola";
6038
5675
 
6039
5676
  // src/proxy/config.ts
6040
5677
  function buildDefaultProxyConfigToml(opts) {
@@ -6071,7 +5708,7 @@ import { spawn } from "child_process";
6071
5708
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
6072
5709
  import { createRequire } from "module";
6073
5710
  import { tmpdir as tmpdir3 } from "os";
6074
- import { dirname as dirname4, join as join13, resolve as resolve3 } from "path";
5711
+ import { dirname as dirname4, join as join14, resolve as resolve3 } from "path";
6075
5712
  var require2 = createRequire(import.meta.url);
6076
5713
  function findProxyBin() {
6077
5714
  const pkgPath = require2.resolve("@openape/proxy/package.json");
@@ -6083,8 +5720,8 @@ function findProxyBin() {
6083
5720
  return resolve3(dirname4(pkgPath), binRel);
6084
5721
  }
6085
5722
  async function startEphemeralProxy(configToml) {
6086
- const tmpDir = mkdtempSync3(join13(tmpdir3(), "openape-proxy-"));
6087
- const configPath = join13(tmpDir, "config.toml");
5723
+ const tmpDir = mkdtempSync3(join14(tmpdir3(), "openape-proxy-"));
5724
+ const configPath = join14(tmpDir, "config.toml");
6088
5725
  writeFileSync8(configPath, configToml, { mode: 384 });
6089
5726
  const binPath = findProxyBin();
6090
5727
  const child = spawn(process.execPath, [binPath, "-c", configPath], {
@@ -6166,6 +5803,51 @@ function waitForListenLine(child) {
6166
5803
  });
6167
5804
  }
6168
5805
 
5806
+ // src/proxy/trust-bundle.ts
5807
+ import { existsSync as existsSync16, mkdtempSync as mkdtempSync4, readFileSync as readFileSync13, rmdirSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync9 } from "fs";
5808
+ import { tmpdir as tmpdir4 } from "os";
5809
+ import { join as join15 } from "path";
5810
+ var CANDIDATES = [
5811
+ "/etc/ssl/cert.pem",
5812
+ // macOS
5813
+ "/etc/ssl/certs/ca-certificates.crt",
5814
+ // Debian / Ubuntu
5815
+ "/etc/pki/tls/certs/ca-bundle.crt",
5816
+ // RHEL / Fedora
5817
+ "/etc/ssl/ca-bundle.pem"
5818
+ // OpenSUSE
5819
+ ];
5820
+ function detectSystemCaPath() {
5821
+ for (const p of CANDIDATES) {
5822
+ if (existsSync16(p)) return p;
5823
+ }
5824
+ throw new Error(
5825
+ `Could not locate a system CA bundle. Tried: ${CANDIDATES.join(", ")}. Set NODE_EXTRA_CA_CERTS yourself or pass --allow-no-system-ca.`
5826
+ );
5827
+ }
5828
+ function buildTrustBundle(opts) {
5829
+ const dir = mkdtempSync4(join15(tmpdir4(), "openape-trust-"));
5830
+ const path2 = join15(dir, "bundle.pem");
5831
+ const sys = readFileSync13(opts.systemCaPath, "utf-8");
5832
+ const local = readFileSync13(opts.localCaPath, "utf-8");
5833
+ writeFileSync9(path2, `${sys.trimEnd()}
5834
+ ${local.trimEnd()}
5835
+ `, { mode: 384 });
5836
+ return {
5837
+ path: path2,
5838
+ cleanup: () => {
5839
+ try {
5840
+ unlinkSync2(path2);
5841
+ } catch {
5842
+ }
5843
+ try {
5844
+ rmdirSync(dir);
5845
+ } catch {
5846
+ }
5847
+ }
5848
+ };
5849
+ }
5850
+
6169
5851
  // src/commands/proxy.ts
6170
5852
  function resolveProxyConfigOptions() {
6171
5853
  const auth = loadAuth();
@@ -6177,10 +5859,10 @@ function resolveProxyConfigOptions() {
6177
5859
  77
6178
5860
  );
6179
5861
  }
6180
- consola37.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
5862
+ consola39.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
6181
5863
  return { agentEmail: auth.email, idpUrl: auth.idp, mediated: true };
6182
5864
  }
6183
- var proxyCommand = defineCommand43({
5865
+ var proxyCommand = defineCommand46({
6184
5866
  meta: {
6185
5867
  name: "proxy",
6186
5868
  description: "Run a command with HTTPS_PROXY routed through the OpenApe egress proxy."
@@ -6197,17 +5879,33 @@ var proxyCommand = defineCommand43({
6197
5879
  if (wrapped.length === 0) {
6198
5880
  throw new CliError("Usage: apes proxy -- <cmd> [args...]");
6199
5881
  }
5882
+ const reuseHostPort = process.env.OPENAPE_PROXY;
6200
5883
  const reuseUrl = process.env.OPENAPE_PROXY_URL;
6201
5884
  let proxyUrl;
5885
+ let bundle = null;
6202
5886
  let close = null;
6203
- if (reuseUrl) {
5887
+ if (reuseHostPort) {
5888
+ proxyUrl = `http://${reuseHostPort}`;
5889
+ consola39.info(`[apes proxy] using long-running daemon at ${proxyUrl}`);
5890
+ const localCaPath = join16(homedir14(), ".openape", "proxy", "ca.crt");
5891
+ if (!existsSync17(localCaPath)) {
5892
+ throw new CliError(
5893
+ `OPENAPE_PROXY is set but no local CA found at ${localCaPath}. Start the daemon (sudo -u <agent> apes proxy --global < secrets.toml) first.`
5894
+ );
5895
+ }
5896
+ bundle = buildTrustBundle({
5897
+ systemCaPath: detectSystemCaPath(),
5898
+ localCaPath
5899
+ });
5900
+ consola39.debug(`[apes proxy] trust bundle: ${bundle.path}`);
5901
+ } else if (reuseUrl) {
6204
5902
  proxyUrl = reuseUrl;
6205
- consola37.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
5903
+ consola39.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
6206
5904
  } else {
6207
5905
  const ephemeral = await startEphemeralProxy(buildDefaultProxyConfigToml(resolveProxyConfigOptions()));
6208
5906
  proxyUrl = ephemeral.url;
6209
5907
  close = ephemeral.close;
6210
- consola37.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
5908
+ consola39.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
6211
5909
  }
6212
5910
  const noProxy = process.env.NO_PROXY ?? process.env.no_proxy ?? "127.0.0.1,localhost";
6213
5911
  const childEnv = {
@@ -6220,30 +5918,42 @@ var proxyCommand = defineCommand43({
6220
5918
  all_proxy: proxyUrl,
6221
5919
  NO_PROXY: noProxy,
6222
5920
  no_proxy: noProxy,
6223
- NODE_USE_ENV_PROXY: "1"
5921
+ NODE_USE_ENV_PROXY: "1",
5922
+ ...bundle ? {
5923
+ NODE_EXTRA_CA_CERTS: bundle.path,
5924
+ SSL_CERT_FILE: bundle.path,
5925
+ CURL_CA_BUNDLE: bundle.path,
5926
+ REQUESTS_CA_BUNDLE: bundle.path,
5927
+ GIT_SSL_CAINFO: bundle.path
5928
+ } : {}
6224
5929
  };
6225
- const exitCode = await new Promise((resolveExit) => {
6226
- const child = spawn2(wrapped[0], wrapped.slice(1), {
6227
- stdio: "inherit",
6228
- env: childEnv
6229
- });
6230
- const forward = (sig) => () => child.kill(sig);
6231
- const onSigint = forward("SIGINT");
6232
- const onSigterm = forward("SIGTERM");
6233
- process.on("SIGINT", onSigint);
6234
- process.on("SIGTERM", onSigterm);
6235
- child.once("exit", (code, signal) => {
6236
- process.off("SIGINT", onSigint);
6237
- process.off("SIGTERM", onSigterm);
6238
- if (signal) resolveExit(128 + (signalNumber(signal) ?? 0));
6239
- else resolveExit(code ?? 0);
6240
- });
6241
- child.once("error", (err) => {
6242
- consola37.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
6243
- resolveExit(127);
5930
+ let exitCode;
5931
+ try {
5932
+ exitCode = await new Promise((resolveExit) => {
5933
+ const child = spawn2(wrapped[0], wrapped.slice(1), {
5934
+ stdio: "inherit",
5935
+ env: childEnv
5936
+ });
5937
+ const forward = (sig) => () => child.kill(sig);
5938
+ const onSigint = forward("SIGINT");
5939
+ const onSigterm = forward("SIGTERM");
5940
+ process.on("SIGINT", onSigint);
5941
+ process.on("SIGTERM", onSigterm);
5942
+ child.once("exit", (code, signal) => {
5943
+ process.off("SIGINT", onSigint);
5944
+ process.off("SIGTERM", onSigterm);
5945
+ if (signal) resolveExit(128 + (signalNumber(signal) ?? 0));
5946
+ else resolveExit(code ?? 0);
5947
+ });
5948
+ child.once("error", (err) => {
5949
+ consola39.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
5950
+ resolveExit(127);
5951
+ });
6244
5952
  });
6245
- });
6246
- if (close) await close();
5953
+ } finally {
5954
+ bundle?.cleanup();
5955
+ if (close) await close();
5956
+ }
6247
5957
  if (exitCode !== 0) process.exit(exitCode);
6248
5958
  }
6249
5959
  });
@@ -6253,8 +5963,8 @@ function signalNumber(signal) {
6253
5963
  }
6254
5964
 
6255
5965
  // src/commands/explain.ts
6256
- import { defineCommand as defineCommand44 } from "citty";
6257
- var explainCommand = defineCommand44({
5966
+ import { defineCommand as defineCommand47 } from "citty";
5967
+ var explainCommand = defineCommand47({
6258
5968
  meta: {
6259
5969
  name: "explain",
6260
5970
  description: "Show what permission a command would need"
@@ -6292,9 +6002,9 @@ var explainCommand = defineCommand44({
6292
6002
  });
6293
6003
 
6294
6004
  // src/commands/config/get.ts
6295
- import { defineCommand as defineCommand45 } from "citty";
6296
- import consola38 from "consola";
6297
- var configGetCommand = defineCommand45({
6005
+ import { defineCommand as defineCommand48 } from "citty";
6006
+ import consola40 from "consola";
6007
+ var configGetCommand = defineCommand48({
6298
6008
  meta: {
6299
6009
  name: "get",
6300
6010
  description: "Get a configuration value"
@@ -6314,7 +6024,7 @@ var configGetCommand = defineCommand45({
6314
6024
  if (idp)
6315
6025
  console.log(idp);
6316
6026
  else
6317
- consola38.info("No IdP configured.");
6027
+ consola40.info("No IdP configured.");
6318
6028
  break;
6319
6029
  }
6320
6030
  case "email": {
@@ -6322,7 +6032,7 @@ var configGetCommand = defineCommand45({
6322
6032
  if (auth?.email)
6323
6033
  console.log(auth.email);
6324
6034
  else
6325
- consola38.info("Not logged in.");
6035
+ consola40.info("Not logged in.");
6326
6036
  break;
6327
6037
  }
6328
6038
  default: {
@@ -6335,7 +6045,7 @@ var configGetCommand = defineCommand45({
6335
6045
  if (sectionObj && field in sectionObj) {
6336
6046
  console.log(sectionObj[field]);
6337
6047
  } else {
6338
- consola38.info(`Key "${key}" not set.`);
6048
+ consola40.info(`Key "${key}" not set.`);
6339
6049
  }
6340
6050
  } else {
6341
6051
  throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
@@ -6346,9 +6056,9 @@ var configGetCommand = defineCommand45({
6346
6056
  });
6347
6057
 
6348
6058
  // src/commands/config/set.ts
6349
- import { defineCommand as defineCommand46 } from "citty";
6350
- import consola39 from "consola";
6351
- var configSetCommand = defineCommand46({
6059
+ import { defineCommand as defineCommand49 } from "citty";
6060
+ import consola41 from "consola";
6061
+ var configSetCommand = defineCommand49({
6352
6062
  meta: {
6353
6063
  name: "set",
6354
6064
  description: "Set a configuration value"
@@ -6384,12 +6094,12 @@ var configSetCommand = defineCommand46({
6384
6094
  throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
6385
6095
  }
6386
6096
  saveConfig(config);
6387
- consola39.success(`Set ${key} = ${value}`);
6097
+ consola41.success(`Set ${key} = ${value}`);
6388
6098
  }
6389
6099
  });
6390
6100
 
6391
6101
  // src/commands/fetch/index.ts
6392
- import { defineCommand as defineCommand47 } from "citty";
6102
+ import { defineCommand as defineCommand50 } from "citty";
6393
6103
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
6394
6104
  const token = getAuthToken();
6395
6105
  if (!token) {
@@ -6425,13 +6135,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
6425
6135
  throw new CliError(`HTTP ${response.status} ${response.statusText}`);
6426
6136
  }
6427
6137
  }
6428
- var fetchCommand = defineCommand47({
6138
+ var fetchCommand = defineCommand50({
6429
6139
  meta: {
6430
6140
  name: "fetch",
6431
6141
  description: "Make authenticated HTTP requests"
6432
6142
  },
6433
6143
  subCommands: {
6434
- get: defineCommand47({
6144
+ get: defineCommand50({
6435
6145
  meta: {
6436
6146
  name: "get",
6437
6147
  description: "GET request with auth token"
@@ -6457,7 +6167,7 @@ var fetchCommand = defineCommand47({
6457
6167
  await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
6458
6168
  }
6459
6169
  }),
6460
- post: defineCommand47({
6170
+ post: defineCommand50({
6461
6171
  meta: {
6462
6172
  name: "post",
6463
6173
  description: "POST request with auth token"
@@ -6496,8 +6206,8 @@ var fetchCommand = defineCommand47({
6496
6206
  });
6497
6207
 
6498
6208
  // src/commands/mcp/index.ts
6499
- import { defineCommand as defineCommand48 } from "citty";
6500
- var mcpCommand = defineCommand48({
6209
+ import { defineCommand as defineCommand51 } from "citty";
6210
+ var mcpCommand = defineCommand51({
6501
6211
  meta: {
6502
6212
  name: "mcp",
6503
6213
  description: "Start MCP server for AI agents"
@@ -6520,48 +6230,48 @@ var mcpCommand = defineCommand48({
6520
6230
  if (transport !== "stdio" && transport !== "sse") {
6521
6231
  throw new Error('Transport must be "stdio" or "sse"');
6522
6232
  }
6523
- const { startMcpServer } = await import("./server-KR6GVKRI.js");
6233
+ const { startMcpServer } = await import("./server-V2V5YHK3.js");
6524
6234
  await startMcpServer(transport, port);
6525
6235
  }
6526
6236
  });
6527
6237
 
6528
6238
  // src/commands/init/index.ts
6529
- import { existsSync as existsSync15, copyFileSync, writeFileSync as writeFileSync9 } from "fs";
6239
+ import { existsSync as existsSync18, copyFileSync, writeFileSync as writeFileSync10 } from "fs";
6530
6240
  import { randomBytes } from "crypto";
6531
- import { execFileSync as execFileSync15 } from "child_process";
6532
- import { join as join14 } from "path";
6533
- import { defineCommand as defineCommand49 } from "citty";
6534
- import consola40 from "consola";
6241
+ import { execFileSync as execFileSync16 } from "child_process";
6242
+ import { join as join17 } from "path";
6243
+ import { defineCommand as defineCommand52 } from "citty";
6244
+ import consola42 from "consola";
6535
6245
  var DEFAULT_IDP_URL = "https://id.openape.at";
6536
6246
  async function downloadTemplate(repo, targetDir) {
6537
6247
  const { downloadTemplate: gigetDownload } = await import("giget");
6538
6248
  await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
6539
6249
  }
6540
6250
  function installDeps(dir) {
6541
- const hasLockFile = (name) => existsSync15(join14(dir, name));
6251
+ const hasLockFile = (name) => existsSync18(join17(dir, name));
6542
6252
  if (hasLockFile("pnpm-lock.yaml")) {
6543
- execFileSync15("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
6253
+ execFileSync16("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
6544
6254
  } else if (hasLockFile("bun.lockb")) {
6545
- execFileSync15("bun", ["install"], { cwd: dir, stdio: "inherit" });
6255
+ execFileSync16("bun", ["install"], { cwd: dir, stdio: "inherit" });
6546
6256
  } else {
6547
- execFileSync15("npm", ["install"], { cwd: dir, stdio: "inherit" });
6257
+ execFileSync16("npm", ["install"], { cwd: dir, stdio: "inherit" });
6548
6258
  }
6549
6259
  }
6550
6260
  async function promptChoice(message, choices) {
6551
- const result = await consola40.prompt(message, { type: "select", options: choices });
6261
+ const result = await consola42.prompt(message, { type: "select", options: choices });
6552
6262
  if (typeof result === "symbol") {
6553
6263
  throw new CliExit(0);
6554
6264
  }
6555
6265
  return result;
6556
6266
  }
6557
6267
  async function promptText(message, defaultValue) {
6558
- const result = await consola40.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
6268
+ const result = await consola42.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
6559
6269
  if (typeof result === "symbol") {
6560
6270
  throw new CliExit(0);
6561
6271
  }
6562
6272
  return result || defaultValue || "";
6563
6273
  }
6564
- var initCommand = defineCommand49({
6274
+ var initCommand = defineCommand52({
6565
6275
  meta: {
6566
6276
  name: "init",
6567
6277
  description: "Scaffold a new OpenApe project"
@@ -6603,23 +6313,23 @@ var initCommand = defineCommand49({
6603
6313
  });
6604
6314
  async function initSP(targetDir) {
6605
6315
  const dir = targetDir || "my-app";
6606
- if (existsSync15(join14(dir, "package.json"))) {
6316
+ if (existsSync18(join17(dir, "package.json"))) {
6607
6317
  throw new CliError(`Directory "${dir}" already contains a project.`);
6608
6318
  }
6609
- consola40.start("Scaffolding SP starter...");
6319
+ consola42.start("Scaffolding SP starter...");
6610
6320
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
6611
- consola40.success("Scaffolded from openape-sp-starter");
6612
- consola40.start("Installing dependencies...");
6321
+ consola42.success("Scaffolded from openape-sp-starter");
6322
+ consola42.start("Installing dependencies...");
6613
6323
  installDeps(dir);
6614
- consola40.success("Dependencies installed");
6615
- const envExample = join14(dir, ".env.example");
6616
- const envFile = join14(dir, ".env");
6617
- if (existsSync15(envExample) && !existsSync15(envFile)) {
6324
+ consola42.success("Dependencies installed");
6325
+ const envExample = join17(dir, ".env.example");
6326
+ const envFile = join17(dir, ".env");
6327
+ if (existsSync18(envExample) && !existsSync18(envFile)) {
6618
6328
  copyFileSync(envExample, envFile);
6619
- consola40.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
6329
+ consola42.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
6620
6330
  }
6621
6331
  console.log("");
6622
- consola40.box([
6332
+ consola42.box([
6623
6333
  `cd ${dir}`,
6624
6334
  "npm run dev",
6625
6335
  "",
@@ -6628,7 +6338,7 @@ async function initSP(targetDir) {
6628
6338
  }
6629
6339
  async function initIdP(targetDir) {
6630
6340
  const dir = targetDir || "my-idp";
6631
- if (existsSync15(join14(dir, "package.json"))) {
6341
+ if (existsSync18(join17(dir, "package.json"))) {
6632
6342
  throw new CliError(`Directory "${dir}" already contains a project.`);
6633
6343
  }
6634
6344
  const domain = await promptText("Domain for the IdP", "localhost");
@@ -6638,15 +6348,15 @@ async function initIdP(targetDir) {
6638
6348
  "s3 (S3-compatible)"
6639
6349
  ]);
6640
6350
  const adminEmail = await promptText("Admin email");
6641
- consola40.start("Scaffolding IdP starter...");
6351
+ consola42.start("Scaffolding IdP starter...");
6642
6352
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
6643
- consola40.success("Scaffolded from openape-idp-starter");
6644
- consola40.start("Installing dependencies...");
6353
+ consola42.success("Scaffolded from openape-idp-starter");
6354
+ consola42.start("Installing dependencies...");
6645
6355
  installDeps(dir);
6646
- consola40.success("Dependencies installed");
6356
+ consola42.success("Dependencies installed");
6647
6357
  const sessionSecret = randomBytes(32).toString("hex");
6648
6358
  const managementToken = randomBytes(32).toString("hex");
6649
- consola40.success("Secrets generated");
6359
+ consola42.success("Secrets generated");
6650
6360
  const isLocalhost = domain === "localhost";
6651
6361
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
6652
6362
  const envContent = [
@@ -6660,11 +6370,11 @@ async function initIdP(targetDir) {
6660
6370
  `NUXT_OPENAPE_RP_ID=${domain}`,
6661
6371
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
6662
6372
  ].join("\n");
6663
- writeFileSync9(join14(dir, ".env"), `${envContent}
6373
+ writeFileSync10(join17(dir, ".env"), `${envContent}
6664
6374
  `, { mode: 384 });
6665
- consola40.success(".env created");
6375
+ consola42.success(".env created");
6666
6376
  console.log("");
6667
- consola40.box([
6377
+ consola42.box([
6668
6378
  `cd ${dir}`,
6669
6379
  "npm run dev",
6670
6380
  "",
@@ -6681,11 +6391,11 @@ async function initIdP(targetDir) {
6681
6391
 
6682
6392
  // src/commands/enroll.ts
6683
6393
  import { Buffer as Buffer5 } from "buffer";
6684
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6394
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
6685
6395
  import { execFile as execFile2 } from "child_process";
6686
6396
  import { sign as sign2 } from "crypto";
6687
- import { defineCommand as defineCommand50 } from "citty";
6688
- import consola41 from "consola";
6397
+ import { defineCommand as defineCommand53 } from "citty";
6398
+ import consola43 from "consola";
6689
6399
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
6690
6400
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
6691
6401
  var POLL_INTERVAL = 3e3;
@@ -6697,7 +6407,7 @@ function openBrowser2(url) {
6697
6407
  }
6698
6408
  async function pollForEnrollment(idp, agentEmail, keyPath) {
6699
6409
  const resolvedKey = resolveKeyPath(keyPath);
6700
- const keyContent = readFileSync12(resolvedKey, "utf-8");
6410
+ const keyContent = readFileSync14(resolvedKey, "utf-8");
6701
6411
  const privateKey = loadEd25519PrivateKey(keyContent);
6702
6412
  const challengeUrl = await getAgentChallengeEndpoint(idp);
6703
6413
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -6728,7 +6438,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
6728
6438
  }
6729
6439
  throw new Error("Enrollment timed out. Please check the browser and try again.");
6730
6440
  }
6731
- var enrollCommand = defineCommand50({
6441
+ var enrollCommand = defineCommand53({
6732
6442
  meta: {
6733
6443
  name: "enroll",
6734
6444
  description: "Enroll an agent with an Identity Provider"
@@ -6748,48 +6458,48 @@ var enrollCommand = defineCommand50({
6748
6458
  }
6749
6459
  },
6750
6460
  async run({ args }) {
6751
- const idp = args.idp || await consola41.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r3) => {
6752
- if (typeof r3 === "symbol") throw new CliExit(0);
6753
- return r3;
6461
+ const idp = args.idp || await consola43.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
6462
+ if (typeof r === "symbol") throw new CliExit(0);
6463
+ return r;
6754
6464
  }) || DEFAULT_IDP_URL2;
6755
- const agentName = args.name || await consola41.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r3) => {
6756
- if (typeof r3 === "symbol") throw new CliExit(0);
6757
- return r3;
6465
+ const agentName = args.name || await consola43.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
6466
+ if (typeof r === "symbol") throw new CliExit(0);
6467
+ return r;
6758
6468
  });
6759
6469
  if (!agentName) {
6760
6470
  throw new CliError("Agent name is required.");
6761
6471
  }
6762
- const keyPath = args.key || await consola41.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r3) => {
6763
- if (typeof r3 === "symbol") throw new CliExit(0);
6764
- return r3;
6472
+ const keyPath = args.key || await consola43.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
6473
+ if (typeof r === "symbol") throw new CliExit(0);
6474
+ return r;
6765
6475
  }) || DEFAULT_KEY_PATH;
6766
6476
  const resolvedKey = resolveKeyPath(keyPath);
6767
6477
  let publicKey;
6768
- if (existsSync16(resolvedKey)) {
6478
+ if (existsSync19(resolvedKey)) {
6769
6479
  publicKey = readPublicKey(resolvedKey);
6770
- consola41.success(`Using existing key ${keyPath}`);
6480
+ consola43.success(`Using existing key ${keyPath}`);
6771
6481
  } else {
6772
- consola41.start(`Generating Ed25519 key pair at ${keyPath}...`);
6482
+ consola43.start(`Generating Ed25519 key pair at ${keyPath}...`);
6773
6483
  publicKey = generateAndSaveKey(keyPath);
6774
- consola41.success(`Key pair generated at ${keyPath}`);
6484
+ consola43.success(`Key pair generated at ${keyPath}`);
6775
6485
  }
6776
6486
  const encodedKey = encodeURIComponent(publicKey);
6777
6487
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
6778
- consola41.info("Opening browser for enrollment...");
6779
- consola41.info(`\u2192 ${idp}/enroll`);
6488
+ consola43.info("Opening browser for enrollment...");
6489
+ consola43.info(`\u2192 ${idp}/enroll`);
6780
6490
  openBrowser2(enrollUrl);
6781
6491
  console.log("");
6782
- const agentEmail = await consola41.prompt(
6492
+ const agentEmail = await consola43.prompt(
6783
6493
  "Agent email (shown in browser after enrollment)",
6784
6494
  { type: "text", placeholder: `agent+${agentName}@...` }
6785
- ).then((r3) => {
6786
- if (typeof r3 === "symbol") throw new CliExit(0);
6787
- return r3;
6495
+ ).then((r) => {
6496
+ if (typeof r === "symbol") throw new CliExit(0);
6497
+ return r;
6788
6498
  });
6789
6499
  if (!agentEmail) {
6790
6500
  throw new CliError("Agent email is required to verify enrollment.");
6791
6501
  }
6792
- consola41.start("Verifying enrollment...");
6502
+ consola43.start("Verifying enrollment...");
6793
6503
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
6794
6504
  saveAuth({
6795
6505
  idp,
@@ -6801,18 +6511,18 @@ var enrollCommand = defineCommand50({
6801
6511
  config.defaults = { ...config.defaults, idp };
6802
6512
  config.agent = { key: keyPath, email: agentEmail };
6803
6513
  saveConfig(config);
6804
- consola41.success(`Agent enrolled as ${agentEmail}`);
6805
- consola41.success("Config saved to ~/.config/apes/");
6514
+ consola43.success(`Agent enrolled as ${agentEmail}`);
6515
+ consola43.success("Config saved to ~/.config/apes/");
6806
6516
  console.log("");
6807
- consola41.info("Verify with: apes whoami");
6517
+ consola43.info("Verify with: apes whoami");
6808
6518
  }
6809
6519
  });
6810
6520
 
6811
6521
  // src/commands/register-user.ts
6812
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
6813
- import { defineCommand as defineCommand51 } from "citty";
6814
- import consola42 from "consola";
6815
- var registerUserCommand = defineCommand51({
6522
+ import { existsSync as existsSync20, readFileSync as readFileSync15 } from "fs";
6523
+ import { defineCommand as defineCommand54 } from "citty";
6524
+ import consola44 from "consola";
6525
+ var registerUserCommand = defineCommand54({
6816
6526
  meta: {
6817
6527
  name: "register-user",
6818
6528
  description: "Register a sub-user with SSH key"
@@ -6848,8 +6558,8 @@ var registerUserCommand = defineCommand51({
6848
6558
  throw new CliError("No IdP URL configured. Run `apes login` first.");
6849
6559
  }
6850
6560
  let publicKey = args.key;
6851
- if (existsSync17(args.key)) {
6852
- publicKey = readFileSync13(args.key, "utf-8").trim();
6561
+ if (existsSync20(args.key)) {
6562
+ publicKey = readFileSync15(args.key, "utf-8").trim();
6853
6563
  }
6854
6564
  if (!publicKey.startsWith("ssh-ed25519 ")) {
6855
6565
  throw new CliError("Public key must be in ssh-ed25519 format.");
@@ -6867,18 +6577,18 @@ var registerUserCommand = defineCommand51({
6867
6577
  ...userType ? { type: userType } : {}
6868
6578
  }
6869
6579
  });
6870
- consola42.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
6580
+ consola44.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
6871
6581
  }
6872
6582
  });
6873
6583
 
6874
6584
  // src/commands/utils/index.ts
6875
- import { defineCommand as defineCommand53 } from "citty";
6585
+ import { defineCommand as defineCommand56 } from "citty";
6876
6586
 
6877
6587
  // src/commands/utils/dig.ts
6878
- import { defineCommand as defineCommand52 } from "citty";
6879
- import consola43 from "consola";
6588
+ import { defineCommand as defineCommand55 } from "citty";
6589
+ import consola45 from "consola";
6880
6590
  import { resolveDDISA as resolveDDISA2 } from "@openape/core";
6881
- var digCommand = defineCommand52({
6591
+ var digCommand = defineCommand55({
6882
6592
  meta: {
6883
6593
  name: "dig",
6884
6594
  description: "Resolve DDISA IdP for a domain or email (admin/diag tool)"
@@ -6951,12 +6661,12 @@ var digCommand = defineCommand52({
6951
6661
  console.log(` domain: ${domain}`);
6952
6662
  console.log("");
6953
6663
  if (!result.ddisa.found) {
6954
- consola43.warn(`No DDISA record at _ddisa.${domain}`);
6664
+ consola45.warn(`No DDISA record at _ddisa.${domain}`);
6955
6665
  if (result.hint) console.log(`
6956
6666
  ${result.hint}`);
6957
6667
  throw new CliError(`No DDISA record found for ${domain}`);
6958
6668
  }
6959
- consola43.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
6669
+ consola45.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
6960
6670
  console.log(` Version: ${result.ddisa.version || "ddisa1"}`);
6961
6671
  console.log(` IdP URL: ${result.ddisa.idp}`);
6962
6672
  if (result.ddisa.mode) console.log(` Mode: ${result.ddisa.mode}`);
@@ -6966,13 +6676,13 @@ ${result.hint}`);
6966
6676
  return;
6967
6677
  }
6968
6678
  if (result.idpDiscovery.ok) {
6969
- consola43.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
6679
+ consola45.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
6970
6680
  if (result.idpDiscovery.issuer) console.log(` Issuer: ${result.idpDiscovery.issuer}`);
6971
6681
  if (result.idpDiscovery.ddisaVersion) console.log(` DDISA: v${result.idpDiscovery.ddisaVersion}`);
6972
6682
  if (result.idpDiscovery.authMethods?.length) console.log(` Auth: ${result.idpDiscovery.authMethods.join(", ")}`);
6973
6683
  if (result.idpDiscovery.grantTypes?.length) console.log(` Grants: ${result.idpDiscovery.grantTypes.join(", ")}`);
6974
6684
  } else {
6975
- consola43.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
6685
+ consola45.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
6976
6686
  if (result.hint) console.log(`
6977
6687
  ${result.hint}`);
6978
6688
  throw new CliError(`IdP at ${result.ddisa.idp} not reachable`);
@@ -6981,7 +6691,7 @@ ${result.hint}`);
6981
6691
  });
6982
6692
 
6983
6693
  // src/commands/utils/index.ts
6984
- var utilsCommand = defineCommand53({
6694
+ var utilsCommand = defineCommand56({
6985
6695
  meta: {
6986
6696
  name: "utils",
6987
6697
  description: "Admin/diagnostic utilities (dig, \u2026)"
@@ -6992,12 +6702,12 @@ var utilsCommand = defineCommand53({
6992
6702
  });
6993
6703
 
6994
6704
  // src/commands/sessions/index.ts
6995
- import { defineCommand as defineCommand56 } from "citty";
6705
+ import { defineCommand as defineCommand59 } from "citty";
6996
6706
 
6997
6707
  // src/commands/sessions/list.ts
6998
- import { defineCommand as defineCommand54 } from "citty";
6999
- import consola44 from "consola";
7000
- var sessionsListCommand = defineCommand54({
6708
+ import { defineCommand as defineCommand57 } from "citty";
6709
+ import consola46 from "consola";
6710
+ var sessionsListCommand = defineCommand57({
7001
6711
  meta: {
7002
6712
  name: "list",
7003
6713
  description: "List your active refresh-token families (one per logged-in device)."
@@ -7015,7 +6725,7 @@ var sessionsListCommand = defineCommand54({
7015
6725
  return;
7016
6726
  }
7017
6727
  if (result.data.length === 0) {
7018
- consola44.info("No active sessions.");
6728
+ consola46.info("No active sessions.");
7019
6729
  return;
7020
6730
  }
7021
6731
  for (const f of result.data) {
@@ -7027,9 +6737,9 @@ var sessionsListCommand = defineCommand54({
7027
6737
  });
7028
6738
 
7029
6739
  // src/commands/sessions/remove.ts
7030
- import { defineCommand as defineCommand55 } from "citty";
7031
- import consola45 from "consola";
7032
- var sessionsRemoveCommand = defineCommand55({
6740
+ import { defineCommand as defineCommand58 } from "citty";
6741
+ import consola47 from "consola";
6742
+ var sessionsRemoveCommand = defineCommand58({
7033
6743
  meta: {
7034
6744
  name: "remove",
7035
6745
  description: "Revoke one of your active refresh-token families by id."
@@ -7045,12 +6755,12 @@ var sessionsRemoveCommand = defineCommand55({
7045
6755
  const id = String(args.familyId).trim();
7046
6756
  if (!id) throw new CliError("familyId required");
7047
6757
  await apiFetch(`/api/me/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
7048
- consola45.success(`Session ${id} revoked. The device using it will need to \`apes login\` again on its next refresh.`);
6758
+ consola47.success(`Session ${id} revoked. The device using it will need to \`apes login\` again on its next refresh.`);
7049
6759
  }
7050
6760
  });
7051
6761
 
7052
6762
  // src/commands/sessions/index.ts
7053
- var sessionsCommand = defineCommand56({
6763
+ var sessionsCommand = defineCommand59({
7054
6764
  meta: {
7055
6765
  name: "sessions",
7056
6766
  description: "Manage your active refresh-token sessions across devices"
@@ -7062,10 +6772,10 @@ var sessionsCommand = defineCommand56({
7062
6772
  });
7063
6773
 
7064
6774
  // src/commands/dns-check.ts
7065
- import { defineCommand as defineCommand57 } from "citty";
7066
- import consola46 from "consola";
6775
+ import { defineCommand as defineCommand60 } from "citty";
6776
+ import consola48 from "consola";
7067
6777
  import { resolveDDISA as resolveDDISA3 } from "@openape/core";
7068
- var dnsCheckCommand = defineCommand57({
6778
+ var dnsCheckCommand = defineCommand60({
7069
6779
  meta: {
7070
6780
  name: "dns-check",
7071
6781
  description: "Validate DDISA DNS TXT records for a domain"
@@ -7079,7 +6789,7 @@ var dnsCheckCommand = defineCommand57({
7079
6789
  },
7080
6790
  async run({ args }) {
7081
6791
  const domain = args.domain;
7082
- consola46.start(`Checking _ddisa.${domain}...`);
6792
+ consola48.start(`Checking _ddisa.${domain}...`);
7083
6793
  try {
7084
6794
  const result = await resolveDDISA3(domain);
7085
6795
  if (!result) {
@@ -7088,7 +6798,7 @@ var dnsCheckCommand = defineCommand57({
7088
6798
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
7089
6799
  throw new CliError(`No DDISA record found for ${domain}`);
7090
6800
  }
7091
- consola46.success(`_ddisa.${domain} \u2192 ${result.idp}`);
6801
+ consola48.success(`_ddisa.${domain} \u2192 ${result.idp}`);
7092
6802
  console.log("");
7093
6803
  console.log(` Version: ${result.version || "ddisa1"}`);
7094
6804
  console.log(` IdP URL: ${result.idp}`);
@@ -7097,14 +6807,14 @@ var dnsCheckCommand = defineCommand57({
7097
6807
  if (result.priority !== void 0)
7098
6808
  console.log(` Priority: ${result.priority}`);
7099
6809
  console.log("");
7100
- consola46.start(`Verifying IdP at ${result.idp}...`);
6810
+ consola48.start(`Verifying IdP at ${result.idp}...`);
7101
6811
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
7102
6812
  if (!discoResp.ok) {
7103
- consola46.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
6813
+ consola48.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
7104
6814
  return;
7105
6815
  }
7106
6816
  const disco = await discoResp.json();
7107
- consola46.success(`IdP is reachable`);
6817
+ consola48.success(`IdP is reachable`);
7108
6818
  console.log(` Issuer: ${disco.issuer}`);
7109
6819
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
7110
6820
  if (disco.ddisa_auth_methods_supported) {
@@ -7122,7 +6832,7 @@ var dnsCheckCommand = defineCommand57({
7122
6832
  // src/commands/health.ts
7123
6833
  import { exec } from "child_process";
7124
6834
  import { promisify } from "util";
7125
- import { defineCommand as defineCommand58 } from "citty";
6835
+ import { defineCommand as defineCommand61 } from "citty";
7126
6836
  var execAsync = promisify(exec);
7127
6837
  async function resolveApeShellPath() {
7128
6838
  try {
@@ -7158,7 +6868,7 @@ async function bestEffortGrantCount(idp) {
7158
6868
  }
7159
6869
  }
7160
6870
  async function runHealth(args) {
7161
- const version = true ? "1.24.1" : "0.0.0";
6871
+ const version = true ? "1.25.0" : "0.0.0";
7162
6872
  const auth = loadAuth();
7163
6873
  if (!auth) {
7164
6874
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -7221,7 +6931,7 @@ async function runHealth(args) {
7221
6931
  throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
7222
6932
  }
7223
6933
  }
7224
- var healthCommand = defineCommand58({
6934
+ var healthCommand = defineCommand61({
7225
6935
  meta: {
7226
6936
  name: "health",
7227
6937
  description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
@@ -7239,8 +6949,8 @@ var healthCommand = defineCommand58({
7239
6949
  });
7240
6950
 
7241
6951
  // src/commands/workflows.ts
7242
- import { defineCommand as defineCommand59 } from "citty";
7243
- import consola47 from "consola";
6952
+ import { defineCommand as defineCommand62 } from "citty";
6953
+ import consola49 from "consola";
7244
6954
 
7245
6955
  // src/guides/index.ts
7246
6956
  var guides = [
@@ -7290,7 +7000,7 @@ var guides = [
7290
7000
  ];
7291
7001
 
7292
7002
  // src/commands/workflows.ts
7293
- var workflowsCommand = defineCommand59({
7003
+ var workflowsCommand = defineCommand62({
7294
7004
  meta: {
7295
7005
  name: "workflows",
7296
7006
  description: "Discover workflow guides"
@@ -7311,7 +7021,7 @@ var workflowsCommand = defineCommand59({
7311
7021
  if (args.id) {
7312
7022
  const guide = guides.find((g) => g.id === String(args.id));
7313
7023
  if (!guide) {
7314
- consola47.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
7024
+ consola49.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
7315
7025
  throw new CliError(`Guide not found: ${args.id}`);
7316
7026
  }
7317
7027
  if (args.json) {
@@ -7351,26 +7061,26 @@ var workflowsCommand = defineCommand59({
7351
7061
  });
7352
7062
 
7353
7063
  // src/version-check.ts
7354
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
7355
- import { homedir as homedir13 } from "os";
7356
- import { join as join15 } from "path";
7357
- import consola48 from "consola";
7064
+ import { existsSync as existsSync21, mkdirSync as mkdirSync6, readFileSync as readFileSync16, writeFileSync as writeFileSync11 } from "fs";
7065
+ import { homedir as homedir15 } from "os";
7066
+ import { join as join18 } from "path";
7067
+ import consola50 from "consola";
7358
7068
  var PACKAGE_NAME = "@openape/apes";
7359
7069
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7360
- var CACHE_FILE = join15(homedir13(), ".config", "apes", ".version-check.json");
7070
+ var CACHE_FILE = join18(homedir15(), ".config", "apes", ".version-check.json");
7361
7071
  function readCache() {
7362
- if (!existsSync18(CACHE_FILE)) return null;
7072
+ if (!existsSync21(CACHE_FILE)) return null;
7363
7073
  try {
7364
- return JSON.parse(readFileSync14(CACHE_FILE, "utf-8"));
7074
+ return JSON.parse(readFileSync16(CACHE_FILE, "utf-8"));
7365
7075
  } catch {
7366
7076
  return null;
7367
7077
  }
7368
7078
  }
7369
7079
  function writeCache(entry) {
7370
7080
  try {
7371
- const dir = join15(homedir13(), ".config", "apes");
7372
- if (!existsSync18(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
7373
- writeFileSync10(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
7081
+ const dir = join18(homedir15(), ".config", "apes");
7082
+ if (!existsSync21(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
7083
+ writeFileSync11(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
7374
7084
  } catch {
7375
7085
  }
7376
7086
  }
@@ -7399,7 +7109,7 @@ async function fetchLatestVersion() {
7399
7109
  }
7400
7110
  function warnIfBehind(currentVersion, latest) {
7401
7111
  if (compareSemver(currentVersion, latest) < 0) {
7402
- consola48.warn(
7112
+ consola50.warn(
7403
7113
  `apes ${currentVersion} is behind latest @openape/apes@${latest}. Run \`npm i -g @openape/apes@latest\` to update. (Suppress with APES_NO_UPDATE_CHECK=1.)`
7404
7114
  );
7405
7115
  }
@@ -7431,10 +7141,10 @@ if (shellRewrite) {
7431
7141
  if (shellRewrite.action === "rewrite") {
7432
7142
  process.argv = shellRewrite.argv;
7433
7143
  } else if (shellRewrite.action === "version") {
7434
- console.log(`ape-shell ${"1.24.1"} (OpenApe DDISA shell wrapper)`);
7144
+ console.log(`ape-shell ${"1.25.0"} (OpenApe DDISA shell wrapper)`);
7435
7145
  process.exit(0);
7436
7146
  } else if (shellRewrite.action === "help") {
7437
- console.log(`ape-shell ${"1.24.1"} \u2014 OpenApe DDISA shell wrapper`);
7147
+ console.log(`ape-shell ${"1.25.0"} \u2014 OpenApe DDISA shell wrapper`);
7438
7148
  console.log("");
7439
7149
  console.log("Usage:");
7440
7150
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -7449,7 +7159,7 @@ if (shellRewrite) {
7449
7159
  console.log(" --help, -h Show this help message");
7450
7160
  process.exit(0);
7451
7161
  } else if (shellRewrite.action === "interactive") {
7452
- const { runInteractiveShell } = await import("./orchestrator-2QS5KXFL.js");
7162
+ const { runInteractiveShell } = await import("./orchestrator-CIDV7OGM.js");
7453
7163
  await runInteractiveShell();
7454
7164
  process.exit(0);
7455
7165
  } else {
@@ -7458,7 +7168,7 @@ if (shellRewrite) {
7458
7168
  }
7459
7169
  }
7460
7170
  var debug = process.argv.includes("--debug");
7461
- var grantsCommand = defineCommand60({
7171
+ var grantsCommand = defineCommand63({
7462
7172
  meta: {
7463
7173
  name: "grants",
7464
7174
  description: "Grant management"
@@ -7479,7 +7189,7 @@ var grantsCommand = defineCommand60({
7479
7189
  "delegation-revoke": delegationRevokeCommand
7480
7190
  }
7481
7191
  });
7482
- var configCommand = defineCommand60({
7192
+ var configCommand = defineCommand63({
7483
7193
  meta: {
7484
7194
  name: "config",
7485
7195
  description: "Configuration management"
@@ -7489,10 +7199,10 @@ var configCommand = defineCommand60({
7489
7199
  set: configSetCommand
7490
7200
  }
7491
7201
  });
7492
- var main = defineCommand60({
7202
+ var main = defineCommand63({
7493
7203
  meta: {
7494
7204
  name: "apes",
7495
- version: "1.24.1",
7205
+ version: "1.25.0",
7496
7206
  description: "Unified CLI for OpenApe"
7497
7207
  },
7498
7208
  subCommands: {
@@ -7507,6 +7217,7 @@ var main = defineCommand60({
7507
7217
  whoami: whoamiCommand,
7508
7218
  health: healthCommand,
7509
7219
  grants: grantsCommand,
7220
+ agent: agentCommand,
7510
7221
  agents: agentsCommand,
7511
7222
  nest: nestCommand,
7512
7223
  yolo: yoloCommand,
@@ -7540,29 +7251,29 @@ var NO_REFRESH_COMMANDS = /* @__PURE__ */ new Set([
7540
7251
  async function maybeRefreshAuth() {
7541
7252
  const sub = process.argv[2];
7542
7253
  if (!sub || NO_REFRESH_COMMANDS.has(sub)) return;
7543
- const { loadAuth: loadAuth2 } = await import("./config-DA2L3XOV.js");
7254
+ const { loadAuth: loadAuth2 } = await import("./config-MOB5DJ6H.js");
7544
7255
  if (!loadAuth2()) return;
7545
7256
  try {
7546
- const { ensureFreshToken } = await import("./http-JWS5LV2U.js");
7257
+ const { ensureFreshToken } = await import("./http-6OKWT52Z.js");
7547
7258
  await ensureFreshToken();
7548
7259
  } catch {
7549
7260
  }
7550
7261
  }
7551
7262
  await maybeRefreshAuth();
7552
- await maybeWarnStaleVersion("1.24.1").catch(() => {
7263
+ await maybeWarnStaleVersion("1.25.0").catch(() => {
7553
7264
  });
7554
7265
  runMain(main).catch((err) => {
7555
7266
  if (err instanceof CliExit) {
7556
7267
  process.exit(err.exitCode);
7557
7268
  }
7558
7269
  if (err instanceof CliError) {
7559
- consola49.error(err.message);
7270
+ consola51.error(err.message);
7560
7271
  process.exit(err.exitCode);
7561
7272
  }
7562
7273
  if (debug) {
7563
- consola49.error(err);
7274
+ consola51.error(err);
7564
7275
  } else {
7565
- consola49.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
7276
+ consola51.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
7566
7277
  }
7567
7278
  process.exit(1);
7568
7279
  });