@openape/apes 1.24.0 → 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 +1122 -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-K77SHBP2.js → server-V2V5YHK3.js} +3 -4
  15. package/dist/{server-K77SHBP2.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;
2330
- }
2331
- if ("params" in context.options) {
2332
- delete context.options.params;
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)`);
2333
1891
  }
2334
- }
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
- }
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;
2357
1896
  }
2358
1897
  }
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
- }
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
1930
  }
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,12 +2112,34 @@ 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
 
2655
- if dscl . -read "/Users/$NAME" >/dev/null 2>&1; then
2656
- echo "User $NAME already exists; refusing to overwrite." >&2
2657
- exit 1
2119
+ # Tombstone-reuse: \`apes agents destroy\` leaves the dscl user
2120
+ # record behind (opendirectoryd refuses \`dscl . -delete\` from
2121
+ # escapes' setuid-root context without admin auth, so we accept the
2122
+ # stranded record as a harmless tombstone \u2014 see
2123
+ # runPhaseGTeardownInProcess). When the operator re-spawns with the
2124
+ # same name, we recycle the tombstone instead of refusing: if the
2125
+ # previously-recorded home dir is gone from disk (matching the
2126
+ # Phase-G teardown's \`rm -rf\`), we reuse the existing UID +
2127
+ # overwrite the home. If the home is still on disk it's a real,
2128
+ # live agent \u2014 refuse.
2129
+ TOMBSTONE_REUSE=0
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}')
2132
+ if [ -n "$EXISTING_HOME" ] && [ -d "$EXISTING_HOME" ]; then
2133
+ echo "User $MACOS_USER already exists with a live home at $EXISTING_HOME; refusing to overwrite." >&2
2134
+ exit 1
2135
+ fi
2136
+ NEXT_UID=$(dscl . -read "/Users/$MACOS_USER" UniqueID 2>/dev/null | awk '/UniqueID:/ {print $2}')
2137
+ if [ -z "$NEXT_UID" ]; then
2138
+ echo "User $MACOS_USER exists but has no UniqueID; record is malformed, refusing to recycle." >&2
2139
+ exit 1
2140
+ fi
2141
+ echo "Recycling tombstone dscl record for $MACOS_USER (uid=$NEXT_UID, prior home $EXISTING_HOME \u2014 gone)"
2142
+ TOMBSTONE_REUSE=1
2658
2143
  fi
2659
2144
 
2660
2145
  # Phase G: agent home dirs live under /var/openape/homes/, not
@@ -2663,49 +2148,66 @@ fi
2663
2148
  mkdir -p /var/openape/homes
2664
2149
  chmod 755 /var/openape/homes
2665
2150
 
2666
- # Pick the next free UID in the [200, 500) hidden service-account range.
2667
- # Starts the running max at 199 so an empty range yields 200 after the
2668
- # floor check; otherwise NEXT_UID = max(existing in-range UIDs) + 1.
2669
- NEXT_UID=199
2670
- for uid in $(dscl . -list /Users UniqueID | awk '$2 >= 200 && $2 < 500 {print $2}'); do
2671
- if [ "$uid" -ge "$NEXT_UID" ]; then
2672
- NEXT_UID=$((uid + 1))
2151
+ if [ "$TOMBSTONE_REUSE" = "0" ]; then
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
2166
+ fi
2167
+ candidate=$((candidate + 1))
2168
+ done
2169
+ if [ -z "$NEXT_UID" ]; then
2170
+ echo "No free UID in [200, 500) \u2014 refusing to clobber a real user." >&2
2171
+ exit 1
2673
2172
  fi
2674
- done
2675
- if [ "$NEXT_UID" -lt 200 ]; then
2676
- NEXT_UID=200
2677
- fi
2678
- if [ "$NEXT_UID" -ge 500 ]; then
2679
- echo "No free UID in [200, 500) \u2014 refusing to clobber a real user." >&2
2680
- exit 1
2173
+
2174
+ dscl . -create "/Users/$MACOS_USER"
2681
2175
  fi
2682
2176
 
2683
- dscl . -create "/Users/$NAME"
2684
- dscl . -create "/Users/$NAME" UserShell "$SHELL_PATH"
2685
- dscl . -create "/Users/$NAME" RealName "OpenApe Agent $NAME"
2686
- dscl . -create "/Users/$NAME" UniqueID "$NEXT_UID"
2687
- dscl . -create "/Users/$NAME" PrimaryGroupID 20
2688
- dscl . -create "/Users/$NAME" NFSHomeDirectory "$HOME_DIR"
2689
- dscl . -create "/Users/$NAME" IsHidden 1
2177
+ # Idempotent attribute writes \u2014 \`dscl . -create\` on an existing
2178
+ # property overwrites in place, so the tombstone-reuse path lands
2179
+ # the same end-state as a fresh create.
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
2690
2186
 
2691
2187
  mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
2692
2188
 
2693
2189
  cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
2694
2190
  cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
2695
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)}
2696
2195
  ${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}${buildTroopBlock(input.troop)}
2697
- chown -R "$NAME:staff" "$HOME_DIR"
2196
+ chown -R "$MACOS_USER:staff" "$HOME_DIR"
2698
2197
  chmod 700 "$HOME_DIR/.ssh"
2699
2198
  chmod 700 "$HOME_DIR/.config"
2199
+ chmod 700 "$HOME_DIR/.config/openape"
2700
2200
  chmod 600 "$HOME_DIR/.ssh/id_ed25519"
2701
2201
  chmod 644 "$HOME_DIR/.ssh/id_ed25519.pub"
2702
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"
2703
2205
  if [ -f "$HOME_DIR/.config/openape/claude-token.env" ]; then
2704
2206
  chmod 700 "$HOME_DIR/.config/openape"
2705
2207
  chmod 600 "$HOME_DIR/.config/openape/claude-token.env"
2706
2208
  fi
2707
2209
 
2708
- echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
2210
+ echo "OK $NAME (macOS user $MACOS_USER) uid=$NEXT_UID home=$HOME_DIR"
2709
2211
  ${buildBridgeBootstrapBlock(input.bridge, name)}${buildTroopBootstrapBlock(input.troop, name)}`;
2710
2212
  }
2711
2213
  function buildBridgeBlock(bridge) {
@@ -2729,9 +2231,10 @@ function buildTroopBootstrapBlock(_troop, _name) {
2729
2231
  }
2730
2232
  function runPhaseGTeardownInProcess(input) {
2731
2233
  const { name, homeDir } = input;
2234
+ const macOSUser = input.macOSUsername ?? name;
2732
2235
  let uid = null;
2733
2236
  try {
2734
- 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" });
2735
2238
  const m = out.match(/UniqueID:\s*(\d+)/);
2736
2239
  if (m) uid = m[1];
2737
2240
  } catch {
@@ -2757,7 +2260,7 @@ function runPhaseGTeardownInProcess(input) {
2757
2260
  } catch {
2758
2261
  }
2759
2262
  }
2760
- 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)`);
2761
2264
  }
2762
2265
  function buildDestroyTeardownScript(input) {
2763
2266
  const { name, homeDir, adminUser } = input;
@@ -2886,6 +2389,38 @@ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
2886
2389
  function isDarwin() {
2887
2390
  return process.platform === "darwin";
2888
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
+ }
2889
2424
  function readMacOSUser(name) {
2890
2425
  let output;
2891
2426
  try {
@@ -2938,7 +2473,7 @@ function isShellRegistered(shellPath) {
2938
2473
  }
2939
2474
 
2940
2475
  // src/commands/agents/allow.ts
2941
- var allowAgentCommand = defineCommand20({
2476
+ var allowAgentCommand = defineCommand22({
2942
2477
  meta: {
2943
2478
  name: "allow",
2944
2479
  description: "Add a peer to the agent's contact-allowlist so the bridge auto-accepts that peer's contact request."
@@ -2967,8 +2502,8 @@ var allowAgentCommand = defineCommand20({
2967
2502
  if (!isDarwin()) {
2968
2503
  throw new CliError("`apes agents allow` is currently macOS-only.");
2969
2504
  }
2970
- if (!readMacOSUser(agent)) {
2971
- 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?`);
2972
2507
  }
2973
2508
  const apes = whichBinary("apes");
2974
2509
  if (!apes) throw new CliError("`apes` not found on PATH.");
@@ -2998,22 +2533,99 @@ print("ok")
2998
2533
  PY
2999
2534
  chmod 600 "$F"
3000
2535
  `;
3001
- consola18.start(`Adding ${email} to ${agent}'s allowlist\u2026`);
2536
+ consola19.start(`Adding ${email} to ${agent}'s allowlist\u2026`);
3002
2537
  execFileSync4(apes, ["run", "--as", agent, "--wait", "--", "bash", "-c", script], { stdio: "inherit" });
3003
- 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).`);
3004
2539
  }
3005
2540
  });
3006
2541
  function shQuote2(s) {
3007
2542
  return `'${s.replace(/'/g, `'\\''`)}'`;
3008
2543
  }
3009
2544
 
