@openape/apes 0.5.5 → 0.6.1
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/README.md +144 -0
- package/dist/chunk-G3Q2TMAI.js +1331 -0
- package/dist/chunk-G3Q2TMAI.js.map +1 -0
- package/dist/{chunk-AGHP6MNV.js → chunk-ZSJU7IXE.js} +19 -1
- package/dist/chunk-ZSJU7IXE.js.map +1 -0
- package/dist/cli.js +921 -263
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +216 -2
- package/dist/index.js +33 -23
- package/dist/index.js.map +1 -1
- package/dist/{server-R2EVEMKX.js → server-FR6GFS3S.js} +11 -14
- package/dist/server-FR6GFS3S.js.map +1 -0
- package/package.json +16 -8
- package/dist/chunk-AGHP6MNV.js.map +0 -1
- package/dist/chunk-KXESKY4X.js +0 -278
- package/dist/chunk-KXESKY4X.js.map +0 -1
- package/dist/server-R2EVEMKX.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,29 +1,72 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CliError,
|
|
4
|
+
CliExit,
|
|
3
5
|
parseDuration
|
|
4
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-ZSJU7IXE.js";
|
|
5
7
|
import {
|
|
6
8
|
loadEd25519PrivateKey
|
|
7
9
|
} from "./chunk-KVBHBOED.js";
|
|
8
10
|
import {
|
|
9
11
|
ApiError,
|
|
10
12
|
apiFetch,
|
|
13
|
+
buildStructuredCliGrantRequest,
|
|
11
14
|
clearAuth,
|
|
15
|
+
createShapesGrant,
|
|
16
|
+
extractOption,
|
|
17
|
+
extractShellCommandString,
|
|
18
|
+
extractWrappedCommand,
|
|
19
|
+
fetchGrantToken,
|
|
20
|
+
fetchRegistry,
|
|
21
|
+
findAdapter,
|
|
22
|
+
findConflictingAdapters,
|
|
23
|
+
findExistingGrant,
|
|
12
24
|
getAgentAuthenticateEndpoint,
|
|
13
25
|
getAgentChallengeEndpoint,
|
|
14
26
|
getAuthToken,
|
|
15
27
|
getDelegationsEndpoint,
|
|
16
28
|
getGrantsEndpoint,
|
|
17
29
|
getIdpUrl,
|
|
30
|
+
getInstalledDigest,
|
|
31
|
+
installAdapter,
|
|
32
|
+
isInstalled,
|
|
33
|
+
loadAdapter,
|
|
18
34
|
loadAuth,
|
|
19
35
|
loadConfig,
|
|
36
|
+
loadOrInstallAdapter,
|
|
37
|
+
parseShellCommand,
|
|
38
|
+
removeAdapter,
|
|
39
|
+
resolveCapabilityRequest,
|
|
40
|
+
resolveCommand,
|
|
20
41
|
saveAuth,
|
|
21
|
-
saveConfig
|
|
22
|
-
|
|
42
|
+
saveConfig,
|
|
43
|
+
searchAdapters,
|
|
44
|
+
verifyAndExecute,
|
|
45
|
+
waitForGrantStatus
|
|
46
|
+
} from "./chunk-G3Q2TMAI.js";
|
|
23
47
|
|
|
24
48
|
// src/cli.ts
|
|
25
|
-
import
|
|
26
|
-
|
|
49
|
+
import consola25 from "consola";
|
|
50
|
+
|
|
51
|
+
// src/ape-shell.ts
|
|
52
|
+
import path from "path";
|
|
53
|
+
function rewriteApeShellArgs(argv) {
|
|
54
|
+
const invokedAs = path.basename(argv[1] ?? "");
|
|
55
|
+
if (invokedAs !== "ape-shell" && invokedAs !== "ape-shell.js")
|
|
56
|
+
return null;
|
|
57
|
+
const shellArgs = argv.slice(2);
|
|
58
|
+
if (shellArgs[0] === "-c" && shellArgs.length > 1) {
|
|
59
|
+
return { action: "rewrite", argv: [argv[0], argv[1], "run", "--shell", "--", "bash", "-c", ...shellArgs.slice(1)] };
|
|
60
|
+
}
|
|
61
|
+
if (shellArgs[0] === "--version")
|
|
62
|
+
return { action: "version" };
|
|
63
|
+
if (shellArgs[0] === "--help" || shellArgs[0] === "-h")
|
|
64
|
+
return { action: "help" };
|
|
65
|
+
return { action: "error" };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/cli.ts
|
|
69
|
+
import { defineCommand as defineCommand31, runMain } from "citty";
|
|
27
70
|
|
|
28
71
|
// src/commands/auth/login.ts
|
|
29
72
|
import { Buffer } from "buffer";
|
|
@@ -57,8 +100,7 @@ var loginCommand = defineCommand({
|
|
|
57
100
|
const config = loadConfig();
|
|
58
101
|
const idp = args.idp || process.env.APES_IDP || process.env.GRAPES_IDP || config.defaults?.idp;
|
|
59
102
|
if (!idp) {
|
|
60
|
-
|
|
61
|
-
return process.exit(1);
|
|
103
|
+
throw new CliError("IdP URL required. Use --idp <url> or set APES_IDP.");
|
|
62
104
|
}
|
|
63
105
|
if (args.key) {
|
|
64
106
|
await loginWithKey(idp, args.key, args.email);
|
|
@@ -87,7 +129,7 @@ async function loginWithPKCE(idp) {
|
|
|
87
129
|
authUrl.searchParams.set("state", state);
|
|
88
130
|
authUrl.searchParams.set("nonce", nonce);
|
|
89
131
|
authUrl.searchParams.set("scope", "openid email profile offline_access");
|
|
90
|
-
const code = await new Promise((
|
|
132
|
+
const code = await new Promise((resolve3, reject) => {
|
|
91
133
|
const server = createServer((req, res) => {
|
|
92
134
|
const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
|
|
93
135
|
if (url.pathname === "/callback") {
|
|
@@ -104,7 +146,7 @@ async function loginWithPKCE(idp) {
|
|
|
104
146
|
res.writeHead(200, { "Content-Type": "text/html" });
|
|
105
147
|
res.end("<h1>Login successful!</h1><p>You can close this window.</p>");
|
|
106
148
|
server.close();
|
|
107
|
-
|
|
149
|
+
resolve3(authCode);
|
|
108
150
|
return;
|
|
109
151
|
}
|
|
110
152
|
res.writeHead(400);
|
|
@@ -137,14 +179,12 @@ async function loginWithPKCE(idp) {
|
|
|
137
179
|
});
|
|
138
180
|
if (!tokenResponse.ok) {
|
|
139
181
|
const text = await tokenResponse.text();
|
|
140
|
-
|
|
141
|
-
return process.exit(1);
|
|
182
|
+
throw new CliError(`Token exchange failed: ${text}`);
|
|
142
183
|
}
|
|
143
184
|
const tokens = await tokenResponse.json();
|
|
144
185
|
const accessToken = tokens.access_token || tokens.id_token || tokens.assertion;
|
|
145
186
|
if (!accessToken) {
|
|
146
|
-
|
|
147
|
-
return process.exit(1);
|
|
187
|
+
throw new CliError("No access token received");
|
|
148
188
|
}
|
|
149
189
|
const payload = JSON.parse(atob(accessToken.split(".")[1]));
|
|
150
190
|
saveAuth({
|
|
@@ -157,13 +197,12 @@ async function loginWithPKCE(idp) {
|
|
|
157
197
|
consola.success(`Logged in as ${payload.email || payload.sub}`);
|
|
158
198
|
}
|
|
159
199
|
async function loginWithKey(idp, keyPath, email) {
|
|
160
|
-
const { readFileSync:
|
|
200
|
+
const { readFileSync: readFileSync4 } = await import("fs");
|
|
161
201
|
const { sign: sign2 } = await import("crypto");
|
|
162
202
|
const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-Q7KG4K25.js");
|
|
163
203
|
const agentEmail = email;
|
|
164
204
|
if (!agentEmail) {
|
|
165
|
-
|
|
166
|
-
return process.exit(1);
|
|
205
|
+
throw new CliError("Agent email required for key-based login. Use --email <agent-email>");
|
|
167
206
|
}
|
|
168
207
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
169
208
|
const challengeResp = await fetch(challengeUrl, {
|
|
@@ -172,11 +211,10 @@ async function loginWithKey(idp, keyPath, email) {
|
|
|
172
211
|
body: JSON.stringify({ agent_id: agentEmail })
|
|
173
212
|
});
|
|
174
213
|
if (!challengeResp.ok) {
|
|
175
|
-
|
|
176
|
-
return process.exit(1);
|
|
214
|
+
throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
|
|
177
215
|
}
|
|
178
216
|
const { challenge } = await challengeResp.json();
|
|
179
|
-
const keyContent =
|
|
217
|
+
const keyContent = readFileSync4(keyPath, "utf-8");
|
|
180
218
|
const privateKey = loadEd25519PrivateKey2(keyContent);
|
|
181
219
|
const signature = sign2(null, Buffer.from(challenge), privateKey).toString("base64");
|
|
182
220
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -190,8 +228,7 @@ async function loginWithKey(idp, keyPath, email) {
|
|
|
190
228
|
})
|
|
191
229
|
});
|
|
192
230
|
if (!authResp.ok) {
|
|
193
|
-
|
|
194
|
-
return process.exit(1);
|
|
231
|
+
throw new CliError(`Authentication failed: ${await authResp.text()}`);
|
|
195
232
|
}
|
|
196
233
|
const { token, expires_in } = await authResp.json();
|
|
197
234
|
saveAuth({
|
|
@@ -200,7 +237,7 @@ async function loginWithKey(idp, keyPath, email) {
|
|
|
200
237
|
email: agentEmail,
|
|
201
238
|
expires_at: Math.floor(Date.now() / 1e3) + (expires_in || 3600)
|
|
202
239
|
});
|
|
203
|
-
consola.success(`Logged in as ${agentEmail}
|
|
240
|
+
consola.success(`Logged in as ${agentEmail}`);
|
|
204
241
|
}
|
|
205
242
|
|
|
206
243
|
// src/commands/auth/logout.ts
|
|
@@ -228,8 +265,7 @@ var whoamiCommand = defineCommand3({
|
|
|
228
265
|
run() {
|
|
229
266
|
const auth = loadAuth();
|
|
230
267
|
if (!auth) {
|
|
231
|
-
|
|
232
|
-
return process.exit(1);
|
|
268
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
233
269
|
}
|
|
234
270
|
const isAgent = auth.email.includes("agent+");
|
|
235
271
|
const expiresAt = new Date(auth.expires_at * 1e3).toISOString();
|
|
@@ -275,8 +311,7 @@ var listCommand = defineCommand4({
|
|
|
275
311
|
async run({ args }) {
|
|
276
312
|
const idp = getIdpUrl();
|
|
277
313
|
if (!idp) {
|
|
278
|
-
|
|
279
|
-
return process.exit(1);
|
|
314
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
280
315
|
}
|
|
281
316
|
const auth = loadAuth();
|
|
282
317
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
@@ -335,13 +370,11 @@ var inboxCommand = defineCommand5({
|
|
|
335
370
|
async run({ args }) {
|
|
336
371
|
const idp = getIdpUrl();
|
|
337
372
|
if (!idp) {
|
|
338
|
-
|
|
339
|
-
return process.exit(1);
|
|
373
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
340
374
|
}
|
|
341
375
|
const auth = loadAuth();
|
|
342
376
|
if (!auth) {
|
|
343
|
-
|
|
344
|
-
return process.exit(1);
|
|
377
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
345
378
|
}
|
|
346
379
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
347
380
|
const params = new URLSearchParams();
|
|
@@ -477,8 +510,7 @@ var requestCommand = defineCommand7({
|
|
|
477
510
|
async run({ args }) {
|
|
478
511
|
const auth = loadAuth();
|
|
479
512
|
if (!auth) {
|
|
480
|
-
|
|
481
|
-
return process.exit(1);
|
|
513
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
482
514
|
}
|
|
483
515
|
const idp = getIdpUrl();
|
|
484
516
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
@@ -516,22 +548,18 @@ async function waitForApproval(grantsUrl, grantId) {
|
|
|
516
548
|
return;
|
|
517
549
|
}
|
|
518
550
|
if (grant.status === "denied") {
|
|
519
|
-
|
|
520
|
-
return process.exit(1);
|
|
551
|
+
throw new CliError("Grant denied.");
|
|
521
552
|
}
|
|
522
553
|
if (grant.status === "revoked") {
|
|
523
|
-
|
|
524
|
-
return process.exit(1);
|
|
554
|
+
throw new CliError("Grant revoked.");
|
|
525
555
|
}
|
|
526
556
|
await new Promise((r) => setTimeout(r, interval));
|
|
527
557
|
}
|
|
528
|
-
|
|
529
|
-
return process.exit(1);
|
|
558
|
+
throw new CliError("Timed out waiting for approval.");
|
|
530
559
|
}
|
|
531
560
|
|
|
532
561
|
// src/commands/grants/request-capability.ts
|
|
533
562
|
import { hostname as hostname2 } from "os";
|
|
534
|
-
import { buildStructuredCliGrantRequest, loadAdapter, resolveCapabilityRequest } from "@openape/shapes";
|
|
535
563
|
import { defineCommand as defineCommand8 } from "citty";
|
|
536
564
|
import consola7 from "consola";
|
|
537
565
|
function parseCapabilityArgs(rawArgs) {
|
|
@@ -644,17 +672,14 @@ async function waitForApproval2(grantsUrl, grantId) {
|
|
|
644
672
|
return;
|
|
645
673
|
}
|
|
646
674
|
if (grant.status === "denied") {
|
|
647
|
-
|
|
648
|
-
process.exit(1);
|
|
675
|
+
throw new CliError("Grant denied.");
|
|
649
676
|
}
|
|
650
677
|
if (grant.status === "revoked") {
|
|
651
|
-
|
|
652
|
-
process.exit(1);
|
|
678
|
+
throw new CliError("Grant revoked.");
|
|
653
679
|
}
|
|
654
|
-
await new Promise((
|
|
680
|
+
await new Promise((resolve3) => setTimeout(resolve3, interval));
|
|
655
681
|
}
|
|
656
|
-
|
|
657
|
-
process.exit(1);
|
|
682
|
+
throw new CliError("Timed out waiting for approval.");
|
|
658
683
|
}
|
|
659
684
|
var requestCapabilityCommand = defineCommand8({
|
|
660
685
|
meta: {
|
|
@@ -713,14 +738,12 @@ var requestCapabilityCommand = defineCommand8({
|
|
|
713
738
|
async run({ rawArgs }) {
|
|
714
739
|
const auth = loadAuth();
|
|
715
740
|
if (!auth) {
|
|
716
|
-
|
|
717
|
-
return process.exit(1);
|
|
741
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
718
742
|
}
|
|
719
743
|
const parsed = parseCapabilityArgs(rawArgs);
|
|
720
744
|
const idp = getIdpUrl(parsed.idp);
|
|
721
745
|
if (!idp) {
|
|
722
|
-
|
|
723
|
-
return process.exit(1);
|
|
746
|
+
throw new CliError("No IdP URL configured. Use --idp or log in first.");
|
|
724
747
|
}
|
|
725
748
|
const loaded = loadAdapter(parsed.cliId, parsed.adapter);
|
|
726
749
|
const resolved = resolveCapabilityRequest(loaded, {
|
|
@@ -822,6 +845,11 @@ var revokeCommand = defineCommand11({
|
|
|
822
845
|
type: "boolean",
|
|
823
846
|
description: "Revoke all own pending grants",
|
|
824
847
|
default: false
|
|
848
|
+
},
|
|
849
|
+
debug: {
|
|
850
|
+
type: "boolean",
|
|
851
|
+
description: "Print debug information (does not include full tokens)",
|
|
852
|
+
default: false
|
|
825
853
|
}
|
|
826
854
|
},
|
|
827
855
|
async run({ args }) {
|
|
@@ -829,25 +857,28 @@ var revokeCommand = defineCommand11({
|
|
|
829
857
|
const token = getAuthToken();
|
|
830
858
|
const idp = getIdpUrl();
|
|
831
859
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
832
|
-
if (
|
|
860
|
+
if (args.debug) {
|
|
833
861
|
consola10.debug(`idp: ${idp}`);
|
|
834
862
|
consola10.debug(`grantsUrl: ${grantsUrl}`);
|
|
835
863
|
consola10.debug(`auth.email: ${auth?.email}`);
|
|
836
864
|
consola10.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
|
|
837
865
|
consola10.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
|
|
838
866
|
}
|
|
867
|
+
if (!auth || !token) {
|
|
868
|
+
throw new CliError("Authentication required. Run `apes login` and try again.");
|
|
869
|
+
}
|
|
839
870
|
const explicitIds = args.id ? [String(args.id), ...args._].filter(Boolean) : [];
|
|
840
871
|
if (args.allPending && explicitIds.length > 0) {
|
|
841
|
-
|
|
842
|
-
return process.exit(1);
|
|
872
|
+
throw new CliError("Use either --all-pending or grant IDs, not both.");
|
|
843
873
|
}
|
|
844
874
|
let ids;
|
|
845
875
|
if (args.allPending) {
|
|
846
876
|
const auth2 = loadAuth();
|
|
847
877
|
const response = await apiFetch(
|
|
848
|
-
`${grantsUrl}?status=pending&limit=100
|
|
878
|
+
`${grantsUrl}?status=pending&limit=100`,
|
|
879
|
+
{ token }
|
|
849
880
|
);
|
|
850
|
-
const ownPending = auth2?.email ? response.data.filter((g) => g.requester === auth2.email) : response.data;
|
|
881
|
+
const ownPending = auth2?.email ? response.data.filter((g) => g.request?.requester === auth2.email) : response.data;
|
|
851
882
|
if (ownPending.length === 0) {
|
|
852
883
|
consola10.info("No pending grants to revoke.");
|
|
853
884
|
return;
|
|
@@ -857,18 +888,17 @@ var revokeCommand = defineCommand11({
|
|
|
857
888
|
} else if (explicitIds.length > 0) {
|
|
858
889
|
ids = explicitIds;
|
|
859
890
|
} else {
|
|
860
|
-
|
|
861
|
-
return process.exit(1);
|
|
891
|
+
throw new CliError("Provide grant ID(s) or use --all-pending.");
|
|
862
892
|
}
|
|
863
893
|
if (ids.length === 1) {
|
|
864
|
-
await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token
|
|
894
|
+
await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token });
|
|
865
895
|
consola10.success(`Grant ${ids[0]} revoked.`);
|
|
866
896
|
return;
|
|
867
897
|
}
|
|
868
898
|
const operations = ids.map((id) => ({ id, action: "revoke" }));
|
|
869
899
|
const { results } = await apiFetch(
|
|
870
900
|
`${grantsUrl}/batch`,
|
|
871
|
-
{ method: "POST", body: { operations }, token
|
|
901
|
+
{ method: "POST", body: { operations }, token }
|
|
872
902
|
);
|
|
873
903
|
let succeeded = 0;
|
|
874
904
|
for (const r of results) {
|
|
@@ -880,8 +910,7 @@ var revokeCommand = defineCommand11({
|
|
|
880
910
|
}
|
|
881
911
|
}
|
|
882
912
|
if (succeeded < results.length) {
|
|
883
|
-
|
|
884
|
-
process.exit(1);
|
|
913
|
+
throw new CliError(`Revoked ${succeeded} of ${results.length} grants.`);
|
|
885
914
|
} else {
|
|
886
915
|
consola10.success(`All ${succeeded} grants revoked.`);
|
|
887
916
|
}
|
|
@@ -890,7 +919,6 @@ var revokeCommand = defineCommand11({
|
|
|
890
919
|
|
|
891
920
|
// src/commands/grants/token.ts
|
|
892
921
|
import { defineCommand as defineCommand12 } from "citty";
|
|
893
|
-
import consola11 from "consola";
|
|
894
922
|
var tokenCommand = defineCommand12({
|
|
895
923
|
meta: {
|
|
896
924
|
name: "token",
|
|
@@ -910,8 +938,7 @@ var tokenCommand = defineCommand12({
|
|
|
910
938
|
method: "POST"
|
|
911
939
|
});
|
|
912
940
|
if (!result.authz_jwt) {
|
|
913
|
-
|
|
914
|
-
return process.exit(1);
|
|
941
|
+
throw new CliError("No token received. Grant may not be approved.");
|
|
915
942
|
}
|
|
916
943
|
process.stdout.write(result.authz_jwt);
|
|
917
944
|
}
|
|
@@ -919,7 +946,7 @@ var tokenCommand = defineCommand12({
|
|
|
919
946
|
|
|
920
947
|
// src/commands/grants/delegate.ts
|
|
921
948
|
import { defineCommand as defineCommand13 } from "citty";
|
|
922
|
-
import
|
|
949
|
+
import consola11 from "consola";
|
|
923
950
|
var delegateCommand = defineCommand13({
|
|
924
951
|
meta: {
|
|
925
952
|
name: "delegate",
|
|
@@ -953,8 +980,7 @@ var delegateCommand = defineCommand13({
|
|
|
953
980
|
async run({ args }) {
|
|
954
981
|
const auth = loadAuth();
|
|
955
982
|
if (!auth) {
|
|
956
|
-
|
|
957
|
-
return process.exit(1);
|
|
983
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
958
984
|
}
|
|
959
985
|
const idp = getIdpUrl();
|
|
960
986
|
const delegationsUrl = await getDelegationsEndpoint(idp);
|
|
@@ -973,7 +999,7 @@ var delegateCommand = defineCommand13({
|
|
|
973
999
|
method: "POST",
|
|
974
1000
|
body
|
|
975
1001
|
});
|
|
976
|
-
|
|
1002
|
+
consola11.success(`Delegation created: ${result.id}`);
|
|
977
1003
|
console.log(` Delegate: ${args.to}`);
|
|
978
1004
|
console.log(` Audience: ${args.at}`);
|
|
979
1005
|
if (args.scopes)
|
|
@@ -986,7 +1012,7 @@ var delegateCommand = defineCommand13({
|
|
|
986
1012
|
|
|
987
1013
|
// src/commands/grants/delegations.ts
|
|
988
1014
|
import { defineCommand as defineCommand14 } from "citty";
|
|
989
|
-
import
|
|
1015
|
+
import consola12 from "consola";
|
|
990
1016
|
var delegationsCommand = defineCommand14({
|
|
991
1017
|
meta: {
|
|
992
1018
|
name: "delegations",
|
|
@@ -1002,13 +1028,14 @@ var delegationsCommand = defineCommand14({
|
|
|
1002
1028
|
async run({ args }) {
|
|
1003
1029
|
const idp = getIdpUrl();
|
|
1004
1030
|
const delegationsUrl = await getDelegationsEndpoint(idp);
|
|
1005
|
-
const
|
|
1031
|
+
const response = await apiFetch(delegationsUrl);
|
|
1032
|
+
const delegations = Array.isArray(response) ? response : response.data;
|
|
1006
1033
|
if (args.json) {
|
|
1007
1034
|
console.log(JSON.stringify(delegations, null, 2));
|
|
1008
1035
|
return;
|
|
1009
1036
|
}
|
|
1010
1037
|
if (delegations.length === 0) {
|
|
1011
|
-
|
|
1038
|
+
consola12.info("No delegations found.");
|
|
1012
1039
|
return;
|
|
1013
1040
|
}
|
|
1014
1041
|
for (const d of delegations) {
|
|
@@ -1019,27 +1046,345 @@ var delegationsCommand = defineCommand14({
|
|
|
1019
1046
|
}
|
|
1020
1047
|
});
|
|
1021
1048
|
|
|
1022
|
-
// src/commands/
|
|
1049
|
+
// src/commands/grants/delegation-revoke.ts
|
|
1023
1050
|
import { defineCommand as defineCommand15 } from "citty";
|
|
1051
|
+
import consola13 from "consola";
|
|
1052
|
+
var delegationRevokeCommand = defineCommand15({
|
|
1053
|
+
meta: {
|
|
1054
|
+
name: "delegation-revoke",
|
|
1055
|
+
description: "Revoke a delegation"
|
|
1056
|
+
},
|
|
1057
|
+
args: {
|
|
1058
|
+
id: {
|
|
1059
|
+
type: "positional",
|
|
1060
|
+
description: "Delegation ID to revoke",
|
|
1061
|
+
required: true
|
|
1062
|
+
}
|
|
1063
|
+
},
|
|
1064
|
+
async run({ args }) {
|
|
1065
|
+
const idp = getIdpUrl();
|
|
1066
|
+
if (!idp) {
|
|
1067
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1068
|
+
}
|
|
1069
|
+
const delegationsUrl = await getDelegationsEndpoint(idp);
|
|
1070
|
+
const id = String(args.id);
|
|
1071
|
+
const result = await apiFetch(
|
|
1072
|
+
`${delegationsUrl}/${id}`,
|
|
1073
|
+
{ method: "DELETE" }
|
|
1074
|
+
);
|
|
1075
|
+
consola13.success(`Delegation ${result.id} revoked.`);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
// src/commands/admin/index.ts
|
|
1080
|
+
import { defineCommand as defineCommand18 } from "citty";
|
|
1081
|
+
|
|
1082
|
+
// src/commands/admin/users.ts
|
|
1083
|
+
import { defineCommand as defineCommand16 } from "citty";
|
|
1024
1084
|
import consola14 from "consola";
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1085
|
+
function getManagementToken() {
|
|
1086
|
+
const token = process.env.APES_MANAGEMENT_TOKEN;
|
|
1087
|
+
if (!token) {
|
|
1088
|
+
throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
|
|
1089
|
+
}
|
|
1090
|
+
return token;
|
|
1091
|
+
}
|
|
1092
|
+
var usersListCommand = defineCommand16({
|
|
1093
|
+
meta: {
|
|
1094
|
+
name: "list",
|
|
1095
|
+
description: "List all users"
|
|
1096
|
+
},
|
|
1097
|
+
args: {
|
|
1098
|
+
json: {
|
|
1099
|
+
type: "boolean",
|
|
1100
|
+
description: "Output as JSON",
|
|
1101
|
+
default: false
|
|
1102
|
+
},
|
|
1103
|
+
limit: {
|
|
1104
|
+
type: "string",
|
|
1105
|
+
description: "Max number of users to return (1-100, default 50)"
|
|
1106
|
+
},
|
|
1107
|
+
cursor: {
|
|
1108
|
+
type: "string",
|
|
1109
|
+
description: "Pagination cursor (email of last item from previous page)"
|
|
1110
|
+
},
|
|
1111
|
+
search: {
|
|
1112
|
+
type: "string",
|
|
1113
|
+
description: "Filter by email or name (case-insensitive)"
|
|
1114
|
+
}
|
|
1115
|
+
},
|
|
1116
|
+
async run({ args }) {
|
|
1117
|
+
const idp = getIdpUrl();
|
|
1118
|
+
if (!idp) {
|
|
1119
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1120
|
+
}
|
|
1121
|
+
const token = getManagementToken();
|
|
1122
|
+
const params = new URLSearchParams();
|
|
1123
|
+
if (args.limit) params.set("limit", args.limit);
|
|
1124
|
+
if (args.cursor) params.set("cursor", args.cursor);
|
|
1125
|
+
if (args.search) params.set("search", args.search);
|
|
1126
|
+
const qs = params.toString();
|
|
1127
|
+
const url = qs ? `${idp}/api/admin/users?${qs}` : `${idp}/api/admin/users`;
|
|
1128
|
+
const result = await apiFetch(url, { token });
|
|
1129
|
+
if (args.json) {
|
|
1130
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
if (result.data.length === 0) {
|
|
1134
|
+
consola14.info("No users found.");
|
|
1135
|
+
return;
|
|
1136
|
+
}
|
|
1137
|
+
for (const u of result.data) {
|
|
1138
|
+
const owner = u.owner ? ` (agent of ${u.owner})` : "";
|
|
1139
|
+
const active = u.isActive ? "" : " [inactive]";
|
|
1140
|
+
console.log(`${u.email} ${u.name}${owner}${active}`);
|
|
1141
|
+
}
|
|
1142
|
+
if (result.pagination.has_more) {
|
|
1143
|
+
consola14.info(`More results available. Use --cursor="${result.pagination.cursor}" to see next page.`);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
});
|
|
1147
|
+
var usersCreateCommand = defineCommand16({
|
|
1148
|
+
meta: {
|
|
1149
|
+
name: "create",
|
|
1150
|
+
description: "Create a user"
|
|
1151
|
+
},
|
|
1152
|
+
args: {
|
|
1153
|
+
email: {
|
|
1154
|
+
type: "string",
|
|
1155
|
+
description: "User email",
|
|
1156
|
+
required: true
|
|
1157
|
+
},
|
|
1158
|
+
name: {
|
|
1159
|
+
type: "string",
|
|
1160
|
+
description: "User name",
|
|
1161
|
+
required: true
|
|
1162
|
+
}
|
|
1163
|
+
},
|
|
1164
|
+
async run({ args }) {
|
|
1165
|
+
const idp = getIdpUrl();
|
|
1166
|
+
if (!idp) {
|
|
1167
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1168
|
+
}
|
|
1169
|
+
const token = getManagementToken();
|
|
1170
|
+
const result = await apiFetch(
|
|
1171
|
+
`${idp}/api/admin/users`,
|
|
1172
|
+
{
|
|
1173
|
+
method: "POST",
|
|
1174
|
+
body: { email: args.email, name: args.name },
|
|
1175
|
+
token
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
consola14.success(`User created: ${result.email} (${result.name})`);
|
|
1179
|
+
}
|
|
1180
|
+
});
|
|
1181
|
+
var usersDeleteCommand = defineCommand16({
|
|
1182
|
+
meta: {
|
|
1183
|
+
name: "delete",
|
|
1184
|
+
description: "Delete a user"
|
|
1185
|
+
},
|
|
1186
|
+
args: {
|
|
1187
|
+
email: {
|
|
1188
|
+
type: "positional",
|
|
1189
|
+
description: "User email",
|
|
1190
|
+
required: true
|
|
1191
|
+
}
|
|
1192
|
+
},
|
|
1193
|
+
async run({ args }) {
|
|
1194
|
+
const idp = getIdpUrl();
|
|
1195
|
+
if (!idp) {
|
|
1196
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1197
|
+
}
|
|
1198
|
+
const token = getManagementToken();
|
|
1199
|
+
const email = String(args.email);
|
|
1200
|
+
await apiFetch(`${idp}/api/admin/users/${encodeURIComponent(email)}`, {
|
|
1201
|
+
method: "DELETE",
|
|
1202
|
+
token
|
|
1203
|
+
});
|
|
1204
|
+
consola14.success(`User deleted: ${email}`);
|
|
1205
|
+
}
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
// src/commands/admin/ssh-keys.ts
|
|
1209
|
+
import { existsSync, readFileSync } from "fs";
|
|
1210
|
+
import { resolve } from "path";
|
|
1211
|
+
import { homedir } from "os";
|
|
1212
|
+
import { defineCommand as defineCommand17 } from "citty";
|
|
1213
|
+
import consola15 from "consola";
|
|
1214
|
+
function getManagementToken2() {
|
|
1215
|
+
const token = process.env.APES_MANAGEMENT_TOKEN;
|
|
1216
|
+
if (!token) {
|
|
1217
|
+
throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
|
|
1218
|
+
}
|
|
1219
|
+
return token;
|
|
1220
|
+
}
|
|
1221
|
+
var sshKeysListCommand = defineCommand17({
|
|
1222
|
+
meta: {
|
|
1223
|
+
name: "list",
|
|
1224
|
+
description: "List SSH keys for a user"
|
|
1225
|
+
},
|
|
1226
|
+
args: {
|
|
1227
|
+
email: {
|
|
1228
|
+
type: "positional",
|
|
1229
|
+
description: "User email",
|
|
1230
|
+
required: true
|
|
1231
|
+
},
|
|
1232
|
+
json: {
|
|
1233
|
+
type: "boolean",
|
|
1234
|
+
description: "Output as JSON",
|
|
1235
|
+
default: false
|
|
1236
|
+
}
|
|
1237
|
+
},
|
|
1238
|
+
async run({ args }) {
|
|
1239
|
+
const idp = getIdpUrl();
|
|
1240
|
+
if (!idp) {
|
|
1241
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1242
|
+
}
|
|
1243
|
+
const token = getManagementToken2();
|
|
1244
|
+
const email = String(args.email);
|
|
1245
|
+
const keys = await apiFetch(
|
|
1246
|
+
`${idp}/api/admin/users/${encodeURIComponent(email)}/ssh-keys`,
|
|
1247
|
+
{ token }
|
|
1248
|
+
);
|
|
1249
|
+
if (args.json) {
|
|
1250
|
+
console.log(JSON.stringify(keys, null, 2));
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
if (keys.length === 0) {
|
|
1254
|
+
consola15.info(`No SSH keys found for ${email}.`);
|
|
1255
|
+
return;
|
|
1256
|
+
}
|
|
1257
|
+
for (const k of keys) {
|
|
1258
|
+
console.log(`${k.keyId} ${k.name} ${k.publicKey.substring(0, 40)}...`);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
});
|
|
1262
|
+
var sshKeysAddCommand = defineCommand17({
|
|
1263
|
+
meta: {
|
|
1264
|
+
name: "add",
|
|
1265
|
+
description: "Add an SSH key for a user"
|
|
1266
|
+
},
|
|
1267
|
+
args: {
|
|
1268
|
+
email: {
|
|
1269
|
+
type: "string",
|
|
1270
|
+
description: "User email",
|
|
1271
|
+
required: true
|
|
1272
|
+
},
|
|
1273
|
+
key: {
|
|
1274
|
+
type: "string",
|
|
1275
|
+
description: "Path to public key file or key string",
|
|
1276
|
+
required: true
|
|
1277
|
+
},
|
|
1278
|
+
name: {
|
|
1279
|
+
type: "string",
|
|
1280
|
+
description: "Key name/label"
|
|
1281
|
+
}
|
|
1282
|
+
},
|
|
1283
|
+
async run({ args }) {
|
|
1284
|
+
const idp = getIdpUrl();
|
|
1285
|
+
if (!idp) {
|
|
1286
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1287
|
+
}
|
|
1288
|
+
const token = getManagementToken2();
|
|
1289
|
+
let publicKey = args.key;
|
|
1290
|
+
const resolved = resolve(args.key.replace(/^~/, homedir()));
|
|
1291
|
+
if (existsSync(resolved)) {
|
|
1292
|
+
publicKey = readFileSync(resolved, "utf-8").trim();
|
|
1293
|
+
}
|
|
1294
|
+
const body = { publicKey };
|
|
1295
|
+
if (args.name) {
|
|
1296
|
+
body.name = args.name;
|
|
1297
|
+
}
|
|
1298
|
+
const result = await apiFetch(
|
|
1299
|
+
`${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys`,
|
|
1300
|
+
{
|
|
1301
|
+
method: "POST",
|
|
1302
|
+
body,
|
|
1303
|
+
token
|
|
1304
|
+
}
|
|
1305
|
+
);
|
|
1306
|
+
consola15.success(`SSH key added: ${result.keyId} (${result.name})`);
|
|
1307
|
+
}
|
|
1308
|
+
});
|
|
1309
|
+
var sshKeysDeleteCommand = defineCommand17({
|
|
1310
|
+
meta: {
|
|
1311
|
+
name: "delete",
|
|
1312
|
+
description: "Delete an SSH key"
|
|
1313
|
+
},
|
|
1314
|
+
args: {
|
|
1315
|
+
email: {
|
|
1316
|
+
type: "string",
|
|
1317
|
+
description: "User email",
|
|
1318
|
+
required: true
|
|
1319
|
+
},
|
|
1320
|
+
keyId: {
|
|
1321
|
+
type: "positional",
|
|
1322
|
+
description: "Key ID",
|
|
1323
|
+
required: true
|
|
1324
|
+
}
|
|
1325
|
+
},
|
|
1326
|
+
async run({ args }) {
|
|
1327
|
+
const idp = getIdpUrl();
|
|
1328
|
+
if (!idp) {
|
|
1329
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1330
|
+
}
|
|
1331
|
+
const token = getManagementToken2();
|
|
1332
|
+
const keyId = String(args.keyId);
|
|
1333
|
+
await apiFetch(
|
|
1334
|
+
`${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys/${keyId}`,
|
|
1335
|
+
{
|
|
1336
|
+
method: "DELETE",
|
|
1337
|
+
token
|
|
1338
|
+
}
|
|
1339
|
+
);
|
|
1340
|
+
consola15.success(`SSH key deleted: ${keyId}`);
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
|
|
1344
|
+
// src/commands/admin/index.ts
|
|
1345
|
+
var usersCommand = defineCommand18({
|
|
1346
|
+
meta: {
|
|
1347
|
+
name: "users",
|
|
1348
|
+
description: "Manage users"
|
|
1349
|
+
},
|
|
1350
|
+
subCommands: {
|
|
1351
|
+
list: usersListCommand,
|
|
1352
|
+
create: usersCreateCommand,
|
|
1353
|
+
delete: usersDeleteCommand
|
|
1354
|
+
}
|
|
1355
|
+
});
|
|
1356
|
+
var sshKeysCommand = defineCommand18({
|
|
1357
|
+
meta: {
|
|
1358
|
+
name: "ssh-keys",
|
|
1359
|
+
description: "Manage SSH keys"
|
|
1360
|
+
},
|
|
1361
|
+
subCommands: {
|
|
1362
|
+
list: sshKeysListCommand,
|
|
1363
|
+
add: sshKeysAddCommand,
|
|
1364
|
+
delete: sshKeysDeleteCommand
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1367
|
+
var adminCommand = defineCommand18({
|
|
1368
|
+
meta: {
|
|
1369
|
+
name: "admin",
|
|
1370
|
+
description: "Admin commands (requires APES_MANAGEMENT_TOKEN)"
|
|
1371
|
+
},
|
|
1372
|
+
subCommands: {
|
|
1373
|
+
users: usersCommand,
|
|
1374
|
+
"ssh-keys": sshKeysCommand
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
|
|
1378
|
+
// src/commands/adapter/index.ts
|
|
1379
|
+
import { defineCommand as defineCommand19 } from "citty";
|
|
1380
|
+
import consola16 from "consola";
|
|
1381
|
+
var adapterCommand = defineCommand19({
|
|
1037
1382
|
meta: {
|
|
1038
1383
|
name: "adapter",
|
|
1039
1384
|
description: "Manage CLI adapters"
|
|
1040
1385
|
},
|
|
1041
1386
|
subCommands: {
|
|
1042
|
-
list:
|
|
1387
|
+
list: defineCommand19({
|
|
1043
1388
|
meta: {
|
|
1044
1389
|
name: "list",
|
|
1045
1390
|
description: "List available adapters"
|
|
@@ -1070,7 +1415,7 @@ var adapterCommand = defineCommand15({
|
|
|
1070
1415
|
`);
|
|
1071
1416
|
return;
|
|
1072
1417
|
}
|
|
1073
|
-
|
|
1418
|
+
consola16.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
|
|
1074
1419
|
for (const a of index2.adapters) {
|
|
1075
1420
|
const installed = isInstalled(a.id, false) ? " [installed]" : "";
|
|
1076
1421
|
console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
|
|
@@ -1081,7 +1426,7 @@ var adapterCommand = defineCommand15({
|
|
|
1081
1426
|
const local = [];
|
|
1082
1427
|
for (const a of index.adapters) {
|
|
1083
1428
|
try {
|
|
1084
|
-
const loaded =
|
|
1429
|
+
const loaded = loadAdapter(a.id);
|
|
1085
1430
|
local.push({ id: a.id, source: loaded.source, digest: loaded.digest });
|
|
1086
1431
|
} catch {
|
|
1087
1432
|
}
|
|
@@ -1092,7 +1437,7 @@ var adapterCommand = defineCommand15({
|
|
|
1092
1437
|
return;
|
|
1093
1438
|
}
|
|
1094
1439
|
if (local.length === 0) {
|
|
1095
|
-
|
|
1440
|
+
consola16.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
|
|
1096
1441
|
return;
|
|
1097
1442
|
}
|
|
1098
1443
|
for (const a of local) {
|
|
@@ -1100,7 +1445,7 @@ var adapterCommand = defineCommand15({
|
|
|
1100
1445
|
}
|
|
1101
1446
|
}
|
|
1102
1447
|
}),
|
|
1103
|
-
install:
|
|
1448
|
+
install: defineCommand19({
|
|
1104
1449
|
meta: {
|
|
1105
1450
|
name: "install",
|
|
1106
1451
|
description: "Install an adapter from the registry"
|
|
@@ -1129,24 +1474,24 @@ var adapterCommand = defineCommand15({
|
|
|
1129
1474
|
for (const id of ids) {
|
|
1130
1475
|
const entry = findAdapter(index, id);
|
|
1131
1476
|
if (!entry) {
|
|
1132
|
-
|
|
1477
|
+
consola16.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
|
|
1133
1478
|
continue;
|
|
1134
1479
|
}
|
|
1135
1480
|
const conflicts = findConflictingAdapters(entry.executable, id);
|
|
1136
1481
|
if (conflicts.length > 0) {
|
|
1137
1482
|
for (const c of conflicts) {
|
|
1138
|
-
|
|
1139
|
-
|
|
1483
|
+
consola16.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
|
|
1484
|
+
consola16.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
|
|
1140
1485
|
}
|
|
1141
1486
|
}
|
|
1142
1487
|
const result = await installAdapter(entry, { local });
|
|
1143
1488
|
const verb = result.updated ? "Updated" : "Installed";
|
|
1144
|
-
|
|
1145
|
-
|
|
1489
|
+
consola16.success(`${verb} ${result.id} \u2192 ${result.path}`);
|
|
1490
|
+
consola16.info(`Digest: ${result.digest}`);
|
|
1146
1491
|
}
|
|
1147
1492
|
}
|
|
1148
1493
|
}),
|
|
1149
|
-
remove:
|
|
1494
|
+
remove: defineCommand19({
|
|
1150
1495
|
meta: {
|
|
1151
1496
|
name: "remove",
|
|
1152
1497
|
description: "Remove an installed adapter"
|
|
@@ -1169,17 +1514,17 @@ var adapterCommand = defineCommand15({
|
|
|
1169
1514
|
let failed = false;
|
|
1170
1515
|
for (const id of ids) {
|
|
1171
1516
|
if (removeAdapter(id, local)) {
|
|
1172
|
-
|
|
1517
|
+
consola16.success(`Removed adapter: ${id}`);
|
|
1173
1518
|
} else {
|
|
1174
|
-
|
|
1519
|
+
consola16.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
1175
1520
|
failed = true;
|
|
1176
1521
|
}
|
|
1177
1522
|
}
|
|
1178
1523
|
if (failed)
|
|
1179
|
-
|
|
1524
|
+
throw new CliError("Some adapters could not be removed");
|
|
1180
1525
|
}
|
|
1181
1526
|
}),
|
|
1182
|
-
info:
|
|
1527
|
+
info: defineCommand19({
|
|
1183
1528
|
meta: {
|
|
1184
1529
|
name: "info",
|
|
1185
1530
|
description: "Show detailed adapter information"
|
|
@@ -1221,7 +1566,7 @@ var adapterCommand = defineCommand15({
|
|
|
1221
1566
|
}
|
|
1222
1567
|
}
|
|
1223
1568
|
}),
|
|
1224
|
-
search:
|
|
1569
|
+
search: defineCommand19({
|
|
1225
1570
|
meta: {
|
|
1226
1571
|
name: "search",
|
|
1227
1572
|
description: "Search adapters in the registry"
|
|
@@ -1253,7 +1598,7 @@ var adapterCommand = defineCommand15({
|
|
|
1253
1598
|
return;
|
|
1254
1599
|
}
|
|
1255
1600
|
if (results.length === 0) {
|
|
1256
|
-
|
|
1601
|
+
consola16.info(`No adapters matching "${query}"`);
|
|
1257
1602
|
return;
|
|
1258
1603
|
}
|
|
1259
1604
|
for (const a of results) {
|
|
@@ -1262,7 +1607,7 @@ var adapterCommand = defineCommand15({
|
|
|
1262
1607
|
}
|
|
1263
1608
|
}
|
|
1264
1609
|
}),
|
|
1265
|
-
update:
|
|
1610
|
+
update: defineCommand19({
|
|
1266
1611
|
meta: {
|
|
1267
1612
|
name: "update",
|
|
1268
1613
|
description: "Update installed adapters"
|
|
@@ -1288,33 +1633,33 @@ var adapterCommand = defineCommand15({
|
|
|
1288
1633
|
const targetId = args.id ? String(args.id) : void 0;
|
|
1289
1634
|
const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
|
|
1290
1635
|
if (targets.length === 0) {
|
|
1291
|
-
|
|
1636
|
+
consola16.info("No adapters installed to update.");
|
|
1292
1637
|
return;
|
|
1293
1638
|
}
|
|
1294
1639
|
for (const id of targets) {
|
|
1295
1640
|
const entry = findAdapter(index, id);
|
|
1296
1641
|
if (!entry) {
|
|
1297
|
-
|
|
1642
|
+
consola16.warn(`${id}: not found in registry, skipping`);
|
|
1298
1643
|
continue;
|
|
1299
1644
|
}
|
|
1300
1645
|
const localDigest = getInstalledDigest(id, false);
|
|
1301
1646
|
if (localDigest === entry.digest) {
|
|
1302
|
-
|
|
1647
|
+
consola16.info(`${id}: already up to date`);
|
|
1303
1648
|
continue;
|
|
1304
1649
|
}
|
|
1305
1650
|
if (localDigest && !args.yes) {
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1651
|
+
consola16.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
|
|
1652
|
+
consola16.info(` Old: ${localDigest}`);
|
|
1653
|
+
consola16.info(` New: ${entry.digest}`);
|
|
1654
|
+
consola16.info(" Use --yes to confirm");
|
|
1310
1655
|
continue;
|
|
1311
1656
|
}
|
|
1312
1657
|
const result = await installAdapter(entry);
|
|
1313
|
-
|
|
1658
|
+
consola16.success(`Updated ${result.id} \u2192 ${result.path}`);
|
|
1314
1659
|
}
|
|
1315
1660
|
}
|
|
1316
1661
|
}),
|
|
1317
|
-
verify:
|
|
1662
|
+
verify: defineCommand19({
|
|
1318
1663
|
meta: {
|
|
1319
1664
|
name: "verify",
|
|
1320
1665
|
description: "Verify installed adapter against registry digest"
|
|
@@ -1347,12 +1692,11 @@ var adapterCommand = defineCommand15({
|
|
|
1347
1692
|
if (!localDigest)
|
|
1348
1693
|
throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
|
|
1349
1694
|
if (localDigest === entry.digest) {
|
|
1350
|
-
|
|
1695
|
+
consola16.success(`${id}: digest matches registry`);
|
|
1351
1696
|
} else {
|
|
1352
|
-
consola14.error(`${id}: digest mismatch`);
|
|
1353
1697
|
console.log(` Local: ${localDigest}`);
|
|
1354
1698
|
console.log(` Registry: ${entry.digest}`);
|
|
1355
|
-
|
|
1699
|
+
throw new CliError(`${id}: digest mismatch`);
|
|
1356
1700
|
}
|
|
1357
1701
|
}
|
|
1358
1702
|
})
|
|
@@ -1362,20 +1706,9 @@ var adapterCommand = defineCommand15({
|
|
|
1362
1706
|
// src/commands/run.ts
|
|
1363
1707
|
import { execFileSync } from "child_process";
|
|
1364
1708
|
import { hostname as hostname3 } from "os";
|
|
1365
|
-
import { defineCommand as
|
|
1366
|
-
import
|
|
1367
|
-
|
|
1368
|
-
extractOption,
|
|
1369
|
-
extractWrappedCommand,
|
|
1370
|
-
fetchGrantToken,
|
|
1371
|
-
findExistingGrant,
|
|
1372
|
-
loadAdapter as loadAdapter3,
|
|
1373
|
-
resolveCommand,
|
|
1374
|
-
verifyAndExecute,
|
|
1375
|
-
waitForGrantStatus
|
|
1376
|
-
} from "@openape/shapes";
|
|
1377
|
-
import consola15 from "consola";
|
|
1378
|
-
var runCommand = defineCommand16({
|
|
1709
|
+
import { defineCommand as defineCommand20 } from "citty";
|
|
1710
|
+
import consola17 from "consola";
|
|
1711
|
+
var runCommand = defineCommand20({
|
|
1379
1712
|
meta: {
|
|
1380
1713
|
name: "run",
|
|
1381
1714
|
description: "Execute a grant-secured command"
|
|
@@ -1411,6 +1744,11 @@ var runCommand = defineCommand16({
|
|
|
1411
1744
|
type: "string",
|
|
1412
1745
|
description: "IdP URL"
|
|
1413
1746
|
},
|
|
1747
|
+
"shell": {
|
|
1748
|
+
type: "boolean",
|
|
1749
|
+
description: "Shell mode: use session grant with audience ape-shell",
|
|
1750
|
+
default: false
|
|
1751
|
+
},
|
|
1414
1752
|
"_": {
|
|
1415
1753
|
type: "positional",
|
|
1416
1754
|
description: "Command to execute (after --)",
|
|
@@ -1419,6 +1757,10 @@ var runCommand = defineCommand16({
|
|
|
1419
1757
|
},
|
|
1420
1758
|
async run({ rawArgs, args }) {
|
|
1421
1759
|
const wrappedCommand = extractWrappedCommand(rawArgs ?? []);
|
|
1760
|
+
if (args.shell && wrappedCommand.length > 0) {
|
|
1761
|
+
await runShellMode(wrappedCommand, args);
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1422
1764
|
if (wrappedCommand.length > 0) {
|
|
1423
1765
|
await runAdapterMode(wrappedCommand, rawArgs ?? [], args);
|
|
1424
1766
|
} else {
|
|
@@ -1429,6 +1771,113 @@ var runCommand = defineCommand16({
|
|
|
1429
1771
|
}
|
|
1430
1772
|
}
|
|
1431
1773
|
});
|
|
1774
|
+
async function runShellMode(command, args) {
|
|
1775
|
+
const auth = loadAuth();
|
|
1776
|
+
if (!auth)
|
|
1777
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
1778
|
+
const idp = getIdpUrl(args.idp);
|
|
1779
|
+
if (!idp)
|
|
1780
|
+
throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1781
|
+
const adapterHandled = await tryAdapterModeFromShell(command, idp, args);
|
|
1782
|
+
if (adapterHandled) return;
|
|
1783
|
+
const grantsUrl = await getGrantsEndpoint(idp);
|
|
1784
|
+
const targetHost = args.host || hostname3();
|
|
1785
|
+
try {
|
|
1786
|
+
const grants = await apiFetch(
|
|
1787
|
+
`${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`
|
|
1788
|
+
);
|
|
1789
|
+
const sessionGrant = grants.data.find(
|
|
1790
|
+
(g) => g.request.audience === "ape-shell" && g.request.target_host === targetHost && g.request.grant_type !== "once"
|
|
1791
|
+
);
|
|
1792
|
+
if (sessionGrant) {
|
|
1793
|
+
execShellCommand(command);
|
|
1794
|
+
return;
|
|
1795
|
+
}
|
|
1796
|
+
} catch {
|
|
1797
|
+
}
|
|
1798
|
+
consola17.info(`Requesting ape-shell session grant on ${targetHost}`);
|
|
1799
|
+
const grant = await apiFetch(grantsUrl, {
|
|
1800
|
+
method: "POST",
|
|
1801
|
+
body: {
|
|
1802
|
+
requester: auth.email,
|
|
1803
|
+
target_host: targetHost,
|
|
1804
|
+
audience: "ape-shell",
|
|
1805
|
+
grant_type: "once",
|
|
1806
|
+
command: command.slice(0, 3),
|
|
1807
|
+
reason: `Shell session: ${command.join(" ").slice(0, 100)}`
|
|
1808
|
+
}
|
|
1809
|
+
});
|
|
1810
|
+
consola17.info(`Grant requested: ${grant.id}`);
|
|
1811
|
+
consola17.info("Waiting for approval...");
|
|
1812
|
+
const maxWait = 3e5;
|
|
1813
|
+
const interval = 3e3;
|
|
1814
|
+
const start = Date.now();
|
|
1815
|
+
while (Date.now() - start < maxWait) {
|
|
1816
|
+
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
1817
|
+
if (status.status === "approved")
|
|
1818
|
+
break;
|
|
1819
|
+
if (status.status === "denied" || status.status === "revoked")
|
|
1820
|
+
throw new CliError(`Grant ${status.status}.`);
|
|
1821
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1822
|
+
}
|
|
1823
|
+
execShellCommand(command);
|
|
1824
|
+
}
|
|
1825
|
+
async function tryAdapterModeFromShell(command, idp, args) {
|
|
1826
|
+
const cmdString = extractShellCommandString(command);
|
|
1827
|
+
if (!cmdString) return false;
|
|
1828
|
+
const parsed = parseShellCommand(cmdString);
|
|
1829
|
+
if (!parsed) return false;
|
|
1830
|
+
if (parsed.isCompound) return false;
|
|
1831
|
+
const loaded = await loadOrInstallAdapter(parsed.executable);
|
|
1832
|
+
if (!loaded) return false;
|
|
1833
|
+
let resolved;
|
|
1834
|
+
try {
|
|
1835
|
+
resolved = await resolveCommand(loaded, [parsed.executable, ...parsed.argv]);
|
|
1836
|
+
} catch (err) {
|
|
1837
|
+
consola17.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
|
|
1838
|
+
return false;
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
1842
|
+
if (existingGrantId) {
|
|
1843
|
+
consola17.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
|
|
1844
|
+
const token2 = await fetchGrantToken(idp, existingGrantId);
|
|
1845
|
+
await verifyAndExecute(token2, resolved);
|
|
1846
|
+
return true;
|
|
1847
|
+
}
|
|
1848
|
+
} catch {
|
|
1849
|
+
}
|
|
1850
|
+
const approval = args.approval ?? "once";
|
|
1851
|
+
consola17.info(`Requesting grant for: ${resolved.detail.display}`);
|
|
1852
|
+
const grant = await createShapesGrant(resolved, {
|
|
1853
|
+
idp,
|
|
1854
|
+
approval,
|
|
1855
|
+
reason: args.reason || `ape-shell: ${resolved.detail.display}`
|
|
1856
|
+
});
|
|
1857
|
+
consola17.info(`Grant requested: ${grant.id}`);
|
|
1858
|
+
consola17.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
1859
|
+
if (grant.similar_grants?.similar_grants?.length) {
|
|
1860
|
+
const n = grant.similar_grants.similar_grants.length;
|
|
1861
|
+
consola17.info("");
|
|
1862
|
+
consola17.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
1863
|
+
}
|
|
1864
|
+
const status = await waitForGrantStatus(idp, grant.id);
|
|
1865
|
+
if (status !== "approved")
|
|
1866
|
+
throw new CliError(`Grant ${status}`);
|
|
1867
|
+
const token = await fetchGrantToken(idp, grant.id);
|
|
1868
|
+
await verifyAndExecute(token, resolved);
|
|
1869
|
+
return true;
|
|
1870
|
+
}
|
|
1871
|
+
function execShellCommand(command) {
|
|
1872
|
+
if (command.length === 0)
|
|
1873
|
+
throw new CliError("No command to execute");
|
|
1874
|
+
try {
|
|
1875
|
+
execFileSync(command[0], command.slice(1), { stdio: "inherit" });
|
|
1876
|
+
} catch (err) {
|
|
1877
|
+
const exitCode = err.status || 1;
|
|
1878
|
+
throw new CliExit(exitCode);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1432
1881
|
function extractPositionals(rawArgs) {
|
|
1433
1882
|
const positionals = [];
|
|
1434
1883
|
const delimiter = rawArgs.indexOf("--");
|
|
@@ -1449,14 +1898,18 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
1449
1898
|
const idp = getIdpUrl(args.idp);
|
|
1450
1899
|
if (!idp)
|
|
1451
1900
|
throw new Error("No IdP URL configured. Run `apes login` first or pass --idp.");
|
|
1901
|
+
if (args.as) {
|
|
1902
|
+
await runAudienceMode("escapes", command.join(" "), args);
|
|
1903
|
+
return;
|
|
1904
|
+
}
|
|
1452
1905
|
const adapterOpt = extractOption(rawArgs, "adapter");
|
|
1453
|
-
const loaded =
|
|
1906
|
+
const loaded = loadAdapter(command[0], adapterOpt);
|
|
1454
1907
|
const resolved = await resolveCommand(loaded, command);
|
|
1455
1908
|
const approval = args.approval ?? "once";
|
|
1456
1909
|
try {
|
|
1457
1910
|
const existingGrantId = await findExistingGrant(resolved, idp);
|
|
1458
1911
|
if (existingGrantId) {
|
|
1459
|
-
|
|
1912
|
+
consola17.info(`Reusing existing grant: ${existingGrantId}`);
|
|
1460
1913
|
const token2 = await fetchGrantToken(idp, existingGrantId);
|
|
1461
1914
|
await verifyAndExecute(token2, resolved);
|
|
1462
1915
|
return;
|
|
@@ -1468,17 +1921,17 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
1468
1921
|
approval,
|
|
1469
1922
|
...args.reason ? { reason: args.reason } : {}
|
|
1470
1923
|
});
|
|
1471
|
-
|
|
1472
|
-
|
|
1924
|
+
consola17.info(`Grant requested: ${grant.id}`);
|
|
1925
|
+
consola17.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
|
|
1473
1926
|
if (grant.similar_grants?.similar_grants?.length) {
|
|
1474
1927
|
const n = grant.similar_grants.similar_grants.length;
|
|
1475
|
-
|
|
1476
|
-
|
|
1928
|
+
consola17.info("");
|
|
1929
|
+
consola17.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
|
|
1477
1930
|
if (grant.similar_grants.widened_details?.length) {
|
|
1478
1931
|
const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
|
|
1479
|
-
|
|
1932
|
+
consola17.info(` Broader scope: ${wider}`);
|
|
1480
1933
|
}
|
|
1481
|
-
|
|
1934
|
+
consola17.info("");
|
|
1482
1935
|
}
|
|
1483
1936
|
const status = await waitForGrantStatus(idp, grant.id);
|
|
1484
1937
|
if (status !== "approved")
|
|
@@ -1489,14 +1942,13 @@ async function runAdapterMode(command, rawArgs, args) {
|
|
|
1489
1942
|
async function runAudienceMode(audience, action, args) {
|
|
1490
1943
|
const auth = loadAuth();
|
|
1491
1944
|
if (!auth) {
|
|
1492
|
-
|
|
1493
|
-
return process.exit(1);
|
|
1945
|
+
throw new CliError("Not logged in. Run `apes login` first.");
|
|
1494
1946
|
}
|
|
1495
1947
|
const idp = getIdpUrl(args.idp);
|
|
1496
1948
|
const grantsUrl = await getGrantsEndpoint(idp);
|
|
1497
1949
|
const command = action.split(" ");
|
|
1498
1950
|
const targetHost = args.host || hostname3();
|
|
1499
|
-
|
|
1951
|
+
consola17.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
|
|
1500
1952
|
const grant = await apiFetch(grantsUrl, {
|
|
1501
1953
|
method: "POST",
|
|
1502
1954
|
body: {
|
|
@@ -1509,36 +1961,35 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1509
1961
|
...args.as ? { run_as: args.as } : {}
|
|
1510
1962
|
}
|
|
1511
1963
|
});
|
|
1512
|
-
|
|
1513
|
-
|
|
1964
|
+
consola17.success(`Grant requested: ${grant.id}`);
|
|
1965
|
+
consola17.info("Waiting for approval...");
|
|
1514
1966
|
const maxWait = 3e5;
|
|
1515
1967
|
const interval = 3e3;
|
|
1516
1968
|
const start = Date.now();
|
|
1517
1969
|
while (Date.now() - start < maxWait) {
|
|
1518
1970
|
const status = await apiFetch(`${grantsUrl}/${grant.id}`);
|
|
1519
1971
|
if (status.status === "approved") {
|
|
1520
|
-
|
|
1972
|
+
consola17.success("Grant approved!");
|
|
1521
1973
|
break;
|
|
1522
1974
|
}
|
|
1523
1975
|
if (status.status === "denied" || status.status === "revoked") {
|
|
1524
|
-
|
|
1525
|
-
return process.exit(1);
|
|
1976
|
+
throw new CliError(`Grant ${status.status}.`);
|
|
1526
1977
|
}
|
|
1527
1978
|
await new Promise((r) => setTimeout(r, interval));
|
|
1528
1979
|
}
|
|
1529
|
-
|
|
1980
|
+
consola17.info("Fetching grant token...");
|
|
1530
1981
|
const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
|
|
1531
1982
|
method: "POST"
|
|
1532
1983
|
});
|
|
1533
1984
|
if (audience === "escapes") {
|
|
1534
|
-
|
|
1985
|
+
consola17.info(`Executing: ${command.join(" ")}`);
|
|
1535
1986
|
try {
|
|
1536
1987
|
execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
|
|
1537
1988
|
stdio: "inherit"
|
|
1538
1989
|
});
|
|
1539
1990
|
} catch (err) {
|
|
1540
1991
|
const exitCode = err.status || 1;
|
|
1541
|
-
|
|
1992
|
+
throw new CliExit(exitCode);
|
|
1542
1993
|
}
|
|
1543
1994
|
} else {
|
|
1544
1995
|
process.stdout.write(authz_jwt);
|
|
@@ -1546,9 +1997,8 @@ async function runAudienceMode(audience, action, args) {
|
|
|
1546
1997
|
}
|
|
1547
1998
|
|
|
1548
1999
|
// src/commands/explain.ts
|
|
1549
|
-
import { defineCommand as
|
|
1550
|
-
|
|
1551
|
-
var explainCommand = defineCommand17({
|
|
2000
|
+
import { defineCommand as defineCommand21 } from "citty";
|
|
2001
|
+
var explainCommand = defineCommand21({
|
|
1552
2002
|
meta: {
|
|
1553
2003
|
name: "explain",
|
|
1554
2004
|
description: "Show what permission a command would need"
|
|
@@ -1565,12 +2015,12 @@ var explainCommand = defineCommand17({
|
|
|
1565
2015
|
}
|
|
1566
2016
|
},
|
|
1567
2017
|
async run({ rawArgs }) {
|
|
1568
|
-
const command =
|
|
2018
|
+
const command = extractWrappedCommand(rawArgs ?? []);
|
|
1569
2019
|
if (command.length === 0)
|
|
1570
2020
|
throw new Error("Missing wrapped command. Usage: apes explain [--adapter <file>] -- <cli> ...");
|
|
1571
|
-
const adapterOpt =
|
|
1572
|
-
const loaded =
|
|
1573
|
-
const resolved = await
|
|
2021
|
+
const adapterOpt = extractOption(rawArgs ?? [], "adapter");
|
|
2022
|
+
const loaded = loadAdapter(command[0], adapterOpt);
|
|
2023
|
+
const resolved = await resolveCommand(loaded, command);
|
|
1574
2024
|
process.stdout.write(`${JSON.stringify({
|
|
1575
2025
|
adapter: resolved.adapter.cli.id,
|
|
1576
2026
|
source: resolved.source,
|
|
@@ -1586,9 +2036,9 @@ var explainCommand = defineCommand17({
|
|
|
1586
2036
|
});
|
|
1587
2037
|
|
|
1588
2038
|
// src/commands/config/get.ts
|
|
1589
|
-
import { defineCommand as
|
|
1590
|
-
import
|
|
1591
|
-
var configGetCommand =
|
|
2039
|
+
import { defineCommand as defineCommand22 } from "citty";
|
|
2040
|
+
import consola18 from "consola";
|
|
2041
|
+
var configGetCommand = defineCommand22({
|
|
1592
2042
|
meta: {
|
|
1593
2043
|
name: "get",
|
|
1594
2044
|
description: "Get a configuration value"
|
|
@@ -1608,7 +2058,7 @@ var configGetCommand = defineCommand18({
|
|
|
1608
2058
|
if (idp)
|
|
1609
2059
|
console.log(idp);
|
|
1610
2060
|
else
|
|
1611
|
-
|
|
2061
|
+
consola18.info("No IdP configured.");
|
|
1612
2062
|
break;
|
|
1613
2063
|
}
|
|
1614
2064
|
case "email": {
|
|
@@ -1616,7 +2066,7 @@ var configGetCommand = defineCommand18({
|
|
|
1616
2066
|
if (auth?.email)
|
|
1617
2067
|
console.log(auth.email);
|
|
1618
2068
|
else
|
|
1619
|
-
|
|
2069
|
+
consola18.info("Not logged in.");
|
|
1620
2070
|
break;
|
|
1621
2071
|
}
|
|
1622
2072
|
default: {
|
|
@@ -1629,11 +2079,10 @@ var configGetCommand = defineCommand18({
|
|
|
1629
2079
|
if (sectionObj && field in sectionObj) {
|
|
1630
2080
|
console.log(sectionObj[field]);
|
|
1631
2081
|
} else {
|
|
1632
|
-
|
|
2082
|
+
consola18.info(`Key "${key}" not set.`);
|
|
1633
2083
|
}
|
|
1634
2084
|
} else {
|
|
1635
|
-
|
|
1636
|
-
process.exit(1);
|
|
2085
|
+
throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
|
|
1637
2086
|
}
|
|
1638
2087
|
}
|
|
1639
2088
|
}
|
|
@@ -1641,9 +2090,9 @@ var configGetCommand = defineCommand18({
|
|
|
1641
2090
|
});
|
|
1642
2091
|
|
|
1643
2092
|
// src/commands/config/set.ts
|
|
1644
|
-
import { defineCommand as
|
|
1645
|
-
import
|
|
1646
|
-
var configSetCommand =
|
|
2093
|
+
import { defineCommand as defineCommand23 } from "citty";
|
|
2094
|
+
import consola19 from "consola";
|
|
2095
|
+
var configSetCommand = defineCommand23({
|
|
1647
2096
|
meta: {
|
|
1648
2097
|
name: "set",
|
|
1649
2098
|
description: "Set a configuration value"
|
|
@@ -1666,8 +2115,7 @@ var configSetCommand = defineCommand19({
|
|
|
1666
2115
|
const config = loadConfig();
|
|
1667
2116
|
const parts = key.split(".");
|
|
1668
2117
|
if (parts.length !== 2) {
|
|
1669
|
-
|
|
1670
|
-
return process.exit(1);
|
|
2118
|
+
throw new CliError(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
|
|
1671
2119
|
}
|
|
1672
2120
|
const [section, field] = parts;
|
|
1673
2121
|
if (section === "defaults") {
|
|
@@ -1677,22 +2125,19 @@ var configSetCommand = defineCommand19({
|
|
|
1677
2125
|
config.agent = config.agent || {};
|
|
1678
2126
|
config.agent[field] = value;
|
|
1679
2127
|
} else {
|
|
1680
|
-
|
|
1681
|
-
return process.exit(1);
|
|
2128
|
+
throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
|
|
1682
2129
|
}
|
|
1683
2130
|
saveConfig(config);
|
|
1684
|
-
|
|
2131
|
+
consola19.success(`Set ${key} = ${value}`);
|
|
1685
2132
|
}
|
|
1686
2133
|
});
|
|
1687
2134
|
|
|
1688
2135
|
// src/commands/fetch/index.ts
|
|
1689
|
-
import { defineCommand as
|
|
1690
|
-
import consola18 from "consola";
|
|
2136
|
+
import { defineCommand as defineCommand24 } from "citty";
|
|
1691
2137
|
async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
1692
2138
|
const token = getAuthToken();
|
|
1693
2139
|
if (!token) {
|
|
1694
|
-
|
|
1695
|
-
return process.exit(1);
|
|
2140
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
1696
2141
|
}
|
|
1697
2142
|
const response = await fetch(url, {
|
|
1698
2143
|
method,
|
|
@@ -1721,16 +2166,16 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
|
|
|
1721
2166
|
}
|
|
1722
2167
|
}
|
|
1723
2168
|
if (!response.ok) {
|
|
1724
|
-
|
|
2169
|
+
throw new CliError(`HTTP ${response.status} ${response.statusText}`);
|
|
1725
2170
|
}
|
|
1726
2171
|
}
|
|
1727
|
-
var fetchCommand =
|
|
2172
|
+
var fetchCommand = defineCommand24({
|
|
1728
2173
|
meta: {
|
|
1729
2174
|
name: "fetch",
|
|
1730
2175
|
description: "Make authenticated HTTP requests"
|
|
1731
2176
|
},
|
|
1732
2177
|
subCommands: {
|
|
1733
|
-
get:
|
|
2178
|
+
get: defineCommand24({
|
|
1734
2179
|
meta: {
|
|
1735
2180
|
name: "get",
|
|
1736
2181
|
description: "GET request with auth token"
|
|
@@ -1756,7 +2201,7 @@ var fetchCommand = defineCommand20({
|
|
|
1756
2201
|
await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
|
|
1757
2202
|
}
|
|
1758
2203
|
}),
|
|
1759
|
-
post:
|
|
2204
|
+
post: defineCommand24({
|
|
1760
2205
|
meta: {
|
|
1761
2206
|
name: "post",
|
|
1762
2207
|
description: "POST request with auth token"
|
|
@@ -1795,8 +2240,8 @@ var fetchCommand = defineCommand20({
|
|
|
1795
2240
|
});
|
|
1796
2241
|
|
|
1797
2242
|
// src/commands/mcp/index.ts
|
|
1798
|
-
import { defineCommand as
|
|
1799
|
-
var mcpCommand =
|
|
2243
|
+
import { defineCommand as defineCommand25 } from "citty";
|
|
2244
|
+
var mcpCommand = defineCommand25({
|
|
1800
2245
|
meta: {
|
|
1801
2246
|
name: "mcp",
|
|
1802
2247
|
description: "Start MCP server for AI agents"
|
|
@@ -1819,25 +2264,25 @@ var mcpCommand = defineCommand21({
|
|
|
1819
2264
|
if (transport !== "stdio" && transport !== "sse") {
|
|
1820
2265
|
throw new Error('Transport must be "stdio" or "sse"');
|
|
1821
2266
|
}
|
|
1822
|
-
const { startMcpServer } = await import("./server-
|
|
2267
|
+
const { startMcpServer } = await import("./server-FR6GFS3S.js");
|
|
1823
2268
|
await startMcpServer(transport, port);
|
|
1824
2269
|
}
|
|
1825
2270
|
});
|
|
1826
2271
|
|
|
1827
2272
|
// src/commands/init/index.ts
|
|
1828
|
-
import { existsSync, copyFileSync, writeFileSync } from "fs";
|
|
2273
|
+
import { existsSync as existsSync2, copyFileSync, writeFileSync } from "fs";
|
|
1829
2274
|
import { randomBytes } from "crypto";
|
|
1830
2275
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1831
2276
|
import { join } from "path";
|
|
1832
|
-
import { defineCommand as
|
|
1833
|
-
import
|
|
2277
|
+
import { defineCommand as defineCommand26 } from "citty";
|
|
2278
|
+
import consola20 from "consola";
|
|
1834
2279
|
var DEFAULT_IDP_URL = "https://id.openape.at";
|
|
1835
2280
|
async function downloadTemplate(repo, targetDir) {
|
|
1836
2281
|
const { downloadTemplate: gigetDownload } = await import("giget");
|
|
1837
2282
|
await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
|
|
1838
2283
|
}
|
|
1839
2284
|
function installDeps(dir) {
|
|
1840
|
-
const hasLockFile = (name) =>
|
|
2285
|
+
const hasLockFile = (name) => existsSync2(join(dir, name));
|
|
1841
2286
|
if (hasLockFile("pnpm-lock.yaml")) {
|
|
1842
2287
|
execFileSync2("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
|
|
1843
2288
|
} else if (hasLockFile("bun.lockb")) {
|
|
@@ -1847,20 +2292,20 @@ function installDeps(dir) {
|
|
|
1847
2292
|
}
|
|
1848
2293
|
}
|
|
1849
2294
|
async function promptChoice(message, choices) {
|
|
1850
|
-
const result = await
|
|
2295
|
+
const result = await consola20.prompt(message, { type: "select", options: choices });
|
|
1851
2296
|
if (typeof result === "symbol") {
|
|
1852
|
-
|
|
2297
|
+
throw new CliExit(0);
|
|
1853
2298
|
}
|
|
1854
2299
|
return result;
|
|
1855
2300
|
}
|
|
1856
2301
|
async function promptText(message, defaultValue) {
|
|
1857
|
-
const result = await
|
|
2302
|
+
const result = await consola20.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
|
|
1858
2303
|
if (typeof result === "symbol") {
|
|
1859
|
-
|
|
2304
|
+
throw new CliExit(0);
|
|
1860
2305
|
}
|
|
1861
2306
|
return result || defaultValue || "";
|
|
1862
2307
|
}
|
|
1863
|
-
var initCommand =
|
|
2308
|
+
var initCommand = defineCommand26({
|
|
1864
2309
|
meta: {
|
|
1865
2310
|
name: "init",
|
|
1866
2311
|
description: "Scaffold a new OpenApe project"
|
|
@@ -1902,24 +2347,23 @@ var initCommand = defineCommand22({
|
|
|
1902
2347
|
});
|
|
1903
2348
|
async function initSP(targetDir) {
|
|
1904
2349
|
const dir = targetDir || "my-app";
|
|
1905
|
-
if (
|
|
1906
|
-
|
|
1907
|
-
return process.exit(1);
|
|
2350
|
+
if (existsSync2(join(dir, "package.json"))) {
|
|
2351
|
+
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
1908
2352
|
}
|
|
1909
|
-
|
|
2353
|
+
consola20.start("Scaffolding SP starter...");
|
|
1910
2354
|
await downloadTemplate("openape-ai/openape-sp-starter", dir);
|
|
1911
|
-
|
|
1912
|
-
|
|
2355
|
+
consola20.success("Scaffolded from openape-sp-starter");
|
|
2356
|
+
consola20.start("Installing dependencies...");
|
|
1913
2357
|
installDeps(dir);
|
|
1914
|
-
|
|
2358
|
+
consola20.success("Dependencies installed");
|
|
1915
2359
|
const envExample = join(dir, ".env.example");
|
|
1916
2360
|
const envFile = join(dir, ".env");
|
|
1917
|
-
if (
|
|
2361
|
+
if (existsSync2(envExample) && !existsSync2(envFile)) {
|
|
1918
2362
|
copyFileSync(envExample, envFile);
|
|
1919
|
-
|
|
2363
|
+
consola20.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
|
|
1920
2364
|
}
|
|
1921
2365
|
console.log("");
|
|
1922
|
-
|
|
2366
|
+
consola20.box([
|
|
1923
2367
|
`cd ${dir}`,
|
|
1924
2368
|
"npm run dev",
|
|
1925
2369
|
"",
|
|
@@ -1928,9 +2372,8 @@ async function initSP(targetDir) {
|
|
|
1928
2372
|
}
|
|
1929
2373
|
async function initIdP(targetDir) {
|
|
1930
2374
|
const dir = targetDir || "my-idp";
|
|
1931
|
-
if (
|
|
1932
|
-
|
|
1933
|
-
return process.exit(1);
|
|
2375
|
+
if (existsSync2(join(dir, "package.json"))) {
|
|
2376
|
+
throw new CliError(`Directory "${dir}" already contains a project.`);
|
|
1934
2377
|
}
|
|
1935
2378
|
const domain = await promptText("Domain for the IdP", "localhost");
|
|
1936
2379
|
const storage = await promptChoice("Storage backend", [
|
|
@@ -1939,15 +2382,15 @@ async function initIdP(targetDir) {
|
|
|
1939
2382
|
"s3 (S3-compatible)"
|
|
1940
2383
|
]);
|
|
1941
2384
|
const adminEmail = await promptText("Admin email");
|
|
1942
|
-
|
|
2385
|
+
consola20.start("Scaffolding IdP starter...");
|
|
1943
2386
|
await downloadTemplate("openape-ai/openape-idp-starter", dir);
|
|
1944
|
-
|
|
1945
|
-
|
|
2387
|
+
consola20.success("Scaffolded from openape-idp-starter");
|
|
2388
|
+
consola20.start("Installing dependencies...");
|
|
1946
2389
|
installDeps(dir);
|
|
1947
|
-
|
|
2390
|
+
consola20.success("Dependencies installed");
|
|
1948
2391
|
const sessionSecret = randomBytes(32).toString("hex");
|
|
1949
2392
|
const managementToken = randomBytes(32).toString("hex");
|
|
1950
|
-
|
|
2393
|
+
consola20.success("Secrets generated");
|
|
1951
2394
|
const isLocalhost = domain === "localhost";
|
|
1952
2395
|
const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
|
|
1953
2396
|
const envContent = [
|
|
@@ -1961,10 +2404,11 @@ async function initIdP(targetDir) {
|
|
|
1961
2404
|
`NUXT_OPENAPE_RP_ID=${domain}`,
|
|
1962
2405
|
`NUXT_OPENAPE_RP_ORIGIN=${origin}`
|
|
1963
2406
|
].join("\n");
|
|
1964
|
-
writeFileSync(join(dir, ".env"), envContent
|
|
1965
|
-
|
|
2407
|
+
writeFileSync(join(dir, ".env"), `${envContent}
|
|
2408
|
+
`, { mode: 384 });
|
|
2409
|
+
consola20.success(".env created");
|
|
1966
2410
|
console.log("");
|
|
1967
|
-
|
|
2411
|
+
consola20.box([
|
|
1968
2412
|
`cd ${dir}`,
|
|
1969
2413
|
"npm run dev",
|
|
1970
2414
|
"",
|
|
@@ -1981,19 +2425,19 @@ async function initIdP(targetDir) {
|
|
|
1981
2425
|
|
|
1982
2426
|
// src/commands/enroll.ts
|
|
1983
2427
|
import { Buffer as Buffer2 } from "buffer";
|
|
1984
|
-
import { existsSync as
|
|
2428
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
|
|
1985
2429
|
import { execFile as execFile2 } from "child_process";
|
|
1986
2430
|
import { generateKeyPairSync, sign } from "crypto";
|
|
1987
|
-
import { dirname, resolve } from "path";
|
|
1988
|
-
import { homedir } from "os";
|
|
1989
|
-
import { defineCommand as
|
|
1990
|
-
import
|
|
2431
|
+
import { dirname, resolve as resolve2 } from "path";
|
|
2432
|
+
import { homedir as homedir2 } from "os";
|
|
2433
|
+
import { defineCommand as defineCommand27 } from "citty";
|
|
2434
|
+
import consola21 from "consola";
|
|
1991
2435
|
var DEFAULT_IDP_URL2 = "https://id.openape.at";
|
|
1992
2436
|
var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
|
|
1993
2437
|
var POLL_INTERVAL = 3e3;
|
|
1994
2438
|
var POLL_TIMEOUT = 3e5;
|
|
1995
2439
|
function resolvePath(p) {
|
|
1996
|
-
return
|
|
2440
|
+
return resolve2(p.replace(/^~/, homedir2()));
|
|
1997
2441
|
}
|
|
1998
2442
|
function openBrowser2(url) {
|
|
1999
2443
|
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
@@ -2002,10 +2446,10 @@ function openBrowser2(url) {
|
|
|
2002
2446
|
}
|
|
2003
2447
|
function readPublicKey(keyPath) {
|
|
2004
2448
|
const pubPath = `${keyPath}.pub`;
|
|
2005
|
-
if (
|
|
2006
|
-
return
|
|
2449
|
+
if (existsSync3(pubPath)) {
|
|
2450
|
+
return readFileSync2(pubPath, "utf-8").trim();
|
|
2007
2451
|
}
|
|
2008
|
-
const keyContent =
|
|
2452
|
+
const keyContent = readFileSync2(keyPath, "utf-8");
|
|
2009
2453
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
2010
2454
|
const jwk = privateKey.export({ format: "jwk" });
|
|
2011
2455
|
const pubBytes = Buffer2.from(jwk.x, "base64url");
|
|
@@ -2020,7 +2464,7 @@ function readPublicKey(keyPath) {
|
|
|
2020
2464
|
function generateAndSaveKey(keyPath) {
|
|
2021
2465
|
const resolved = resolvePath(keyPath);
|
|
2022
2466
|
const dir = dirname(resolved);
|
|
2023
|
-
if (!
|
|
2467
|
+
if (!existsSync3(dir)) {
|
|
2024
2468
|
mkdirSync(dir, { recursive: true });
|
|
2025
2469
|
}
|
|
2026
2470
|
const { publicKey, privateKey } = generateKeyPairSync("ed25519");
|
|
@@ -2041,7 +2485,7 @@ function generateAndSaveKey(keyPath) {
|
|
|
2041
2485
|
}
|
|
2042
2486
|
async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
2043
2487
|
const resolvedKey = resolvePath(keyPath);
|
|
2044
|
-
const keyContent =
|
|
2488
|
+
const keyContent = readFileSync2(resolvedKey, "utf-8");
|
|
2045
2489
|
const privateKey = loadEd25519PrivateKey(keyContent);
|
|
2046
2490
|
const challengeUrl = await getAgentChallengeEndpoint(idp);
|
|
2047
2491
|
const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
|
|
@@ -2068,11 +2512,11 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
|
|
|
2068
2512
|
}
|
|
2069
2513
|
} catch {
|
|
2070
2514
|
}
|
|
2071
|
-
await new Promise((
|
|
2515
|
+
await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
|
|
2072
2516
|
}
|
|
2073
2517
|
throw new Error("Enrollment timed out. Please check the browser and try again.");
|
|
2074
2518
|
}
|
|
2075
|
-
var enrollCommand =
|
|
2519
|
+
var enrollCommand = defineCommand27({
|
|
2076
2520
|
meta: {
|
|
2077
2521
|
name: "enroll",
|
|
2078
2522
|
description: "Enroll an agent with an Identity Provider"
|
|
@@ -2092,38 +2536,48 @@ var enrollCommand = defineCommand23({
|
|
|
2092
2536
|
}
|
|
2093
2537
|
},
|
|
2094
2538
|
async run({ args }) {
|
|
2095
|
-
const idp = args.idp || await
|
|
2096
|
-
|
|
2539
|
+
const idp = args.idp || await consola21.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
|
|
2540
|
+
if (typeof r === "symbol") throw new CliExit(0);
|
|
2541
|
+
return r;
|
|
2542
|
+
}) || DEFAULT_IDP_URL2;
|
|
2543
|
+
const agentName = args.name || await consola21.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
|
|
2544
|
+
if (typeof r === "symbol") throw new CliExit(0);
|
|
2545
|
+
return r;
|
|
2546
|
+
});
|
|
2097
2547
|
if (!agentName) {
|
|
2098
|
-
|
|
2099
|
-
return process.exit(1);
|
|
2548
|
+
throw new CliError("Agent name is required.");
|
|
2100
2549
|
}
|
|
2101
|
-
const keyPath = args.key || await
|
|
2550
|
+
const keyPath = args.key || await consola21.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
|
|
2551
|
+
if (typeof r === "symbol") throw new CliExit(0);
|
|
2552
|
+
return r;
|
|
2553
|
+
}) || DEFAULT_KEY_PATH;
|
|
2102
2554
|
const resolvedKey = resolvePath(keyPath);
|
|
2103
2555
|
let publicKey;
|
|
2104
|
-
if (
|
|
2556
|
+
if (existsSync3(resolvedKey)) {
|
|
2105
2557
|
publicKey = readPublicKey(resolvedKey);
|
|
2106
|
-
|
|
2558
|
+
consola21.success(`Using existing key ${keyPath}`);
|
|
2107
2559
|
} else {
|
|
2108
|
-
|
|
2560
|
+
consola21.start(`Generating Ed25519 key pair at ${keyPath}...`);
|
|
2109
2561
|
publicKey = generateAndSaveKey(keyPath);
|
|
2110
|
-
|
|
2562
|
+
consola21.success(`Key pair generated at ${keyPath}`);
|
|
2111
2563
|
}
|
|
2112
2564
|
const encodedKey = encodeURIComponent(publicKey);
|
|
2113
2565
|
const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
|
|
2114
|
-
|
|
2115
|
-
|
|
2566
|
+
consola21.info("Opening browser for enrollment...");
|
|
2567
|
+
consola21.info(`\u2192 ${idp}/enroll`);
|
|
2116
2568
|
openBrowser2(enrollUrl);
|
|
2117
2569
|
console.log("");
|
|
2118
|
-
const agentEmail = await
|
|
2570
|
+
const agentEmail = await consola21.prompt(
|
|
2119
2571
|
"Agent email (shown in browser after enrollment)",
|
|
2120
2572
|
{ type: "text", placeholder: `agent+${agentName}@...` }
|
|
2121
|
-
).then((r) =>
|
|
2573
|
+
).then((r) => {
|
|
2574
|
+
if (typeof r === "symbol") throw new CliExit(0);
|
|
2575
|
+
return r;
|
|
2576
|
+
});
|
|
2122
2577
|
if (!agentEmail) {
|
|
2123
|
-
|
|
2124
|
-
return process.exit(1);
|
|
2578
|
+
throw new CliError("Agent email is required to verify enrollment.");
|
|
2125
2579
|
}
|
|
2126
|
-
|
|
2580
|
+
consola21.start("Verifying enrollment...");
|
|
2127
2581
|
const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
|
|
2128
2582
|
saveAuth({
|
|
2129
2583
|
idp,
|
|
@@ -2135,18 +2589,81 @@ var enrollCommand = defineCommand23({
|
|
|
2135
2589
|
config.defaults = { ...config.defaults, idp };
|
|
2136
2590
|
config.agent = { key: keyPath, email: agentEmail };
|
|
2137
2591
|
saveConfig(config);
|
|
2138
|
-
|
|
2139
|
-
|
|
2592
|
+
consola21.success(`Agent enrolled as ${agentEmail}`);
|
|
2593
|
+
consola21.success("Config saved to ~/.config/apes/");
|
|
2140
2594
|
console.log("");
|
|
2141
|
-
|
|
2595
|
+
consola21.info("Verify with: apes whoami");
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
|
|
2599
|
+
// src/commands/register-user.ts
|
|
2600
|
+
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
2601
|
+
import { defineCommand as defineCommand28 } from "citty";
|
|
2602
|
+
import consola22 from "consola";
|
|
2603
|
+
var registerUserCommand = defineCommand28({
|
|
2604
|
+
meta: {
|
|
2605
|
+
name: "register-user",
|
|
2606
|
+
description: "Register a sub-user with SSH key"
|
|
2607
|
+
},
|
|
2608
|
+
args: {
|
|
2609
|
+
email: {
|
|
2610
|
+
type: "string",
|
|
2611
|
+
description: "Email for the new user",
|
|
2612
|
+
required: true
|
|
2613
|
+
},
|
|
2614
|
+
name: {
|
|
2615
|
+
type: "string",
|
|
2616
|
+
description: "Name for the new user",
|
|
2617
|
+
required: true
|
|
2618
|
+
},
|
|
2619
|
+
key: {
|
|
2620
|
+
type: "string",
|
|
2621
|
+
description: "Path to SSH public key file or key string",
|
|
2622
|
+
required: true
|
|
2623
|
+
},
|
|
2624
|
+
type: {
|
|
2625
|
+
type: "string",
|
|
2626
|
+
description: "User type: human or agent (default: agent)"
|
|
2627
|
+
}
|
|
2628
|
+
},
|
|
2629
|
+
async run({ args }) {
|
|
2630
|
+
const auth = loadAuth();
|
|
2631
|
+
if (!auth) {
|
|
2632
|
+
throw new CliError("Not authenticated. Run `apes login` first.");
|
|
2633
|
+
}
|
|
2634
|
+
const idp = getIdpUrl();
|
|
2635
|
+
if (!idp) {
|
|
2636
|
+
throw new CliError("No IdP URL configured. Run `apes login` first.");
|
|
2637
|
+
}
|
|
2638
|
+
let publicKey = args.key;
|
|
2639
|
+
if (existsSync4(args.key)) {
|
|
2640
|
+
publicKey = readFileSync3(args.key, "utf-8").trim();
|
|
2641
|
+
}
|
|
2642
|
+
if (!publicKey.startsWith("ssh-ed25519 ")) {
|
|
2643
|
+
throw new CliError("Public key must be in ssh-ed25519 format.");
|
|
2644
|
+
}
|
|
2645
|
+
const userType = args.type;
|
|
2646
|
+
if (userType && userType !== "human" && userType !== "agent") {
|
|
2647
|
+
throw new CliError('Type must be "human" or "agent".');
|
|
2648
|
+
}
|
|
2649
|
+
const result = await apiFetch(`${idp}/api/auth/enroll`, {
|
|
2650
|
+
method: "POST",
|
|
2651
|
+
body: {
|
|
2652
|
+
email: args.email,
|
|
2653
|
+
name: args.name,
|
|
2654
|
+
publicKey,
|
|
2655
|
+
...userType ? { type: userType } : {}
|
|
2656
|
+
}
|
|
2657
|
+
});
|
|
2658
|
+
consola22.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
|
|
2142
2659
|
}
|
|
2143
2660
|
});
|
|
2144
2661
|
|
|
2145
2662
|
// src/commands/dns-check.ts
|
|
2146
|
-
import { defineCommand as
|
|
2147
|
-
import
|
|
2663
|
+
import { defineCommand as defineCommand29 } from "citty";
|
|
2664
|
+
import consola23 from "consola";
|
|
2148
2665
|
import { resolveDDISA } from "@openape/core";
|
|
2149
|
-
var dnsCheckCommand =
|
|
2666
|
+
var dnsCheckCommand = defineCommand29({
|
|
2150
2667
|
meta: {
|
|
2151
2668
|
name: "dns-check",
|
|
2152
2669
|
description: "Validate DDISA DNS TXT records for a domain"
|
|
@@ -2160,17 +2677,16 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2160
2677
|
},
|
|
2161
2678
|
async run({ args }) {
|
|
2162
2679
|
const domain = args.domain;
|
|
2163
|
-
|
|
2680
|
+
consola23.start(`Checking _ddisa.${domain}...`);
|
|
2164
2681
|
try {
|
|
2165
2682
|
const result = await resolveDDISA(domain);
|
|
2166
2683
|
if (!result) {
|
|
2167
|
-
consola21.error(`No DDISA record found for ${domain}`);
|
|
2168
2684
|
console.log("");
|
|
2169
2685
|
console.log("To set up DDISA, add a DNS TXT record:");
|
|
2170
2686
|
console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
|
|
2171
|
-
|
|
2687
|
+
throw new CliError(`No DDISA record found for ${domain}`);
|
|
2172
2688
|
}
|
|
2173
|
-
|
|
2689
|
+
consola23.success(`_ddisa.${domain} \u2192 ${result.idp}`);
|
|
2174
2690
|
console.log("");
|
|
2175
2691
|
console.log(` Version: ${result.version || "ddisa1"}`);
|
|
2176
2692
|
console.log(` IdP URL: ${result.idp}`);
|
|
@@ -2179,14 +2695,14 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2179
2695
|
if (result.priority !== void 0)
|
|
2180
2696
|
console.log(` Priority: ${result.priority}`);
|
|
2181
2697
|
console.log("");
|
|
2182
|
-
|
|
2698
|
+
consola23.start(`Verifying IdP at ${result.idp}...`);
|
|
2183
2699
|
const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
|
|
2184
2700
|
if (!discoResp.ok) {
|
|
2185
|
-
|
|
2701
|
+
consola23.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
|
|
2186
2702
|
return;
|
|
2187
2703
|
}
|
|
2188
2704
|
const disco = await discoResp.json();
|
|
2189
|
-
|
|
2705
|
+
consola23.success(`IdP is reachable`);
|
|
2190
2706
|
console.log(` Issuer: ${disco.issuer}`);
|
|
2191
2707
|
console.log(` DDISA: v${disco.ddisa_version || "?"}`);
|
|
2192
2708
|
if (disco.ddisa_auth_methods_supported) {
|
|
@@ -2196,15 +2712,146 @@ var dnsCheckCommand = defineCommand24({
|
|
|
2196
2712
|
console.log(` Grants: ${disco.openape_grant_types_supported.join(", ")}`);
|
|
2197
2713
|
}
|
|
2198
2714
|
} catch (err) {
|
|
2199
|
-
|
|
2200
|
-
return process.exit(1);
|
|
2715
|
+
throw new CliError(`DNS check failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2201
2716
|
}
|
|
2202
2717
|
}
|
|
2203
2718
|
});
|
|
2204
2719
|
|
|
2720
|
+
// src/commands/workflows.ts
|
|
2721
|
+
import { defineCommand as defineCommand30 } from "citty";
|
|
2722
|
+
import consola24 from "consola";
|
|
2723
|
+
|
|
2724
|
+
// src/guides/index.ts
|
|
2725
|
+
var guides = [
|
|
2726
|
+
{
|
|
2727
|
+
id: "timed-session",
|
|
2728
|
+
title: "Timed maintenance session",
|
|
2729
|
+
description: "Request a timed grant for multiple commands without per-command approval.",
|
|
2730
|
+
steps: [
|
|
2731
|
+
{ description: "Request a timed grant (e.g. 1 hour)", command: "apes run --approval timed -- <your-command>" },
|
|
2732
|
+
{ description: "Approve the grant in the browser (link is printed)" },
|
|
2733
|
+
{ description: "Subsequent commands reuse the timed grant until it expires" },
|
|
2734
|
+
{ note: "Use --approval always for standing permissions (revoke manually when done)" }
|
|
2735
|
+
]
|
|
2736
|
+
},
|
|
2737
|
+
{
|
|
2738
|
+
id: "agent-onboarding",
|
|
2739
|
+
title: "Onboard a new agent",
|
|
2740
|
+
description: "Register an AI agent with a DDISA identity in under 3 minutes.",
|
|
2741
|
+
steps: [
|
|
2742
|
+
{ description: "Initialize a new project (optional)", command: "apes init --sp my-app" },
|
|
2743
|
+
{ description: "Enroll the agent at an IdP", command: "apes enroll" },
|
|
2744
|
+
{ description: "Verify enrollment", command: "apes whoami" },
|
|
2745
|
+
{ description: "Check DNS discovery", command: "apes dns-check" }
|
|
2746
|
+
]
|
|
2747
|
+
},
|
|
2748
|
+
{
|
|
2749
|
+
id: "delegation",
|
|
2750
|
+
title: "Delegate permissions",
|
|
2751
|
+
description: "Let an agent act on your behalf at a specific service.",
|
|
2752
|
+
steps: [
|
|
2753
|
+
{ description: "Create a delegation", command: "apes grants delegate --to agent@example.com --at api.example.com" },
|
|
2754
|
+
{ description: "List active delegations", command: "apes grants delegations" },
|
|
2755
|
+
{ description: "Revoke when no longer needed", command: "apes grants revoke <delegation-id>" }
|
|
2756
|
+
]
|
|
2757
|
+
},
|
|
2758
|
+
{
|
|
2759
|
+
id: "privilege-escalation",
|
|
2760
|
+
title: "Run commands as root (escapes)",
|
|
2761
|
+
description: "Execute privileged commands with grant-verified escalation.",
|
|
2762
|
+
steps: [
|
|
2763
|
+
{ description: "Request a grant to run a command as root", command: "apes run --as root -- apt-get upgrade" },
|
|
2764
|
+
{ description: "Approve the grant in the browser" },
|
|
2765
|
+
{ description: "The command executes via escapes with verified authorization" },
|
|
2766
|
+
{ note: "escapes must be installed on the target machine (cargo build && sudo make install)" }
|
|
2767
|
+
]
|
|
2768
|
+
}
|
|
2769
|
+
];
|
|
2770
|
+
|
|
2771
|
+
// src/commands/workflows.ts
|
|
2772
|
+
var workflowsCommand = defineCommand30({
|
|
2773
|
+
meta: {
|
|
2774
|
+
name: "workflows",
|
|
2775
|
+
description: "Discover workflow guides"
|
|
2776
|
+
},
|
|
2777
|
+
args: {
|
|
2778
|
+
id: {
|
|
2779
|
+
type: "positional",
|
|
2780
|
+
description: "Guide ID to show (omit for list)",
|
|
2781
|
+
required: false
|
|
2782
|
+
},
|
|
2783
|
+
json: {
|
|
2784
|
+
type: "boolean",
|
|
2785
|
+
description: "Output as JSON",
|
|
2786
|
+
default: false
|
|
2787
|
+
}
|
|
2788
|
+
},
|
|
2789
|
+
run({ args }) {
|
|
2790
|
+
if (args.id) {
|
|
2791
|
+
const guide = guides.find((g) => g.id === String(args.id));
|
|
2792
|
+
if (!guide) {
|
|
2793
|
+
consola24.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
|
|
2794
|
+
throw new CliError(`Guide not found: ${args.id}`);
|
|
2795
|
+
}
|
|
2796
|
+
if (args.json) {
|
|
2797
|
+
console.log(JSON.stringify(guide, null, 2));
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
console.log(`
|
|
2801
|
+
${guide.title}`);
|
|
2802
|
+
console.log(` ${guide.description}
|
|
2803
|
+
`);
|
|
2804
|
+
for (let i = 0; i < guide.steps.length; i++) {
|
|
2805
|
+
const step = guide.steps[i];
|
|
2806
|
+
if (step.note) {
|
|
2807
|
+
console.log(` Note: ${step.note}`);
|
|
2808
|
+
} else {
|
|
2809
|
+
console.log(` ${i + 1}. ${step.description}`);
|
|
2810
|
+
if (step.command) {
|
|
2811
|
+
console.log(` $ ${step.command}`);
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
console.log();
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
if (args.json) {
|
|
2819
|
+
console.log(JSON.stringify(guides.map((g) => ({ id: g.id, title: g.title, description: g.description })), null, 2));
|
|
2820
|
+
return;
|
|
2821
|
+
}
|
|
2822
|
+
console.log("\n Workflow Guides\n");
|
|
2823
|
+
for (const guide of guides) {
|
|
2824
|
+
console.log(` ${guide.id.padEnd(24)} ${guide.title}`);
|
|
2825
|
+
}
|
|
2826
|
+
console.log(`
|
|
2827
|
+
Show a guide: apes workflows <id>
|
|
2828
|
+
`);
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2205
2832
|
// src/cli.ts
|
|
2833
|
+
process.stdout.on("error", (err) => {
|
|
2834
|
+
if (err.code === "EPIPE") process.exit(0);
|
|
2835
|
+
throw err;
|
|
2836
|
+
});
|
|
2837
|
+
var shellRewrite = rewriteApeShellArgs(process.argv);
|
|
2838
|
+
if (shellRewrite) {
|
|
2839
|
+
if (shellRewrite.action === "rewrite") {
|
|
2840
|
+
process.argv = shellRewrite.argv;
|
|
2841
|
+
} else if (shellRewrite.action === "version") {
|
|
2842
|
+
console.log(`ape-shell (OpenApe DDISA shell wrapper)`);
|
|
2843
|
+
process.exit(0);
|
|
2844
|
+
} else if (shellRewrite.action === "help") {
|
|
2845
|
+
console.log("Usage: ape-shell -c <command>");
|
|
2846
|
+
console.log("Routes all commands through apes run for grant-based authorization.");
|
|
2847
|
+
process.exit(0);
|
|
2848
|
+
} else {
|
|
2849
|
+
console.error("ape-shell: only -c <command> mode is supported");
|
|
2850
|
+
process.exit(1);
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2206
2853
|
var debug = process.argv.includes("--debug");
|
|
2207
|
-
var grantsCommand =
|
|
2854
|
+
var grantsCommand = defineCommand31({
|
|
2208
2855
|
meta: {
|
|
2209
2856
|
name: "grants",
|
|
2210
2857
|
description: "Grant management"
|
|
@@ -2220,10 +2867,11 @@ var grantsCommand = defineCommand25({
|
|
|
2220
2867
|
revoke: revokeCommand,
|
|
2221
2868
|
token: tokenCommand,
|
|
2222
2869
|
delegate: delegateCommand,
|
|
2223
|
-
delegations: delegationsCommand
|
|
2870
|
+
delegations: delegationsCommand,
|
|
2871
|
+
"delegation-revoke": delegationRevokeCommand
|
|
2224
2872
|
}
|
|
2225
2873
|
});
|
|
2226
|
-
var configCommand =
|
|
2874
|
+
var configCommand = defineCommand31({
|
|
2227
2875
|
meta: {
|
|
2228
2876
|
name: "config",
|
|
2229
2877
|
description: "Configuration management"
|
|
@@ -2233,33 +2881,43 @@ var configCommand = defineCommand25({
|
|
|
2233
2881
|
set: configSetCommand
|
|
2234
2882
|
}
|
|
2235
2883
|
});
|
|
2236
|
-
var main =
|
|
2884
|
+
var main = defineCommand31({
|
|
2237
2885
|
meta: {
|
|
2238
2886
|
name: "apes",
|
|
2239
|
-
version: "0.
|
|
2887
|
+
version: "0.6.1",
|
|
2240
2888
|
description: "Unified CLI for OpenApe"
|
|
2241
2889
|
},
|
|
2242
2890
|
subCommands: {
|
|
2243
2891
|
init: initCommand,
|
|
2244
2892
|
enroll: enrollCommand,
|
|
2893
|
+
"register-user": registerUserCommand,
|
|
2245
2894
|
"dns-check": dnsCheckCommand,
|
|
2246
2895
|
login: loginCommand,
|
|
2247
2896
|
logout: logoutCommand,
|
|
2248
2897
|
whoami: whoamiCommand,
|
|
2249
2898
|
grants: grantsCommand,
|
|
2899
|
+
admin: adminCommand,
|
|
2250
2900
|
run: runCommand,
|
|
2251
2901
|
explain: explainCommand,
|
|
2252
2902
|
adapter: adapterCommand,
|
|
2253
2903
|
config: configCommand,
|
|
2254
2904
|
fetch: fetchCommand,
|
|
2255
|
-
mcp: mcpCommand
|
|
2905
|
+
mcp: mcpCommand,
|
|
2906
|
+
workflows: workflowsCommand
|
|
2256
2907
|
}
|
|
2257
2908
|
});
|
|
2258
2909
|
runMain(main).catch((err) => {
|
|
2910
|
+
if (err instanceof CliExit) {
|
|
2911
|
+
process.exit(err.exitCode);
|
|
2912
|
+
}
|
|
2913
|
+
if (err instanceof CliError) {
|
|
2914
|
+
consola25.error(err.message);
|
|
2915
|
+
process.exit(err.exitCode);
|
|
2916
|
+
}
|
|
2259
2917
|
if (debug) {
|
|
2260
|
-
|
|
2918
|
+
consola25.error(err);
|
|
2261
2919
|
} else {
|
|
2262
|
-
|
|
2920
|
+
consola25.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
|
|
2263
2921
|
}
|
|
2264
2922
|
process.exit(1);
|
|
2265
2923
|
});
|