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