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