3010
- // src/commands/agents/destroy.ts
2545
+ // src/commands/agents/cleanup-orphans.ts
3011
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";
3012
2624
  import { mkdtempSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
3013
2625
  import { tmpdir, userInfo } from "os";
3014
2626
  import { join as join3 } from "path";
3015
- import { defineCommand as defineCommand21 } from "citty";
3016
- import consola19 from "consola";
2627
+ import { defineCommand as defineCommand24 } from "citty";
2628
+ import consola21 from "consola";
3017
2629
 
3018
2630
  // src/lib/nest-registry.ts
3019
2631
  import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
@@ -3119,7 +2731,7 @@ function readPasswordSilent(prompt) {
3119
2731
  }
3120
2732
 
3121
2733
  // src/commands/agents/destroy.ts
3122
- var destroyAgentCommand = defineCommand21({
2734
+ var destroyAgentCommand = defineCommand24({
3123
2735
  meta: {
3124
2736
  name: "destroy",
3125
2737
  description: "Tear down an agent: remove macOS user, hard-delete IdP agent, drop all SSH keys"
@@ -3158,9 +2770,11 @@ var destroyAgentCommand = defineCommand21({
3158
2770
  if (process.geteuid?.() !== 0) {
3159
2771
  throw new CliError("--root-stage was passed but this process is not running as root. Refusing to continue.");
3160
2772
  }
3161
- const homeDir = readMacOSUser(name)?.homeDir ?? `/var/openape/homes/${name}`;
3162
- consola19.start(`Running teardown for ${name} (Phase-G, root-stage)\u2026`);
3163
- 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 });
3164
2778
  return;
3165
2779
  }
3166
2780
  const auth = loadAuth();
@@ -3174,29 +2788,29 @@ var destroyAgentCommand = defineCommand21({
3174
2788
  const owned = await apiFetch("/api/my-agents", { idp });
3175
2789
  const idpAgent = owned.find((u) => u.name === name);
3176
2790
  const idpExists = idpAgent !== void 0;
3177
- const osUser = isDarwin() ? readMacOSUser(name) : null;
2791
+ const osUser = isDarwin() ? lookupMacOSUserForAgent(name) : null;
3178
2792
  const osUserExists = !args["keep-os-user"] && osUser !== null;
3179
2793
  if (!idpExists && !osUserExists) {
3180
- 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}".`);
3181
2795
  return;
3182
2796
  }
3183
2797
  if (!args.force) {
3184
2798
  const consequences = [];
3185
2799
  if (osUserExists) {
3186
2800
  const home = osUser?.homeDir ?? `/Users/${name}`;
3187
- 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}`);
3188
2802
  }
3189
2803
  if (idpExists) {
3190
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`);
3191
2805
  }
3192
- consola19.warn(`About to destroy "${name}":
2806
+ consola21.warn(`About to destroy "${name}":
3193
2807
  ${consequences.join("\n")}`);
3194
2808
  if (!process.stdin.isTTY) {
3195
2809
  throw new CliError(
3196
2810
  "No TTY available for the interactive confirmation. Re-run with --force to skip the prompt (this is the same flag CI uses)."
3197
2811
  );
3198
2812
  }
3199
- const confirmed = await consola19.prompt("Proceed?", { type: "confirm", initial: false });
2813
+ const confirmed = await consola21.prompt("Proceed?", { type: "confirm", initial: false });
3200
2814
  if (typeof confirmed === "symbol" || !confirmed) {
3201
2815
  throw new CliExit(0);
3202
2816
  }
@@ -3205,24 +2819,26 @@ ${consequences.join("\n")}`);
3205
2819
  const id = encodeURIComponent(idpAgent.email);
3206
2820
  if (args.soft) {
3207
2821
  await apiFetch(`/api/my-agents/${id}`, { method: "PATCH", body: { isActive: false }, idp });
3208
- consola19.success(`Deactivated IdP agent ${idpAgent.email}`);
2822
+ consola21.success(`Deactivated IdP agent ${idpAgent.email}`);
3209
2823
  } else {
3210
2824
  await apiFetch(`/api/my-agents/${id}`, { method: "DELETE", idp });
3211
- consola19.success(`Deleted IdP agent ${idpAgent.email}`);
2825
+ consola21.success(`Deleted IdP agent ${idpAgent.email}`);
3212
2826
  }
3213
2827
  } else {
3214
- consola19.info("No IdP agent to remove (skipped).");
2828
+ consola21.info("No IdP agent to remove (skipped).");
3215
2829
  }
3216
2830
  if (osUserExists) {
3217
- 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;
3218
2834
  const isPhaseG = homeDir.startsWith("/var/openape/homes/");
3219
2835
  if (isPhaseG) {
3220
2836
  if (process.geteuid?.() === 0) {
3221
- consola19.start("Running teardown (Phase G \u2014 already root, no grant needed)\u2026");
3222
- runPhaseGTeardownInProcess({ name, homeDir });
2837
+ consola21.start("Running teardown (Phase G \u2014 already root, no grant needed)\u2026");
2838
+ runPhaseGTeardownInProcess({ name, homeDir, macOSUsername });
3223
2839
  } else {
3224
- consola19.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
3225
- execFileSync5("apes", [
2840
+ consola21.start("Running teardown (Phase G \u2014 no admin password needed)\u2026");
2841
+ execFileSync6("apes", [
3226
2842
  "run",
3227
2843
  "--as",
3228
2844
  "root",
@@ -3236,7 +2852,7 @@ ${consequences.join("\n")}`);
3236
2852
  "--root-stage"
3237
2853
  ], { stdio: "inherit" });
3238
2854
  }
3239
- 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.`);
3240
2856
  } else {
3241
2857
  const sudo = whichBinary("sudo");
3242
2858
  if (!sudo) {
@@ -3249,7 +2865,7 @@ ${consequences.join("\n")}`);
3249
2865
  } catch (err) {
3250
2866
  const headless = !process.stdin.isTTY && !process.env.APES_ADMIN_PASSWORD;
3251
2867
  if (headless) {
3252
- 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.`);
3253
2869
  adminPassword = "";
3254
2870
  } else {
3255
2871
  throw err;
@@ -3261,8 +2877,8 @@ ${consequences.join("\n")}`);
3261
2877
  try {
3262
2878
  const script = buildDestroyTeardownScript({ name, homeDir, adminUser });
3263
2879
  writeFileSync2(scriptPath, script, { mode: 448 });
3264
- consola19.start("Running teardown via sudo\u2026");
3265
- execFileSync5(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
2880
+ consola21.start("Running teardown via sudo\u2026");
2881
+ execFileSync6(sudo, ["-S", "--prompt=", "--", "bash", scriptPath], {
3266
2882
  input: `${adminPassword}
3267
2883
  ${adminPassword}
3268
2884
  `,
@@ -3274,14 +2890,14 @@ ${adminPassword}
3274
2890
  }
3275
2891
  }
3276
2892
  } else if (!args["keep-os-user"] && isDarwin()) {
3277
- consola19.info("No macOS user to remove (skipped).");
2893
+ consola21.info("No macOS user to remove (skipped).");
3278
2894
  }
3279
2895
  try {
3280
2896
  removeNestAgent(name);
3281
2897
  } catch (err) {
3282
- 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)}`);
3283
2899
  }
3284
- consola19.success(`Destroyed ${name}.`);
2900
+ consola21.success(`Destroyed ${name}.`);
3285
2901
  }
3286
2902
  });
3287
2903
  async function collectAdminPassword(opts) {
@@ -3295,9 +2911,9 @@ async function collectAdminPassword(opts) {
3295
2911
  }
3296
2912
 
3297
2913
  // src/commands/agents/list.ts
3298
- import { defineCommand as defineCommand22 } from "citty";
3299
- import consola20 from "consola";
3300
- var listAgentsCommand = defineCommand22({
2914
+ import { defineCommand as defineCommand25 } from "citty";
2915
+ import consola22 from "consola";
2916
+ var listAgentsCommand = defineCommand25({
3301
2917
  meta: {
3302
2918
  name: "list",
3303
2919
  description: "List agents owned by the current user, with local OS-user status"
@@ -3324,46 +2940,52 @@ var listAgentsCommand = defineCommand22({
3324
2940
  const all = await apiFetch("/api/my-agents", { idp });
3325
2941
  const filtered = args["include-inactive"] ? all : all.filter((u) => u.isActive !== false);
3326
2942
  const osUsers = isDarwin() ? listMacOSUserNames() : /* @__PURE__ */ new Set();
3327
- const homeOf = (name) => {
3328
- if (!osUsers.has(name)) return null;
3329
- const u = readMacOSUser(name);
3330
- 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 };
3331
2950
  };
3332
- const rows = filtered.map((u) => ({
3333
- name: u.name,
3334
- email: u.email,
3335
- isActive: u.isActive !== false,
3336
- osUser: osUsers.has(u.name),
3337
- home: homeOf(u.name)
3338
- }));
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
+ });
3339
2961
  if (args.json) {
3340
2962
  process.stdout.write(`${JSON.stringify(rows, null, 2)}
3341
2963
  `);
3342
2964
  return;
3343
2965
  }
3344
2966
  if (rows.length === 0) {
3345
- 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.");
3346
2968
  return;
3347
2969
  }
3348
- const nameW = Math.max(4, ...rows.map((r3) => r3.name.length));
3349
- 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));
3350
2972
  const header = `${"NAME".padEnd(nameW)} ${"EMAIL".padEnd(emailW)} ACTIVE OS-USER HOME`;
3351
2973
  console.log(header);
3352
2974
  console.log("-".repeat(header.length));
3353
- for (const r3 of rows) {
3354
- const active = r3.isActive ? "\u2713" : "\u2717";
3355
- const os = r3.osUser ? "\u2713" : "\u2717";
3356
- const homeCol = r3.home ?? (isDarwin() ? "(missing)" : "(non-darwin)");
3357
- 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}`);
3358
2980
  }
3359
2981
  }
3360
2982
  });
3361
2983
 
3362
2984
  // src/commands/agents/register.ts
3363
2985
  import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
3364
- import { defineCommand as defineCommand23 } from "citty";
3365
- import consola21 from "consola";
3366
- var registerAgentCommand = defineCommand23({
2986
+ import { defineCommand as defineCommand26 } from "citty";
2987
+ import consola23 from "consola";
2988
+ var registerAgentCommand = defineCommand26({
3367
2989
  meta: {
3368
2990
  name: "register",
3369
2991
  description: "Register an agent at the IdP using a supplied public key"
@@ -3433,7 +3055,7 @@ var registerAgentCommand = defineCommand23({
3433
3055
  `);
3434
3056
  return;
3435
3057
  }
3436
- consola21.success("Agent registered.");
3058
+ consola23.success("Agent registered.");
3437
3059
  console.log(` Name: ${result.name}`);
3438
3060
  console.log(` Email: ${result.email}`);
3439
3061
  console.log(` IdP: ${idp}`);
@@ -3445,78 +3067,93 @@ var registerAgentCommand = defineCommand23({
3445
3067
  }
3446
3068
  });
3447
3069
 
3448
- // src/commands/agents/run.ts
3449
- 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";
3450
3079
  import { homedir as homedir5 } from "os";
3451
3080
  import { join as join4 } from "path";
3452
- import { defineCommand as defineCommand24 } from "citty";
3453
- import consola22 from "consola";
3454
-
3455
- // src/lib/troop-client.ts
3456
- var DEFAULT_TROOP_URL = "https://troop.openape.ai";
3457
- var TroopClient = class {
3458
- constructor(troopUrl, agentJwt) {
3459
- this.troopUrl = troopUrl;
3460
- this.agentJwt = agentJwt;
3461
- }
3462
- async request(path2, init) {
3463
- const res = await fetch(`${this.troopUrl}${path2}`, {
3464
- ...init,
3465
- headers: {
3466
- ...init?.headers ?? {},
3467
- "Authorization": `Bearer ${this.agentJwt}`,
3468
- "Content-Type": "application/json"
3469
- }
3470
- });
3471
- if (!res.ok) {
3472
- const text = await res.text().catch(() => "");
3473
- 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}`);
3474
3114
  }
3475
- if (res.status === 204) return void 0;
3476
- return await res.json();
3477
- }
3478
- sync(input) {
3479
- return this.request("/api/agents/me/sync", {
3480
- method: "POST",
3481
- body: JSON.stringify({
3482
- hostname: input.hostname,
3483
- host_id: input.hostId,
3484
- owner_email: input.ownerEmail,
3485
- ...input.pubkeySsh ? { pubkey_ssh: input.pubkeySsh } : {}
3486
- })
3487
- });
3488
- }
3489
- listTasks() {
3490
- return this.request("/api/agents/me/tasks");
3491
- }
3492
- startRun(taskId) {
3493
- return this.request("/api/agents/me/runs", {
3494
- method: "POST",
3495
- body: JSON.stringify({ task_id: taskId })
3496
- });
3497
3115
  }
3498
- finaliseRun(id, payload) {
3499
- return this.request(`/api/agents/me/runs/${id}`, {
3500
- method: "PATCH",
3501
- body: JSON.stringify(payload)
3502
- });
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
+ }
3503
3122
  }
3504
- };
3505
- function resolveTroopUrl(override) {
3506
- if (override) return override.replace(/\/$/, "");
3507
- const fromEnv = process.env.OPENAPE_TROOP_URL;
3508
- if (fromEnv) return fromEnv.replace(/\/$/, "");
3509
- 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
+ };
3510
3147
  }
3511
3148
 
3512
3149
  // src/commands/agents/run.ts
3513
- var AUTH_PATH = join4(homedir5(), ".config", "apes", "auth.json");
3514
- 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");
3515
3152
  function readAuth() {
3516
- if (!existsSync6(AUTH_PATH)) {
3153
+ if (!existsSync7(AUTH_PATH)) {
3517
3154
  throw new CliError(`No agent auth found at ${AUTH_PATH}. Run \`apes agents spawn <name>\` first.`);
3518
3155
  }
3519
- const parsed = JSON.parse(readFileSync5(AUTH_PATH, "utf8"));
3156
+ const parsed = JSON.parse(readFileSync6(AUTH_PATH, "utf8"));
3520
3157
  if (!parsed.access_token) throw new CliError("auth.json missing access_token");
3521
3158
  return parsed;
3522
3159
  }
@@ -3529,9 +3166,9 @@ async function postRunResultToChat(opts) {
3529
3166
  if (!contactsRes.ok) return;
3530
3167
  const contacts = await contactsRes.json();
3531
3168
  const ownerLower = opts.ownerEmail.toLowerCase();
3532
- 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);
3533
3170
  if (!ownerRow?.roomId) {
3534
- 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)");
3535
3172
  return;
3536
3173
  }
3537
3174
  const prefix = opts.status === "ok" ? "\u2705" : "\u274C";
@@ -3545,33 +3182,33 @@ ${msg}`.slice(0, 9e3);
3545
3182
  body: JSON.stringify({ body })
3546
3183
  });
3547
3184
  if (!postRes.ok) {
3548
- consola22.warn(`chat DM post failed: ${postRes.status}`);
3185
+ consola24.warn(`chat DM post failed: ${postRes.status}`);
3549
3186
  }
3550
3187
  } catch (err) {
3551
- consola22.warn(`chat DM error: ${err.message}`);
3188
+ consola24.warn(`chat DM error: ${err.message}`);
3552
3189
  }
3553
3190
  }
3554
3191
  function readTaskSpec(taskId) {
3555
- const path2 = join4(TASK_CACHE_DIR, `${taskId}.json`);
3556
- if (!existsSync6(path2)) {
3192
+ const path2 = join5(TASK_CACHE_DIR, `${taskId}.json`);
3193
+ if (!existsSync7(path2)) {
3557
3194
  throw new CliError(`No cached task spec at ${path2}. Run \`apes agents sync\` first to pull the task list from troop.`);
3558
3195
  }
3559
- return JSON.parse(readFileSync5(path2, "utf8"));
3196
+ return JSON.parse(readFileSync6(path2, "utf8"));
3560
3197
  }
