@openape/apes 0.6.0 → 0.7.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
@@ -5,35 +5,154 @@ import {
5
5
  parseDuration
6
6
  } from "./chunk-ZSJU7IXE.js";
7
7
  import {
8
- loadEd25519PrivateKey
9
- } from "./chunk-KVBHBOED.js";
8
+ loadEd25519PrivateKey,
9
+ readPublicKeyComment
10
+ } from "./chunk-ION3CWD5.js";
10
11
  import {
11
12
  ApiError,
12
13
  apiFetch,
13
- clearAuth,
14
+ buildStructuredCliGrantRequest,
15
+ createShapesGrant,
16
+ extractOption,
17
+ extractShellCommandString,
18
+ extractWrappedCommand,
19
+ fetchGrantToken,
20
+ fetchRegistry,
21
+ findAdapter,
22
+ findConflictingAdapters,
23
+ findExistingGrant,
14
24
  getAgentAuthenticateEndpoint,
15
25
  getAgentChallengeEndpoint,
16
- getAuthToken,
17
26
  getDelegationsEndpoint,
18
27
  getGrantsEndpoint,
28
+ getInstalledDigest,
29
+ installAdapter,
30
+ isInstalled,
31
+ loadAdapter,
32
+ loadOrInstallAdapter,
33
+ parseShellCommand,
34
+ removeAdapter,
35
+ resolveCapabilityRequest,
36
+ resolveCommand,
37
+ searchAdapters,
38
+ verifyAndExecute,
39
+ waitForGrantStatus
40
+ } from "./chunk-B32ZQP5K.js";
41
+ import {
42
+ clearAuth,
43
+ getAuthToken,
19
44
  getIdpUrl,
20
45
  loadAuth,
21
46
  loadConfig,
22
47
  saveAuth,
23
48
  saveConfig
24
- } from "./chunk-KXESKY4X.js";
49
+ } from "./chunk-TBYYREL6.js";
25
50
 
26
51
  // src/cli.ts
27
- import consola21 from "consola";
28
- import { defineCommand as defineCommand26, runMain } from "citty";
52
+ import consola26 from "consola";
53
+
54
+ // src/ape-shell.ts
55
+ import path from "path";
56
+ function rewriteApeShellArgs(argv, argv0) {
57
+ const rawInvokedAs = argv[1] ?? "";
58
+ const dashFromArgv1 = rawInvokedAs.startsWith("-");
59
+ const dashFromArgv0 = typeof argv0 === "string" && argv0.startsWith("-");
60
+ const looksLikeLoginShell = dashFromArgv1 || dashFromArgv0;
61
+ const normalizedInvokedAs = dashFromArgv1 ? rawInvokedAs.slice(1) : rawInvokedAs;
62
+ const invokedAs = path.basename(normalizedInvokedAs);
63
+ const wrapperEnv = typeof process !== "undefined" && process.env?.APES_SHELL_WRAPPER === "1";
64
+ const argvMatch = invokedAs === "ape-shell" || invokedAs === "ape-shell.js";
65
+ if (!wrapperEnv && !argvMatch)
66
+ return null;
67
+ const shellArgs = argv.slice(2);
68
+ if (shellArgs[0] === "-c" && shellArgs.length > 1) {
69
+ return { action: "rewrite", argv: [argv[0], argv[1], "run", "--shell", "--", "bash", "-c", ...shellArgs.slice(1)] };
70
+ }
71
+ if (shellArgs[0] === "--version" || shellArgs[0] === "-v")
72
+ return { action: "version" };
73
+ if (shellArgs[0] === "--help" || shellArgs[0] === "-h")
74
+ return { action: "help" };
75
+ if (shellArgs.length === 0 || shellArgs[0] === "-i" || shellArgs[0] === "-l" || shellArgs[0] === "--login" || looksLikeLoginShell) {
76
+ return { action: "interactive" };
77
+ }
78
+ return { action: "error" };
79
+ }
80
+
81
+ // src/cli.ts
82
+ import { defineCommand as defineCommand31, runMain } from "citty";
29
83
 
30
84
  // src/commands/auth/login.ts
31
85
  import { Buffer } from "buffer";
32
86
  import { execFile } from "child_process";
33
87
  import { createServer } from "http";
88
+ import { homedir as homedir2 } from "os";
89
+ import { resolve as resolvePath } from "path";
34
90
  import { defineCommand } from "citty";
35
91
  import { generateCodeChallenge, generateCodeVerifier } from "@openape/core";
92
+ import consola2 from "consola";
93
+
94
+ // src/commands/auth/resolve-login.ts
95
+ import { existsSync } from "fs";
96
+ import { homedir } from "os";
97
+ import { join } from "path";
98
+ import { resolveDDISA } from "@openape/core";
36
99
  import consola from "consola";
100
+ var DEFAULT_KEY = join(homedir(), ".ssh", "id_ed25519");
101
+ async function resolveLoginInputs(flags) {
102
+ const config = loadConfig();
103
+ let keyPath;
104
+ if (!flags.browser) {
105
+ if (flags.key) {
106
+ keyPath = flags.key;
107
+ } else if (process.env.APES_KEY) {
108
+ keyPath = process.env.APES_KEY;
109
+ consola.info(`Using key from APES_KEY: ${keyPath}`);
110
+ } else if (config.agent?.key) {
111
+ keyPath = config.agent.key;
112
+ consola.info(`Using key from config: ${keyPath}`);
113
+ } else if (existsSync(DEFAULT_KEY)) {
114
+ keyPath = DEFAULT_KEY;
115
+ consola.info(`Using default key: ${keyPath}`);
116
+ }
117
+ }
118
+ let email;
119
+ if (flags.email) {
120
+ email = flags.email;
121
+ } else if (process.env.APES_EMAIL) {
122
+ email = process.env.APES_EMAIL;
123
+ } else if (config.agent?.email) {
124
+ email = config.agent.email;
125
+ } else if (keyPath) {
126
+ const comment = readPublicKeyComment(`${keyPath}.pub`);
127
+ if (comment && comment.includes("@")) {
128
+ email = comment;
129
+ consola.info(`Using email from ${keyPath}.pub comment: ${email}`);
130
+ }
131
+ }
132
+ let idp;
133
+ if (flags.idp) {
134
+ idp = flags.idp;
135
+ } else if (process.env.APES_IDP) {
136
+ idp = process.env.APES_IDP;
137
+ } else if (process.env.GRAPES_IDP) {
138
+ idp = process.env.GRAPES_IDP;
139
+ } else if (config.defaults?.idp) {
140
+ idp = config.defaults.idp;
141
+ } else if (email && email.includes("@")) {
142
+ const domain = email.split("@")[1];
143
+ try {
144
+ const record = await resolveDDISA(domain);
145
+ if (record?.idp) {
146
+ idp = record.idp;
147
+ consola.info(`Discovered IdP via DDISA (_ddisa.${domain}): ${idp}`);
148
+ }
149
+ } catch {
150
+ }
151
+ }
152
+ return { keyPath, email, idp };
153
+ }
154
+
155
+ // src/commands/auth/login.ts
37
156
  var CALLBACK_PORT = 9876;
38
157
  var CLIENT_ID = "grapes-cli";
