@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/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-AGHP6MNV.js";
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
- } from "./chunk-KXESKY4X.js";
42
+ saveConfig,
43
+ searchAdapters,
44
+ verifyAndExecute,
45
+ waitForGrantStatus
46
+ } from "./chunk-G3Q2TMAI.js";
23
47
 
24
48
  // src/cli.ts
25
- import consola22 from "consola";
26
- import { defineCommand as defineCommand25, runMain } from "citty";
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
- consola.error("IdP URL required. Use --idp <url> or set APES_IDP.");
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((resolve2, reject) => {
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
- resolve2(authCode);
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
- consola.error(`Token exchange failed: ${text}`);
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
- consola.error("No access token received");
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: readFileSync2 } = await import("fs");
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
- consola.error("Agent email required for key-based login. Use --email <agent-email>");
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
- consola.error(`Challenge failed: ${await challengeResp.text()}`);
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 = readFileSync2(keyPath, "utf-8");
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
- consola.error(`Authentication failed: ${await authResp.text()}`);
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} (agent)`);
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
- consola3.error("Not logged in. Run `apes login` first.");
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
- consola4.error("No IdP URL configured. Run `apes login` first or pass --idp.");
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
- consola5.error("No IdP URL configured. Run `apes login` first.");
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
- consola5.error("Not logged in. Run `apes login` first.");
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
- consola6.error("Not logged in. Run `apes login` first.");
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
- consola6.error("Grant denied.");
520
- return process.exit(1);
551
+ throw new CliError("Grant denied.");
521
552
  }
522
553
  if (grant.status === "revoked") {
523
- consola6.error("Grant revoked.");
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
- consola6.error("Timed out waiting for approval.");
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
- consola7.error("Grant denied.");
648
- process.exit(1);
675
+ throw new CliError("Grant denied.");
649
676
  }
650
677
  if (grant.status === "revoked") {
651
- consola7.error("Grant revoked.");
652
- process.exit(1);
678
+ throw new CliError("Grant revoked.");
653
679
  }
654
- await new Promise((resolve2) => setTimeout(resolve2, interval));
680
+ await new Promise((resolve3) => setTimeout(resolve3, interval));
655
681
  }
656
- consola7.error("Timed out waiting for approval.");
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
- consola7.error("Not logged in. Run `apes login` first.");
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
- consola7.error("No IdP URL configured. Use --idp or log in first.");
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 (process.argv.includes("--debug")) {
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
- consola10.error("Use either --all-pending or grant IDs, not both.");
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
- consola10.error("Provide grant ID(s) or use --all-pending.");
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: token || void 0 });
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: token || void 0 }
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
- consola10.info(`Revoked ${succeeded} of ${results.length} grants.`);
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
- consola11.error("No token received. Grant may not be approved.");
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 consola12 from "consola";
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
- consola12.error("Not logged in. Run `apes login` first.");
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
- consola12.success(`Delegation created: ${result.id}`);
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 consola13 from "consola";
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 delegations = await apiFetch(delegationsUrl);
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
- consola13.info("No delegations found.");
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/adapter/index.ts
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
- import {
1026
- fetchRegistry,
1027
- findAdapter,
1028
- findConflictingAdapters,
1029
- getInstalledDigest,
1030
- installAdapter,
1031
- isInstalled,
1032
- loadAdapter as loadAdapter2,
1033
- removeAdapter,
1034
- searchAdapters
1035
- } from "@openape/shapes";
1036
- var adapterCommand = defineCommand15({
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: defineCommand15({
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
- consola14.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
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 = loadAdapter2(a.id);
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
- consola14.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
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: defineCommand15({
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
- consola14.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
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
- consola14.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1139
- consola14.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
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
- consola14.success(`${verb} ${result.id} \u2192 ${result.path}`);
1145
- consola14.info(`Digest: ${result.digest}`);
1489
+ consola16.success(`${verb} ${result.id} \u2192 ${result.path}`);
1490
+ consola16.info(`Digest: ${result.digest}`);
1146
1491
  }
1147
1492
  }
1148
1493
  }),
1149
- remove: defineCommand15({
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
- consola14.success(`Removed adapter: ${id}`);
1517
+ consola16.success(`Removed adapter: ${id}`);
1173
1518
  } else {
1174
- consola14.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1519
+ consola16.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1175
1520
  failed = true;
1176
1521
  }
1177
1522
  }
1178
1523
  if (failed)
1179
- process.exit(1);
1524
+ throw new CliError("Some adapters could not be removed");
1180
1525
  }
1181
1526
  }),
1182
- info: defineCommand15({
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: defineCommand15({
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
- consola14.info(`No adapters matching "${query}"`);
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: defineCommand15({
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
- consola14.info("No adapters installed to update.");
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
- consola14.warn(`${id}: not found in registry, skipping`);
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
- consola14.info(`${id}: already up to date`);
1647
+ consola16.info(`${id}: already up to date`);
1303
1648
  continue;
1304
1649
  }
1305
1650
  if (localDigest && !args.yes) {
1306
- consola14.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1307
- consola14.info(` Old: ${localDigest}`);
1308
- consola14.info(` New: ${entry.digest}`);
1309
- consola14.info(" Use --yes to confirm");
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
- consola14.success(`Updated ${result.id} \u2192 ${result.path}`);
1658
+ consola16.success(`Updated ${result.id} \u2192 ${result.path}`);
1314
1659
  }
1315
1660
  }
1316
1661
  }),
1317
- verify: defineCommand15({
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
- consola14.success(`${id}: digest matches registry`);
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
- process.exit(1);
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 defineCommand16 } from "citty";
1366
- import {
1367
- createShapesGrant,
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 = loadAdapter3(command[0], adapterOpt);
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
- consola15.info(`Reusing existing grant: ${existingGrantId}`);
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
- consola15.info(`Grant requested: ${grant.id}`);
1472
- consola15.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
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
- consola15.info("");
1476
- consola15.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
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
- consola15.info(` Broader scope: ${wider}`);
1932
+ consola17.info(` Broader scope: ${wider}`);
1480
1933
  }
1481
- consola15.info("");
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
- consola15.error("Not logged in. Run `apes login` first.");
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
- consola15.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
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
- consola15.success(`Grant requested: ${grant.id}`);
1513
- consola15.info("Waiting for approval...");
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
- consola15.success("Grant approved!");
1972
+ consola17.success("Grant approved!");
1521
1973
  break;
1522
1974
  }
1523
1975
  if (status.status === "denied" || status.status === "revoked") {
1524
- consola15.error(`Grant ${status.status}.`);
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
- consola15.info("Fetching grant token...");
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
- consola15.info(`Executing: ${command.join(" ")}`);
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
- process.exit(exitCode);
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 defineCommand17 } from "citty";
1550
- import { extractOption as extractOption2, extractWrappedCommand as extractWrappedCommand2, loadAdapter as loadAdapter4, resolveCommand as resolveCommand2 } from "@openape/shapes";
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 = extractWrappedCommand2(rawArgs ?? []);
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 = extractOption2(rawArgs ?? [], "adapter");
1572
- const loaded = loadAdapter4(command[0], adapterOpt);
1573
- const resolved = await resolveCommand2(loaded, command);
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 defineCommand18 } from "citty";
1590
- import consola16 from "consola";
1591
- var configGetCommand = defineCommand18({
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
- consola16.info("No IdP configured.");
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
- consola16.info("Not logged in.");
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
- consola16.info(`Key "${key}" not set.`);
2082
+ consola18.info(`Key "${key}" not set.`);
1633
2083
  }
1634
2084
  } else {
1635
- consola16.error(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
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 defineCommand19 } from "citty";
1645
- import consola17 from "consola";
1646
- var configSetCommand = defineCommand19({
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
- consola17.error(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
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
- consola17.error(`Unknown section: "${section}". Use: defaults, agent`);
1681
- return process.exit(1);
2128
+ throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
1682
2129
  }
1683
2130
  saveConfig(config);
1684
- consola17.success(`Set ${key} = ${value}`);
2131
+ consola19.success(`Set ${key} = ${value}`);
1685
2132
  }
1686
2133
  });
1687
2134
 
1688
2135
  // src/commands/fetch/index.ts
1689
- import { defineCommand as defineCommand20 } from "citty";
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
- consola18.error("Not authenticated. Run `apes login` first.");
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
- process.exit(1);
2169
+ throw new CliError(`HTTP ${response.status} ${response.statusText}`);
1725
2170
  }
1726
2171
  }
1727
- var fetchCommand = defineCommand20({
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: defineCommand20({
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: defineCommand20({
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 defineCommand21 } from "citty";
1799
- var mcpCommand = defineCommand21({
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-R2EVEMKX.js");
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 defineCommand22 } from "citty";
1833
- import consola19 from "consola";
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) => existsSync(join(dir, 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 consola19.prompt(message, { type: "select", options: choices });
2295
+ const result = await consola20.prompt(message, { type: "select", options: choices });
1851
2296
  if (typeof result === "symbol") {
1852
- process.exit(0);
2297
+ throw new CliExit(0);
1853
2298
  }
1854
2299
  return result;
1855
2300
  }
1856
2301
  async function promptText(message, defaultValue) {
1857
- const result = await consola19.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
2302
+ const result = await consola20.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
1858
2303
  if (typeof result === "symbol") {
1859
- process.exit(0);
2304
+ throw new CliExit(0);
1860
2305
  }
1861
2306
  return result || defaultValue || "";
1862
2307
  }
1863
- var initCommand = defineCommand22({
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 (existsSync(join(dir, "package.json"))) {
1906
- consola19.error(`Directory "${dir}" already contains a project.`);
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
- consola19.start("Scaffolding SP starter...");
2353
+ consola20.start("Scaffolding SP starter...");
1910
2354
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
1911
- consola19.success("Scaffolded from openape-sp-starter");
1912
- consola19.start("Installing dependencies...");
2355
+ consola20.success("Scaffolded from openape-sp-starter");
2356
+ consola20.start("Installing dependencies...");
1913
2357
  installDeps(dir);
1914
- consola19.success("Dependencies installed");
2358
+ consola20.success("Dependencies installed");
1915
2359
  const envExample = join(dir, ".env.example");
1916
2360
  const envFile = join(dir, ".env");
1917
- if (existsSync(envExample) && !existsSync(envFile)) {
2361
+ if (existsSync2(envExample) && !existsSync2(envFile)) {
1918
2362
  copyFileSync(envExample, envFile);
1919
- consola19.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
2363
+ consola20.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1920
2364
  }
1921
2365
  console.log("");
1922
- consola19.box([
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 (existsSync(join(dir, "package.json"))) {
1932
- consola19.error(`Directory "${dir}" already contains a project.`);
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
- consola19.start("Scaffolding IdP starter...");
2385
+ consola20.start("Scaffolding IdP starter...");
1943
2386
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
1944
- consola19.success("Scaffolded from openape-idp-starter");
1945
- consola19.start("Installing dependencies...");
2387
+ consola20.success("Scaffolded from openape-idp-starter");
2388
+ consola20.start("Installing dependencies...");
1946
2389
  installDeps(dir);
1947
- consola19.success("Dependencies installed");
2390
+ consola20.success("Dependencies installed");
1948
2391
  const sessionSecret = randomBytes(32).toString("hex");
1949
2392
  const managementToken = randomBytes(32).toString("hex");
1950
- consola19.success("Secrets generated");
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 + "\n", { mode: 384 });
1965
- consola19.success(".env created");
2407
+ writeFileSync(join(dir, ".env"), `${envContent}
2408
+ `, { mode: 384 });
2409
+ consola20.success(".env created");
1966
2410
  console.log("");
1967
- consola19.box([
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 existsSync2, readFileSync, writeFileSync as writeFileSync2, mkdirSync } from "fs";
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 defineCommand23 } from "citty";
1990
- import consola20 from "consola";
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 resolve(p.replace(/^~/, homedir()));
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 (existsSync2(pubPath)) {
2006
- return readFileSync(pubPath, "utf-8").trim();
2449
+ if (existsSync3(pubPath)) {
2450
+ return readFileSync2(pubPath, "utf-8").trim();
2007
2451
  }
2008
- const keyContent = readFileSync(keyPath, "utf-8");
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 (!existsSync2(dir)) {
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 = readFileSync(resolvedKey, "utf-8");
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((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
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 = defineCommand23({
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 consola20.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => typeof r === "symbol" ? process.exit(0) : r) || DEFAULT_IDP_URL2;
2096
- const agentName = args.name || await consola20.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => typeof r === "symbol" ? process.exit(0) : r);
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
- consola20.error("Agent name is required.");
2099
- return process.exit(1);
2548
+ throw new CliError("Agent name is required.");
2100
2549
  }
2101
- const keyPath = args.key || await consola20.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => typeof r === "symbol" ? process.exit(0) : r) || DEFAULT_KEY_PATH;
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 (existsSync2(resolvedKey)) {
2556
+ if (existsSync3(resolvedKey)) {
2105
2557
  publicKey = readPublicKey(resolvedKey);
2106
- consola20.success(`Using existing key ${keyPath}`);
2558
+ consola21.success(`Using existing key ${keyPath}`);
2107
2559
  } else {
2108
- consola20.start(`Generating Ed25519 key pair at ${keyPath}...`);
2560
+ consola21.start(`Generating Ed25519 key pair at ${keyPath}...`);
2109
2561
  publicKey = generateAndSaveKey(keyPath);
2110
- consola20.success(`Key pair generated at ${keyPath}`);
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
- consola20.info("Opening browser for enrollment...");
2115
- consola20.info(`\u2192 ${idp}/enroll`);
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 consola20.prompt(
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) => typeof r === "symbol" ? process.exit(0) : r);
2573
+ ).then((r) => {
2574
+ if (typeof r === "symbol") throw new CliExit(0);
2575
+ return r;
2576
+ });
2122
2577
  if (!agentEmail) {
2123
- consola20.error("Agent email is required to verify enrollment.");
2124
- return process.exit(1);
2578
+ throw new CliError("Agent email is required to verify enrollment.");
2125
2579
  }
2126
- consola20.start("Verifying enrollment...");
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
- consola20.success(`Agent enrolled as ${agentEmail}`);
2139
- consola20.success("Config saved to ~/.config/apes/");
2592
+ consola21.success(`Agent enrolled as ${agentEmail}`);
2593
+ consola21.success("Config saved to ~/.config/apes/");
2140
2594
  console.log("");
2141
- consola20.info("Verify with: apes whoami");
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 defineCommand24 } from "citty";
2147
- import consola21 from "consola";
2663
+ import { defineCommand as defineCommand29 } from "citty";
2664
+ import consola23 from "consola";
2148
2665
  import { resolveDDISA } from "@openape/core";
2149
- var dnsCheckCommand = defineCommand24({
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
- consola21.start(`Checking _ddisa.${domain}...`);
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
- return process.exit(1);
2687
+ throw new CliError(`No DDISA record found for ${domain}`);
2172
2688
  }
2173
- consola21.success(`_ddisa.${domain} \u2192 ${result.idp}`);
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
- consola21.start(`Verifying IdP at ${result.idp}...`);
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
- consola21.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
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
- consola21.success(`IdP is reachable`);
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
- consola21.error(`DNS check failed: ${err instanceof Error ? err.message : String(err)}`);
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 = defineCommand25({
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 = defineCommand25({
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 = defineCommand25({
2884
+ var main = defineCommand31({
2237
2885
  meta: {
2238
2886
  name: "apes",
2239
- version: "0.5.5",
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
- consola22.error(err);
2918
+ consola25.error(err);
2261
2919
  } else {
2262
- consola22.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
2920
+ consola25.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
2263
2921
  }
2264
2922
  process.exit(1);
2265
2923
  });