3561
- var AGENT_CONFIG_PATH = join4(homedir5(), ".openape", "agent", "agent.json");
3198
+ var AGENT_CONFIG_PATH = join5(homedir6(), ".openape", "agent", "agent.json");
3562
3199
  function readAgentConfig() {
3563
- if (!existsSync6(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
3200
+ if (!existsSync7(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
3564
3201
  try {
3565
- return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf8"));
3202
+ return JSON.parse(readFileSync6(AGENT_CONFIG_PATH, "utf8"));
3566
3203
  } catch {
3567
3204
  return { systemPrompt: "" };
3568
3205
  }
3569
3206
  }
3570
3207
  function readLitellmConfig(model) {
3571
- const envPath = join4(homedir5(), "litellm", ".env");
3208
+ const envPath = join5(homedir6(), "litellm", ".env");
3572
3209
  const env = {};
3573
- if (existsSync6(envPath)) {
3574
- 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/)) {
3575
3212
  const m = line.match(/^([A-Z_]+)=(.*)$/);
3576
3213
  if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3577
3214
  }
@@ -3586,7 +3223,7 @@ function readLitellmConfig(model) {
3586
3223
  }
3587
3224
  return { apiBase, apiKey, model: model || "claude-haiku-4-5" };
3588
3225
  }
3589
- var runAgentCommand = defineCommand24({
3226
+ var runAgentCommand = defineCommand27({
3590
3227
  meta: {
3591
3228
  name: "run",
3592
3229
  description: "Execute one task (typically launchd-invoked). Reports the run record to troop."
@@ -3609,6 +3246,7 @@ var runAgentCommand = defineCommand24({
3609
3246
  async run({ args }) {
3610
3247
  const taskId = args["task-id"];
3611
3248
  const auth = readAuth();
3249
+ materializeSecrets({ log: (l) => consola24.info(l) });
3612
3250
  const spec = readTaskSpec(taskId);
3613
3251
  const agentCfg = readAgentConfig();
3614
3252
  const config = readLitellmConfig(args.model);
@@ -3620,7 +3258,7 @@ var runAgentCommand = defineCommand24({
3620
3258
  }
3621
3259
  const troop = new TroopClient(resolveTroopUrl(args["troop-url"]), auth.access_token);
3622
3260
  const { id: runId } = await troop.startRun(taskId);
3623
- consola22.info(`Run ${runId} started for task ${taskId}`);
3261
+ consola24.info(`Run ${runId} started for task ${taskId}`);
3624
3262
  try {
3625
3263
  const result = await runLoop({
3626
3264
  config,
@@ -3639,7 +3277,7 @@ var runAgentCommand = defineCommand24({
3639
3277
  step_count: result.stepCount,
3640
3278
  trace: result.trace
3641
3279
  });
3642
- consola22.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3280
+ consola24.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3643
3281
  if (auth.owner_email) {
3644
3282
  await postRunResultToChat({
3645
3283
  authToken: auth.access_token,
@@ -3676,17 +3314,17 @@ var runAgentCommand = defineCommand24({
3676
3314
  });
3677
3315
 
3678
3316
  // src/commands/agents/serve.ts
3679
- import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
3680
- import { homedir as homedir6 } from "os";
3681
- 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";
3682
3320
  import { createInterface } from "readline";
3683
- import { defineCommand as defineCommand25 } from "citty";
3684
- 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");
3685
3323
  function readLitellmConfig2(model) {
3686
- const envPath = join5(homedir6(), "litellm", ".env");
3324
+ const envPath = join6(homedir7(), "litellm", ".env");
3687
3325
  const env = {};
3688
- if (existsSync7(envPath)) {
3689
- 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/)) {
3690
3328
  const m = line.match(/^([A-Z_]+)=(.*)$/);
3691
3329
  if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3692
3330
  }
@@ -3703,7 +3341,7 @@ function emit(event) {
3703
3341
  process.stdout.write(`${JSON.stringify(event)}
3704
3342
  `);
3705
3343
  }
3706
- var serveAgentCommand = defineCommand25({
3344
+ var serveAgentCommand = defineCommand28({
3707
3345
  meta: {
3708
3346
  name: "serve",
3709
3347
  description: "Long-running stdio RPC server for chat-bridge subprocess use."
@@ -3718,15 +3356,20 @@ var serveAgentCommand = defineCommand25({
3718
3356
  if (!args.rpc) {
3719
3357
  throw new CliError("apes agents serve currently only supports --rpc mode");
3720
3358
  }
3721
- if (existsSync7(AUTH_PATH2)) {
3359
+ if (existsSync8(AUTH_PATH2)) {
3722
3360
  try {
3723
- JSON.parse(readFileSync6(AUTH_PATH2, "utf8"));
3361
+ JSON.parse(readFileSync7(AUTH_PATH2, "utf8"));
3724
3362
  } catch {
3725
3363
  }
3726
3364
  }
3365
+ const stopSecrets = startSecretsWatcher({ log: (l) => process.stderr.write(`${l}
3366
+ `) });
3727
3367
  const sessions = new RpcSessionMap();
3728
3368
  const evictTimer = setInterval(() => sessions.evictStale(), 5 * 60 * 1e3);
3729
- process.on("exit", () => clearInterval(evictTimer));
3369
+ process.on("exit", () => {
3370
+ clearInterval(evictTimer);
3371
+ stopSecrets();
3372
+ });
3730
3373
  const rl = createInterface({ input: process.stdin, terminal: false });
3731
3374
  rl.on("line", async (line) => {
3732
3375
  const trimmed = line.trim();
@@ -3795,12 +3438,12 @@ async function handleInbound(msg, sessions) {
3795
3438
  }
3796
3439
 
3797
3440
  // src/commands/agents/spawn.ts
3798
- import { execFileSync as execFileSync7 } from "child_process";
3441
+ import { execFileSync as execFileSync8 } from "child_process";
3799
3442
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3800
3443
  import { tmpdir as tmpdir2 } from "os";
3801
- import { join as join7 } from "path";
3802
- import { defineCommand as defineCommand26 } from "citty";
3803
- import consola23 from "consola";
3444
+ import { join as join8 } from "path";
3445
+ import { defineCommand as defineCommand29 } from "citty";
3446
+ import consola25 from "consola";
3804
3447
 
3805
3448
  // src/lib/troop-bootstrap.ts
3806
3449
  var SYNC_LABEL_PREFIX = "openape.troop.sync";
@@ -3856,12 +3499,13 @@ ${envBlock} <key>StartInterval</key>
3856
3499
 
3857
3500
  // src/lib/keygen.ts
3858
3501
  import { Buffer as Buffer4 } from "buffer";
3859
- 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";
3860
3503
  import { generateKeyPairSync } from "crypto";
3861
- import { homedir as homedir7 } from "os";
3504
+ import { homedir as homedir8 } from "os";
3862
3505
  import { dirname, resolve as resolve2 } from "path";
3863
- function resolveKeyPath(p2) {
3864
- return resolve2(p2.replace(/^~/, homedir7()));
3506
+ import { generateX25519KeyPair } from "@openape/core";
3507
+ function resolveKeyPath(p) {
3508
+ return resolve2(p.replace(/^~/, homedir8()));
3865
3509
  }
3866
3510
  function buildSshEd25519Line(rawPub) {
3867
3511
  const keyTypeStr = "ssh-ed25519";
@@ -3874,10 +3518,10 @@ function buildSshEd25519Line(rawPub) {
3874
3518
  }
3875
3519
  function readPublicKey(keyPath) {
3876
3520
  const pubPath = `${keyPath}.pub`;
3877
- if (existsSync8(pubPath)) {
3878
- return readFileSync7(pubPath, "utf-8").trim();
3521
+ if (existsSync9(pubPath)) {
3522
+ return readFileSync8(pubPath, "utf-8").trim();
3879
3523
  }
3880
- const keyContent = readFileSync7(keyPath, "utf-8");
3524
+ const keyContent = readFileSync8(keyPath, "utf-8");
3881
3525
  const privateKey = loadEd25519PrivateKey(keyContent);
3882
3526
  const jwk = privateKey.export({ format: "jwk" });
3883
3527
  const pubBytes = Buffer4.from(jwk.x, "base64url");
@@ -3886,7 +3530,7 @@ function readPublicKey(keyPath) {
3886
3530
  function generateAndSaveKey(keyPath) {
3887
3531
  const resolved = resolveKeyPath(keyPath);
3888
3532
  const dir = dirname(resolved);
3889
- if (!existsSync8(dir)) {
3533
+ if (!existsSync9(dir)) {
3890
3534
  mkdirSync2(dir, { recursive: true });
3891
3535
  }
3892
3536
  const { publicKey, privateKey } = generateKeyPairSync("ed25519");
@@ -3904,22 +3548,25 @@ function generateKeyPairInMemory() {
3904
3548
  const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
3905
3549
  const jwk = publicKey.export({ format: "jwk" });
3906
3550
  const pubBytes = Buffer4.from(jwk.x, "base64url");
3551
+ const enc = generateX25519KeyPair();
3907
3552
  return {
3908
3553
  privatePem,
3909
- publicSshLine: buildSshEd25519Line(pubBytes)
3554
+ publicSshLine: buildSshEd25519Line(pubBytes),
3555
+ x25519PrivateKey: enc.privateKey,
3556
+ x25519PublicKey: enc.publicKey
3910
3557
  };
3911
3558
  }
3912
3559
 
3913
3560
  // src/lib/llm-bridge.ts
3914
- import { execFileSync as execFileSync6 } from "child_process";
3915
- import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
3916
- import { homedir as homedir8 } from "os";
3917
- 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";
3918
3565
  var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
3919
- function readLitellmEnv(envPath = join6(homedir8(), "litellm", ".env")) {
3920
- if (!existsSync9(envPath)) return null;
3566
+ function readLitellmEnv(envPath = join7(homedir9(), "litellm", ".env")) {
3567
+ if (!existsSync10(envPath)) return null;
3921
3568
  try {
3922
- const text = readFileSync8(envPath, "utf8");
3569
+ const text = readFileSync9(envPath, "utf8");
3923
3570
  const out = {};
3924
3571
  for (const line of text.split("\n")) {
3925
3572
  const trimmed = line.trim();
@@ -3955,7 +3602,7 @@ function captureHostBinDirs() {
3955
3602
  for (const bin of ["node", "ape-agent", "apes"]) {
3956
3603
  let resolved;
3957
3604
  try {
3958
- resolved = execFileSync6("/usr/bin/which", [bin], { encoding: "utf8" }).trim();
3605
+ resolved = execFileSync7("/usr/bin/which", [bin], { encoding: "utf8" }).trim();
3959
3606
  } catch {
3960
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)";
3961
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.`);
@@ -4056,7 +3703,7 @@ function readMacOSUidOrNull(name) {
4056
3703
  return null;
4057
3704
  }
4058
3705
  }
4059
- var spawnAgentCommand = defineCommand26({
3706
+ var spawnAgentCommand = defineCommand29({
4060
3707
  meta: {
4061
3708
  name: "spawn",
4062
3709
  description: "Provision a local macOS agent end-to-end (OS user, keypair, IdP agent, Claude hook)"
@@ -4138,20 +3785,21 @@ var spawnAgentCommand = defineCommand26({
4138
3785
  and try again.`
4139
3786
  );
4140
3787
  }
4141
- const existing = readMacOSUser(name);
3788
+ const macOSUsername = macOSUsernameForAgent(name);
3789
+ const existing = readMacOSUser(macOSUsername) ?? readMacOSUser(name);
4142
3790
  if (existing) {
4143
- 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.`);
4144
3792
  }
4145
- const homeDir = `/var/openape/homes/${name}`;
4146
- const scratch = mkdtempSync2(join7(tmpdir2(), `apes-spawn-${name}-`));
4147
- 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");
4148
3796
  try {
4149
- consola23.start(`Generating keypair for ${name}\u2026`);
4150
- const { privatePem, publicSshLine } = generateKeyPairInMemory();
4151
- 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`);
4152
3800
  const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
4153
- consola23.success(`Registered as ${registration.email}`);
4154
- consola23.start("Issuing agent access token\u2026");
3801
+ consola25.success(`Registered as ${registration.email}`);
3802
+ consola25.start("Issuing agent access token\u2026");
4155
3803
  const { token, expiresIn } = await issueAgentToken({
4156
3804
  idp,
4157
3805
  agentEmail: registration.email,
@@ -4204,10 +3852,13 @@ and try again.`
4204
3852
  };
4205
3853
  const script = buildSpawnSetupScript({
4206
3854
  name,
3855
+ macOSUsername,
4207
3856
  homeDir,
4208
3857
  shellPath: loginShell,
4209
3858
  privateKeyPem: privatePem,
4210
3859
  publicKeySshLine: publicSshLine,
3860
+ x25519PrivateKey,
3861
+ x25519PublicKey,
4211
3862
  authJson,
4212
3863
  claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
4213
3864
  hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null,
@@ -4218,15 +3869,15 @@ and try again.`
4218
3869
  writeFileSync4(scriptPath, script, { mode: 448 });
4219
3870
  const alreadyRoot = process.getuid?.() === 0;
4220
3871
  if (alreadyRoot) {
4221
- consola23.start("Running privileged setup directly (already root)\u2026");
4222
- execFileSync7("bash", [scriptPath], { stdio: "inherit" });
3872
+ consola25.start("Running privileged setup directly (already root)\u2026");
3873
+ execFileSync8("bash", [scriptPath], { stdio: "inherit" });
4223
3874
  } else {
4224
- consola23.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
4225
- consola23.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
4226
- 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" });
4227
3878
  }
4228
3879
  try {
4229
- const uid = readMacOSUidOrNull(name);
3880
+ const uid = readMacOSUidOrNull(macOSUsername) ?? readMacOSUidOrNull(name);
4230
3881
  upsertNestAgent({
4231
3882
  name,
4232
3883
  uid: uid ?? -1,
@@ -4240,13 +3891,13 @@ and try again.`
4240
3891
  } : void 0
4241
3892
  });
4242
3893
  } catch (err) {
4243
- 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)}`);
4244
3895
  }
4245
- consola23.success(`Agent ${name} spawned.`);
4246
- 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}`);
4247
3898
  if (withBridge) {
4248
- consola23.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
4249
- 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.");
4250
3901
  }
4251
3902
  console.log("");
4252
3903
  console.log("Run as the agent with:");
@@ -4280,18 +3931,18 @@ async function resolveClaudeToken(opts) {
4280
3931
  }
4281
3932
 
4282
3933
  // src/commands/agents/sync.ts
4283
- import { chownSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync9, rmSync as rmSync4, statSync, writeFileSync as writeFileSync5 } from "fs";
4284
- import { homedir as homedir9 } from "os";
4285
- import { join as join8 } from "path";
4286
- import { defineCommand as defineCommand27 } from "citty";
4287
- 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";
4288
3939
 
4289
3940
  // src/lib/macos-host.ts
4290
- import { execFileSync as execFileSync8 } from "child_process";
3941
+ import { execFileSync as execFileSync9 } from "child_process";
4291
3942
  import { hostname as hostname3 } from "os";
4292
3943
  function getHostId() {
4293
3944
  try {
4294
- const output = execFileSync8(
3945
+ const output = execFileSync9(
4295
3946
  "/usr/sbin/ioreg",
4296
3947
  ["-d2", "-c", "IOPlatformExpertDevice"],
4297
3948
  { encoding: "utf8", timeout: 2e3 }
@@ -4311,15 +3962,15 @@ function getHostname() {
4311
3962
  }
4312
3963
 
4313
3964
  // src/commands/agents/sync.ts
4314
- var AUTH_PATH3 = join8(homedir9(), ".config", "apes", "auth.json");
4315
- 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");
4316
3967
  function readAuthJson() {
4317
- if (!existsSync10(AUTH_PATH3)) {
3968
+ if (!existsSync11(AUTH_PATH3)) {
4318
3969
  throw new CliError(
4319
3970
  `No agent auth found at ${AUTH_PATH3}. Run \`apes agents spawn <name>\` to provision an agent first.`
4320
3971
  );
4321
3972
  }
4322
- const raw = readFileSync9(AUTH_PATH3, "utf8");
3973
+ const raw = readFileSync10(AUTH_PATH3, "utf8");
4323
3974
  let parsed;
4324
3975
  try {
4325
3976
  parsed = JSON.parse(raw);
@@ -4343,7 +3994,7 @@ function agentNameFromEmail(email) {
4343
3994
  }
4344
3995
  return before.slice(0, dashIdx);
4345
3996
  }
4346
- var syncAgentCommand = defineCommand27({
3997
+ var syncAgentCommand = defineCommand30({
4347
3998
  meta: {
4348
3999
  name: "sync",
4349
4000
  description: "Pull this agent's task list from troop.openape.ai and reconcile launchd plists"
@@ -4367,22 +4018,22 @@ var syncAgentCommand = defineCommand27({
4367
4018
  if (!auth.owner_email) {
4368
4019
  throw new CliError(`${AUTH_PATH3} is missing owner_email \u2014 re-run \`apes agents spawn\` to update.`);
4369
4020
  }
4370
- 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}`);
4371
4022
  const sync = await client.sync({
4372
4023
  hostname: host,
4373
4024
  hostId,
4374
4025
  ownerEmail: auth.owner_email
4375
4026
  });
4376
- 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");
4377
4028
  const { system_prompt: systemPrompt, tools, skills, tasks } = await client.listTasks();
4378
- consola24.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
4379
- consola24.info(`Tools enabled: ${tools.length === 0 ? "(none)" : tools.join(", ")}`);
4380
- 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(", ")}`);
4381
4032
  let agentUid = null;
4382
4033
  let agentGid = null;
4383
4034
  if (process.geteuid?.() === 0) {
4384
4035
  try {
4385
- const homeStat = statSync(homedir9());
4036
+ const homeStat = statSync(homedir10());
4386
4037
  agentUid = homeStat.uid;
4387
4038
  agentGid = homeStat.gid;
4388
4039
  } catch {
@@ -4396,11 +4047,11 @@ var syncAgentCommand = defineCommand27({
4396
4047
  }
4397
4048
  }
4398
4049
  }
4399
- const agentDir = join8(homedir9(), ".openape", "agent");
4050
+ const agentDir = join9(homedir10(), ".openape", "agent");
4400
4051
  mkdirSync3(agentDir, { recursive: true });
4401
- chownToAgent(join8(homedir9(), ".openape"));
4052
+ chownToAgent(join9(homedir10(), ".openape"));
4402
4053
  chownToAgent(agentDir);
4403
- const agentJsonPath = join8(agentDir, "agent.json");
4054
+ const agentJsonPath = join9(agentDir, "agent.json");
4404
4055
  writeFileSync5(
4405
4056
  agentJsonPath,
4406
4057
  `${JSON.stringify({ systemPrompt, tools }, null, 2)}
@@ -4411,43 +4062,43 @@ var syncAgentCommand = defineCommand27({
4411
4062
  mkdirSync3(TASK_CACHE_DIR2, { recursive: true });
4412
4063
  chownToAgent(TASK_CACHE_DIR2);
4413
4064
  for (const task of tasks) {
4414
- const path2 = join8(TASK_CACHE_DIR2, `${task.taskId}.json`);
4065
+ const path2 = join9(TASK_CACHE_DIR2, `${task.taskId}.json`);
4415
4066
  writeFileSync5(path2, `${JSON.stringify(task, null, 2)}
4416
4067
  `, { mode: 384 });
4417
4068
  chownToAgent(path2);
4418
4069
  }
4419
- const skillsDir = join8(agentDir, "skills");
4070
+ const skillsDir = join9(agentDir, "skills");
4420
4071
  mkdirSync3(skillsDir, { recursive: true });
4421
4072
  chownToAgent(skillsDir);
4422
4073
  const incomingNames = new Set(skills.map((s) => s.name));
4423
4074
  try {
4424
- for (const entry of readdirSync(skillsDir)) {
4075
+ for (const entry of readdirSync2(skillsDir)) {
4425
4076
  if (incomingNames.has(entry)) continue;
4426
4077
  try {
4427
- rmSync4(join8(skillsDir, entry), { recursive: true, force: true });
4078
+ rmSync4(join9(skillsDir, entry), { recursive: true, force: true });
4428
4079
  } catch {
4429
4080
  }
4430
4081
  }
4431
4082
  } catch {
4432
4083
  }
4433
4084
  for (const skill of skills) {
4434
- const skillDir = join8(skillsDir, skill.name);
4085
+ const skillDir = join9(skillsDir, skill.name);
4435
4086
  mkdirSync3(skillDir, { recursive: true });
4436
4087
  chownToAgent(skillDir);
4437
- const skillPath = join8(skillDir, "SKILL.md");
4088
+ const skillPath = join9(skillDir, "SKILL.md");
4438
4089
  writeFileSync5(skillPath, skill.body.endsWith("\n") ? skill.body : `${skill.body}
4439
4090
  `, { mode: 384 });
4440
4091
  chownToAgent(skillPath);
4441
4092
  }
4442
- consola24.success("Sync complete.");
4093
+ consola26.success("Sync complete.");
4443
4094
  }
4444
4095
  });
4445
4096
 
4446
4097
  // src/commands/agents/index.ts
4447
- var agentsCommand = defineCommand28({
4098
+ var agentsCommand = defineCommand31({
4448
4099
  meta: {
4449
4100
  name: "agents",
4450
- 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)"
4451
4102
  },
4452
4103
  subCommands: {
4453
4104
  register: registerAgentCommand,
@@ -4457,27 +4108,28 @@ var agentsCommand = defineCommand28({
4457
4108
  allow: allowAgentCommand,
4458
4109
  sync: syncAgentCommand,
4459
4110
  run: runAgentCommand,
4460
- serve: serveAgentCommand
4111
+ serve: serveAgentCommand,
4112
+ "cleanup-orphans": cleanupOrphansCommand
4461
4113
  }
4462
4114
  });
4463
4115
 
4464
4116
  // src/commands/nest/index.ts
4465
- import { defineCommand as defineCommand36 } from "citty";
4117
+ import { defineCommand as defineCommand39 } from "citty";
4466
4118
 
4467
4119
  // src/commands/nest/authorize.ts
4468
- import { execFileSync as execFileSync9 } from "child_process";
4469
- import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
4470
- import { join as join10 } from "path";
4471
- import { defineCommand as defineCommand30 } from "citty";
4472
- 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";
4473
4125
 
4474
4126
  // src/commands/nest/enroll.ts
4475
- import { hostname as hostname4, homedir as homedir10 } from "os";
4476
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6, chmodSync } from "fs";
4477
- import { join as join9 } from "path";
4478
- import { defineCommand as defineCommand29 } from "citty";
4479
- import consola25 from "consola";
4480
- 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");
4481
4133
  function nestAgentName() {
4482
4134
  const raw = hostname4().toLowerCase();
4483
4135
  const head = raw.split(".")[0] ?? raw;
@@ -4485,7 +4137,7 @@ function nestAgentName() {
4485
4137
  const trimmed = safe.slice(0, 16);
4486
4138
  return `nest-${trimmed || "host"}`;
4487
4139
  }
4488
- var enrollNestCommand = defineCommand29({
4140
+ var enrollNestCommand = defineCommand32({
4489
4141
  meta: {
4490
4142
  name: "enroll",
4491
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."
@@ -4510,25 +4162,25 @@ var enrollNestCommand = defineCommand29({
4510
4162
  throw new CliError("Run `apes login <email>` first \u2014 nest enroll attaches the new identity to your owner account.");
4511
4163
  }
4512
4164
  const name = args.name || nestAgentName();
4513
- const authPath = join9(NEST_DATA_DIR, ".config", "apes", "auth.json");
4514
- if (existsSync11(authPath) && !args.force) {
4165
+ const authPath = join10(NEST_DATA_DIR, ".config", "apes", "auth.json");
4166
+ if (existsSync12(authPath) && !args.force) {
4515
4167
  throw new CliError(`Nest already enrolled at ${authPath}. Pass --force to re-enroll.`);
4516
4168
  }
4517
- const sshDir = join9(NEST_DATA_DIR, ".ssh");
4518
- 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");
4519
4171
  mkdirSync4(sshDir, { recursive: true });
4520
4172
  mkdirSync4(configDir, { recursive: true });
4521
- consola25.start(`Generating keypair for ${name}\u2026`);
4173
+ consola27.start(`Generating keypair for ${name}\u2026`);
4522
4174
  const { privatePem, publicSshLine } = generateKeyPairInMemory();
4523
- writeFileSync6(join9(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
4175
+ writeFileSync6(join10(sshDir, "id_ed25519"), `${privatePem.trimEnd()}
4524
4176
  `, { mode: 384 });
4525
- writeFileSync6(join9(sshDir, "id_ed25519.pub"), `${publicSshLine}
4177
+ writeFileSync6(join10(sshDir, "id_ed25519.pub"), `${publicSshLine}
4526
4178
  `, { mode: 420 });
4527
4179
  chmodSync(sshDir, 448);
4528
- consola25.start(`Registering nest at ${idp}\u2026`);
4180
+ consola27.start(`Registering nest at ${idp}\u2026`);
4529
4181
  const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
4530
- consola25.success(`Registered as ${registration.email}`);
4531
- consola25.start("Issuing nest access token\u2026");
4182
+ consola27.success(`Registered as ${registration.email}`);
4183
+ consola27.start("Issuing nest access token\u2026");
4532
4184
  const { token, expiresIn } = await issueAgentToken({
4533
4185
  idp,
4534
4186
  agentEmail: registration.email,
@@ -4539,16 +4191,16 @@ var enrollNestCommand = defineCommand29({
4539
4191
  accessToken: token,
4540
4192
  email: registration.email,
4541
4193
  expiresAt: Math.floor(Date.now() / 1e3) + expiresIn,
4542
- keyPath: join9(sshDir, "id_ed25519"),
4194
+ keyPath: join10(sshDir, "id_ed25519"),
4543
4195
  ownerEmail: ownerAuth.email
4544
4196
  });
4545
4197
  writeFileSync6(authPath, authJson, { mode: 384 });
4546
4198
  chmodSync(configDir, 448);
4547
- consola25.success(`Nest enrolled \u2014 auth.json at ${authPath}`);
4548
- consola25.info("");
4549
- consola25.info("Next: configure the YOLO-policy so the nest can spawn/destroy without prompts:");
4550
- consola25.info("");
4551
- 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");
4552
4204
  }
4553
4205
  });
4554
4206
 
@@ -4595,7 +4247,7 @@ var DEFAULT_ALLOW_PATTERNS = [
4595
4247
  "pm2 delete openape-bridge-*",
4596
4248
  "pm2 jlist"
4597
4249
  ];
4598
- var authorizeNestCommand = defineCommand30({
4250
+ var authorizeNestCommand = defineCommand33({
4599
4251
  meta: {
4600
4252
  name: "authorize",
4601
4253
  description: "Set the YOLO-policy that lets the local nest spawn/destroy without per-call DDISA prompts (wraps `apes yolo set`)"
@@ -4611,14 +4263,14 @@ var authorizeNestCommand = defineCommand30({
4611
4263
  }
4612
4264
  },
4613
4265
  async run({ args }) {
4614
- const nestAuthPath = join10(NEST_DATA_DIR, ".config", "apes", "auth.json");
4615
- if (!existsSync12(nestAuthPath)) {
4266
+ const nestAuthPath = join11(NEST_DATA_DIR, ".config", "apes", "auth.json");
4267
+ if (!existsSync13(nestAuthPath)) {
4616
4268
  throw new CliError("Nest not enrolled. Run `apes nest enroll` first.");
4617
4269
  }
4618
- const nestAuth = JSON.parse(readFileSync10(nestAuthPath, "utf8"));
4270
+ const nestAuth = JSON.parse(readFileSync11(nestAuthPath, "utf8"));
4619
4271
  if (!nestAuth.email) throw new CliError(`${nestAuthPath} has no email`);
4620
4272
  const allow = args.allow ?? DEFAULT_ALLOW_PATTERNS.join(",");
4621
- 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`);
4622
4274
  const cmdArgs = [
4623
4275
  "yolo",
4624
4276
  "set",
@@ -4632,20 +4284,20 @@ var authorizeNestCommand = defineCommand30({
4632
4284
  cmdArgs.push("--expires-in", args["expires-in"]);
4633
4285
  }
4634
4286
  try {
4635
- execFileSync9("apes", cmdArgs, { stdio: "inherit" });
4287
+ execFileSync10("apes", cmdArgs, { stdio: "inherit" });
4636
4288
  } catch (err) {
4637
4289
  throw new CliError(err instanceof Error ? err.message : String(err));
4638
4290
  }
4639
- consola26.success("Nest-driven agent lifecycle is now zero-prompt.");
4640
- 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.");
4641
4293
  }
4642
4294
  });
4643
4295
 
4644
4296
  // src/commands/nest/destroy.ts
4645
- import { execFileSync as execFileSync10 } from "child_process";
4646
- import { defineCommand as defineCommand31 } from "citty";
4647
- import consola27 from "consola";
4648
- 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({
4649
4301
  meta: {
4650
4302
  name: "destroy",
4651
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."
@@ -4656,8 +4308,8 @@ var destroyNestCommand = defineCommand31({
4656
4308
  async run({ args }) {
4657
4309
  const name = String(args.name);
4658
4310
  try {
4659
- execFileSync10("apes", ["run", "--as", "root", "--wait", "--", "apes", "agents", "destroy", name, "--force"], { stdio: "inherit" });
4660
- 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).`);
4661
4313
  } catch (err) {
4662
4314
  const status = err.status ?? 1;
4663
4315
  throw new CliExit(status);
@@ -4666,12 +4318,12 @@ var destroyNestCommand = defineCommand31({
4666
4318
  });
4667
4319
 
4668
4320
  // src/commands/nest/install.ts
4669
- import { execFileSync as execFileSync11 } from "child_process";
4670
- import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
4671
- import { homedir as homedir11, userInfo as userInfo2 } from "os";
4672
- import { dirname as dirname3, join as join11 } from "path";
4673
- import { defineCommand as defineCommand32 } from "citty";
4674
- 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";
4675
4327
 
4676
4328
  // src/commands/nest/apes-agents-adapter.ts
4677
4329
  var APES_AGENTS_ADAPTER_TOML = `schema = "openape-shapes/v1"
@@ -4738,13 +4390,13 @@ resource_chain = ["agents:name={name}", "allowlist:email={peer_email}"]
4738
4390
  // src/commands/nest/install.ts
4739
4391
  var PLIST_LABEL = "ai.openape.nest";
4740
4392
  function plistPath() {
4741
- return join11(homedir11(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
4393
+ return join12(homedir12(), "Library", "LaunchAgents", `${PLIST_LABEL}.plist`);
4742
4394
  }
4743
4395
  function escape2(s) {
4744
4396
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
4745
4397
  }
4746
4398
  function buildPlist(args) {
4747
- const logsDir = join11(args.userHome, "Library", "Logs");
4399
+ const logsDir = join12(args.userHome, "Library", "Logs");
4748
4400
  return `<?xml version="1.0" encoding="UTF-8"?>
4749
4401
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
4750
4402
  <plist version="1.0">
@@ -4779,25 +4431,25 @@ function buildPlist(args) {
4779
4431
  `;
4780
4432
  }
4781
4433
  function installAdapter2() {
4782
- const target = join11(homedir11(), ".openape", "shapes", "adapters", "apes-agents.toml");
4434
+ const target = join12(homedir12(), ".openape", "shapes", "adapters", "apes-agents.toml");
4783
4435
  mkdirSync5(dirname3(target), { recursive: true });
4784
4436
  let existing = "";
4785
4437
  try {
4786
- existing = readFileSync11(target, "utf8");
4438
+ existing = readFileSync12(target, "utf8");
4787
4439
  } catch {
4788
4440
  }
4789
4441
  if (existing === APES_AGENTS_ADAPTER_TOML) return false;
4790
4442
  writeFileSync7(target, APES_AGENTS_ADAPTER_TOML, { mode: 420 });
4791
- consola28.success(`Wrote shapes adapter ${target}`);
4443
+ consola30.success(`Wrote shapes adapter ${target}`);
4792
4444
  return true;
4793
4445
  }
4794
4446
  function writeBridgeModelDefault(model) {
4795
- for (const envDir of [join11(homedir11(), "litellm"), join11(NEST_DATA_DIR, "litellm")]) {
4796
- const envFile = join11(envDir, ".env");
4447
+ for (const envDir of [join12(homedir12(), "litellm"), join12(NEST_DATA_DIR, "litellm")]) {
4448
+ const envFile = join12(envDir, ".env");
4797
4449
  mkdirSync5(envDir, { recursive: true });
4798
4450
  let lines = [];
4799
- if (existsSync13(envFile)) {
4800
- 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="));
4801
4453
  }
4802
4454
  lines.push(`APE_CHAT_BRIDGE_MODEL=${model}`);
4803
4455
  while (lines.length > 0 && lines.at(-1).trim() === "") lines.pop();
@@ -4807,17 +4459,17 @@ function writeBridgeModelDefault(model) {
4807
4459
  }
4808
4460
  function findBinary(name) {
4809
4461
  for (const dir of [
4810
- join11(homedir11(), ".bun", "bin"),
4462
+ join12(homedir12(), ".bun", "bin"),
4811
4463
  "/opt/homebrew/bin",
4812
4464
  "/usr/local/bin",
4813
4465
  "/usr/bin"
4814
4466
  ]) {
4815
- const p2 = join11(dir, name);
4816
- if (existsSync13(p2)) return p2;
4467
+ const p = join12(dir, name);
4468
+ if (existsSync14(p)) return p;
4817
4469
  }
4818
4470
  throw new Error(`could not locate ${name} on PATH; install it first`);
4819
4471
  }
4820
- var installNestCommand = defineCommand32({
4472
+ var installNestCommand = defineCommand35({
4821
4473
  meta: {
4822
4474
  name: "install",
4823
4475
  description: "Install + start the local nest-daemon (idempotent \u2014 re-running just restarts)"
@@ -4833,57 +4485,57 @@ var installNestCommand = defineCommand32({
4833
4485
  }
4834
4486
  },
4835
4487
  async run({ args }) {
4836
- const homeDir = homedir11();
4488
+ const homeDir = homedir12();
4837
4489
  const port = Number(args.port ?? 9091);
4838
4490
  if (!Number.isInteger(port) || port < 1024 || port > 65535) {
4839
4491
  throw new Error(`invalid port ${port}`);
4840
4492
  }
4841
4493
  const nestBin = findBinary("openape-nest");
4842
4494
  const apesBin = findBinary("apes");
4843
- consola28.info(`Installing nest at ${plistPath()}`);
4844
- consola28.info(` nest binary: ${nestBin}`);
4845
- consola28.info(` apes binary: ${apesBin}`);
4846
- 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}`);
4847
4499
  if (typeof args["bridge-model"] === "string" && args["bridge-model"]) {
4848
4500
  writeBridgeModelDefault(args["bridge-model"]);
4849
- 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)`);
4850
4502
  }
4851
4503
  installAdapter2();
4852
- mkdirSync5(join11(homeDir, "Library", "LaunchAgents"), { recursive: true });
4504
+ mkdirSync5(join12(homeDir, "Library", "LaunchAgents"), { recursive: true });
4853
4505
  mkdirSync5(NEST_DATA_DIR, { recursive: true });
4854
4506
  const desired = buildPlist({ nestBin, apesBin, userHome: homeDir, nestHome: NEST_DATA_DIR, port });
4855
4507
  let existing = "";
4856
4508
  try {
4857
- existing = readFileSync11(plistPath(), "utf8");
4509
+ existing = readFileSync12(plistPath(), "utf8");
4858
4510
  } catch {
4859
4511
  }
4860
4512
  if (existing !== desired) {
4861
4513
  writeFileSync7(plistPath(), desired, { mode: 420 });
4862
- consola28.success("Wrote launchd plist");
4514
+ consola30.success("Wrote launchd plist");
4863
4515
  } else {
4864
- consola28.info("plist already up to date");
4516
+ consola30.info("plist already up to date");
4865
4517
  }
4866
4518
  const uid = userInfo2().uid;
4867
4519
  try {
4868
- execFileSync11("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore" });
4520
+ execFileSync12("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL}`], { stdio: "ignore" });
4869
4521
  } catch {
4870
4522
  }
4871
- execFileSync11("/bin/launchctl", ["bootstrap", `gui/${uid}`, plistPath()], { stdio: "inherit" });
4872
- consola28.success(`Nest daemon bootstrapped \u2014 http://127.0.0.1:${port}`);
4873
- consola28.info("");
4874
- consola28.info("Next steps for zero-prompt spawn \u2014 both one-time:");
4875
- consola28.info("");
4876
- consola28.info(" 1. apes nest enroll # register nest as DDISA agent (creates own auth.json)");
4877
- consola28.info(" 2. apes nest authorize # set YOLO-policy on the nest agent");
4878
- consola28.info("");
4879
- 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.");
4880
4532
  }
4881
4533
  });
4882
4534
 
4883
4535
  // src/commands/nest/list.ts
4884
- import { defineCommand as defineCommand33 } from "citty";
4885
- import consola29 from "consola";
4886
- var listNestCommand = defineCommand33({
4536
+ import { defineCommand as defineCommand36 } from "citty";
4537
+ import consola31 from "consola";
4538
+ var listNestCommand = defineCommand36({
4887
4539
  meta: {
4888
4540
  name: "list",
4889
4541
  description: "List agents registered with the local nest. Reads /var/openape/nest/agents.json directly."
@@ -4898,22 +4550,22 @@ var listNestCommand = defineCommand33({
4898
4550
  return;
4899
4551
  }
4900
4552
  if (reg.agents.length === 0) {
4901
- consola29.info("(no agents registered with this nest)");
4553
+ consola31.info("(no agents registered with this nest)");
4902
4554
  return;
4903
4555
  }
4904
- consola29.info(`${reg.agents.length} agent(s) registered with this nest:`);
4556
+ consola31.info(`${reg.agents.length} agent(s) registered with this nest:`);
4905
4557
  for (const a of reg.agents) {
4906
4558
  const bridge = a.bridge ? " bridge=on" : "";
4907
- 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}`);
4908
4560
  }
4909
4561
  }
4910
4562
  });
4911
4563
 
4912
4564
  // src/commands/nest/spawn.ts
4913
- import { execFileSync as execFileSync12 } from "child_process";
4914
- import { defineCommand as defineCommand34 } from "citty";
4915
- import consola30 from "consola";
4916
- 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({
4917
4569
  meta: {
4918
4570
  name: "spawn",
4919
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."
@@ -4943,8 +4595,8 @@ var spawnNestCommand = defineCommand34({
4943
4595
  if (typeof args["bridge-base-url"] === "string") apesArgs.push("--bridge-base-url", args["bridge-base-url"]);
4944
4596
  if (typeof args["bridge-model"] === "string") apesArgs.push("--bridge-model", args["bridge-model"]);
4945
4597
  try {
4946
- execFileSync12("apes", apesArgs, { stdio: "inherit" });
4947
- 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).`);
4948
4600
  } catch (err) {
4949
4601
  const status = err.status ?? 1;
4950
4602
  throw new CliExit(status);
@@ -4953,37 +4605,37 @@ var spawnNestCommand = defineCommand34({
4953
4605
  });
4954
4606
 
4955
4607
  // src/commands/nest/uninstall.ts
4956
- import { execFileSync as execFileSync13 } from "child_process";
4957
- import { existsSync as existsSync14, unlinkSync } from "fs";
4958
- import { homedir as homedir12, userInfo as userInfo3 } from "os";
4959
- import { join as join12 } from "path";
4960
- import { defineCommand as defineCommand35 } from "citty";
4961
- 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";
4962
4614
  var PLIST_LABEL2 = "ai.openape.nest";
4963
- var uninstallNestCommand = defineCommand35({
4615
+ var uninstallNestCommand = defineCommand38({
4964
4616
  meta: {
4965
4617
  name: "uninstall",
4966
4618
  description: "Stop + remove the local nest-daemon (registry + agents preserved)"
4967
4619
  },
4968
4620
  async run() {
4969
4621
  const uid = userInfo3().uid;
4970
- const path2 = join12(homedir12(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
4622
+ const path2 = join13(homedir13(), "Library", "LaunchAgents", `${PLIST_LABEL2}.plist`);
4971
4623
  try {
4972
- execFileSync13("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
4973
- consola31.success("Nest daemon stopped");
4624
+ execFileSync14("/bin/launchctl", ["bootout", `gui/${uid}/${PLIST_LABEL2}`], { stdio: "ignore" });
4625
+ consola33.success("Nest daemon stopped");
4974
4626
  } catch {
4975
- consola31.info("Nest daemon was not loaded");
4627
+ consola33.info("Nest daemon was not loaded");
4976
4628
  }
4977
- if (existsSync14(path2)) {
4629
+ if (existsSync15(path2)) {
4978
4630
  unlinkSync(path2);
4979
- consola31.success(`Removed ${path2}`);
4631
+ consola33.success(`Removed ${path2}`);
4980
4632
  }
4981
- 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.");
4982
4634
  }
4983
4635
  });
4984
4636
 
4985
4637
  // src/commands/nest/index.ts
4986
- var nestCommand = defineCommand36({
4638
+ var nestCommand = defineCommand39({
4987
4639
  meta: {
4988
4640
  name: "nest",
4989
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."
@@ -5000,12 +4652,12 @@ var nestCommand = defineCommand36({
5000
4652
  });
5001
4653
 
5002
4654
  // src/commands/yolo/index.ts
5003
- import { defineCommand as defineCommand40 } from "citty";
4655
+ import { defineCommand as defineCommand43 } from "citty";
5004
4656
 
5005
4657
  // src/commands/yolo/clear.ts
5006
- import { defineCommand as defineCommand37 } from "citty";
5007
- import consola32 from "consola";
5008
- var yoloClearCommand = defineCommand37({
4658
+ import { defineCommand as defineCommand40 } from "citty";
4659
+ import consola34 from "consola";
4660
+ var yoloClearCommand = defineCommand40({
5009
4661
  meta: {
5010
4662
  name: "clear",
5011
4663
  description: "Remove the YOLO-policy from a DDISA agent (subsequent grants need human approval)"
@@ -5034,15 +4686,15 @@ var yoloClearCommand = defineCommand37({
5034
4686
  const text = await res.text().catch(() => "");
5035
4687
  throw new CliError(`DELETE /yolo-policy failed (${res.status}): ${text}`);
5036
4688
  }
5037
- consola32.success(`YOLO-policy cleared on ${email}`);
4689
+ consola34.success(`YOLO-policy cleared on ${email}`);
5038
4690
  }
5039
4691
  });
5040
4692
 
5041
4693
  // src/commands/yolo/set.ts
5042
- import { defineCommand as defineCommand38 } from "citty";
5043
- import consola33 from "consola";
4694
+ import { defineCommand as defineCommand41 } from "citty";
4695
+ import consola35 from "consola";
5044
4696
  var VALID_MODES = ["allow-list", "deny-list"];
5045
- var yoloSetCommand = defineCommand38({
4697
+ var yoloSetCommand = defineCommand41({
5046
4698
  meta: {
5047
4699
  name: "set",
5048
4700
  description: "Write a YOLO-policy on a DDISA agent you own"
@@ -5090,12 +4742,12 @@ var yoloSetCommand = defineCommand38({
5090
4742
  const denyPatterns = parseList(args.deny);
5091
4743
  const denyRiskThreshold = args["deny-risk"] ?? null;
5092
4744
  const expiresAt = parseExpiresIn(args["expires-in"]);
5093
- consola33.info(`Setting YOLO-policy on ${email}`);
5094
- consola33.info(` mode: ${mode}`);
5095
- if (allowPatterns.length) consola33.info(` allow_patterns: ${allowPatterns.join(", ")}`);
5096
- if (denyPatterns.length) consola33.info(` deny_patterns: ${denyPatterns.join(", ")}`);
5097
- if (denyRiskThreshold) consola33.info(` deny_risk: ${denyRiskThreshold}`);
5098
- 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()}`);
5099
4751
  const url = `${idp}/api/users/${encodeURIComponent(email)}/yolo-policy`;
5100
4752
  const res = await fetch(url, {
5101
4753
  method: "PUT",
@@ -5115,27 +4767,27 @@ var yoloSetCommand = defineCommand38({
5115
4767
  const text = await res.text().catch(() => "");
5116
4768
  throw new CliError(`PUT /yolo-policy failed (${res.status}): ${text}`);
5117
4769
  }
5118
- consola33.success(`YOLO-policy applied to ${email}`);
4770
+ consola35.success(`YOLO-policy applied to ${email}`);
5119
4771
  }
5120
4772
  });
5121
4773
  function parseList(s) {
5122
4774
  if (!s) return [];
5123
- return s.split(",").map((p2) => p2.trim()).filter(Boolean);
4775
+ return s.split(",").map((p) => p.trim()).filter(Boolean);
5124
4776
  }
5125
4777
  function parseExpiresIn(s) {
5126
4778
  if (!s) return null;
5127
4779
  const m = s.match(/^(\d+)([hdw])$/);
5128
4780
  if (!m) throw new CliError(`Invalid --expires-in "${s}" \u2014 expected forms like 30d, 6h, 2w`);
5129
- const n2 = Number(m[1]);
4781
+ const n = Number(m[1]);
5130
4782
  const unit = m[2];
5131
4783
  const seconds = unit === "h" ? 3600 : unit === "d" ? 86400 : 7 * 86400;
5132
- return Math.floor(Date.now() / 1e3) + n2 * seconds;
4784
+ return Math.floor(Date.now() / 1e3) + n * seconds;
5133
4785
  }
5134
4786
 
5135
4787
  // src/commands/yolo/show.ts
5136
- import { defineCommand as defineCommand39 } from "citty";
5137
- import consola34 from "consola";
5138
- var yoloShowCommand = defineCommand39({
4788
+ import { defineCommand as defineCommand42 } from "citty";
4789
+ import consola36 from "consola";
4790
+ var yoloShowCommand = defineCommand42({
5139
4791
  meta: {
5140
4792
  name: "show",
5141
4793
  description: "Print the YOLO-policy currently set on a DDISA agent"
@@ -5172,17 +4824,17 @@ var yoloShowCommand = defineCommand39({
5172
4824
  console.log(JSON.stringify(policy, null, 2));
5173
4825
  return;
5174
4826
  }
5175
- consola34.info(`YOLO-policy for ${email}`);
5176
- consola34.info(` mode: ${policy.mode}`);
5177
- consola34.info(` allow_patterns: ${policy.allowPatterns.length ? policy.allowPatterns.join(", ") : "(none)"}`);
5178
- consola34.info(` deny_patterns: ${policy.denyPatterns.length ? policy.denyPatterns.join(", ") : "(none)"}`);
5179
- consola34.info(` deny_risk: ${policy.denyRiskThreshold ?? "(none)"}`);
5180
- 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)"}`);
5181
4833
  }
5182
4834
  });
5183
4835
 
5184
4836
  // src/commands/yolo/index.ts
5185
- var yoloCommand = defineCommand40({
4837
+ var yoloCommand = defineCommand43({
5186
4838
  meta: {
5187
4839
  name: "yolo",
5188
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)."
@@ -5195,15 +4847,15 @@ var yoloCommand = defineCommand40({
5195
4847
  });
5196
4848
 
5197
4849
  // src/commands/adapter/index.ts
5198
- import { defineCommand as defineCommand41 } from "citty";
5199
- import consola35 from "consola";
5200
- var adapterCommand = defineCommand41({
4850
+ import { defineCommand as defineCommand44 } from "citty";
4851
+ import consola37 from "consola";
4852
+ var adapterCommand = defineCommand44({
5201
4853
  meta: {
5202
4854
  name: "adapter",
5203
4855
  description: "Manage CLI adapters"
5204
4856
  },
5205
4857
  subCommands: {
5206
- list: defineCommand41({
4858
+ list: defineCommand44({
5207
4859
  meta: {
5208
4860
  name: "list",
5209
4861
  description: "List available adapters"
@@ -5234,7 +4886,7 @@ var adapterCommand = defineCommand41({
5234
4886
  `);
5235
4887
  return;
5236
4888
  }
5237
- consola35.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
4889
+ consola37.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
5238
4890
  for (const a of index2.adapters) {
5239
4891
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
5240
4892
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -5256,7 +4908,7 @@ var adapterCommand = defineCommand41({
5256
4908
  return;
5257
4909
  }
5258
4910
  if (local.length === 0) {
5259
- 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.");
5260
4912
  return;
5261
4913
  }
5262
4914
  for (const a of local) {
@@ -5264,7 +4916,7 @@ var adapterCommand = defineCommand41({
5264
4916
  }
5265
4917
  }
5266
4918
  }),
5267
- install: defineCommand41({
4919
+ install: defineCommand44({
5268
4920
  meta: {
5269
4921
  name: "install",
5270
4922
  description: "Install an adapter from the registry"
@@ -5293,24 +4945,24 @@ var adapterCommand = defineCommand41({
5293
4945
  for (const id of ids) {
5294
4946
  const entry = findAdapter(index, id);
5295
4947
  if (!entry) {
5296
- 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.`);
5297
4949
  continue;
5298
4950
  }
5299
4951
  const conflicts = findConflictingAdapters(entry.executable, id);
5300
4952
  if (conflicts.length > 0) {
5301
- for (const c2 of conflicts) {
5302
- consola35.warn(`Conflicting adapter found: ${c2.path} (id: ${c2.adapterId}, executable: ${c2.executable})`);
5303
- 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}`);
5304
4956
  }
5305
4957
  }
5306
4958
  const result = await installAdapter(entry, { local });
5307
4959
  const verb = result.updated ? "Updated" : "Installed";
5308
- consola35.success(`${verb} ${result.id} \u2192 ${result.path}`);
5309
- consola35.info(`Digest: ${result.digest}`);
4960
+ consola37.success(`${verb} ${result.id} \u2192 ${result.path}`);
4961
+ consola37.info(`Digest: ${result.digest}`);
5310
4962
  }
5311
4963
  }
5312
4964
  }),
5313
- remove: defineCommand41({
4965
+ remove: defineCommand44({
5314
4966
  meta: {
5315
4967
  name: "remove",
5316
4968
  description: "Remove an installed adapter"
@@ -5333,9 +4985,9 @@ var adapterCommand = defineCommand41({
5333
4985
  let failed = false;
5334
4986
  for (const id of ids) {
5335
4987
  if (removeAdapter(id, local)) {
5336
- consola35.success(`Removed adapter: ${id}`);
4988
+ consola37.success(`Removed adapter: ${id}`);
5337
4989
  } else {
5338
- consola35.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
4990
+ consola37.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
5339
4991
  failed = true;
5340
4992
  }
5341
4993
  }
@@ -5343,7 +4995,7 @@ var adapterCommand = defineCommand41({
5343
4995
  throw new CliError("Some adapters could not be removed");
5344
4996
  }
5345
4997
  }),
5346
- info: defineCommand41({
4998
+ info: defineCommand44({
5347
4999
  meta: {
5348
5000
  name: "info",
5349
5001
  description: "Show detailed adapter information"
@@ -5385,7 +5037,7 @@ var adapterCommand = defineCommand41({
5385
5037
  }
5386
5038
  }
5387
5039
  }),
5388
- search: defineCommand41({
5040
+ search: defineCommand44({
5389
5041
  meta: {
5390
5042
  name: "search",
5391
5043
  description: "Search adapters in the registry"
@@ -5417,7 +5069,7 @@ var adapterCommand = defineCommand41({
5417
5069
  return;
5418
5070
  }
5419
5071
  if (results.length === 0) {
5420
- consola35.info(`No adapters matching "${query}"`);
5072
+ consola37.info(`No adapters matching "${query}"`);
5421
5073
  return;
5422
5074
  }
5423
5075
  for (const a of results) {
@@ -5426,7 +5078,7 @@ var adapterCommand = defineCommand41({
5426
5078
  }
5427
5079
  }
5428
5080
  }),
5429
- update: defineCommand41({
5081
+ update: defineCommand44({
5430
5082
  meta: {
5431
5083
  name: "update",
5432
5084
  description: "Update installed adapters"
@@ -5452,33 +5104,33 @@ var adapterCommand = defineCommand41({
5452
5104
  const targetId = args.id ? String(args.id) : void 0;
5453
5105
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
5454
5106
  if (targets.length === 0) {
5455
- consola35.info("No adapters installed to update.");
5107
+ consola37.info("No adapters installed to update.");
5456
5108
  return;
5457
5109
  }
5458
5110
  for (const id of targets) {
5459
5111
  const entry = findAdapter(index, id);
5460
5112
  if (!entry) {
5461
- consola35.warn(`${id}: not found in registry, skipping`);
5113
+ consola37.warn(`${id}: not found in registry, skipping`);
5462
5114
  continue;
5463
5115
  }
5464
5116
  const localDigest = getInstalledDigest(id, false);
5465
5117
  if (localDigest === entry.digest) {
5466
- consola35.info(`${id}: already up to date`);
5118
+ consola37.info(`${id}: already up to date`);
5467
5119
  continue;
5468
5120
  }
5469
5121
  if (localDigest && !args.yes) {
5470
- consola35.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
5471
- consola35.info(` Old: ${localDigest}`);
5472
- consola35.info(` New: ${entry.digest}`);
5473
- 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");
5474
5126
  continue;
5475
5127
  }
5476
5128
  const result = await installAdapter(entry);
5477
- consola35.success(`Updated ${result.id} \u2192 ${result.path}`);
5129
+ consola37.success(`Updated ${result.id} \u2192 ${result.path}`);
5478
5130
  }
5479
5131
  }
5480
5132
  }),
5481
- verify: defineCommand41({
5133
+ verify: defineCommand44({
5482
5134
  meta: {
5483
5135
  name: "verify",
5484
5136
  description: "Verify installed adapter against registry digest"
@@ -5511,7 +5163,7 @@ var adapterCommand = defineCommand41({
5511
5163
  if (!localDigest)
5512
5164
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
5513
5165
  if (localDigest === entry.digest) {
5514
- consola35.success(`${id}: digest matches registry`);
5166
+ consola37.success(`${id}: digest matches registry`);
5515
5167
  } else {
5516
5168
  console.log(` Local: ${localDigest}`);
5517
5169
  console.log(` Registry: ${entry.digest}`);
@@ -5523,11 +5175,20 @@ var adapterCommand = defineCommand41({
5523
5175
  });
5524
5176
 
5525
5177
  // src/commands/run.ts
5526
- import { execFileSync as execFileSync14 } from "child_process";
5178
+ import { execFileSync as execFileSync15 } from "child_process";
5527
5179
  import { hostname as hostname5 } from "os";
5528
5180
  import { basename } from "path";
5529
- import { defineCommand as defineCommand42 } from "citty";
5530
- 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
+ }
5531
5192
  function shouldWaitForGrant(args) {
5532
5193
  return args.wait === true || process.env.APE_WAIT === "1";
5533
5194
  }
@@ -5545,16 +5206,16 @@ function getUserMode() {
5545
5206
  function getAsyncExitCode() {
5546
5207
  const envValue = process.env.APES_ASYNC_EXIT_CODE;
5547
5208
  if (envValue !== void 0 && envValue !== "") {
5548
- const n2 = Number(envValue);
5549
- if (Number.isFinite(n2) && n2 >= 0 && n2 <= 255)
5550
- return Math.floor(n2);
5209
+ const n = Number(envValue);
5210
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
5211
+ return Math.floor(n);
5551
5212
  }
5552
5213
  const cfg = loadConfig();
5553
5214
  const cfgValue = cfg.defaults?.async_exit_code;
5554
5215
  if (cfgValue !== void 0 && cfgValue !== "") {
5555
- const n2 = Number(cfgValue);
5556
- if (Number.isFinite(n2) && n2 >= 0 && n2 <= 255)
5557
- return Math.floor(n2);
5216
+ const n = Number(cfgValue);
5217
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
5218
+ return Math.floor(n);
5558
5219
  }
5559
5220
  return 75;
5560
5221
  }
@@ -5564,7 +5225,7 @@ function printPendingGrantInfo(grant, idp) {
5564
5225
  const statusCmd = `apes grants status ${grant.id}`;
5565
5226
  const executeCmd = `apes grants run ${grant.id}`;
5566
5227
  if (mode === "human") {
5567
- consola36.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
5228
+ consola38.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
5568
5229
  console.log(` Approve in browser: ${approveUrl}`);
5569
5230
  console.log(` Check status: ${statusCmd}`);
5570
5231
  console.log(` Run after approval: ${executeCmd}`);
@@ -5574,7 +5235,7 @@ function printPendingGrantInfo(grant, idp) {
5574
5235
  return;
5575
5236
  }
5576
5237
  const maxMin = getPollMaxMinutes();
5577
- consola36.success(`Grant ${grant.id} created (pending approval)`);
5238
+ consola38.success(`Grant ${grant.id} created (pending approval)`);
5578
5239
  console.log(` Approve: ${approveUrl}`);
5579
5240
  console.log(` Status: ${statusCmd} [--json]`);
5580
5241
  console.log(` Execute: ${executeCmd} --wait`);
@@ -5596,7 +5257,7 @@ function printPendingGrantInfo(grant, idp) {
5596
5257
  console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
5597
5258
  console.log(" grant be reused on subsequent invocations without re-approval.");
5598
5259
  }
5599
- var runCommand = defineCommand42({
5260
+ var runCommand = defineCommand45({
5600
5261
  meta: {
5601
5262
  name: "run",
5602
5263
  description: "Execute a grant-secured command"
@@ -5699,7 +5360,7 @@ async function runShellMode(command, args) {
5699
5360
  }
5700
5361
  } catch {
5701
5362
  }
5702
- consola36.info(`Requesting ape-shell session grant on ${targetHost}`);
5363
+ consola38.info(`Requesting ape-shell session grant on ${targetHost}`);
5703
5364
  const grant = await apiFetch(grantsUrl, {
5704
5365
  method: "POST",
5705
5366
  body: {
@@ -5719,8 +5380,8 @@ async function runShellMode(command, args) {
5719
5380
  host: targetHost
5720
5381
  });
5721
5382
  if (shouldWaitForGrant(args)) {
5722
- consola36.info(`Grant requested: ${grant.id}`);
5723
- consola36.info("Waiting for approval...");
5383
+ consola38.info(`Grant requested: ${grant.id}`);
5384
+ consola38.info("Waiting for approval...");
5724
5385
  const maxWait = 3e5;
5725
5386
  const interval = 3e3;
5726
5387
  const start = Date.now();
@@ -5730,7 +5391,7 @@ async function runShellMode(command, args) {
5730
5391
  break;
5731
5392
  if (status.status === "denied" || status.status === "revoked")
5732
5393
  throw new CliError(`Grant ${status.status}.`);
5733
- await new Promise((r3) => setTimeout(r3, interval));
5394
+ await new Promise((r) => setTimeout(r, interval));
5734
5395
  }
5735
5396
  execShellCommand(command);
5736
5397
  return;
@@ -5751,13 +5412,13 @@ async function tryAdapterModeFromShell(command, idp, args) {
5751
5412
  try {
5752
5413
  resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
5753
5414
  } catch (err) {
5754
- consola36.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
5415
+ consola38.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
5755
5416
  return false;
5756
5417
  }
5757
5418
  try {
5758
5419
  const existingGrantId = await findExistingGrant(resolved, idp);
5759
5420
  if (existingGrantId) {
5760
- consola36.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
5421
+ consola38.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
5761
5422
  const token = await fetchGrantToken(idp, existingGrantId);
5762
5423
  await verifyAndExecute(token, resolved, existingGrantId);
5763
5424
  return true;
@@ -5765,16 +5426,16 @@ async function tryAdapterModeFromShell(command, idp, args) {
5765
5426
  } catch {
5766
5427
  }
5767
5428
  const approval = args.approval ?? "once";
5768
- consola36.info(`Requesting grant for: ${resolved.detail.display}`);
5429
+ consola38.info(`Requesting grant for: ${resolved.detail.display}`);
5769
5430
  const grant = await createShapesGrant(resolved, {
5770
5431
  idp,
5771
5432
  approval,
5772
5433
  reason: args.reason || `ape-shell: ${resolved.detail.display}`
5773
5434
  });
5774
5435
  if (grant.similar_grants?.similar_grants?.length) {
5775
- const n2 = grant.similar_grants.similar_grants.length;
5776
- consola36.info("");
5777
- 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.`);
5778
5439
  }
5779
5440
  notifyGrantPending({
5780
5441
  grantId: grant.id,
@@ -5784,8 +5445,8 @@ async function tryAdapterModeFromShell(command, idp, args) {
5784
5445
  host: args.host || hostname5()
5785
5446
  });
5786
5447
  if (shouldWaitForGrant(args)) {
5787
- consola36.info(`Grant requested: ${grant.id}`);
5788
- 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}`);
5789
5450
  const status = await waitForGrantStatus(idp, grant.id);
5790
5451
  if (status !== "approved")
5791
5452
  throw new CliError(`Grant ${status}`);
@@ -5801,7 +5462,7 @@ function execShellCommand(command) {
5801
5462
  throw new CliError("No command to execute");
5802
5463
  try {
5803
5464
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
5804
- execFileSync14(command[0], command.slice(1), {
5465
+ execFileSync15(command[0], command.slice(1), {
5805
5466
  stdio: "inherit",
5806
5467
  env: inheritedEnv
5807
5468
  });
@@ -5859,7 +5520,7 @@ async function runAdapterMode(command, rawArgs, args) {
5859
5520
  try {
5860
5521
  const existingGrantId = await findExistingGrant(resolved, idp);
5861
5522
  if (existingGrantId) {
5862
- consola36.info(`Reusing existing grant: ${existingGrantId}`);
5523
+ consola38.info(`Reusing existing grant: ${existingGrantId}`);
5863
5524
  const token = await fetchGrantToken(idp, existingGrantId);
5864
5525
  await verifyAndExecute(token, resolved, existingGrantId);
5865
5526
  return;
@@ -5872,18 +5533,18 @@ async function runAdapterMode(command, rawArgs, args) {
5872
5533
  ...args.reason ? { reason: args.reason } : {}
5873
5534
  });
5874
5535
  if (grant.similar_grants?.similar_grants?.length) {
5875
- const n2 = grant.similar_grants.similar_grants.length;
5876
- consola36.info("");
5877
- 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.`);
5878
5539
  if (grant.similar_grants.widened_details?.length) {
5879
5540
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
5880
- consola36.info(` Broader scope: ${wider}`);
5541
+ consola38.info(` Broader scope: ${wider}`);
5881
5542
  }
5882
- consola36.info("");
5543
+ consola38.info("");
5883
5544
  }
5884
5545
  if (shouldWaitForGrant(args)) {
5885
- consola36.info(`Grant requested: ${grant.id}`);
5886
- 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}`);
5887
5548
  const status = await waitForGrantStatus(idp, grant.id);
5888
5549
  if (status !== "approved")
5889
5550
  throw new Error(`Grant ${status}`);
@@ -5903,7 +5564,7 @@ async function runAudienceMode(audience, action, args) {
5903
5564
  const grantsUrl = await getGrantsEndpoint(idp);
5904
5565
  const command = action.split(" ");
5905
5566
  const targetHost = args.host || hostname5();
5906
- const runAs = args.as ?? void 0;
5567
+ const runAs = resolveRunAsTarget(args.as ?? void 0);
5907
5568
  const reusableId = await findReusableAudienceGrant({
5908
5569
  grantsUrl,
5909
5570
  requester: auth.email,
@@ -5916,7 +5577,7 @@ async function runAudienceMode(audience, action, args) {
5916
5577
  const { authz_jwt: authz_jwt2 } = await apiFetch(`${grantsUrl}/${reusableId}/token`, { method: "POST" });
5917
5578
  return executeWithGrantToken({ audience, command, args, token: authz_jwt2 });
5918
5579
  }
5919
- consola36.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
5580
+ consola38.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
5920
5581
  const grant = await apiFetch(grantsUrl, {
5921
5582
  method: "POST",
5922
5583
  body: {
@@ -5933,9 +5594,9 @@ async function runAudienceMode(audience, action, args) {
5933
5594
  printPendingGrantInfo(grant, idp);
5934
5595
  throw new CliExit(getAsyncExitCode());
5935
5596
  }
5936
- consola36.success(`Grant requested: ${grant.id}`);
5937
- consola36.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
5938
- 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...");
5939
5600
  const maxWait = 15 * 60 * 1e3;
5940
5601
  const interval = 3e3;
5941
5602
  const start = Date.now();
@@ -5943,14 +5604,14 @@ async function runAudienceMode(audience, action, args) {
5943
5604
  while (Date.now() - start < maxWait) {
5944
5605
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
5945
5606
  if (status.status === "approved") {
5946
- consola36.success("Grant approved!");
5607
+ consola38.success("Grant approved!");
5947
5608
  approved = true;
5948
5609
  break;
5949
5610
  }
5950
5611
  if (status.status === "denied" || status.status === "revoked") {
5951
5612
  throw new CliError(`Grant ${status.status}.`);
5952
5613
  }
5953
- await new Promise((r3) => setTimeout(r3, interval));
5614
+ await new Promise((r) => setTimeout(r, interval));
5954
5615
  }
5955
5616
  if (!approved) {
5956
5617
  const minutes = Math.round(maxWait / 6e4);
@@ -5958,7 +5619,7 @@ async function runAudienceMode(audience, action, args) {
5958
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.`
5959
5620
  );
5960
5621
  }
5961
- consola36.info("Fetching grant token...");
5622
+ consola38.info("Fetching grant token...");
5962
5623
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
5963
5624
  method: "POST"
5964
5625
  });
@@ -5967,10 +5628,10 @@ async function runAudienceMode(audience, action, args) {
5967
5628
  function executeWithGrantToken(opts) {
5968
5629
  const { audience, command, args, token } = opts;
5969
5630
  if (audience === "escapes") {
5970
- consola36.info(`Executing: ${command.join(" ")}`);
5631
+ consola38.info(`Executing: ${command.join(" ")}`);
5971
5632
  try {
5972
5633
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
5973
- execFileSync14(args["escapes-path"] || "escapes", ["--grant", token, "--", ...command], {
5634
+ execFileSync15(args["escapes-path"] || "escapes", ["--grant", token, "--", ...command], {
5974
5635
  stdio: "inherit",
5975
5636
  env: inheritedEnv
5976
5637
  });
@@ -5987,15 +5648,15 @@ async function findReusableAudienceGrant(opts) {
5987
5648
  const grants = await apiFetch(`${opts.grantsUrl}?requester=${encodeURIComponent(opts.requester)}&status=approved&limit=50`);
5988
5649
  const now = Math.floor(Date.now() / 1e3);
5989
5650
  const match = grants.data.find((g) => {
5990
- const r3 = g.request;
5991
- if (r3.audience !== opts.audience) return false;
5992
- if (r3.target_host !== opts.targetHost) return false;
5993
- if (r3.grant_type === "once") return false;
5994
- if (r3.grant_type === "timed" && g.expires_at && g.expires_at <= now) return false;
5995
- 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 ?? [];
5996
5657
  if (cmd.length !== opts.command.length) return false;
5997
- if (!cmd.every((c2, i) => c2 === opts.command[i])) return false;
5998
- 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;
5999
5660
  return true;
6000
5661
  });
6001
5662
  return match?.id ?? null;
@@ -6006,8 +5667,11 @@ async function findReusableAudienceGrant(opts) {
6006
5667
 
6007
5668
  // src/commands/proxy.ts
6008
5669
  import { spawn as spawn2 } from "child_process";
6009
- import { defineCommand as defineCommand43 } from "citty";
6010
- 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";
6011
5675
 
6012
5676
  // src/proxy/config.ts
6013
5677
  function buildDefaultProxyConfigToml(opts) {
@@ -6044,7 +5708,7 @@ import { spawn } from "child_process";
6044
5708
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
6045
5709
  import { createRequire } from "module";
6046
5710
  import { tmpdir as tmpdir3 } from "os";
6047
- 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";
6048
5712
  var require2 = createRequire(import.meta.url);
6049
5713
  function findProxyBin() {
6050
5714
  const pkgPath = require2.resolve("@openape/proxy/package.json");
@@ -6056,8 +5720,8 @@ function findProxyBin() {
6056
5720
  return resolve3(dirname4(pkgPath), binRel);
6057
5721
  }
6058
5722
  async function startEphemeralProxy(configToml) {
6059
- const tmpDir = mkdtempSync3(join13(tmpdir3(), "openape-proxy-"));
6060
- const configPath = join13(tmpDir, "config.toml");
5723
+ const tmpDir = mkdtempSync3(join14(tmpdir3(), "openape-proxy-"));
5724
+ const configPath = join14(tmpDir, "config.toml");
6061
5725
  writeFileSync8(configPath, configToml, { mode: 384 });
6062
5726
  const binPath = findProxyBin();
6063
5727
  const child = spawn(process.execPath, [binPath, "-c", configPath], {
@@ -6139,6 +5803,51 @@ function waitForListenLine(child) {
6139
5803
  });
6140
5804
  }
6141
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
+
6142
5851
  // src/commands/proxy.ts
6143
5852
  function resolveProxyConfigOptions() {
6144
5853
  const auth = loadAuth();
@@ -6150,10 +5859,10 @@ function resolveProxyConfigOptions() {
6150
5859
  77
6151
5860
  );
6152
5861
  }
6153
- 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}`);
6154
5863
  return { agentEmail: auth.email, idpUrl: auth.idp, mediated: true };
6155
5864
  }
6156
- var proxyCommand = defineCommand43({
5865
+ var proxyCommand = defineCommand46({
6157
5866
  meta: {
6158
5867
  name: "proxy",
6159
5868
  description: "Run a command with HTTPS_PROXY routed through the OpenApe egress proxy."
@@ -6170,17 +5879,33 @@ var proxyCommand = defineCommand43({
6170
5879
  if (wrapped.length === 0) {
6171
5880
  throw new CliError("Usage: apes proxy -- <cmd> [args...]");
6172
5881
  }
5882
+ const reuseHostPort = process.env.OPENAPE_PROXY;
6173
5883
  const reuseUrl = process.env.OPENAPE_PROXY_URL;
6174
5884
  let proxyUrl;
5885
+ let bundle = null;
6175
5886
  let close = null;
6176
- 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) {
6177
5902
  proxyUrl = reuseUrl;
6178
- consola37.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
5903
+ consola39.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
6179
5904
  } else {
6180
5905
  const ephemeral = await startEphemeralProxy(buildDefaultProxyConfigToml(resolveProxyConfigOptions()));
6181
5906
  proxyUrl = ephemeral.url;
6182
5907
  close = ephemeral.close;
6183
- consola37.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
5908
+ consola39.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
6184
5909
  }
6185
5910
  const noProxy = process.env.NO_PROXY ?? process.env.no_proxy ?? "127.0.0.1,localhost";
6186
5911
  const childEnv = {
@@ -6193,30 +5918,42 @@ var proxyCommand = defineCommand43({
6193
5918
  all_proxy: proxyUrl,
6194
5919
  NO_PROXY: noProxy,
6195
5920
  no_proxy: noProxy,
6196
- 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
+ } : {}
6197
5929
  };
6198
- const exitCode = await new Promise((resolveExit) => {
6199
- const child = spawn2(wrapped[0], wrapped.slice(1), {
6200
- stdio: "inherit",
6201
- env: childEnv
6202
- });
6203
- const forward = (sig) => () => child.kill(sig);
6204
- const onSigint = forward("SIGINT");
6205
- const onSigterm = forward("SIGTERM");
6206
- process.on("SIGINT", onSigint);
6207
- process.on("SIGTERM", onSigterm);
6208
- child.once("exit", (code, signal) => {
6209
- process.off("SIGINT", onSigint);
6210
- process.off("SIGTERM", onSigterm);
6211
- if (signal) resolveExit(128 + (signalNumber(signal) ?? 0));
6212
- else resolveExit(code ?? 0);
6213
- });
6214
- child.once("error", (err) => {
6215
- consola37.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
6216
- 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
+ });
6217
5952
  });
6218
- });
6219
- if (close) await close();
5953
+ } finally {
5954
+ bundle?.cleanup();
5955
+ if (close) await close();
5956
+ }
6220
5957
  if (exitCode !== 0) process.exit(exitCode);
6221
5958
  }
6222
5959
  });
@@ -6226,8 +5963,8 @@ function signalNumber(signal) {
6226
5963
  }
6227
5964
 
6228
5965
  // src/commands/explain.ts
6229
- import { defineCommand as defineCommand44 } from "citty";
6230
- var explainCommand = defineCommand44({
5966
+ import { defineCommand as defineCommand47 } from "citty";
5967
+ var explainCommand = defineCommand47({
6231
5968
  meta: {
6232
5969
  name: "explain",
6233
5970
  description: "Show what permission a command would need"
@@ -6265,9 +6002,9 @@ var explainCommand = defineCommand44({
6265
6002
  });
6266
6003
 
6267
6004
  // src/commands/config/get.ts
6268
- import { defineCommand as defineCommand45 } from "citty";
6269
- import consola38 from "consola";
6270
- var configGetCommand = defineCommand45({
6005
+ import { defineCommand as defineCommand48 } from "citty";
6006
+ import consola40 from "consola";
6007
+ var configGetCommand = defineCommand48({
6271
6008
  meta: {
6272
6009
  name: "get",
6273
6010
  description: "Get a configuration value"
@@ -6287,7 +6024,7 @@ var configGetCommand = defineCommand45({
6287
6024
  if (idp)
6288
6025
  console.log(idp);
6289
6026
  else
6290
- consola38.info("No IdP configured.");
6027
+ consola40.info("No IdP configured.");
6291
6028
  break;
6292
6029
  }
6293
6030
  case "email": {
@@ -6295,7 +6032,7 @@ var configGetCommand = defineCommand45({
6295
6032
  if (auth?.email)
6296
6033
  console.log(auth.email);
6297
6034
  else
6298
- consola38.info("Not logged in.");
6035
+ consola40.info("Not logged in.");
6299
6036
  break;
6300
6037
  }
6301
6038
  default: {
@@ -6308,7 +6045,7 @@ var configGetCommand = defineCommand45({
6308
6045
  if (sectionObj && field in sectionObj) {
6309
6046
  console.log(sectionObj[field]);
6310
6047
  } else {
6311
- consola38.info(`Key "${key}" not set.`);
6048
+ consola40.info(`Key "${key}" not set.`);
6312
6049
  }
6313
6050
  } else {
6314
6051
  throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
@@ -6319,9 +6056,9 @@ var configGetCommand = defineCommand45({
6319
6056
  });
6320
6057
 
6321
6058
  // src/commands/config/set.ts
6322
- import { defineCommand as defineCommand46 } from "citty";
6323
- import consola39 from "consola";
6324
- var configSetCommand = defineCommand46({
6059
+ import { defineCommand as defineCommand49 } from "citty";
6060
+ import consola41 from "consola";
6061
+ var configSetCommand = defineCommand49({
6325
6062
  meta: {
6326
6063
  name: "set",
6327
6064
  description: "Set a configuration value"
@@ -6357,12 +6094,12 @@ var configSetCommand = defineCommand46({
6357
6094
  throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
6358
6095
  }
6359
6096
  saveConfig(config);
6360
- consola39.success(`Set ${key} = ${value}`);
6097
+ consola41.success(`Set ${key} = ${value}`);
6361
6098
  }
6362
6099
  });
6363
6100
 
6364
6101
  // src/commands/fetch/index.ts
6365
- import { defineCommand as defineCommand47 } from "citty";
6102
+ import { defineCommand as defineCommand50 } from "citty";
6366
6103
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
6367
6104
  const token = getAuthToken();
6368
6105
  if (!token) {
@@ -6398,13 +6135,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
6398
6135
  throw new CliError(`HTTP ${response.status} ${response.statusText}`);
6399
6136
  }
6400
6137
  }
6401
- var fetchCommand = defineCommand47({
6138
+ var fetchCommand = defineCommand50({
6402
6139
  meta: {
6403
6140
  name: "fetch",
6404
6141
  description: "Make authenticated HTTP requests"
6405
6142
  },
6406
6143
  subCommands: {
6407
- get: defineCommand47({
6144
+ get: defineCommand50({
6408
6145
  meta: {
6409
6146
  name: "get",
6410
6147
  description: "GET request with auth token"
@@ -6430,7 +6167,7 @@ var fetchCommand = defineCommand47({
6430
6167
  await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
6431
6168
  }
6432
6169
  }),
6433
- post: defineCommand47({
6170
+ post: defineCommand50({
6434
6171
  meta: {
6435
6172
  name: "post",
6436
6173
  description: "POST request with auth token"
@@ -6469,8 +6206,8 @@ var fetchCommand = defineCommand47({
6469
6206
  });
6470
6207
 
6471
6208
  // src/commands/mcp/index.ts
6472
- import { defineCommand as defineCommand48 } from "citty";
6473
- var mcpCommand = defineCommand48({
6209
+ import { defineCommand as defineCommand51 } from "citty";
6210
+ var mcpCommand = defineCommand51({
6474
6211
  meta: {
6475
6212
  name: "mcp",
6476
6213
  description: "Start MCP server for AI agents"
@@ -6493,48 +6230,48 @@ var mcpCommand = defineCommand48({
6493
6230
  if (transport !== "stdio" && transport !== "sse") {
6494
6231
  throw new Error('Transport must be "stdio" or "sse"');
6495
6232
  }
6496
- const { startMcpServer } = await import("./server-K77SHBP2.js");
6233
+ const { startMcpServer } = await import("./server-V2V5YHK3.js");
6497
6234
  await startMcpServer(transport, port);
6498
6235
  }
6499
6236
  });
6500
6237
 
6501
6238
  // src/commands/init/index.ts
6502
- import { existsSync as existsSync15, copyFileSync, writeFileSync as writeFileSync9 } from "fs";
6239
+ import { existsSync as existsSync18, copyFileSync, writeFileSync as writeFileSync10 } from "fs";
6503
6240
  import { randomBytes } from "crypto";
6504
- import { execFileSync as execFileSync15 } from "child_process";
6505
- import { join as join14 } from "path";
6506
- import { defineCommand as defineCommand49 } from "citty";
6507
- 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";
6508
6245
  var DEFAULT_IDP_URL = "https://id.openape.at";
6509
6246
  async function downloadTemplate(repo, targetDir) {
6510
6247
  const { downloadTemplate: gigetDownload } = await import("giget");
6511
6248
  await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
6512
6249
  }
6513
6250
  function installDeps(dir) {
6514
- const hasLockFile = (name) => existsSync15(join14(dir, name));
6251
+ const hasLockFile = (name) => existsSync18(join17(dir, name));
6515
6252
  if (hasLockFile("pnpm-lock.yaml")) {
6516
- execFileSync15("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
6253
+ execFileSync16("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
6517
6254
  } else if (hasLockFile("bun.lockb")) {
6518
- execFileSync15("bun", ["install"], { cwd: dir, stdio: "inherit" });
6255
+ execFileSync16("bun", ["install"], { cwd: dir, stdio: "inherit" });
6519
6256
  } else {
6520
- execFileSync15("npm", ["install"], { cwd: dir, stdio: "inherit" });
6257
+ execFileSync16("npm", ["install"], { cwd: dir, stdio: "inherit" });
6521
6258
  }
6522
6259
  }
6523
6260
  async function promptChoice(message, choices) {
6524
- const result = await consola40.prompt(message, { type: "select", options: choices });
6261
+ const result = await consola42.prompt(message, { type: "select", options: choices });
6525
6262
  if (typeof result === "symbol") {
6526
6263
  throw new CliExit(0);
6527
6264
  }
6528
6265
  return result;
6529
6266
  }
6530
6267
  async function promptText(message, defaultValue) {
6531
- 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 });
6532
6269
  if (typeof result === "symbol") {
6533
6270
  throw new CliExit(0);
6534
6271
  }
6535
6272
  return result || defaultValue || "";
6536
6273
  }
6537
- var initCommand = defineCommand49({
6274
+ var initCommand = defineCommand52({
6538
6275
  meta: {
6539
6276
  name: "init",
6540
6277
  description: "Scaffold a new OpenApe project"
@@ -6576,23 +6313,23 @@ var initCommand = defineCommand49({
6576
6313
  });
6577
6314
  async function initSP(targetDir) {
6578
6315
  const dir = targetDir || "my-app";
6579
- if (existsSync15(join14(dir, "package.json"))) {
6316
+ if (existsSync18(join17(dir, "package.json"))) {
6580
6317
  throw new CliError(`Directory "${dir}" already contains a project.`);
6581
6318
  }
6582
- consola40.start("Scaffolding SP starter...");
6319
+ consola42.start("Scaffolding SP starter...");
6583
6320
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
6584
- consola40.success("Scaffolded from openape-sp-starter");
6585
- consola40.start("Installing dependencies...");
6321
+ consola42.success("Scaffolded from openape-sp-starter");
6322
+ consola42.start("Installing dependencies...");
6586
6323
  installDeps(dir);
6587
- consola40.success("Dependencies installed");
6588
- const envExample = join14(dir, ".env.example");
6589
- const envFile = join14(dir, ".env");
6590
- 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)) {
6591
6328
  copyFileSync(envExample, envFile);
6592
- consola40.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
6329
+ consola42.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
6593
6330
  }
6594
6331
  console.log("");
6595
- consola40.box([
6332
+ consola42.box([
6596
6333
  `cd ${dir}`,
6597
6334
  "npm run dev",
6598
6335
  "",
@@ -6601,7 +6338,7 @@ async function initSP(targetDir) {
6601
6338
  }
6602
6339
  async function initIdP(targetDir) {
6603
6340
  const dir = targetDir || "my-idp";
6604
- if (existsSync15(join14(dir, "package.json"))) {
6341
+ if (existsSync18(join17(dir, "package.json"))) {
6605
6342
  throw new CliError(`Directory "${dir}" already contains a project.`);
6606
6343
  }
6607
6344
  const domain = await promptText("Domain for the IdP", "localhost");
@@ -6611,15 +6348,15 @@ async function initIdP(targetDir) {
6611
6348
  "s3 (S3-compatible)"
6612
6349
  ]);
6613
6350
  const adminEmail = await promptText("Admin email");
6614
- consola40.start("Scaffolding IdP starter...");
6351
+ consola42.start("Scaffolding IdP starter...");
6615
6352
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
6616
- consola40.success("Scaffolded from openape-idp-starter");
6617
- consola40.start("Installing dependencies...");
6353
+ consola42.success("Scaffolded from openape-idp-starter");
6354
+ consola42.start("Installing dependencies...");
6618
6355
  installDeps(dir);
6619
- consola40.success("Dependencies installed");
6356
+ consola42.success("Dependencies installed");
6620
6357
  const sessionSecret = randomBytes(32).toString("hex");
6621
6358
  const managementToken = randomBytes(32).toString("hex");
6622
- consola40.success("Secrets generated");
6359
+ consola42.success("Secrets generated");
6623
6360
  const isLocalhost = domain === "localhost";
6624
6361
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
6625
6362
  const envContent = [
@@ -6633,11 +6370,11 @@ async function initIdP(targetDir) {
6633
6370
  `NUXT_OPENAPE_RP_ID=${domain}`,
6634
6371
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
6635
6372
  ].join("\n");
6636
- writeFileSync9(join14(dir, ".env"), `${envContent}
6373
+ writeFileSync10(join17(dir, ".env"), `${envContent}
6637
6374
  `, { mode: 384 });
6638
- consola40.success(".env created");
6375
+ consola42.success(".env created");
6639
6376
  console.log("");
6640
- consola40.box([
6377
+ consola42.box([
6641
6378
  `cd ${dir}`,
6642
6379
  "npm run dev",
6643
6380
  "",
@@ -6654,11 +6391,11 @@ async function initIdP(targetDir) {
6654
6391
 
6655
6392
  // src/commands/enroll.ts
6656
6393
  import { Buffer as Buffer5 } from "buffer";
6657
- import { existsSync as existsSync16, readFileSync as readFileSync12 } from "fs";
6394
+ import { existsSync as existsSync19, readFileSync as readFileSync14 } from "fs";
6658
6395
  import { execFile as execFile2 } from "child_process";
6659
6396
  import { sign as sign2 } from "crypto";
6660
- import { defineCommand as defineCommand50 } from "citty";
6661
- import consola41 from "consola";
6397
+ import { defineCommand as defineCommand53 } from "citty";
6398
+ import consola43 from "consola";
6662
6399
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
6663
6400
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
6664
6401
  var POLL_INTERVAL = 3e3;
@@ -6670,7 +6407,7 @@ function openBrowser2(url) {
6670
6407
  }
6671
6408
  async function pollForEnrollment(idp, agentEmail, keyPath) {
6672
6409
  const resolvedKey = resolveKeyPath(keyPath);
6673
- const keyContent = readFileSync12(resolvedKey, "utf-8");
6410
+ const keyContent = readFileSync14(resolvedKey, "utf-8");
6674
6411
  const privateKey = loadEd25519PrivateKey(keyContent);
6675
6412
  const challengeUrl = await getAgentChallengeEndpoint(idp);
6676
6413
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -6701,7 +6438,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
6701
6438
  }
6702
6439
  throw new Error("Enrollment timed out. Please check the browser and try again.");
6703
6440
  }
6704
- var enrollCommand = defineCommand50({
6441
+ var enrollCommand = defineCommand53({
6705
6442
  meta: {
6706
6443
  name: "enroll",
6707
6444
  description: "Enroll an agent with an Identity Provider"
@@ -6721,48 +6458,48 @@ var enrollCommand = defineCommand50({
6721
6458
  }
6722
6459
  },
6723
6460
  async run({ args }) {
6724
- const idp = args.idp || await consola41.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r3) => {
6725
- if (typeof r3 === "symbol") throw new CliExit(0);
6726
- 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;
6727
6464
  }) || DEFAULT_IDP_URL2;
6728
- const agentName = args.name || await consola41.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r3) => {
6729
- if (typeof r3 === "symbol") throw new CliExit(0);
6730
- 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;
6731
6468
  });
6732
6469
  if (!agentName) {
6733
6470
  throw new CliError("Agent name is required.");
6734
6471
  }
6735
- const keyPath = args.key || await consola41.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r3) => {
6736
- if (typeof r3 === "symbol") throw new CliExit(0);
6737
- 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;
6738
6475
  }) || DEFAULT_KEY_PATH;
6739
6476
  const resolvedKey = resolveKeyPath(keyPath);
6740
6477
  let publicKey;
6741
- if (existsSync16(resolvedKey)) {
6478
+ if (existsSync19(resolvedKey)) {
6742
6479
  publicKey = readPublicKey(resolvedKey);
6743
- consola41.success(`Using existing key ${keyPath}`);
6480
+ consola43.success(`Using existing key ${keyPath}`);
6744
6481
  } else {
6745
- consola41.start(`Generating Ed25519 key pair at ${keyPath}...`);
6482
+ consola43.start(`Generating Ed25519 key pair at ${keyPath}...`);
6746
6483
  publicKey = generateAndSaveKey(keyPath);
6747
- consola41.success(`Key pair generated at ${keyPath}`);
6484
+ consola43.success(`Key pair generated at ${keyPath}`);
6748
6485
  }
6749
6486
  const encodedKey = encodeURIComponent(publicKey);
6750
6487
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
6751
- consola41.info("Opening browser for enrollment...");
6752
- consola41.info(`\u2192 ${idp}/enroll`);
6488
+ consola43.info("Opening browser for enrollment...");
6489
+ consola43.info(`\u2192 ${idp}/enroll`);
6753
6490
  openBrowser2(enrollUrl);
6754
6491
  console.log("");
6755
- const agentEmail = await consola41.prompt(
6492
+ const agentEmail = await consola43.prompt(
6756
6493
  "Agent email (shown in browser after enrollment)",
6757
6494
  { type: "text", placeholder: `agent+${agentName}@...` }
6758
- ).then((r3) => {
6759
- if (typeof r3 === "symbol") throw new CliExit(0);
6760
- return r3;
6495
+ ).then((r) => {
6496
+ if (typeof r === "symbol") throw new CliExit(0);
6497
+ return r;
6761
6498
  });
6762
6499
  if (!agentEmail) {
6763
6500
  throw new CliError("Agent email is required to verify enrollment.");
6764
6501
  }
6765
- consola41.start("Verifying enrollment...");
6502
+ consola43.start("Verifying enrollment...");
6766
6503
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
6767
6504
  saveAuth({
6768
6505
  idp,
@@ -6774,18 +6511,18 @@ var enrollCommand = defineCommand50({
6774
6511
  config.defaults = { ...config.defaults, idp };
6775
6512
  config.agent = { key: keyPath, email: agentEmail };
6776
6513
  saveConfig(config);
6777
- consola41.success(`Agent enrolled as ${agentEmail}`);
6778
- consola41.success("Config saved to ~/.config/apes/");
6514
+ consola43.success(`Agent enrolled as ${agentEmail}`);
6515
+ consola43.success("Config saved to ~/.config/apes/");
6779
6516
  console.log("");
6780
- consola41.info("Verify with: apes whoami");
6517
+ consola43.info("Verify with: apes whoami");
6781
6518
  }
6782
6519
  });
6783
6520
 
6784
6521
  // src/commands/register-user.ts
6785
- import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
6786
- import { defineCommand as defineCommand51 } from "citty";
6787
- import consola42 from "consola";
6788
- 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({
6789
6526
  meta: {
6790
6527
  name: "register-user",
6791
6528
  description: "Register a sub-user with SSH key"
@@ -6821,8 +6558,8 @@ var registerUserCommand = defineCommand51({
6821
6558
  throw new CliError("No IdP URL configured. Run `apes login` first.");
6822
6559
  }
6823
6560
  let publicKey = args.key;
6824
- if (existsSync17(args.key)) {
6825
- publicKey = readFileSync13(args.key, "utf-8").trim();
6561
+ if (existsSync20(args.key)) {
6562
+ publicKey = readFileSync15(args.key, "utf-8").trim();
6826
6563
  }
6827
6564
  if (!publicKey.startsWith("ssh-ed25519 ")) {
6828
6565
  throw new CliError("Public key must be in ssh-ed25519 format.");
@@ -6840,18 +6577,18 @@ var registerUserCommand = defineCommand51({
6840
6577
  ...userType ? { type: userType } : {}
6841
6578
  }
6842
6579
  });
6843
- 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})`);
6844
6581
  }
6845
6582
  });
6846
6583
 
6847
6584
  // src/commands/utils/index.ts
6848
- import { defineCommand as defineCommand53 } from "citty";
6585
+ import { defineCommand as defineCommand56 } from "citty";
6849
6586
 
6850
6587
  // src/commands/utils/dig.ts
6851
- import { defineCommand as defineCommand52 } from "citty";
6852
- import consola43 from "consola";
6588
+ import { defineCommand as defineCommand55 } from "citty";
6589
+ import consola45 from "consola";
6853
6590
  import { resolveDDISA as resolveDDISA2 } from "@openape/core";
6854
- var digCommand = defineCommand52({
6591
+ var digCommand = defineCommand55({
6855
6592
  meta: {
6856
6593
  name: "dig",
6857
6594
  description: "Resolve DDISA IdP for a domain or email (admin/diag tool)"
@@ -6924,12 +6661,12 @@ var digCommand = defineCommand52({
6924
6661
  console.log(` domain: ${domain}`);
6925
6662
  console.log("");
6926
6663
  if (!result.ddisa.found) {
6927
- consola43.warn(`No DDISA record at _ddisa.${domain}`);
6664
+ consola45.warn(`No DDISA record at _ddisa.${domain}`);
6928
6665
  if (result.hint) console.log(`
6929
6666
  ${result.hint}`);
6930
6667
  throw new CliError(`No DDISA record found for ${domain}`);
6931
6668
  }
6932
- consola43.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
6669
+ consola45.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
6933
6670
  console.log(` Version: ${result.ddisa.version || "ddisa1"}`);
6934
6671
  console.log(` IdP URL: ${result.ddisa.idp}`);
6935
6672
  if (result.ddisa.mode) console.log(` Mode: ${result.ddisa.mode}`);
@@ -6939,13 +6676,13 @@ ${result.hint}`);
6939
6676
  return;
6940
6677
  }
6941
6678
  if (result.idpDiscovery.ok) {
6942
- consola43.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
6679
+ consola45.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
6943
6680
  if (result.idpDiscovery.issuer) console.log(` Issuer: ${result.idpDiscovery.issuer}`);
6944
6681
  if (result.idpDiscovery.ddisaVersion) console.log(` DDISA: v${result.idpDiscovery.ddisaVersion}`);
6945
6682
  if (result.idpDiscovery.authMethods?.length) console.log(` Auth: ${result.idpDiscovery.authMethods.join(", ")}`);
6946
6683
  if (result.idpDiscovery.grantTypes?.length) console.log(` Grants: ${result.idpDiscovery.grantTypes.join(", ")}`);
6947
6684
  } else {
6948
- 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})` : ""}`);
6949
6686
  if (result.hint) console.log(`
6950
6687
  ${result.hint}`);
6951
6688
  throw new CliError(`IdP at ${result.ddisa.idp} not reachable`);
@@ -6954,7 +6691,7 @@ ${result.hint}`);
6954
6691
  });
6955
6692
 
6956
6693
  // src/commands/utils/index.ts
6957
- var utilsCommand = defineCommand53({
6694
+ var utilsCommand = defineCommand56({
6958
6695
  meta: {
6959
6696
  name: "utils",
6960
6697
  description: "Admin/diagnostic utilities (dig, \u2026)"
@@ -6965,12 +6702,12 @@ var utilsCommand = defineCommand53({
6965
6702
  });
6966
6703
 
6967
6704
  // src/commands/sessions/index.ts
6968
- import { defineCommand as defineCommand56 } from "citty";
6705
+ import { defineCommand as defineCommand59 } from "citty";
6969
6706
 
6970
6707
  // src/commands/sessions/list.ts
6971
- import { defineCommand as defineCommand54 } from "citty";
6972
- import consola44 from "consola";
6973
- var sessionsListCommand = defineCommand54({
6708
+ import { defineCommand as defineCommand57 } from "citty";
6709
+ import consola46 from "consola";
6710
+ var sessionsListCommand = defineCommand57({
6974
6711
  meta: {
6975
6712
  name: "list",
6976
6713
  description: "List your active refresh-token families (one per logged-in device)."
@@ -6988,7 +6725,7 @@ var sessionsListCommand = defineCommand54({
6988
6725
  return;
6989
6726
  }
6990
6727
  if (result.data.length === 0) {
6991
- consola44.info("No active sessions.");
6728
+ consola46.info("No active sessions.");
6992
6729
  return;
6993
6730
  }
6994
6731
  for (const f of result.data) {
@@ -7000,9 +6737,9 @@ var sessionsListCommand = defineCommand54({
7000
6737
  });
7001
6738
 
7002
6739
  // src/commands/sessions/remove.ts
7003
- import { defineCommand as defineCommand55 } from "citty";
7004
- import consola45 from "consola";
7005
- var sessionsRemoveCommand = defineCommand55({
6740
+ import { defineCommand as defineCommand58 } from "citty";
6741
+ import consola47 from "consola";
6742
+ var sessionsRemoveCommand = defineCommand58({
7006
6743
  meta: {
7007
6744
  name: "remove",
7008
6745
  description: "Revoke one of your active refresh-token families by id."
@@ -7018,12 +6755,12 @@ var sessionsRemoveCommand = defineCommand55({
7018
6755
  const id = String(args.familyId).trim();
7019
6756
  if (!id) throw new CliError("familyId required");
7020
6757
  await apiFetch(`/api/me/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
7021
- 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.`);
7022
6759
  }
7023
6760
  });
7024
6761
 
7025
6762
  // src/commands/sessions/index.ts
7026
- var sessionsCommand = defineCommand56({
6763
+ var sessionsCommand = defineCommand59({
7027
6764
  meta: {
7028
6765
  name: "sessions",
7029
6766
  description: "Manage your active refresh-token sessions across devices"
@@ -7035,10 +6772,10 @@ var sessionsCommand = defineCommand56({
7035
6772
  });
7036
6773
 
7037
6774
  // src/commands/dns-check.ts
7038
- import { defineCommand as defineCommand57 } from "citty";
7039
- import consola46 from "consola";
6775
+ import { defineCommand as defineCommand60 } from "citty";
6776
+ import consola48 from "consola";
7040
6777
  import { resolveDDISA as resolveDDISA3 } from "@openape/core";
7041
- var dnsCheckCommand = defineCommand57({
6778
+ var dnsCheckCommand = defineCommand60({
7042
6779
  meta: {
7043
6780
  name: "dns-check",
7044
6781
  description: "Validate DDISA DNS TXT records for a domain"
@@ -7052,7 +6789,7 @@ var dnsCheckCommand = defineCommand57({
7052
6789
  },
7053
6790
  async run({ args }) {
7054
6791
  const domain = args.domain;
7055
- consola46.start(`Checking _ddisa.${domain}...`);
6792
+ consola48.start(`Checking _ddisa.${domain}...`);
7056
6793
  try {
7057
6794
  const result = await resolveDDISA3(domain);
7058
6795
  if (!result) {
@@ -7061,7 +6798,7 @@ var dnsCheckCommand = defineCommand57({
7061
6798
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
7062
6799
  throw new CliError(`No DDISA record found for ${domain}`);
7063
6800
  }
7064
- consola46.success(`_ddisa.${domain} \u2192 ${result.idp}`);
6801
+ consola48.success(`_ddisa.${domain} \u2192 ${result.idp}`);
7065
6802
  console.log("");
7066
6803
  console.log(` Version: ${result.version || "ddisa1"}`);
7067
6804
  console.log(` IdP URL: ${result.idp}`);
@@ -7070,14 +6807,14 @@ var dnsCheckCommand = defineCommand57({
7070
6807
  if (result.priority !== void 0)
7071
6808
  console.log(` Priority: ${result.priority}`);
7072
6809
  console.log("");
7073
- consola46.start(`Verifying IdP at ${result.idp}...`);
6810
+ consola48.start(`Verifying IdP at ${result.idp}...`);
7074
6811
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
7075
6812
  if (!discoResp.ok) {
7076
- 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}?`);
7077
6814
  return;
7078
6815
  }
7079
6816
  const disco = await discoResp.json();
7080
- consola46.success(`IdP is reachable`);
6817
+ consola48.success(`IdP is reachable`);
7081
6818
  console.log(` Issuer: ${disco.issuer}`);
7082
6819
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
7083
6820
  if (disco.ddisa_auth_methods_supported) {
@@ -7095,7 +6832,7 @@ var dnsCheckCommand = defineCommand57({
7095
6832
  // src/commands/health.ts
7096
6833
  import { exec } from "child_process";
7097
6834
  import { promisify } from "util";
7098
- import { defineCommand as defineCommand58 } from "citty";
6835
+ import { defineCommand as defineCommand61 } from "citty";
7099
6836
  var execAsync = promisify(exec);
7100
6837
  async function resolveApeShellPath() {
7101
6838
  try {
@@ -7131,7 +6868,7 @@ async function bestEffortGrantCount(idp) {
7131
6868
  }
7132
6869
  }
7133
6870
  async function runHealth(args) {
7134
- const version = true ? "1.24.0" : "0.0.0";
6871
+ const version = true ? "1.25.0" : "0.0.0";
7135
6872
  const auth = loadAuth();
7136
6873
  if (!auth) {
7137
6874
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -7194,7 +6931,7 @@ async function runHealth(args) {
7194
6931
  throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
7195
6932
  }
7196
6933
  }
7197
- var healthCommand = defineCommand58({
6934
+ var healthCommand = defineCommand61({
7198
6935
  meta: {
7199
6936
  name: "health",
7200
6937
  description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
@@ -7212,8 +6949,8 @@ var healthCommand = defineCommand58({
7212
6949
  });
7213
6950
 
7214
6951
  // src/commands/workflows.ts
7215
- import { defineCommand as defineCommand59 } from "citty";
7216
- import consola47 from "consola";
6952
+ import { defineCommand as defineCommand62 } from "citty";
6953
+ import consola49 from "consola";
7217
6954
 
7218
6955
  // src/guides/index.ts
7219
6956
  var guides = [
@@ -7263,7 +7000,7 @@ var guides = [
7263
7000
  ];
7264
7001
 
7265
7002
  // src/commands/workflows.ts
7266
- var workflowsCommand = defineCommand59({
7003
+ var workflowsCommand = defineCommand62({
7267
7004
  meta: {
7268
7005
  name: "workflows",
7269
7006
  description: "Discover workflow guides"
@@ -7284,7 +7021,7 @@ var workflowsCommand = defineCommand59({
7284
7021
  if (args.id) {
7285
7022
  const guide = guides.find((g) => g.id === String(args.id));
7286
7023
  if (!guide) {
7287
- consola47.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
7024
+ consola49.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
7288
7025
  throw new CliError(`Guide not found: ${args.id}`);
7289
7026
  }
7290
7027
  if (args.json) {
@@ -7324,26 +7061,26 @@ var workflowsCommand = defineCommand59({
7324
7061
  });
7325
7062
 
7326
7063
  // src/version-check.ts
7327
- import { existsSync as existsSync18, mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync10 } from "fs";
7328
- import { homedir as homedir13 } from "os";
7329
- import { join as join15 } from "path";
7330
- 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";
7331
7068
  var PACKAGE_NAME = "@openape/apes";
7332
7069
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
7333
- var CACHE_FILE = join15(homedir13(), ".config", "apes", ".version-check.json");
7070
+ var CACHE_FILE = join18(homedir15(), ".config", "apes", ".version-check.json");
7334
7071
  function readCache() {
7335
- if (!existsSync18(CACHE_FILE)) return null;
7072
+ if (!existsSync21(CACHE_FILE)) return null;
7336
7073
  try {
7337
- return JSON.parse(readFileSync14(CACHE_FILE, "utf-8"));
7074
+ return JSON.parse(readFileSync16(CACHE_FILE, "utf-8"));
7338
7075
  } catch {
7339
7076
  return null;
7340
7077
  }
7341
7078
  }
7342
7079
  function writeCache(entry) {
7343
7080
  try {
7344
- const dir = join15(homedir13(), ".config", "apes");
7345
- if (!existsSync18(dir)) mkdirSync6(dir, { recursive: true, mode: 448 });
7346
- 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 });
7347
7084
  } catch {
7348
7085
  }
7349
7086
  }
@@ -7372,7 +7109,7 @@ async function fetchLatestVersion() {
7372
7109
  }
7373
7110
  function warnIfBehind(currentVersion, latest) {
7374
7111
  if (compareSemver(currentVersion, latest) < 0) {
7375
- consola48.warn(
7112
+ consola50.warn(
7376
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.)`
7377
7114
  );
7378
7115
  }
@@ -7404,10 +7141,10 @@ if (shellRewrite) {
7404
7141
  if (shellRewrite.action === "rewrite") {
7405
7142
  process.argv = shellRewrite.argv;
7406
7143
  } else if (shellRewrite.action === "version") {
7407
- console.log(`ape-shell ${"1.24.0"} (OpenApe DDISA shell wrapper)`);
7144
+ console.log(`ape-shell ${"1.25.0"} (OpenApe DDISA shell wrapper)`);
7408
7145
  process.exit(0);
7409
7146
  } else if (shellRewrite.action === "help") {
7410
- console.log(`ape-shell ${"1.24.0"} \u2014 OpenApe DDISA shell wrapper`);
7147
+ console.log(`ape-shell ${"1.25.0"} \u2014 OpenApe DDISA shell wrapper`);
7411
7148
  console.log("");
7412
7149
  console.log("Usage:");
7413
7150
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -7422,7 +7159,7 @@ if (shellRewrite) {
7422
7159
  console.log(" --help, -h Show this help message");
7423
7160
  process.exit(0);
7424
7161
  } else if (shellRewrite.action === "interactive") {
7425
- const { runInteractiveShell } = await import("./orchestrator-2QS5KXFL.js");
7162
+ const { runInteractiveShell } = await import("./orchestrator-CIDV7OGM.js");
7426
7163
  await runInteractiveShell();
7427
7164
  process.exit(0);
7428
7165
  } else {
@@ -7431,7 +7168,7 @@ if (shellRewrite) {
7431
7168
  }
7432
7169
  }
7433
7170
  var debug = process.argv.includes("--debug");
7434
- var grantsCommand = defineCommand60({
7171
+ var grantsCommand = defineCommand63({
7435
7172
  meta: {
7436
7173
  name: "grants",
7437
7174
  description: "Grant management"
@@ -7452,7 +7189,7 @@ var grantsCommand = defineCommand60({
7452
7189
  "delegation-revoke": delegationRevokeCommand
7453
7190
  }
7454
7191
  });
7455
- var configCommand = defineCommand60({
7192
+ var configCommand = defineCommand63({
7456
7193
  meta: {
7457
7194
  name: "config",
7458
7195
  description: "Configuration management"
@@ -7462,10 +7199,10 @@ var configCommand = defineCommand60({
7462
7199
  set: configSetCommand
7463
7200
  }
7464
7201
  });
7465
- var main = defineCommand60({
7202
+ var main = defineCommand63({
7466
7203
  meta: {
7467
7204
  name: "apes",
7468
- version: "1.24.0",
7205
+ version: "1.25.0",
7469
7206
  description: "Unified CLI for OpenApe"
7470
7207
  },
7471
7208
  subCommands: {
@@ -7480,6 +7217,7 @@ var main = defineCommand60({
7480
7217
  whoami: whoamiCommand,
7481
7218
  health: healthCommand,
7482
7219
  grants: grantsCommand,
7220
+ agent: agentCommand,
7483
7221
  agents: agentsCommand,
7484
7222
  nest: nestCommand,
7485
7223
  yolo: yoloCommand,
@@ -7513,29 +7251,29 @@ var NO_REFRESH_COMMANDS = /* @__PURE__ */ new Set([
7513
7251
  async function maybeRefreshAuth() {
7514
7252
  const sub = process.argv[2];
7515
7253
  if (!sub || NO_REFRESH_COMMANDS.has(sub)) return;
7516
- const { loadAuth: loadAuth2 } = await import("./config-DA2L3XOV.js");
7254
+ const { loadAuth: loadAuth2 } = await import("./config-MOB5DJ6H.js");
7517
7255
  if (!loadAuth2()) return;
7518
7256
  try {
7519
- const { ensureFreshToken } = await import("./http-JWS5LV2U.js");
7257
+ const { ensureFreshToken } = await import("./http-6OKWT52Z.js");
7520
7258
  await ensureFreshToken();
7521
7259
  } catch {
7522
7260
  }
7523
7261
  }
7524
7262
  await maybeRefreshAuth();
7525
- await maybeWarnStaleVersion("1.24.0").catch(() => {
7263
+ await maybeWarnStaleVersion("1.25.0").catch(() => {
7526
7264
  });
7527
7265
  runMain(main).catch((err) => {
7528
7266
  if (err instanceof CliExit) {
7529
7267
  process.exit(err.exitCode);
7530
7268
  }
7531
7269
  if (err instanceof CliError) {
7532
- consola49.error(err.message);
7270
+ consola51.error(err.message);
7533
7271
  process.exit(err.exitCode);
7534
7272
  }
7535
7273
  if (debug) {
7536
- consola49.error(err);
7274
+ consola51.error(err);
7537
7275
  } else {
7538
- 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));
7539
7277
  }
7540
7278
  process.exit(1);
7541
7279
  });