39
158
  var loginCommand = defineCommand({
@@ -44,27 +163,56 @@ var loginCommand = defineCommand({
44
163
  args: {
45
164
  idp: {
46
165
  type: "string",
47
- description: "IdP URL (e.g. https://id.openape.at)"
166
+ description: "IdP URL (e.g. https://id.openape.at). Auto-discovered via DDISA DNS if omitted."
48
167
  },
49
168
  key: {
50
169
  type: "string",
51
- description: "Path to agent private key (agent mode)"
170
+ description: "Path to agent private key. Defaults to ~/.ssh/id_ed25519 if present."
52
171
  },
53
172
  email: {
54
173
  type: "string",
55
- description: "Agent email (for DNS discovery)"
174
+ description: "Agent email. Extracted from <key>.pub comment if omitted."
175
+ },
176
+ browser: {
177
+ type: "boolean",
178
+ description: "Force browser (PKCE) login even if an SSH key exists"
56
179
  }
57
180
  },
58
181
  async run({ args }) {
59
- const config = loadConfig();
60
- const idp = args.idp || process.env.APES_IDP || process.env.GRAPES_IDP || config.defaults?.idp;
61
- if (!idp) {
62
- throw new CliError("IdP URL required. Use --idp <url> or set APES_IDP.");
63
- }
64
- if (args.key) {
65
- await loginWithKey(idp, args.key, args.email);
182
+ const resolved = await resolveLoginInputs({
183
+ key: args.key,
184
+ idp: args.idp,
185
+ email: args.email,
186
+ browser: args.browser
187
+ });
188
+ if (resolved.keyPath) {
189
+ if (!resolved.email) {
190
+ throw new CliError(
191
+ `Agent email required for key-based login. Add an email comment to ${resolved.keyPath}.pub (ssh-keygen -C <email>) or pass --email <agent-email>.`
192
+ );
193
+ }
194
+ if (!resolved.idp) {
195
+ const domain = resolved.email.split("@")[1];
196
+ throw new CliError(
197
+ `No IdP found for ${resolved.email}.
198
+
199
+ There is no DDISA TXT record for ${domain} and no --idp was provided.
200
+
201
+ Options:
202
+ \u2022 Run your own IdP (recommended for production)
203
+ See: https://docs.openape.at
204
+ \u2022 Publish a DDISA TXT record for your domain:
205
+ _ddisa.${domain} TXT "v=ddisa1 idp=https://your-idp.example"
206
+ \u2022 Use OpenApe's free hosted IdP for testing:
207
+ apes login --idp https://id.openape.at`
208
+ );
209
+ }
210
+ await loginWithKey(resolved.idp, resolved.keyPath, resolved.email);
66
211
  } else {
67
- await loginWithPKCE(idp);
212
+ if (!resolved.idp) {
213
+ throw new CliError("IdP URL required for browser login. Use --idp <url> or set APES_IDP.");
214
+ }
215
+ await loginWithPKCE(resolved.idp);
68
216
  }
69
217
  }
70
218
  });
@@ -88,7 +236,7 @@ async function loginWithPKCE(idp) {
88
236
  authUrl.searchParams.set("state", state);
89
237
  authUrl.searchParams.set("nonce", nonce);
90
238
  authUrl.searchParams.set("scope", "openid email profile offline_access");
91
- const code = await new Promise((resolve2, reject) => {
239
+ const code = await new Promise((resolve3, reject) => {
92
240
  const server = createServer((req, res) => {
93
241
  const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
94
242
  if (url.pathname === "/callback") {
@@ -105,7 +253,7 @@ async function loginWithPKCE(idp) {
105
253
  res.writeHead(200, { "Content-Type": "text/html" });
106
254
  res.end("<h1>Login successful!</h1><p>You can close this window.</p>");
107
255
  server.close();
108
- resolve2(authCode);
256
+ resolve3(authCode);
109
257
  return;
110
258
  }
111
259
  res.writeHead(400);
@@ -116,7 +264,12 @@ async function loginWithPKCE(idp) {
116
264
  }
117
265
  });
118
266
  server.listen(CALLBACK_PORT, () => {
119
- consola.info(`Opening browser for login at ${idp}...`);
267
+ console.log("");
268
+ console.log("Visit the following URL in a browser to authenticate:");
269
+ console.log("");
270
+ console.log(` ${authUrl.toString()}`);
271
+ console.log("");
272
+ consola2.info(`Waiting for authentication callback on ${redirectUri} ...`);
120
273
  openBrowser(authUrl.toString());
121
274
  });
122
275
  const timeout = setTimeout(() => {
@@ -153,16 +306,12 @@ async function loginWithPKCE(idp) {
153
306
  email: payload.email || payload.sub,
154
307
  expires_at: Math.floor(Date.now() / 1e3) + (tokens.expires_in || 3600)
155
308
  });
156
- consola.success(`Logged in as ${payload.email || payload.sub}`);
309
+ consola2.success(`Logged in as ${payload.email || payload.sub}`);
157
310
  }
158
- async function loginWithKey(idp, keyPath, email) {
159
- const { readFileSync: readFileSync2 } = await import("fs");
311
+ async function loginWithKey(idp, keyPath, agentEmail) {
312
+ const { readFileSync: readFileSync4 } = await import("fs");
160
313
  const { sign: sign2 } = await import("crypto");
161
- const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-Q7KG4K25.js");
162
- const agentEmail = email;
163
- if (!agentEmail) {
164
- throw new CliError("Agent email required for key-based login. Use --email <agent-email>");
165
- }
314
+ const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
166
315
  const challengeUrl = await getAgentChallengeEndpoint(idp);
167
316
  const challengeResp = await fetch(challengeUrl, {
168
317
  method: "POST",
@@ -173,7 +322,7 @@ async function loginWithKey(idp, keyPath, email) {
173
322
  throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
174
323
  }
175
324
  const { challenge } = await challengeResp.json();
176
- const keyContent = readFileSync2(keyPath, "utf-8");
325
+ const keyContent = readFileSync4(keyPath, "utf-8");
177
326
  const privateKey = loadEd25519PrivateKey2(keyContent);
178
327
  const signature = sign2(null, Buffer.from(challenge), privateKey).toString("base64");
179
328
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -196,12 +345,23 @@ async function loginWithKey(idp, keyPath, email) {
196
345
  email: agentEmail,
197
346
  expires_at: Math.floor(Date.now() / 1e3) + (expires_in || 3600)
198
347
  });
199
- consola.success(`Logged in as ${agentEmail} (agent)`);
348
+ const absoluteKeyPath = resolvePath(keyPath.replace(/^~/, homedir2()));
349
+ const existingConfig = loadConfig();
350
+ saveConfig({
351
+ ...existingConfig,
352
+ agent: {
353
+ ...existingConfig.agent,
354
+ key: absoluteKeyPath,
355
+ email: agentEmail
356
+ }
357
+ });
358
+ consola2.success(`Logged in as ${agentEmail}`);
359
+ consola2.info(`Auto-refresh enabled (key path saved to ~/.config/apes/config.toml)`);
200
360
  }
201
361
 
202
362
  // src/commands/auth/logout.ts
203
363
  import { defineCommand as defineCommand2 } from "citty";
204
- import consola2 from "consola";
364
+ import consola3 from "consola";
205
365
  var logoutCommand = defineCommand2({
206
366
  meta: {
207
367
  name: "logout",
@@ -209,13 +369,13 @@ var logoutCommand = defineCommand2({
209
369
  },
210
370
  run() {
211
371
  clearAuth();
212
- consola2.success("Logged out.");
372
+ consola3.success("Logged out.");
213
373
  }
214
374
  });
215
375
 
216
376
  // src/commands/auth/whoami.ts
217
377
  import { defineCommand as defineCommand3 } from "citty";
218
- import consola3 from "consola";
378
+ import consola4 from "consola";
219
379
  var whoamiCommand = defineCommand3({
220
380
  meta: {
221
381
  name: "whoami",
@@ -234,14 +394,14 @@ var whoamiCommand = defineCommand3({
234
394
  console.log(`IdP: ${auth.idp}`);
235
395
  console.log(`Token: ${isExpired ? "\u26A0 EXPIRED" : "valid"} (until ${expiresAt})`);
236
396
  if (isExpired) {
237
- consola3.warn("Token is expired. Run `apes login` to re-authenticate.");
397
+ consola4.warn("Token is expired. Run `apes login` to re-authenticate.");
238
398
  }
239
399
  }
240
400
  });
241
401
 
242
402
  // src/commands/grants/list.ts
243
403
  import { defineCommand as defineCommand4 } from "citty";
244
- import consola4 from "consola";
404
+ import consola5 from "consola";
245
405
  var listCommand = defineCommand4({
246
406
  meta: {
247
407
  name: "list",
@@ -290,7 +450,7 @@ var listCommand = defineCommand4({
290
450
  return;
291
451
  }
292
452
  if (grants.length === 0) {
293
- consola4.info(args.all ? "No grants found." : "No grants found. Use --all to see all visible grants.");
453
+ consola5.info(args.all ? "No grants found." : "No grants found. Use --all to see all visible grants.");
294
454
  return;
295
455
  }
296
456
  for (const grant of grants) {
@@ -302,14 +462,14 @@ var listCommand = defineCommand4({
302
462
  }
303
463
  }
304
464
  if (response.pagination.has_more) {
305
- consola4.info("More results available. Use --limit or pagination cursor.");
465
+ consola5.info("More results available. Use --limit or pagination cursor.");
306
466
  }
307
467
  }
308
468
  });
309
469
 
310
470
  // src/commands/grants/inbox.ts
311
471
  import { defineCommand as defineCommand5 } from "citty";
312
- import consola5 from "consola";
472
+ import consola6 from "consola";
313
473
  var inboxCommand = defineCommand5({
314
474
  meta: {
315
475
  name: "inbox",
@@ -348,10 +508,10 @@ var inboxCommand = defineCommand5({
348
508
  return;
349
509
  }
350
510
  if (grants.length === 0) {
351
- consola5.info("No pending grants to approve.");
511
+ consola6.info("No pending grants to approve.");
352
512
  return;
353
513
  }
354
- consola5.info(`${grants.length} grant(s) awaiting approval:
514
+ consola6.info(`${grants.length} grant(s) awaiting approval:
355
515
  `);
356
516
  for (const grant of grants) {
357
517
  const cmd = grant.request?.command?.join(" ") || "(no command)";
@@ -366,7 +526,7 @@ var inboxCommand = defineCommand5({
366
526
  }
367
527
  console.log();
368
528
  }
369
- consola5.info("Use `apes grants approve <id>` or `apes grants deny <id>` to respond.");
529
+ consola6.info("Use `apes grants approve <id>` or `apes grants deny <id>` to respond.");
370
530
  }
371
531
  });
372
532
 
@@ -422,7 +582,7 @@ var statusCommand = defineCommand6({
422
582
  // src/commands/grants/request.ts
423
583
  import { hostname } from "os";
424
584
  import { defineCommand as defineCommand7 } from "citty";
425
- import consola6 from "consola";
585
+ import consola7 from "consola";
426
586
  var requestCommand = defineCommand7({
427
587
  meta: {
428
588
  name: "request",
@@ -489,9 +649,9 @@ var requestCommand = defineCommand7({
489
649
  ...args["run-as"] ? { run_as: args["run-as"] } : {}
490
650
  }
491
651
  });
492
- consola6.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
652
+ consola7.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
493
653
  if (args.wait) {
494
- consola6.info("Waiting for approval...");
654
+ consola7.info("Waiting for approval...");
495
655
  await waitForApproval(grantsUrl, grant.id);
496
656
  }
497
657
  }
@@ -503,7 +663,7 @@ async function waitForApproval(grantsUrl, grantId) {
503
663
  while (Date.now() - start < maxWait) {
504
664
  const grant = await apiFetch(`${grantsUrl}/${grantId}`);
505
665
  if (grant.status === "approved") {
506
- consola6.success("Grant approved!");
666
+ consola7.success("Grant approved!");
507
667
  return;
508
668
  }
509
669
  if (grant.status === "denied") {
@@ -519,9 +679,8 @@ async function waitForApproval(grantsUrl, grantId) {
519
679
 
520
680
  // src/commands/grants/request-capability.ts
521
681
  import { hostname as hostname2 } from "os";
522
- import { buildStructuredCliGrantRequest, loadAdapter, resolveCapabilityRequest } from "@openape/shapes";
523
682
  import { defineCommand as defineCommand8 } from "citty";
524
- import consola7 from "consola";
683
+ import consola8 from "consola";
525
684
  function parseCapabilityArgs(rawArgs) {
526
685
  const tokens = [...rawArgs];
527
686
  if (tokens[0] === "request-capability") {
@@ -628,7 +787,7 @@ async function waitForApproval2(grantsUrl, grantId) {
628
787
  while (Date.now() - start < maxWait) {
629
788
  const grant = await apiFetch(`${grantsUrl}/${grantId}`);
630
789
  if (grant.status === "approved") {
631
- consola7.success("Grant approved!");
790
+ consola8.success("Grant approved!");
632
791
  return;
633
792
  }
634
793
  if (grant.status === "denied") {
@@ -637,7 +796,7 @@ async function waitForApproval2(grantsUrl, grantId) {
637
796
  if (grant.status === "revoked") {
638
797
  throw new CliError("Grant revoked.");
639
798
  }
640
- await new Promise((resolve2) => setTimeout(resolve2, interval));
799
+ await new Promise((resolve3) => setTimeout(resolve3, interval));
641
800
  }
642
801
  throw new CliError("Timed out waiting for approval.");
643
802
  }
@@ -729,9 +888,9 @@ var requestCapabilityCommand = defineCommand8({
729
888
  idp,
730
889
  body: request
731
890
  });
732
- consola7.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
891
+ consola8.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
733
892
  if (parsed.wait) {
734
- consola7.info("Waiting for approval...");
893
+ consola8.info("Waiting for approval...");
735
894
  await waitForApproval2(grantsUrl, grant.id);
736
895
  }
737
896
  }
@@ -739,7 +898,7 @@ var requestCapabilityCommand = defineCommand8({
739
898
 
740
899
  // src/commands/grants/approve.ts
741
900
  import { defineCommand as defineCommand9 } from "citty";
742
- import consola8 from "consola";
901
+ import consola9 from "consola";
743
902
  var approveCommand = defineCommand9({
744
903
  meta: {
745
904
  name: "approve",
@@ -758,13 +917,13 @@ var approveCommand = defineCommand9({
758
917
  await apiFetch(`${grantsUrl}/${args.id}/approve`, {
759
918
  method: "POST"
760
919
  });
761
- consola8.success(`Grant ${args.id} approved.`);
920
+ consola9.success(`Grant ${args.id} approved.`);
762
921
  }
763
922
  });
764
923
 
765
924
  // src/commands/grants/deny.ts
766
925
  import { defineCommand as defineCommand10 } from "citty";
767
- import consola9 from "consola";
926
+ import consola10 from "consola";
768
927
  var denyCommand = defineCommand10({
769
928
  meta: {
770
929
  name: "deny",
@@ -783,13 +942,13 @@ var denyCommand = defineCommand10({
783
942
  await apiFetch(`${grantsUrl}/${args.id}/deny`, {
784
943
  method: "POST"
785
944
  });
786
- consola9.success(`Grant ${args.id} denied.`);
945
+ consola10.success(`Grant ${args.id} denied.`);
787
946
  }
788
947
  });
789
948
 
790
949
  // src/commands/grants/revoke.ts
791
950
  import { defineCommand as defineCommand11 } from "citty";
792
- import consola10 from "consola";
951
+ import consola11 from "consola";
793
952
  var revokeCommand = defineCommand11({
794
953
  meta: {
795
954
  name: "revoke",
@@ -818,11 +977,11 @@ var revokeCommand = defineCommand11({
818
977
  const idp = getIdpUrl();
819
978
  const grantsUrl = await getGrantsEndpoint(idp);
820
979
  if (args.debug) {
821
- consola10.debug(`idp: ${idp}`);
822
- consola10.debug(`grantsUrl: ${grantsUrl}`);
823
- consola10.debug(`auth.email: ${auth?.email}`);
824
- consola10.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
825
- consola10.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
980
+ consola11.debug(`idp: ${idp}`);
981
+ consola11.debug(`grantsUrl: ${grantsUrl}`);
982
+ consola11.debug(`auth.email: ${auth?.email}`);
983
+ consola11.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
984
+ consola11.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
826
985
  }
827
986
  if (!auth || !token) {
828
987
  throw new CliError("Authentication required. Run `apes login` and try again.");
@@ -840,11 +999,11 @@ var revokeCommand = defineCommand11({
840
999
  );
841
1000
  const ownPending = auth2?.email ? response.data.filter((g) => g.request?.requester === auth2.email) : response.data;
842
1001
  if (ownPending.length === 0) {
843
- consola10.info("No pending grants to revoke.");
1002
+ consola11.info("No pending grants to revoke.");
844
1003
  return;
845
1004
  }
846
1005
  ids = ownPending.map((g) => g.id);
847
- consola10.info(`Found ${ids.length} pending grant(s) to revoke.`);
1006
+ consola11.info(`Found ${ids.length} pending grant(s) to revoke.`);
848
1007
  } else if (explicitIds.length > 0) {
849
1008
  ids = explicitIds;
850
1009
  } else {
@@ -852,7 +1011,7 @@ var revokeCommand = defineCommand11({
852
1011
  }
853
1012
  if (ids.length === 1) {
854
1013
  await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token });
855
- consola10.success(`Grant ${ids[0]} revoked.`);
1014
+ consola11.success(`Grant ${ids[0]} revoked.`);
856
1015
  return;
857
1016
  }
858
1017
  const operations = ids.map((id) => ({ id, action: "revoke" }));
@@ -863,16 +1022,16 @@ var revokeCommand = defineCommand11({
863
1022
  let succeeded = 0;
864
1023
  for (const r of results) {
865
1024
  if (r.success) {
866
- consola10.success(`Grant ${r.id} revoked.`);
1025
+ consola11.success(`Grant ${r.id} revoked.`);
867
1026
  succeeded++;
868
1027
  } else {
869
- consola10.error(`Grant ${r.id}: ${r.error?.title || "Failed"}`);
1028
+ consola11.error(`Grant ${r.id}: ${r.error?.title || "Failed"}`);
870
1029
  }
871
1030
  }
872
1031
  if (succeeded < results.length) {
873
1032
  throw new CliError(`Revoked ${succeeded} of ${results.length} grants.`);
874
1033
  } else {
875
- consola10.success(`All ${succeeded} grants revoked.`);
1034
+ consola11.success(`All ${succeeded} grants revoked.`);
876
1035
  }
877
1036
  }
878
1037
  });
@@ -906,7 +1065,7 @@ var tokenCommand = defineCommand12({
906
1065
 
907
1066
  // src/commands/grants/delegate.ts
908
1067
  import { defineCommand as defineCommand13 } from "citty";
909
- import consola11 from "consola";
1068
+ import consola12 from "consola";
910
1069
  var delegateCommand = defineCommand13({
911
1070
  meta: {
912
1071
  name: "delegate",
@@ -959,7 +1118,7 @@ var delegateCommand = defineCommand13({
959
1118
  method: "POST",
960
1119
  body
961
1120
  });
962
- consola11.success(`Delegation created: ${result.id}`);
1121
+ consola12.success(`Delegation created: ${result.id}`);
963
1122
  console.log(` Delegate: ${args.to}`);
964
1123
  console.log(` Audience: ${args.at}`);
965
1124
  if (args.scopes)
@@ -972,7 +1131,7 @@ var delegateCommand = defineCommand13({
972
1131
 
973
1132
  // src/commands/grants/delegations.ts
974
1133
  import { defineCommand as defineCommand14 } from "citty";
975
- import consola12 from "consola";
1134
+ import consola13 from "consola";
976
1135
  var delegationsCommand = defineCommand14({
977
1136
  meta: {
978
1137
  name: "delegations",
@@ -988,13 +1147,14 @@ var delegationsCommand = defineCommand14({
988
1147
  async run({ args }) {
989
1148
  const idp = getIdpUrl();
990
1149
  const delegationsUrl = await getDelegationsEndpoint(idp);
991
- const delegations = await apiFetch(delegationsUrl);
1150
+ const response = await apiFetch(delegationsUrl);
1151
+ const delegations = Array.isArray(response) ? response : response.data;
992
1152
  if (args.json) {
993
1153
  console.log(JSON.stringify(delegations, null, 2));
994
1154
  return;
995
1155
  }
996
1156
  if (delegations.length === 0) {
997
- consola12.info("No delegations found.");
1157
+ consola13.info("No delegations found.");
998
1158
  return;
999
1159
  }
1000
1160
  for (const d of delegations) {
@@ -1005,27 +1165,345 @@ var delegationsCommand = defineCommand14({
1005
1165
  }
1006
1166
  });
1007
1167
 
1008
- // src/commands/adapter/index.ts
1168
+ // src/commands/grants/delegation-revoke.ts
1009
1169
  import { defineCommand as defineCommand15 } from "citty";
1010
- import consola13 from "consola";
1011
- import {
1012
- fetchRegistry,
1013
- findAdapter,
1014
- findConflictingAdapters,
1015
- getInstalledDigest,
1016
- installAdapter,
1017
- isInstalled,
1018
- loadAdapter as loadAdapter2,
1019
- removeAdapter,
1020
- searchAdapters
1021
- } from "@openape/shapes";
1022
- var adapterCommand = defineCommand15({
1170
+ import consola14 from "consola";
1171
+ var delegationRevokeCommand = defineCommand15({
1172
+ meta: {
1173
+ name: "delegation-revoke",
1174
+ description: "Revoke a delegation"
1175
+ },
1176
+ args: {
1177
+ id: {
1178
+ type: "positional",
1179
+ description: "Delegation ID to revoke",
1180
+ required: true
1181
+ }
1182
+ },
1183
+ async run({ args }) {
1184
+ const idp = getIdpUrl();
1185
+ if (!idp) {
1186
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1187
+ }
1188
+ const delegationsUrl = await getDelegationsEndpoint(idp);
1189
+ const id = String(args.id);
1190
+ const result = await apiFetch(
1191
+ `${delegationsUrl}/${id}`,
1192
+ { method: "DELETE" }
1193
+ );
1194
+ consola14.success(`Delegation ${result.id} revoked.`);
1195
+ }
1196
+ });
1197
+
1198
+ // src/commands/admin/index.ts
1199
+ import { defineCommand as defineCommand18 } from "citty";
1200
+
1201
+ // src/commands/admin/users.ts
1202
+ import { defineCommand as defineCommand16 } from "citty";
1203
+ import consola15 from "consola";
1204
+ function getManagementToken() {
1205
+ const token = process.env.APES_MANAGEMENT_TOKEN;
1206
+ if (!token) {
1207
+ throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
1208
+ }
1209
+ return token;
1210
+ }
1211
+ var usersListCommand = defineCommand16({
1212
+ meta: {
1213
+ name: "list",
1214
+ description: "List all users"
1215
+ },
1216
+ args: {
1217
+ json: {
1218
+ type: "boolean",
1219
+ description: "Output as JSON",
1220
+ default: false
1221
+ },
1222
+ limit: {
1223
+ type: "string",
1224
+ description: "Max number of users to return (1-100, default 50)"
1225
+ },
1226
+ cursor: {
1227
+ type: "string",
1228
+ description: "Pagination cursor (email of last item from previous page)"
1229
+ },
1230
+ search: {
1231
+ type: "string",
1232
+ description: "Filter by email or name (case-insensitive)"
1233
+ }
1234
+ },
1235
+ async run({ args }) {
1236
+ const idp = getIdpUrl();
1237
+ if (!idp) {
1238
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1239
+ }
1240
+ const token = getManagementToken();
1241
+ const params = new URLSearchParams();
1242
+ if (args.limit) params.set("limit", args.limit);
1243
+ if (args.cursor) params.set("cursor", args.cursor);
1244
+ if (args.search) params.set("search", args.search);
1245
+ const qs = params.toString();
1246
+ const url = qs ? `${idp}/api/admin/users?${qs}` : `${idp}/api/admin/users`;
1247
+ const result = await apiFetch(url, { token });
1248
+ if (args.json) {
1249
+ console.log(JSON.stringify(result, null, 2));
1250
+ return;
1251
+ }
1252
+ if (result.data.length === 0) {
1253
+ consola15.info("No users found.");
1254
+ return;
1255
+ }
1256
+ for (const u of result.data) {
1257
+ const owner = u.owner ? ` (agent of ${u.owner})` : "";
1258
+ const active = u.isActive ? "" : " [inactive]";
1259
+ console.log(`${u.email} ${u.name}${owner}${active}`);
1260
+ }
1261
+ if (result.pagination.has_more) {
1262
+ consola15.info(`More results available. Use --cursor="${result.pagination.cursor}" to see next page.`);
1263
+ }
1264
+ }
1265
+ });
1266
+ var usersCreateCommand = defineCommand16({
1267
+ meta: {
1268
+ name: "create",
1269
+ description: "Create a user"
1270
+ },
1271
+ args: {
1272
+ email: {
1273
+ type: "string",
1274
+ description: "User email",
1275
+ required: true
1276
+ },
1277
+ name: {
1278
+ type: "string",
1279
+ description: "User name",
1280
+ required: true
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 = getManagementToken();
1289
+ const result = await apiFetch(
1290
+ `${idp}/api/admin/users`,
1291
+ {
1292
+ method: "POST",
1293
+ body: { email: args.email, name: args.name },
1294
+ token
1295
+ }
1296
+ );
1297
+ consola15.success(`User created: ${result.email} (${result.name})`);
1298
+ }
1299
+ });
1300
+ var usersDeleteCommand = defineCommand16({
1301
+ meta: {
1302
+ name: "delete",
1303
+ description: "Delete a user"
1304
+ },
1305
+ args: {
1306
+ email: {
1307
+ type: "positional",
1308
+ description: "User email",
1309
+ required: true
1310
+ }
1311
+ },
1312
+ async run({ args }) {
1313
+ const idp = getIdpUrl();
1314
+ if (!idp) {
1315
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1316
+ }
1317
+ const token = getManagementToken();
1318
+ const email = String(args.email);
1319
+ await apiFetch(`${idp}/api/admin/users/${encodeURIComponent(email)}`, {
1320
+ method: "DELETE",
1321
+ token
1322
+ });
1323
+ consola15.success(`User deleted: ${email}`);
1324
+ }
1325
+ });
1326
+
1327
+ // src/commands/admin/ssh-keys.ts
1328
+ import { existsSync as existsSync2, readFileSync } from "fs";
1329
+ import { resolve } from "path";
1330
+ import { homedir as homedir3 } from "os";
1331
+ import { defineCommand as defineCommand17 } from "citty";
1332
+ import consola16 from "consola";
1333
+ function getManagementToken2() {
1334
+ const token = process.env.APES_MANAGEMENT_TOKEN;
1335
+ if (!token) {
1336
+ throw new CliError("Management token required. Set APES_MANAGEMENT_TOKEN environment variable.");
1337
+ }
1338
+ return token;
1339
+ }
1340
+ var sshKeysListCommand = defineCommand17({
1341
+ meta: {
1342
+ name: "list",
1343
+ description: "List SSH keys for a user"
1344
+ },
1345
+ args: {
1346
+ email: {
1347
+ type: "positional",
1348
+ description: "User email",
1349
+ required: true
1350
+ },
1351
+ json: {
1352
+ type: "boolean",
1353
+ description: "Output as JSON",
1354
+ default: false
1355
+ }
1356
+ },
1357
+ async run({ args }) {
1358
+ const idp = getIdpUrl();
1359
+ if (!idp) {
1360
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1361
+ }
1362
+ const token = getManagementToken2();
1363
+ const email = String(args.email);
1364
+ const keys = await apiFetch(
1365
+ `${idp}/api/admin/users/${encodeURIComponent(email)}/ssh-keys`,
1366
+ { token }
1367
+ );
1368
+ if (args.json) {
1369
+ console.log(JSON.stringify(keys, null, 2));
1370
+ return;
1371
+ }
1372
+ if (keys.length === 0) {
1373
+ consola16.info(`No SSH keys found for ${email}.`);
1374
+ return;
1375
+ }
1376
+ for (const k of keys) {
1377
+ console.log(`${k.keyId} ${k.name} ${k.publicKey.substring(0, 40)}...`);
1378
+ }
1379
+ }
1380
+ });
1381
+ var sshKeysAddCommand = defineCommand17({
1382
+ meta: {
1383
+ name: "add",
1384
+ description: "Add an SSH key for a user"
1385
+ },
1386
+ args: {
1387
+ email: {
1388
+ type: "string",
1389
+ description: "User email",
1390
+ required: true
1391
+ },
1392
+ key: {
1393
+ type: "string",
1394
+ description: "Path to public key file or key string",
1395
+ required: true
1396
+ },
1397
+ name: {
1398
+ type: "string",
1399
+ description: "Key name/label"
1400
+ }
1401
+ },
1402
+ async run({ args }) {
1403
+ const idp = getIdpUrl();
1404
+ if (!idp) {
1405
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1406
+ }
1407
+ const token = getManagementToken2();
1408
+ let publicKey = args.key;
1409
+ const resolved = resolve(args.key.replace(/^~/, homedir3()));
1410
+ if (existsSync2(resolved)) {
1411
+ publicKey = readFileSync(resolved, "utf-8").trim();
1412
+ }
1413
+ const body = { publicKey };
1414
+ if (args.name) {
1415
+ body.name = args.name;
1416
+ }
1417
+ const result = await apiFetch(
1418
+ `${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys`,
1419
+ {
1420
+ method: "POST",
1421
+ body,
1422
+ token
1423
+ }
1424
+ );
1425
+ consola16.success(`SSH key added: ${result.keyId} (${result.name})`);
1426
+ }
1427
+ });
1428
+ var sshKeysDeleteCommand = defineCommand17({
1429
+ meta: {
1430
+ name: "delete",
1431
+ description: "Delete an SSH key"
1432
+ },
1433
+ args: {
1434
+ email: {
1435
+ type: "string",
1436
+ description: "User email",
1437
+ required: true
1438
+ },
1439
+ keyId: {
1440
+ type: "positional",
1441
+ description: "Key ID",
1442
+ required: true
1443
+ }
1444
+ },
1445
+ async run({ args }) {
1446
+ const idp = getIdpUrl();
1447
+ if (!idp) {
1448
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1449
+ }
1450
+ const token = getManagementToken2();
1451
+ const keyId = String(args.keyId);
1452
+ await apiFetch(
1453
+ `${idp}/api/admin/users/${encodeURIComponent(args.email)}/ssh-keys/${keyId}`,
1454
+ {
1455
+ method: "DELETE",
1456
+ token
1457
+ }
1458
+ );
1459
+ consola16.success(`SSH key deleted: ${keyId}`);
1460
+ }
1461
+ });
1462
+
1463
+ // src/commands/admin/index.ts
1464
+ var usersCommand = defineCommand18({
1465
+ meta: {
1466
+ name: "users",
1467
+ description: "Manage users"
1468
+ },
1469
+ subCommands: {
1470
+ list: usersListCommand,
1471
+ create: usersCreateCommand,
1472
+ delete: usersDeleteCommand
1473
+ }
1474
+ });
1475
+ var sshKeysCommand = defineCommand18({
1476
+ meta: {
1477
+ name: "ssh-keys",
1478
+ description: "Manage SSH keys"
1479
+ },
1480
+ subCommands: {
1481
+ list: sshKeysListCommand,
1482
+ add: sshKeysAddCommand,
1483
+ delete: sshKeysDeleteCommand
1484
+ }
1485
+ });
1486
+ var adminCommand = defineCommand18({
1487
+ meta: {
1488
+ name: "admin",
1489
+ description: "Admin commands (requires APES_MANAGEMENT_TOKEN)"
1490
+ },
1491
+ subCommands: {
1492
+ users: usersCommand,
1493
+ "ssh-keys": sshKeysCommand
1494
+ }
1495
+ });
1496
+
1497
+ // src/commands/adapter/index.ts
1498
+ import { defineCommand as defineCommand19 } from "citty";
1499
+ import consola17 from "consola";
1500
+ var adapterCommand = defineCommand19({
1023
1501
  meta: {
1024
1502
  name: "adapter",
1025
1503
  description: "Manage CLI adapters"
1026
1504
  },
1027
1505
  subCommands: {
1028
- list: defineCommand15({
1506
+ list: defineCommand19({
1029
1507
  meta: {
1030
1508
  name: "list",
1031
1509
  description: "List available adapters"
@@ -1056,7 +1534,7 @@ var adapterCommand = defineCommand15({
1056
1534
  `);
1057
1535
  return;
1058
1536
  }
1059
- consola13.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1537
+ consola17.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1060
1538
  for (const a of index2.adapters) {
1061
1539
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
1062
1540
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -1067,7 +1545,7 @@ var adapterCommand = defineCommand15({
1067
1545
  const local = [];
1068
1546
  for (const a of index.adapters) {
1069
1547
  try {
1070
- const loaded = loadAdapter2(a.id);
1548
+ const loaded = loadAdapter(a.id);
1071
1549
  local.push({ id: a.id, source: loaded.source, digest: loaded.digest });
1072
1550
  } catch {
1073
1551
  }
@@ -1078,7 +1556,7 @@ var adapterCommand = defineCommand15({
1078
1556
  return;
1079
1557
  }
1080
1558
  if (local.length === 0) {
1081
- consola13.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1559
+ consola17.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1082
1560
  return;
1083
1561
  }
1084
1562
  for (const a of local) {
@@ -1086,7 +1564,7 @@ var adapterCommand = defineCommand15({
1086
1564
  }
1087
1565
  }
1088
1566
  }),
1089
- install: defineCommand15({
1567
+ install: defineCommand19({
1090
1568
  meta: {
1091
1569
  name: "install",
1092
1570
  description: "Install an adapter from the registry"
@@ -1115,24 +1593,24 @@ var adapterCommand = defineCommand15({
1115
1593
  for (const id of ids) {
1116
1594
  const entry = findAdapter(index, id);
1117
1595
  if (!entry) {
1118
- consola13.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1596
+ consola17.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1119
1597
  continue;
1120
1598
  }
1121
1599
  const conflicts = findConflictingAdapters(entry.executable, id);
1122
1600
  if (conflicts.length > 0) {
1123
1601
  for (const c of conflicts) {
1124
- consola13.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1125
- consola13.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1602
+ consola17.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1603
+ consola17.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1126
1604
  }
1127
1605
  }
1128
1606
  const result = await installAdapter(entry, { local });
1129
1607
  const verb = result.updated ? "Updated" : "Installed";
1130
- consola13.success(`${verb} ${result.id} \u2192 ${result.path}`);
1131
- consola13.info(`Digest: ${result.digest}`);
1608
+ consola17.success(`${verb} ${result.id} \u2192 ${result.path}`);
1609
+ consola17.info(`Digest: ${result.digest}`);
1132
1610
  }
1133
1611
  }
1134
1612
  }),
1135
- remove: defineCommand15({
1613
+ remove: defineCommand19({
1136
1614
  meta: {
1137
1615
  name: "remove",
1138
1616
  description: "Remove an installed adapter"
@@ -1155,9 +1633,9 @@ var adapterCommand = defineCommand15({
1155
1633
  let failed = false;
1156
1634
  for (const id of ids) {
1157
1635
  if (removeAdapter(id, local)) {
1158
- consola13.success(`Removed adapter: ${id}`);
1636
+ consola17.success(`Removed adapter: ${id}`);
1159
1637
  } else {
1160
- consola13.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1638
+ consola17.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1161
1639
  failed = true;
1162
1640
  }
1163
1641
  }
@@ -1165,7 +1643,7 @@ var adapterCommand = defineCommand15({
1165
1643
  throw new CliError("Some adapters could not be removed");
1166
1644
  }
1167
1645
  }),
1168
- info: defineCommand15({
1646
+ info: defineCommand19({
1169
1647
  meta: {
1170
1648
  name: "info",
1171
1649
  description: "Show detailed adapter information"
@@ -1207,7 +1685,7 @@ var adapterCommand = defineCommand15({
1207
1685
  }
1208
1686
  }
1209
1687
  }),
1210
- search: defineCommand15({
1688
+ search: defineCommand19({
1211
1689
  meta: {
1212
1690
  name: "search",
1213
1691
  description: "Search adapters in the registry"
@@ -1239,7 +1717,7 @@ var adapterCommand = defineCommand15({
1239
1717
  return;
1240
1718
  }
1241
1719
  if (results.length === 0) {
1242
- consola13.info(`No adapters matching "${query}"`);
1720
+ consola17.info(`No adapters matching "${query}"`);
1243
1721
  return;
1244
1722
  }
1245
1723
  for (const a of results) {
@@ -1248,7 +1726,7 @@ var adapterCommand = defineCommand15({
1248
1726
  }
1249
1727
  }
1250
1728
  }),
1251
- update: defineCommand15({
1729
+ update: defineCommand19({
1252
1730
  meta: {
1253
1731
  name: "update",
1254
1732
  description: "Update installed adapters"
@@ -1274,33 +1752,33 @@ var adapterCommand = defineCommand15({
1274
1752
  const targetId = args.id ? String(args.id) : void 0;
1275
1753
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
1276
1754
  if (targets.length === 0) {
1277
- consola13.info("No adapters installed to update.");
1755
+ consola17.info("No adapters installed to update.");
1278
1756
  return;
1279
1757
  }
1280
1758
  for (const id of targets) {
1281
1759
  const entry = findAdapter(index, id);
1282
1760
  if (!entry) {
1283
- consola13.warn(`${id}: not found in registry, skipping`);
1761
+ consola17.warn(`${id}: not found in registry, skipping`);
1284
1762
  continue;
1285
1763
  }
1286
1764
  const localDigest = getInstalledDigest(id, false);
1287
1765
  if (localDigest === entry.digest) {
1288
- consola13.info(`${id}: already up to date`);
1766
+ consola17.info(`${id}: already up to date`);
1289
1767
  continue;
1290
1768
  }
1291
1769
  if (localDigest && !args.yes) {
1292
- consola13.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1293
- consola13.info(` Old: ${localDigest}`);
1294
- consola13.info(` New: ${entry.digest}`);
1295
- consola13.info(" Use --yes to confirm");
1770
+ consola17.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1771
+ consola17.info(` Old: ${localDigest}`);
1772
+ consola17.info(` New: ${entry.digest}`);
1773
+ consola17.info(" Use --yes to confirm");
1296
1774
  continue;
1297
1775
  }
1298
1776
  const result = await installAdapter(entry);
1299
- consola13.success(`Updated ${result.id} \u2192 ${result.path}`);
1777
+ consola17.success(`Updated ${result.id} \u2192 ${result.path}`);
1300
1778
  }
1301
1779
  }
1302
1780
  }),
1303
- verify: defineCommand15({
1781
+ verify: defineCommand19({
1304
1782
  meta: {
1305
1783
  name: "verify",
1306
1784
  description: "Verify installed adapter against registry digest"
@@ -1333,7 +1811,7 @@ var adapterCommand = defineCommand15({
1333
1811
  if (!localDigest)
1334
1812
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1335
1813
  if (localDigest === entry.digest) {
1336
- consola13.success(`${id}: digest matches registry`);
1814
+ consola17.success(`${id}: digest matches registry`);
1337
1815
  } else {
1338
1816
  console.log(` Local: ${localDigest}`);
1339
1817
  console.log(` Registry: ${entry.digest}`);
@@ -1347,20 +1825,10 @@ var adapterCommand = defineCommand15({
1347
1825
  // src/commands/run.ts
1348
1826
  import { execFileSync } from "child_process";
1349
1827
  import { hostname as hostname3 } from "os";
1350
- import { defineCommand as defineCommand16 } from "citty";
1351
- import {
1352
- createShapesGrant,
1353
- extractOption,
1354
- extractWrappedCommand,
1355
- fetchGrantToken,
1356
- findExistingGrant,
1357
- loadAdapter as loadAdapter3,
1358
- resolveCommand,
1359
- verifyAndExecute,
1360
- waitForGrantStatus
1361
- } from "@openape/shapes";
1362
- import consola14 from "consola";
1363
- var runCommand = defineCommand16({
1828
+ import { basename } from "path";
1829
+ import { defineCommand as defineCommand20 } from "citty";
1830
+ import consola18 from "consola";
1831
+ var runCommand = defineCommand20({
1364
1832
  meta: {
1365
1833
  name: "run",
1366
1834
  description: "Execute a grant-secured command"
@@ -1396,6 +1864,11 @@ var runCommand = defineCommand16({
1396
1864
  type: "string",
1397
1865
  description: "IdP URL"
1398
1866
  },
1867
+ "shell": {
1868
+ type: "boolean",
1869
+ description: "Shell mode: use session grant with audience ape-shell",
1870
+ default: false
1871
+ },
1399
1872
  "_": {
1400
1873
  type: "positional",
1401
1874
  description: "Command to execute (after --)",
@@ -1404,6 +1877,10 @@ var runCommand = defineCommand16({
1404
1877
  },
1405
1878
  async run({ rawArgs, args }) {
1406
1879
  const wrappedCommand = extractWrappedCommand(rawArgs ?? []);
1880
+ if (args.shell && wrappedCommand.length > 0) {
1881
+ await runShellMode(wrappedCommand, args);
1882
+ return;
1883
+ }
1407
1884
  if (wrappedCommand.length > 0) {
1408
1885
  await runAdapterMode(wrappedCommand, rawArgs ?? [], args);
1409
1886
  } else {
@@ -1414,6 +1891,114 @@ var runCommand = defineCommand16({
1414
1891
  }
1415
1892
  }
1416
1893
  });
1894
+ async function runShellMode(command, args) {
1895
+ const auth = loadAuth();
1896
+ if (!auth)
1897
+ throw new CliError("Not logged in. Run `apes login` first.");
1898
+ const idp = getIdpUrl(args.idp);
1899
+ if (!idp)
1900
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1901
+ const adapterHandled = await tryAdapterModeFromShell(command, idp, args);
1902
+ if (adapterHandled) return;
1903
+ const grantsUrl = await getGrantsEndpoint(idp);
1904
+ const targetHost = args.host || hostname3();
1905
+ try {
1906
+ const grants = await apiFetch(
1907
+ `${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`
1908
+ );
1909
+ const sessionGrant = grants.data.find(
1910
+ (g) => g.request.audience === "ape-shell" && g.request.target_host === targetHost && g.request.grant_type !== "once"
1911
+ );
1912
+ if (sessionGrant) {
1913
+ execShellCommand(command);
1914
+ return;
1915
+ }
1916
+ } catch {
1917
+ }
1918
+ consola18.info(`Requesting ape-shell session grant on ${targetHost}`);
1919
+ const grant = await apiFetch(grantsUrl, {
1920
+ method: "POST",
1921
+ body: {
1922
+ requester: auth.email,
1923
+ target_host: targetHost,
1924
+ audience: "ape-shell",
1925
+ grant_type: "once",
1926
+ command: command.slice(0, 3),
1927
+ reason: `Shell session: ${command.join(" ").slice(0, 100)}`
1928
+ }
1929
+ });
1930
+ consola18.info(`Grant requested: ${grant.id}`);
1931
+ consola18.info("Waiting for approval...");
1932
+ const maxWait = 3e5;
1933
+ const interval = 3e3;
1934
+ const start = Date.now();
1935
+ while (Date.now() - start < maxWait) {
1936
+ const status = await apiFetch(`${grantsUrl}/${grant.id}`);
1937
+ if (status.status === "approved")
1938
+ break;
1939
+ if (status.status === "denied" || status.status === "revoked")
1940
+ throw new CliError(`Grant ${status.status}.`);
1941
+ await new Promise((r) => setTimeout(r, interval));
1942
+ }
1943
+ execShellCommand(command);
1944
+ }
1945
+ async function tryAdapterModeFromShell(command, idp, args) {
1946
+ const cmdString = extractShellCommandString(command);
1947
+ if (!cmdString) return false;
1948
+ const parsed = parseShellCommand(cmdString);
1949
+ if (!parsed) return false;
1950
+ if (parsed.isCompound) return false;
1951
+ const loaded = await loadOrInstallAdapter(parsed.executable);
1952
+ if (!loaded) return false;
1953
+ const normalizedExecutable = basename(parsed.executable);
1954
+ let resolved;
1955
+ try {
1956
+ resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
1957
+ } catch (err) {
1958
+ consola18.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
1959
+ return false;
1960
+ }
1961
+ try {
1962
+ const existingGrantId = await findExistingGrant(resolved, idp);
1963
+ if (existingGrantId) {
1964
+ consola18.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
1965
+ const token2 = await fetchGrantToken(idp, existingGrantId);
1966
+ await verifyAndExecute(token2, resolved);
1967
+ return true;
1968
+ }
1969
+ } catch {
1970
+ }
1971
+ const approval = args.approval ?? "once";
1972
+ consola18.info(`Requesting grant for: ${resolved.detail.display}`);
1973
+ const grant = await createShapesGrant(resolved, {
1974
+ idp,
1975
+ approval,
1976
+ reason: args.reason || `ape-shell: ${resolved.detail.display}`
1977
+ });
1978
+ consola18.info(`Grant requested: ${grant.id}`);
1979
+ consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1980
+ if (grant.similar_grants?.similar_grants?.length) {
1981
+ const n = grant.similar_grants.similar_grants.length;
1982
+ consola18.info("");
1983
+ consola18.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
1984
+ }
1985
+ const status = await waitForGrantStatus(idp, grant.id);
1986
+ if (status !== "approved")
1987
+ throw new CliError(`Grant ${status}`);
1988
+ const token = await fetchGrantToken(idp, grant.id);
1989
+ await verifyAndExecute(token, resolved);
1990
+ return true;
1991
+ }
1992
+ function execShellCommand(command) {
1993
+ if (command.length === 0)
1994
+ throw new CliError("No command to execute");
1995
+ try {
1996
+ execFileSync(command[0], command.slice(1), { stdio: "inherit" });
1997
+ } catch (err) {
1998
+ const exitCode = err.status || 1;
1999
+ throw new CliExit(exitCode);
2000
+ }
2001
+ }
1417
2002
  function extractPositionals(rawArgs) {
1418
2003
  const positionals = [];
1419
2004
  const delimiter = rawArgs.indexOf("--");
@@ -1439,13 +2024,13 @@ async function runAdapterMode(command, rawArgs, args) {
1439
2024
  return;
1440
2025
  }
1441
2026
  const adapterOpt = extractOption(rawArgs, "adapter");
1442
- const loaded = loadAdapter3(command[0], adapterOpt);
2027
+ const loaded = loadAdapter(command[0], adapterOpt);
1443
2028
  const resolved = await resolveCommand(loaded, command);
1444
2029
  const approval = args.approval ?? "once";
1445
2030
  try {
1446
2031
  const existingGrantId = await findExistingGrant(resolved, idp);
1447
2032
  if (existingGrantId) {
1448
- consola14.info(`Reusing existing grant: ${existingGrantId}`);
2033
+ consola18.info(`Reusing existing grant: ${existingGrantId}`);
1449
2034
  const token2 = await fetchGrantToken(idp, existingGrantId);
1450
2035
  await verifyAndExecute(token2, resolved);
1451
2036
  return;
@@ -1457,17 +2042,17 @@ async function runAdapterMode(command, rawArgs, args) {
1457
2042
  approval,
1458
2043
  ...args.reason ? { reason: args.reason } : {}
1459
2044
  });
1460
- consola14.info(`Grant requested: ${grant.id}`);
1461
- consola14.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
2045
+ consola18.info(`Grant requested: ${grant.id}`);
2046
+ consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1462
2047
  if (grant.similar_grants?.similar_grants?.length) {
1463
2048
  const n = grant.similar_grants.similar_grants.length;
1464
- consola14.info("");
1465
- consola14.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
2049
+ consola18.info("");
2050
+ consola18.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
1466
2051
  if (grant.similar_grants.widened_details?.length) {
1467
2052
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
1468
- consola14.info(` Broader scope: ${wider}`);
2053
+ consola18.info(` Broader scope: ${wider}`);
1469
2054
  }
1470
- consola14.info("");
2055
+ consola18.info("");
1471
2056
  }
1472
2057
  const status = await waitForGrantStatus(idp, grant.id);
1473
2058
  if (status !== "approved")
@@ -1484,7 +2069,7 @@ async function runAudienceMode(audience, action, args) {
1484
2069
  const grantsUrl = await getGrantsEndpoint(idp);
1485
2070
  const command = action.split(" ");
1486
2071
  const targetHost = args.host || hostname3();
1487
- consola14.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
2072
+ consola18.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1488
2073
  const grant = await apiFetch(grantsUrl, {
1489
2074
  method: "POST",
1490
2075
  body: {
@@ -1497,15 +2082,15 @@ async function runAudienceMode(audience, action, args) {
1497
2082
  ...args.as ? { run_as: args.as } : {}
1498
2083
  }
1499
2084
  });
1500
- consola14.success(`Grant requested: ${grant.id}`);
1501
- consola14.info("Waiting for approval...");
2085
+ consola18.success(`Grant requested: ${grant.id}`);
2086
+ consola18.info("Waiting for approval...");
1502
2087
  const maxWait = 3e5;
1503
2088
  const interval = 3e3;
1504
2089
  const start = Date.now();
1505
2090
  while (Date.now() - start < maxWait) {
1506
2091
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
1507
2092
  if (status.status === "approved") {
1508
- consola14.success("Grant approved!");
2093
+ consola18.success("Grant approved!");
1509
2094
  break;
1510
2095
  }
1511
2096
  if (status.status === "denied" || status.status === "revoked") {
@@ -1513,12 +2098,12 @@ async function runAudienceMode(audience, action, args) {
1513
2098
  }
1514
2099
  await new Promise((r) => setTimeout(r, interval));
1515
2100
  }
1516
- consola14.info("Fetching grant token...");
2101
+ consola18.info("Fetching grant token...");
1517
2102
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
1518
2103
  method: "POST"
1519
2104
  });
1520
2105
  if (audience === "escapes") {
1521
- consola14.info(`Executing: ${command.join(" ")}`);
2106
+ consola18.info(`Executing: ${command.join(" ")}`);
1522
2107
  try {
1523
2108
  execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
1524
2109
  stdio: "inherit"
@@ -1533,9 +2118,8 @@ async function runAudienceMode(audience, action, args) {
1533
2118
  }
1534
2119
 
1535
2120
  // src/commands/explain.ts
1536
- import { defineCommand as defineCommand17 } from "citty";
1537
- import { extractOption as extractOption2, extractWrappedCommand as extractWrappedCommand2, loadAdapter as loadAdapter4, resolveCommand as resolveCommand2 } from "@openape/shapes";
1538
- var explainCommand = defineCommand17({
2121
+ import { defineCommand as defineCommand21 } from "citty";
2122
+ var explainCommand = defineCommand21({
1539
2123
  meta: {
1540
2124
  name: "explain",
1541
2125
  description: "Show what permission a command would need"
@@ -1552,12 +2136,12 @@ var explainCommand = defineCommand17({
1552
2136
  }
1553
2137
  },
1554
2138
  async run({ rawArgs }) {
1555
- const command = extractWrappedCommand2(rawArgs ?? []);
2139
+ const command = extractWrappedCommand(rawArgs ?? []);
1556
2140
  if (command.length === 0)
1557
2141
  throw new Error("Missing wrapped command. Usage: apes explain [--adapter <file>] -- <cli> ...");
1558
- const adapterOpt = extractOption2(rawArgs ?? [], "adapter");
1559
- const loaded = loadAdapter4(command[0], adapterOpt);
1560
- const resolved = await resolveCommand2(loaded, command);
2142
+ const adapterOpt = extractOption(rawArgs ?? [], "adapter");
2143
+ const loaded = loadAdapter(command[0], adapterOpt);
2144
+ const resolved = await resolveCommand(loaded, command);
1561
2145
  process.stdout.write(`${JSON.stringify({
1562
2146
  adapter: resolved.adapter.cli.id,
1563
2147
  source: resolved.source,
@@ -1573,9 +2157,9 @@ var explainCommand = defineCommand17({
1573
2157
  });
1574
2158
 
1575
2159
  // src/commands/config/get.ts
1576
- import { defineCommand as defineCommand18 } from "citty";
1577
- import consola15 from "consola";
1578
- var configGetCommand = defineCommand18({
2160
+ import { defineCommand as defineCommand22 } from "citty";
2161
+ import consola19 from "consola";
2162
+ var configGetCommand = defineCommand22({
1579
2163
  meta: {
1580
2164
  name: "get",
1581
2165
  description: "Get a configuration value"
@@ -1595,7 +2179,7 @@ var configGetCommand = defineCommand18({
1595
2179
  if (idp)
1596
2180
  console.log(idp);
1597
2181
  else
1598
- consola15.info("No IdP configured.");
2182
+ consola19.info("No IdP configured.");
1599
2183
  break;
1600
2184
  }
1601
2185
  case "email": {
@@ -1603,7 +2187,7 @@ var configGetCommand = defineCommand18({
1603
2187
  if (auth?.email)
1604
2188
  console.log(auth.email);
1605
2189
  else
1606
- consola15.info("Not logged in.");
2190
+ consola19.info("Not logged in.");
1607
2191
  break;
1608
2192
  }
1609
2193
  default: {
@@ -1616,7 +2200,7 @@ var configGetCommand = defineCommand18({
1616
2200
  if (sectionObj && field in sectionObj) {
1617
2201
  console.log(sectionObj[field]);
1618
2202
  } else {
1619
- consola15.info(`Key "${key}" not set.`);
2203
+ consola19.info(`Key "${key}" not set.`);
1620
2204
  }
1621
2205
  } else {
1622
2206
  throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
@@ -1627,9 +2211,9 @@ var configGetCommand = defineCommand18({
1627
2211
  });
1628
2212
 
1629
2213
  // src/commands/config/set.ts
1630
- import { defineCommand as defineCommand19 } from "citty";
1631
- import consola16 from "consola";
1632
- var configSetCommand = defineCommand19({
2214
+ import { defineCommand as defineCommand23 } from "citty";
2215
+ import consola20 from "consola";
2216
+ var configSetCommand = defineCommand23({
1633
2217
  meta: {
1634
2218
  name: "set",
1635
2219
  description: "Set a configuration value"
@@ -1665,12 +2249,12 @@ var configSetCommand = defineCommand19({
1665
2249
  throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
1666
2250
  }
1667
2251
  saveConfig(config);
1668
- consola16.success(`Set ${key} = ${value}`);
2252
+ consola20.success(`Set ${key} = ${value}`);
1669
2253
  }
1670
2254
  });
1671
2255
 
1672
2256
  // src/commands/fetch/index.ts
1673
- import { defineCommand as defineCommand20 } from "citty";
2257
+ import { defineCommand as defineCommand24 } from "citty";
1674
2258
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
1675
2259
  const token = getAuthToken();
1676
2260
  if (!token) {
@@ -1706,13 +2290,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
1706
2290
  throw new CliError(`HTTP ${response.status} ${response.statusText}`);
1707
2291
  }
1708
2292
  }
1709
- var fetchCommand = defineCommand20({
2293
+ var fetchCommand = defineCommand24({
1710
2294
  meta: {
1711
2295
  name: "fetch",
1712
2296
  description: "Make authenticated HTTP requests"
1713
2297
  },
1714
2298
  subCommands: {
1715
- get: defineCommand20({
2299
+ get: defineCommand24({
1716
2300
  meta: {
1717
2301
  name: "get",
1718
2302
  description: "GET request with auth token"
@@ -1738,7 +2322,7 @@ var fetchCommand = defineCommand20({
1738
2322
  await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
1739
2323
  }
1740
2324
  }),
1741
- post: defineCommand20({
2325
+ post: defineCommand24({
1742
2326
  meta: {
1743
2327
  name: "post",
1744
2328
  description: "POST request with auth token"
@@ -1777,8 +2361,8 @@ var fetchCommand = defineCommand20({
1777
2361
  });
1778
2362
 
1779
2363
  // src/commands/mcp/index.ts
1780
- import { defineCommand as defineCommand21 } from "citty";
1781
- var mcpCommand = defineCommand21({
2364
+ import { defineCommand as defineCommand25 } from "citty";
2365
+ var mcpCommand = defineCommand25({
1782
2366
  meta: {
1783
2367
  name: "mcp",
1784
2368
  description: "Start MCP server for AI agents"
@@ -1801,25 +2385,25 @@ var mcpCommand = defineCommand21({
1801
2385
  if (transport !== "stdio" && transport !== "sse") {
1802
2386
  throw new Error('Transport must be "stdio" or "sse"');
1803
2387
  }
1804
- const { startMcpServer } = await import("./server-IYR5LM63.js");
2388
+ const { startMcpServer } = await import("./server-UTCZSPCU.js");
1805
2389
  await startMcpServer(transport, port);
1806
2390
  }
1807
2391
  });
1808
2392
 
1809
2393
  // src/commands/init/index.ts
1810
- import { existsSync, copyFileSync, writeFileSync } from "fs";
2394
+ import { existsSync as existsSync3, copyFileSync, writeFileSync } from "fs";
1811
2395
  import { randomBytes } from "crypto";
1812
2396
  import { execFileSync as execFileSync2 } from "child_process";
1813
- import { join } from "path";
1814
- import { defineCommand as defineCommand22 } from "citty";
1815
- import consola17 from "consola";
2397
+ import { join as join2 } from "path";
2398
+ import { defineCommand as defineCommand26 } from "citty";
2399
+ import consola21 from "consola";
1816
2400
  var DEFAULT_IDP_URL = "https://id.openape.at";
1817
2401
  async function downloadTemplate(repo, targetDir) {
1818
2402
  const { downloadTemplate: gigetDownload } = await import("giget");
1819
2403
  await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
1820
2404
  }
1821
2405
  function installDeps(dir) {
1822
- const hasLockFile = (name) => existsSync(join(dir, name));
2406
+ const hasLockFile = (name) => existsSync3(join2(dir, name));
1823
2407
  if (hasLockFile("pnpm-lock.yaml")) {
1824
2408
  execFileSync2("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
1825
2409
  } else if (hasLockFile("bun.lockb")) {
@@ -1829,20 +2413,20 @@ function installDeps(dir) {
1829
2413
  }
1830
2414
  }
1831
2415
  async function promptChoice(message, choices) {
1832
- const result = await consola17.prompt(message, { type: "select", options: choices });
2416
+ const result = await consola21.prompt(message, { type: "select", options: choices });
1833
2417
  if (typeof result === "symbol") {
1834
2418
  throw new CliExit(0);
1835
2419
  }
1836
2420
  return result;
1837
2421
  }
1838
2422
  async function promptText(message, defaultValue) {
1839
- const result = await consola17.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
2423
+ const result = await consola21.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
1840
2424
  if (typeof result === "symbol") {
1841
2425
  throw new CliExit(0);
1842
2426
  }
1843
2427
  return result || defaultValue || "";
1844
2428
  }
1845
- var initCommand = defineCommand22({
2429
+ var initCommand = defineCommand26({
1846
2430
  meta: {
1847
2431
  name: "init",
1848
2432
  description: "Scaffold a new OpenApe project"
@@ -1884,23 +2468,23 @@ var initCommand = defineCommand22({
1884
2468
  });
1885
2469
  async function initSP(targetDir) {
1886
2470
  const dir = targetDir || "my-app";
1887
- if (existsSync(join(dir, "package.json"))) {
2471
+ if (existsSync3(join2(dir, "package.json"))) {
1888
2472
  throw new CliError(`Directory "${dir}" already contains a project.`);
1889
2473
  }
1890
- consola17.start("Scaffolding SP starter...");
2474
+ consola21.start("Scaffolding SP starter...");
1891
2475
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
1892
- consola17.success("Scaffolded from openape-sp-starter");
1893
- consola17.start("Installing dependencies...");
2476
+ consola21.success("Scaffolded from openape-sp-starter");
2477
+ consola21.start("Installing dependencies...");
1894
2478
  installDeps(dir);
1895
- consola17.success("Dependencies installed");
1896
- const envExample = join(dir, ".env.example");
1897
- const envFile = join(dir, ".env");
1898
- if (existsSync(envExample) && !existsSync(envFile)) {
2479
+ consola21.success("Dependencies installed");
2480
+ const envExample = join2(dir, ".env.example");
2481
+ const envFile = join2(dir, ".env");
2482
+ if (existsSync3(envExample) && !existsSync3(envFile)) {
1899
2483
  copyFileSync(envExample, envFile);
1900
- consola17.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
2484
+ consola21.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1901
2485
  }
1902
2486
  console.log("");
1903
- consola17.box([
2487
+ consola21.box([
1904
2488
  `cd ${dir}`,
1905
2489
  "npm run dev",
1906
2490
  "",
@@ -1909,7 +2493,7 @@ async function initSP(targetDir) {
1909
2493
  }
1910
2494
  async function initIdP(targetDir) {
1911
2495
  const dir = targetDir || "my-idp";
1912
- if (existsSync(join(dir, "package.json"))) {
2496
+ if (existsSync3(join2(dir, "package.json"))) {
1913
2497
  throw new CliError(`Directory "${dir}" already contains a project.`);
1914
2498
  }
1915
2499
  const domain = await promptText("Domain for the IdP", "localhost");
@@ -1919,15 +2503,15 @@ async function initIdP(targetDir) {
1919
2503
  "s3 (S3-compatible)"
1920
2504
  ]);
1921
2505
  const adminEmail = await promptText("Admin email");
1922
- consola17.start("Scaffolding IdP starter...");
2506
+ consola21.start("Scaffolding IdP starter...");
1923
2507
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
1924
- consola17.success("Scaffolded from openape-idp-starter");
1925
- consola17.start("Installing dependencies...");
2508
+ consola21.success("Scaffolded from openape-idp-starter");
2509
+ consola21.start("Installing dependencies...");
1926
2510
  installDeps(dir);
1927
- consola17.success("Dependencies installed");
2511
+ consola21.success("Dependencies installed");
1928
2512
  const sessionSecret = randomBytes(32).toString("hex");
1929
2513
  const managementToken = randomBytes(32).toString("hex");
1930
- consola17.success("Secrets generated");
2514
+ consola21.success("Secrets generated");
1931
2515
  const isLocalhost = domain === "localhost";
1932
2516
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
1933
2517
  const envContent = [
@@ -1941,11 +2525,11 @@ async function initIdP(targetDir) {
1941
2525
  `NUXT_OPENAPE_RP_ID=${domain}`,
1942
2526
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
1943
2527
  ].join("\n");
1944
- writeFileSync(join(dir, ".env"), `${envContent}
2528
+ writeFileSync(join2(dir, ".env"), `${envContent}
1945
2529
  `, { mode: 384 });
1946
- consola17.success(".env created");
2530
+ consola21.success(".env created");
1947
2531
  console.log("");
1948
- consola17.box([
2532
+ consola21.box([
1949
2533
  `cd ${dir}`,
1950
2534
  "npm run dev",
1951
2535
  "",
@@ -1962,19 +2546,19 @@ async function initIdP(targetDir) {
1962
2546
 
1963
2547
  // src/commands/enroll.ts
1964
2548
  import { Buffer as Buffer2 } from "buffer";
1965
- import { existsSync as existsSync2, readFileSync, writeFileSync as writeFileSync2, mkdirSync } from "fs";
2549
+ import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync } from "fs";
1966
2550
  import { execFile as execFile2 } from "child_process";
1967
2551
  import { generateKeyPairSync, sign } from "crypto";
1968
- import { dirname, resolve } from "path";
1969
- import { homedir } from "os";
1970
- import { defineCommand as defineCommand23 } from "citty";
1971
- import consola18 from "consola";
2552
+ import { dirname, resolve as resolve2 } from "path";
2553
+ import { homedir as homedir4 } from "os";
2554
+ import { defineCommand as defineCommand27 } from "citty";
2555
+ import consola22 from "consola";
1972
2556
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
1973
2557
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
1974
2558
  var POLL_INTERVAL = 3e3;
1975
2559
  var POLL_TIMEOUT = 3e5;
1976
- function resolvePath(p) {
1977
- return resolve(p.replace(/^~/, homedir()));
2560
+ function resolvePath2(p) {
2561
+ return resolve2(p.replace(/^~/, homedir4()));
1978
2562
  }
1979
2563
  function openBrowser2(url) {
1980
2564
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
@@ -1983,10 +2567,10 @@ function openBrowser2(url) {
1983
2567
  }
1984
2568
  function readPublicKey(keyPath) {
1985
2569
  const pubPath = `${keyPath}.pub`;
1986
- if (existsSync2(pubPath)) {
1987
- return readFileSync(pubPath, "utf-8").trim();
2570
+ if (existsSync4(pubPath)) {
2571
+ return readFileSync2(pubPath, "utf-8").trim();
1988
2572
  }
1989
- const keyContent = readFileSync(keyPath, "utf-8");
2573
+ const keyContent = readFileSync2(keyPath, "utf-8");
1990
2574
  const privateKey = loadEd25519PrivateKey(keyContent);
1991
2575
  const jwk = privateKey.export({ format: "jwk" });
1992
2576
  const pubBytes = Buffer2.from(jwk.x, "base64url");
@@ -1999,9 +2583,9 @@ function readPublicKey(keyPath) {
1999
2583
  return `ssh-ed25519 ${blob.toString("base64")}`;
2000
2584
  }
2001
2585
  function generateAndSaveKey(keyPath) {
2002
- const resolved = resolvePath(keyPath);
2586
+ const resolved = resolvePath2(keyPath);
2003
2587
  const dir = dirname(resolved);
2004
- if (!existsSync2(dir)) {
2588
+ if (!existsSync4(dir)) {
2005
2589
  mkdirSync(dir, { recursive: true });
2006
2590
  }
2007
2591
  const { publicKey, privateKey } = generateKeyPairSync("ed25519");
@@ -2021,8 +2605,8 @@ function generateAndSaveKey(keyPath) {
2021
2605
  return pubKeyStr;
2022
2606
  }
2023
2607
  async function pollForEnrollment(idp, agentEmail, keyPath) {
2024
- const resolvedKey = resolvePath(keyPath);
2025
- const keyContent = readFileSync(resolvedKey, "utf-8");
2608
+ const resolvedKey = resolvePath2(keyPath);
2609
+ const keyContent = readFileSync2(resolvedKey, "utf-8");
2026
2610
  const privateKey = loadEd25519PrivateKey(keyContent);
2027
2611
  const challengeUrl = await getAgentChallengeEndpoint(idp);
2028
2612
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -2049,11 +2633,11 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
2049
2633
  }
2050
2634
  } catch {
2051
2635
  }
2052
- await new Promise((resolve2) => setTimeout(resolve2, POLL_INTERVAL));
2636
+ await new Promise((resolve3) => setTimeout(resolve3, POLL_INTERVAL));
2053
2637
  }
2054
2638
  throw new Error("Enrollment timed out. Please check the browser and try again.");
2055
2639
  }
2056
- var enrollCommand = defineCommand23({
2640
+ var enrollCommand = defineCommand27({
2057
2641
  meta: {
2058
2642
  name: "enroll",
2059
2643
  description: "Enroll an agent with an Identity Provider"
@@ -2073,38 +2657,38 @@ var enrollCommand = defineCommand23({
2073
2657
  }
2074
2658
  },
2075
2659
  async run({ args }) {
2076
- const idp = args.idp || await consola18.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
2660
+ const idp = args.idp || await consola22.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
2077
2661
  if (typeof r === "symbol") throw new CliExit(0);
2078
2662
  return r;
2079
2663
  }) || DEFAULT_IDP_URL2;
2080
- const agentName = args.name || await consola18.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
2664
+ const agentName = args.name || await consola22.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
2081
2665
  if (typeof r === "symbol") throw new CliExit(0);
2082
2666
  return r;
2083
2667
  });
2084
2668
  if (!agentName) {
2085
2669
  throw new CliError("Agent name is required.");
2086
2670
  }
2087
- const keyPath = args.key || await consola18.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
2671
+ const keyPath = args.key || await consola22.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
2088
2672
  if (typeof r === "symbol") throw new CliExit(0);
2089
2673
  return r;
2090
2674
  }) || DEFAULT_KEY_PATH;
2091
- const resolvedKey = resolvePath(keyPath);
2675
+ const resolvedKey = resolvePath2(keyPath);
2092
2676
  let publicKey;
2093
- if (existsSync2(resolvedKey)) {
2677
+ if (existsSync4(resolvedKey)) {
2094
2678
  publicKey = readPublicKey(resolvedKey);
2095
- consola18.success(`Using existing key ${keyPath}`);
2679
+ consola22.success(`Using existing key ${keyPath}`);
2096
2680
  } else {
2097
- consola18.start(`Generating Ed25519 key pair at ${keyPath}...`);
2681
+ consola22.start(`Generating Ed25519 key pair at ${keyPath}...`);
2098
2682
  publicKey = generateAndSaveKey(keyPath);
2099
- consola18.success(`Key pair generated at ${keyPath}`);
2683
+ consola22.success(`Key pair generated at ${keyPath}`);
2100
2684
  }
2101
2685
  const encodedKey = encodeURIComponent(publicKey);
2102
2686
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
2103
- consola18.info("Opening browser for enrollment...");
2104
- consola18.info(`\u2192 ${idp}/enroll`);
2687
+ consola22.info("Opening browser for enrollment...");
2688
+ consola22.info(`\u2192 ${idp}/enroll`);
2105
2689
  openBrowser2(enrollUrl);
2106
2690
  console.log("");
2107
- const agentEmail = await consola18.prompt(
2691
+ const agentEmail = await consola22.prompt(
2108
2692
  "Agent email (shown in browser after enrollment)",
2109
2693
  { type: "text", placeholder: `agent+${agentName}@...` }
2110
2694
  ).then((r) => {
@@ -2114,7 +2698,7 @@ var enrollCommand = defineCommand23({
2114
2698
  if (!agentEmail) {
2115
2699
  throw new CliError("Agent email is required to verify enrollment.");
2116
2700
  }
2117
- consola18.start("Verifying enrollment...");
2701
+ consola22.start("Verifying enrollment...");
2118
2702
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
2119
2703
  saveAuth({
2120
2704
  idp,
@@ -2126,18 +2710,81 @@ var enrollCommand = defineCommand23({
2126
2710
  config.defaults = { ...config.defaults, idp };
2127
2711
  config.agent = { key: keyPath, email: agentEmail };
2128
2712
  saveConfig(config);
2129
- consola18.success(`Agent enrolled as ${agentEmail}`);
2130
- consola18.success("Config saved to ~/.config/apes/");
2713
+ consola22.success(`Agent enrolled as ${agentEmail}`);
2714
+ consola22.success("Config saved to ~/.config/apes/");
2131
2715
  console.log("");
2132
- consola18.info("Verify with: apes whoami");
2716
+ consola22.info("Verify with: apes whoami");
2717
+ }
2718
+ });
2719
+
2720
+ // src/commands/register-user.ts
2721
+ import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
2722
+ import { defineCommand as defineCommand28 } from "citty";
2723
+ import consola23 from "consola";
2724
+ var registerUserCommand = defineCommand28({
2725
+ meta: {
2726
+ name: "register-user",
2727
+ description: "Register a sub-user with SSH key"
2728
+ },
2729
+ args: {
2730
+ email: {
2731
+ type: "string",
2732
+ description: "Email for the new user",
2733
+ required: true
2734
+ },
2735
+ name: {
2736
+ type: "string",
2737
+ description: "Name for the new user",
2738
+ required: true
2739
+ },
2740
+ key: {
2741
+ type: "string",
2742
+ description: "Path to SSH public key file or key string",
2743
+ required: true
2744
+ },
2745
+ type: {
2746
+ type: "string",
2747
+ description: "User type: human or agent (default: agent)"
2748
+ }
2749
+ },
2750
+ async run({ args }) {
2751
+ const auth = loadAuth();
2752
+ if (!auth) {
2753
+ throw new CliError("Not authenticated. Run `apes login` first.");
2754
+ }
2755
+ const idp = getIdpUrl();
2756
+ if (!idp) {
2757
+ throw new CliError("No IdP URL configured. Run `apes login` first.");
2758
+ }
2759
+ let publicKey = args.key;
2760
+ if (existsSync5(args.key)) {
2761
+ publicKey = readFileSync3(args.key, "utf-8").trim();
2762
+ }
2763
+ if (!publicKey.startsWith("ssh-ed25519 ")) {
2764
+ throw new CliError("Public key must be in ssh-ed25519 format.");
2765
+ }
2766
+ const userType = args.type;
2767
+ if (userType && userType !== "human" && userType !== "agent") {
2768
+ throw new CliError('Type must be "human" or "agent".');
2769
+ }
2770
+ const result = await apiFetch(`${idp}/api/auth/enroll`, {
2771
+ method: "POST",
2772
+ body: {
2773
+ email: args.email,
2774
+ name: args.name,
2775
+ publicKey,
2776
+ ...userType ? { type: userType } : {}
2777
+ }
2778
+ });
2779
+ consola23.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
2133
2780
  }
2134
2781
  });
2135
2782
 
2136
2783
  // src/commands/dns-check.ts
2137
- import { defineCommand as defineCommand24 } from "citty";
2138
- import consola19 from "consola";
2139
- import { resolveDDISA } from "@openape/core";
2140
- var dnsCheckCommand = defineCommand24({
2784
+ import { defineCommand as defineCommand29 } from "citty";
2785
+ import consola24 from "consola";
2786
+ import { resolveDDISA as resolveDDISA2 } from "@openape/core";
2787
+ var dnsCheckCommand = defineCommand29({
2141
2788
  meta: {
2142
2789
  name: "dns-check",
2143
2790
  description: "Validate DDISA DNS TXT records for a domain"
@@ -2151,16 +2798,16 @@ var dnsCheckCommand = defineCommand24({
2151
2798
  },
2152
2799
  async run({ args }) {
2153
2800
  const domain = args.domain;
2154
- consola19.start(`Checking _ddisa.${domain}...`);
2801
+ consola24.start(`Checking _ddisa.${domain}...`);
2155
2802
  try {
2156
- const result = await resolveDDISA(domain);
2803
+ const result = await resolveDDISA2(domain);
2157
2804
  if (!result) {
2158
2805
  console.log("");
2159
2806
  console.log("To set up DDISA, add a DNS TXT record:");
2160
2807
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
2161
2808
  throw new CliError(`No DDISA record found for ${domain}`);
2162
2809
  }
2163
- consola19.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2810
+ consola24.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2164
2811
  console.log("");
2165
2812
  console.log(` Version: ${result.version || "ddisa1"}`);
2166
2813
  console.log(` IdP URL: ${result.idp}`);
@@ -2169,14 +2816,14 @@ var dnsCheckCommand = defineCommand24({
2169
2816
  if (result.priority !== void 0)
2170
2817
  console.log(` Priority: ${result.priority}`);
2171
2818
  console.log("");
2172
- consola19.start(`Verifying IdP at ${result.idp}...`);
2819
+ consola24.start(`Verifying IdP at ${result.idp}...`);
2173
2820
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
2174
2821
  if (!discoResp.ok) {
2175
- consola19.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2822
+ consola24.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2176
2823
  return;
2177
2824
  }
2178
2825
  const disco = await discoResp.json();
2179
- consola19.success(`IdP is reachable`);
2826
+ consola24.success(`IdP is reachable`);
2180
2827
  console.log(` Issuer: ${disco.issuer}`);
2181
2828
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
2182
2829
  if (disco.ddisa_auth_methods_supported) {
@@ -2192,8 +2839,8 @@ var dnsCheckCommand = defineCommand24({
2192
2839
  });
2193
2840
 
2194
2841
  // src/commands/workflows.ts
2195
- import { defineCommand as defineCommand25 } from "citty";
2196
- import consola20 from "consola";
2842
+ import { defineCommand as defineCommand30 } from "citty";
2843
+ import consola25 from "consola";
2197
2844
 
2198
2845
  // src/guides/index.ts
2199
2846
  var guides = [
@@ -2243,7 +2890,7 @@ var guides = [
2243
2890
  ];
2244
2891
 
2245
2892
  // src/commands/workflows.ts
2246
- var workflowsCommand = defineCommand25({
2893
+ var workflowsCommand = defineCommand30({
2247
2894
  meta: {
2248
2895
  name: "workflows",
2249
2896
  description: "Discover workflow guides"
@@ -2264,7 +2911,7 @@ var workflowsCommand = defineCommand25({
2264
2911
  if (args.id) {
2265
2912
  const guide = guides.find((g) => g.id === String(args.id));
2266
2913
  if (!guide) {
2267
- consola20.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
2914
+ consola25.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
2268
2915
  throw new CliError(`Guide not found: ${args.id}`);
2269
2916
  }
2270
2917
  if (args.json) {
@@ -2308,8 +2955,39 @@ process.stdout.on("error", (err) => {
2308
2955
  if (err.code === "EPIPE") process.exit(0);
2309
2956
  throw err;
2310
2957
  });
2958
+ var shellRewrite = rewriteApeShellArgs(process.argv, process.argv0);
2959
+ if (shellRewrite) {
2960
+ if (shellRewrite.action === "rewrite") {
2961
+ process.argv = shellRewrite.argv;
2962
+ } else if (shellRewrite.action === "version") {
2963
+ console.log(`ape-shell ${"0.7.1"} (OpenApe DDISA shell wrapper)`);
2964
+ process.exit(0);
2965
+ } else if (shellRewrite.action === "help") {
2966
+ console.log(`ape-shell ${"0.7.1"} \u2014 OpenApe DDISA shell wrapper`);
2967
+ console.log("");
2968
+ console.log("Usage:");
2969
+ console.log(" ape-shell Start interactive grant-mediated REPL");
2970
+ console.log(" ape-shell -c <command> Run a single command through the grant flow");
2971
+ console.log(" ape-shell -i | -l Force interactive mode");
2972
+ console.log("");
2973
+ console.log("Options:");
2974
+ console.log(" -c <command> Execute <command> via the apes grant flow and exit");
2975
+ console.log(" -i Interactive REPL (default when no args are given)");
2976
+ console.log(" -l, --login Login shell semantics \u2014 currently same as -i");
2977
+ console.log(" --version, -v Show ape-shell version");
2978
+ console.log(" --help, -h Show this help message");
2979
+ process.exit(0);
2980
+ } else if (shellRewrite.action === "interactive") {
2981
+ const { runInteractiveShell } = await import("./orchestrator-JAMWD6DD.js");
2982
+ await runInteractiveShell();
2983
+ process.exit(0);
2984
+ } else {
2985
+ console.error("ape-shell: unsupported invocation. Try `ape-shell --help`.");
2986
+ process.exit(1);
2987
+ }
2988
+ }
2311
2989
  var debug = process.argv.includes("--debug");
2312
- var grantsCommand = defineCommand26({
2990
+ var grantsCommand = defineCommand31({
2313
2991
  meta: {
2314
2992
  name: "grants",
2315
2993
  description: "Grant management"
@@ -2325,10 +3003,11 @@ var grantsCommand = defineCommand26({
2325
3003
  revoke: revokeCommand,
2326
3004
  token: tokenCommand,
2327
3005
  delegate: delegateCommand,
2328
- delegations: delegationsCommand
3006
+ delegations: delegationsCommand,
3007
+ "delegation-revoke": delegationRevokeCommand
2329
3008
  }
2330
3009
  });
2331
- var configCommand = defineCommand26({
3010
+ var configCommand = defineCommand31({
2332
3011
  meta: {
2333
3012
  name: "config",
2334
3013
  description: "Configuration management"
@@ -2338,20 +3017,22 @@ var configCommand = defineCommand26({
2338
3017
  set: configSetCommand
2339
3018
  }
2340
3019
  });
2341
- var main = defineCommand26({
3020
+ var main = defineCommand31({
2342
3021
  meta: {
2343
3022
  name: "apes",
2344
- version: "0.6.0",
3023
+ version: "0.7.1",
2345
3024
  description: "Unified CLI for OpenApe"
2346
3025
  },
2347
3026
  subCommands: {
2348
3027
  init: initCommand,
2349
3028
  enroll: enrollCommand,
3029
+ "register-user": registerUserCommand,
2350
3030
  "dns-check": dnsCheckCommand,
2351
3031
  login: loginCommand,
2352
3032
  logout: logoutCommand,
2353
3033
  whoami: whoamiCommand,
2354
3034
  grants: grantsCommand,
3035
+ admin: adminCommand,
2355
3036
  run: runCommand,
2356
3037
  explain: explainCommand,
2357
3038
  adapter: adapterCommand,
@@ -2366,13 +3047,13 @@ runMain(main).catch((err) => {
2366
3047
  process.exit(err.exitCode);
2367
3048
  }
2368
3049
  if (err instanceof CliError) {
2369
- consola21.error(err.message);
3050
+ consola26.error(err.message);
2370
3051
  process.exit(err.exitCode);
2371
3052
  }
2372
3053
  if (debug) {
2373
- consola21.error(err);
3054
+ consola26.error(err);
2374
3055
  } else {
2375
- consola21.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
3056
+ consola26.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
2376
3057
  }
2377
3058
  process.exit(1);
2378
3059
  });