@openape/apes 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,1753 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ parseDuration
4
+ } from "./chunk-AGHP6MNV.js";
5
+ import {
6
+ ApiError,
7
+ apiFetch,
8
+ clearAuth,
9
+ getAgentAuthenticateEndpoint,
10
+ getAgentChallengeEndpoint,
11
+ getAuthToken,
12
+ getDelegationsEndpoint,
13
+ getGrantsEndpoint,
14
+ getIdpUrl,
15
+ loadAuth,
16
+ loadConfig,
17
+ saveAuth,
18
+ saveConfig
19
+ } from "./chunk-2JEBWXMI.js";
20
+
21
+ // src/cli.ts
22
+ import consola19 from "consola";
23
+ import { defineCommand as defineCommand22, runMain } from "citty";
24
+
25
+ // src/commands/auth/login.ts
26
+ import { Buffer } from "buffer";
27
+ import { execFile } from "child_process";
28
+ import { createServer } from "http";
29
+ import { defineCommand } from "citty";
30
+ import { generateCodeChallenge, generateCodeVerifier } from "@openape/core";
31
+ import consola from "consola";
32
+ var CALLBACK_PORT = 9876;
33
+ var CLIENT_ID = "grapes-cli";
34
+ var loginCommand = defineCommand({
35
+ meta: {
36
+ name: "login",
37
+ description: "Authenticate with an OpenApe IdP"
38
+ },
39
+ args: {
40
+ idp: {
41
+ type: "string",
42
+ description: "IdP URL (e.g. https://id.openape.at)"
43
+ },
44
+ key: {
45
+ type: "string",
46
+ description: "Path to agent private key (agent mode)"
47
+ },
48
+ email: {
49
+ type: "string",
50
+ description: "Agent email (for DNS discovery)"
51
+ }
52
+ },
53
+ async run({ args }) {
54
+ const config = loadConfig();
55
+ const idp = args.idp || process.env.APES_IDP || process.env.GRAPES_IDP || config.defaults?.idp;
56
+ if (!idp) {
57
+ consola.error("IdP URL required. Use --idp <url> or set APES_IDP.");
58
+ return process.exit(1);
59
+ }
60
+ if (args.key) {
61
+ await loginWithKey(idp, args.key, args.email);
62
+ } else {
63
+ await loginWithPKCE(idp);
64
+ }
65
+ }
66
+ });
67
+ function openBrowser(url) {
68
+ const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
69
+ execFile(cmd, [url], () => {
70
+ });
71
+ }
72
+ async function loginWithPKCE(idp) {
73
+ const codeVerifier = generateCodeVerifier();
74
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
75
+ const redirectUri = `http://localhost:${CALLBACK_PORT}/callback`;
76
+ const state = crypto.randomUUID();
77
+ const nonce = crypto.randomUUID();
78
+ const authUrl = new URL(`${idp}/authorize`);
79
+ authUrl.searchParams.set("response_type", "code");
80
+ authUrl.searchParams.set("client_id", CLIENT_ID);
81
+ authUrl.searchParams.set("redirect_uri", redirectUri);
82
+ authUrl.searchParams.set("code_challenge", codeChallenge);
83
+ authUrl.searchParams.set("code_challenge_method", "S256");
84
+ authUrl.searchParams.set("state", state);
85
+ authUrl.searchParams.set("nonce", nonce);
86
+ authUrl.searchParams.set("scope", "openid email profile offline_access");
87
+ const code = await new Promise((resolve, reject) => {
88
+ const server = createServer((req, res) => {
89
+ const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
90
+ if (url.pathname === "/callback") {
91
+ const authCode = url.searchParams.get("code");
92
+ const error = url.searchParams.get("error");
93
+ if (error) {
94
+ res.writeHead(200, { "Content-Type": "text/html" });
95
+ res.end("<h1>Login failed</h1><p>You can close this window.</p>");
96
+ server.close();
97
+ reject(new Error(`Auth error: ${error}`));
98
+ return;
99
+ }
100
+ if (authCode) {
101
+ res.writeHead(200, { "Content-Type": "text/html" });
102
+ res.end("<h1>Login successful!</h1><p>You can close this window.</p>");
103
+ server.close();
104
+ resolve(authCode);
105
+ return;
106
+ }
107
+ res.writeHead(400);
108
+ res.end("Missing code");
109
+ } else {
110
+ res.writeHead(404);
111
+ res.end();
112
+ }
113
+ });
114
+ server.listen(CALLBACK_PORT, () => {
115
+ consola.info(`Opening browser for login at ${idp}...`);
116
+ openBrowser(authUrl.toString());
117
+ });
118
+ const timeout = setTimeout(() => {
119
+ server.close();
120
+ reject(new Error("Login timed out"));
121
+ }, 3e5);
122
+ timeout.unref();
123
+ });
124
+ const tokenResponse = await fetch(`${idp}/token`, {
125
+ method: "POST",
126
+ headers: { "Content-Type": "application/json" },
127
+ body: JSON.stringify({
128
+ grant_type: "authorization_code",
129
+ code,
130
+ code_verifier: codeVerifier,
131
+ redirect_uri: redirectUri,
132
+ client_id: CLIENT_ID
133
+ })
134
+ });
135
+ if (!tokenResponse.ok) {
136
+ const text = await tokenResponse.text();
137
+ consola.error(`Token exchange failed: ${text}`);
138
+ return process.exit(1);
139
+ }
140
+ const tokens = await tokenResponse.json();
141
+ const accessToken = tokens.access_token || tokens.id_token || tokens.assertion;
142
+ if (!accessToken) {
143
+ consola.error("No access token received");
144
+ return process.exit(1);
145
+ }
146
+ const payload = JSON.parse(atob(accessToken.split(".")[1]));
147
+ saveAuth({
148
+ idp,
149
+ access_token: accessToken,
150
+ ...tokens.refresh_token ? { refresh_token: tokens.refresh_token } : {},
151
+ email: payload.email || payload.sub,
152
+ expires_at: Math.floor(Date.now() / 1e3) + (tokens.expires_in || 3600)
153
+ });
154
+ consola.success(`Logged in as ${payload.email || payload.sub}`);
155
+ }
156
+ async function loginWithKey(idp, keyPath, email) {
157
+ const { readFileSync } = await import("fs");
158
+ const { sign } = await import("crypto");
159
+ const { loadEd25519PrivateKey } = await import("./ssh-key-ABJCJDH7.js");
160
+ const agentEmail = email;
161
+ if (!agentEmail) {
162
+ consola.error("Agent email required for key-based login. Use --email <agent-email>");
163
+ return process.exit(1);
164
+ }
165
+ const challengeUrl = await getAgentChallengeEndpoint(idp);
166
+ const challengeResp = await fetch(challengeUrl, {
167
+ method: "POST",
168
+ headers: { "Content-Type": "application/json" },
169
+ body: JSON.stringify({ agent_id: agentEmail })
170
+ });
171
+ if (!challengeResp.ok) {
172
+ consola.error(`Challenge failed: ${await challengeResp.text()}`);
173
+ return process.exit(1);
174
+ }
175
+ const { challenge } = await challengeResp.json();
176
+ const keyContent = readFileSync(keyPath, "utf-8");
177
+ const privateKey = loadEd25519PrivateKey(keyContent);
178
+ const signature = sign(null, Buffer.from(challenge), privateKey).toString("base64");
179
+ const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
180
+ const authResp = await fetch(authenticateUrl, {
181
+ method: "POST",
182
+ headers: { "Content-Type": "application/json" },
183
+ body: JSON.stringify({
184
+ agent_id: agentEmail,
185
+ challenge,
186
+ signature
187
+ })
188
+ });
189
+ if (!authResp.ok) {
190
+ consola.error(`Authentication failed: ${await authResp.text()}`);
191
+ return process.exit(1);
192
+ }
193
+ const { token, expires_in } = await authResp.json();
194
+ saveAuth({
195
+ idp,
196
+ access_token: token,
197
+ email: agentEmail,
198
+ expires_at: Math.floor(Date.now() / 1e3) + (expires_in || 3600)
199
+ });
200
+ consola.success(`Logged in as ${agentEmail} (agent)`);
201
+ }
202
+
203
+ // src/commands/auth/logout.ts
204
+ import { defineCommand as defineCommand2 } from "citty";
205
+ import consola2 from "consola";
206
+ var logoutCommand = defineCommand2({
207
+ meta: {
208
+ name: "logout",
209
+ description: "Clear stored credentials"
210
+ },
211
+ run() {
212
+ clearAuth();
213
+ consola2.success("Logged out.");
214
+ }
215
+ });
216
+
217
+ // src/commands/auth/whoami.ts
218
+ import { defineCommand as defineCommand3 } from "citty";
219
+ import consola3 from "consola";
220
+ var whoamiCommand = defineCommand3({
221
+ meta: {
222
+ name: "whoami",
223
+ description: "Show current identity"
224
+ },
225
+ run() {
226
+ const auth = loadAuth();
227
+ if (!auth) {
228
+ consola3.error("Not logged in. Run `apes login` first.");
229
+ return process.exit(1);
230
+ }
231
+ const isAgent = auth.email.includes("agent+");
232
+ const expiresAt = new Date(auth.expires_at * 1e3).toISOString();
233
+ console.log(`Email: ${auth.email}`);
234
+ console.log(`Type: ${isAgent ? "agent" : "human"}`);
235
+ console.log(`IdP: ${auth.idp}`);
236
+ console.log(`Token valid until: ${expiresAt}`);
237
+ }
238
+ });
239
+
240
+ // src/commands/grants/list.ts
241
+ import { defineCommand as defineCommand4 } from "citty";
242
+ import consola4 from "consola";
243
+ var listCommand = defineCommand4({
244
+ meta: {
245
+ name: "list",
246
+ description: "List your grants (as requester)"
247
+ },
248
+ args: {
249
+ status: {
250
+ type: "string",
251
+ description: "Filter by status (pending, approved, denied, revoked, used)"
252
+ },
253
+ all: {
254
+ type: "boolean",
255
+ description: "Show all visible grants (not just your own)",
256
+ default: false
257
+ },
258
+ json: {
259
+ type: "boolean",
260
+ description: "Output as JSON",
261
+ default: false
262
+ },
263
+ limit: {
264
+ type: "string",
265
+ description: "Max results (default 20, max 100)"
266
+ }
267
+ },
268
+ async run({ args }) {
269
+ const idp = getIdpUrl();
270
+ if (!idp) {
271
+ consola4.error("No IdP URL configured. Run `apes login` first or pass --idp.");
272
+ return process.exit(1);
273
+ }
274
+ const auth = loadAuth();
275
+ const grantsUrl = await getGrantsEndpoint(idp);
276
+ const params = new URLSearchParams();
277
+ if (args.status)
278
+ params.set("status", args.status);
279
+ if (args.limit)
280
+ params.set("limit", args.limit);
281
+ const query = params.toString() ? `?${params.toString()}` : "";
282
+ const response = await apiFetch(`${grantsUrl}${query}`);
283
+ let grants = response.data;
284
+ if (!args.all && auth?.email) {
285
+ grants = grants.filter((g) => g.requester === auth.email);
286
+ }
287
+ if (args.json) {
288
+ console.log(JSON.stringify(args.all ? response : { ...response, data: grants }, null, 2));
289
+ return;
290
+ }
291
+ if (grants.length === 0) {
292
+ consola4.info(args.all ? "No grants found." : "No grants found. Use --all to see all visible grants.");
293
+ return;
294
+ }
295
+ for (const grant of grants) {
296
+ const cmd = grant.request?.command?.join(" ") || "(no command)";
297
+ const type = grant.request?.grant_type || grant.type;
298
+ console.log(`${grant.id} ${grant.status.padEnd(8)} ${type.padEnd(6)} ${cmd}`);
299
+ if (grant.request?.reason) {
300
+ console.log(` Reason: ${grant.request.reason}`);
301
+ }
302
+ }
303
+ if (response.pagination.has_more) {
304
+ consola4.info("More results available. Use --limit or pagination cursor.");
305
+ }
306
+ }
307
+ });
308
+
309
+ // src/commands/grants/inbox.ts
310
+ import { defineCommand as defineCommand5 } from "citty";
311
+ import consola5 from "consola";
312
+ var inboxCommand = defineCommand5({
313
+ meta: {
314
+ name: "inbox",
315
+ description: "Show grants awaiting your approval"
316
+ },
317
+ args: {
318
+ json: {
319
+ type: "boolean",
320
+ description: "Output as JSON",
321
+ default: false
322
+ },
323
+ limit: {
324
+ type: "string",
325
+ description: "Max results (default 20, max 100)"
326
+ }
327
+ },
328
+ async run({ args }) {
329
+ const idp = getIdpUrl();
330
+ if (!idp) {
331
+ consola5.error("No IdP URL configured. Run `apes login` first.");
332
+ return process.exit(1);
333
+ }
334
+ const auth = loadAuth();
335
+ if (!auth) {
336
+ consola5.error("Not logged in. Run `apes login` first.");
337
+ return process.exit(1);
338
+ }
339
+ const grantsUrl = await getGrantsEndpoint(idp);
340
+ const params = new URLSearchParams();
341
+ params.set("status", "pending");
342
+ if (args.limit)
343
+ params.set("limit", args.limit);
344
+ const query = `?${params.toString()}`;
345
+ const response = await apiFetch(`${grantsUrl}${query}`);
346
+ const grants = response.data.filter((g) => g.requester !== auth.email);
347
+ if (args.json) {
348
+ console.log(JSON.stringify({ ...response, data: grants }, null, 2));
349
+ return;
350
+ }
351
+ if (grants.length === 0) {
352
+ consola5.info("No pending grants to approve.");
353
+ return;
354
+ }
355
+ consola5.info(`${grants.length} grant(s) awaiting approval:
356
+ `);
357
+ for (const grant of grants) {
358
+ const cmd = grant.request?.command?.join(" ") || "(no command)";
359
+ const type = grant.request?.grant_type || grant.type;
360
+ console.log(`${grant.id} ${type.padEnd(6)} from ${grant.requester}`);
361
+ console.log(` Command: ${cmd}`);
362
+ if (grant.request?.reason) {
363
+ console.log(` Reason: ${grant.request.reason}`);
364
+ }
365
+ if (grant.created_at) {
366
+ console.log(` Created: ${grant.created_at}`);
367
+ }
368
+ console.log();
369
+ }
370
+ consola5.info("Use `apes grants approve <id>` or `apes grants deny <id>` to respond.");
371
+ }
372
+ });
373
+
374
+ // src/commands/grants/status.ts
375
+ import { defineCommand as defineCommand6 } from "citty";
376
+ var statusCommand = defineCommand6({
377
+ meta: {
378
+ name: "status",
379
+ description: "Show grant status"
380
+ },
381
+ args: {
382
+ id: {
383
+ type: "positional",
384
+ description: "Grant ID",
385
+ required: true
386
+ },
387
+ json: {
388
+ type: "boolean",
389
+ description: "Output as JSON",
390
+ default: false
391
+ }
392
+ },
393
+ async run({ args }) {
394
+ const idp = getIdpUrl();
395
+ const grantsUrl = await getGrantsEndpoint(idp);
396
+ const grant = await apiFetch(`${grantsUrl}/${args.id}`);
397
+ if (args.json) {
398
+ console.log(JSON.stringify(grant, null, 2));
399
+ return;
400
+ }
401
+ console.log(`Grant: ${grant.id}`);
402
+ console.log(`Status: ${grant.status}`);
403
+ console.log(`Type: ${grant.type}`);
404
+ console.log(`Requester: ${grant.requester}`);
405
+ console.log(`Owner: ${grant.owner}`);
406
+ if (grant.approver)
407
+ console.log(`Approver: ${grant.approver}`);
408
+ if (grant.request?.command)
409
+ console.log(`Command: ${grant.request.command.join(" ")}`);
410
+ if (grant.request?.grant_type)
411
+ console.log(`Approval: ${grant.request.grant_type}`);
412
+ if (grant.request?.reason)
413
+ console.log(`Reason: ${grant.request.reason}`);
414
+ if (grant.decided_by)
415
+ console.log(`Decided by: ${grant.decided_by}`);
416
+ if (grant.decided_at)
417
+ console.log(`Decided at: ${grant.decided_at}`);
418
+ if (grant.expires_at)
419
+ console.log(`Expires: ${grant.expires_at}`);
420
+ }
421
+ });
422
+
423
+ // src/commands/grants/request.ts
424
+ import { hostname } from "os";
425
+ import { defineCommand as defineCommand7 } from "citty";
426
+ import consola6 from "consola";
427
+ var requestCommand = defineCommand7({
428
+ meta: {
429
+ name: "request",
430
+ description: "Request a new grant"
431
+ },
432
+ args: {
433
+ command: {
434
+ type: "positional",
435
+ description: "Command to request permission for",
436
+ required: true
437
+ },
438
+ audience: {
439
+ type: "string",
440
+ description: 'Service identifier (e.g. "escapes", "proxy")',
441
+ required: true
442
+ },
443
+ host: {
444
+ type: "string",
445
+ description: "Target host (default: system hostname)"
446
+ },
447
+ reason: {
448
+ type: "string",
449
+ description: "Reason for the request"
450
+ },
451
+ approval: {
452
+ type: "string",
453
+ description: "Approval type: once, timed, always",
454
+ default: "once"
455
+ },
456
+ duration: {
457
+ type: "string",
458
+ description: "Duration for timed grants (e.g. 30m, 1h, 7d)"
459
+ },
460
+ "run-as": {
461
+ type: "string",
462
+ description: "Execute as this user (e.g. openclaw, root)"
463
+ },
464
+ wait: {
465
+ type: "boolean",
466
+ description: "Wait for approval",
467
+ default: false
468
+ }
469
+ },
470
+ async run({ args }) {
471
+ const auth = loadAuth();
472
+ if (!auth) {
473
+ consola6.error("Not logged in. Run `apes login` first.");
474
+ return process.exit(1);
475
+ }
476
+ const idp = getIdpUrl();
477
+ const grantsUrl = await getGrantsEndpoint(idp);
478
+ const command = args.command.split(" ");
479
+ const targetHost = args.host || hostname();
480
+ const duration = args.duration ? parseDuration(args.duration) : void 0;
481
+ const grant = await apiFetch(grantsUrl, {
482
+ method: "POST",
483
+ body: {
484
+ requester: auth.email,
485
+ target_host: targetHost,
486
+ audience: args.audience,
487
+ grant_type: args.approval,
488
+ command,
489
+ reason: args.reason || command.join(" "),
490
+ ...duration != null ? { duration } : {},
491
+ ...args["run-as"] ? { run_as: args["run-as"] } : {}
492
+ }
493
+ });
494
+ consola6.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
495
+ if (args.wait) {
496
+ consola6.info("Waiting for approval...");
497
+ await waitForApproval(grantsUrl, grant.id);
498
+ }
499
+ }
500
+ });
501
+ async function waitForApproval(grantsUrl, grantId) {
502
+ const maxWait = 3e5;
503
+ const interval = 3e3;
504
+ const start = Date.now();
505
+ while (Date.now() - start < maxWait) {
506
+ const grant = await apiFetch(`${grantsUrl}/${grantId}`);
507
+ if (grant.status === "approved") {
508
+ consola6.success("Grant approved!");
509
+ return;
510
+ }
511
+ if (grant.status === "denied") {
512
+ consola6.error("Grant denied.");
513
+ return process.exit(1);
514
+ }
515
+ if (grant.status === "revoked") {
516
+ consola6.error("Grant revoked.");
517
+ return process.exit(1);
518
+ }
519
+ await new Promise((r) => setTimeout(r, interval));
520
+ }
521
+ consola6.error("Timed out waiting for approval.");
522
+ return process.exit(1);
523
+ }
524
+
525
+ // src/commands/grants/request-capability.ts
526
+ import { hostname as hostname2 } from "os";
527
+ import { buildStructuredCliGrantRequest, loadAdapter, resolveCapabilityRequest } from "@openape/shapes";
528
+ import { defineCommand as defineCommand8 } from "citty";
529
+ import consola7 from "consola";
530
+ function parseCapabilityArgs(rawArgs) {
531
+ const tokens = [...rawArgs];
532
+ if (tokens[0] === "request-capability") {
533
+ tokens.shift();
534
+ }
535
+ const cliId = tokens.shift();
536
+ if (!cliId || cliId.startsWith("-")) {
537
+ throw new Error("Missing CLI identifier");
538
+ }
539
+ const resources = [];
540
+ const selectors = [];
541
+ const actions = [];
542
+ let adapter;
543
+ let idp;
544
+ let approval = "once";
545
+ let reason;
546
+ let duration;
547
+ let runAs;
548
+ let wait = false;
549
+ for (let index = 0; index < tokens.length; index += 1) {
550
+ const token = tokens[index];
551
+ const next = tokens[index + 1];
552
+ switch (token) {
553
+ case "--resource":
554
+ if (!next)
555
+ throw new Error("Missing value for --resource");
556
+ resources.push(next);
557
+ index += 1;
558
+ break;
559
+ case "--selector":
560
+ if (!next)
561
+ throw new Error("Missing value for --selector");
562
+ selectors.push(next);
563
+ index += 1;
564
+ break;
565
+ case "--action":
566
+ if (!next)
567
+ throw new Error("Missing value for --action");
568
+ actions.push(next);
569
+ index += 1;
570
+ break;
571
+ case "--adapter":
572
+ if (!next)
573
+ throw new Error("Missing value for --adapter");
574
+ adapter = next;
575
+ index += 1;
576
+ break;
577
+ case "--idp":
578
+ if (!next)
579
+ throw new Error("Missing value for --idp");
580
+ idp = next;
581
+ index += 1;
582
+ break;
583
+ case "--approval":
584
+ if (!next || !["once", "timed", "always"].includes(next)) {
585
+ throw new Error("Approval must be one of: once, timed, always");
586
+ }
587
+ approval = next;
588
+ index += 1;
589
+ break;
590
+ case "--reason":
591
+ if (!next)
592
+ throw new Error("Missing value for --reason");
593
+ reason = next;
594
+ index += 1;
595
+ break;
596
+ case "--duration":
597
+ if (!next)
598
+ throw new Error("Missing value for --duration");
599
+ duration = parseDuration(next);
600
+ index += 1;
601
+ break;
602
+ case "--run-as":
603
+ if (!next)
604
+ throw new Error("Missing value for --run-as");
605
+ runAs = next;
606
+ index += 1;
607
+ break;
608
+ case "--wait":
609
+ wait = true;
610
+ break;
611
+ default:
612
+ throw new Error(`Unknown argument: ${token}`);
613
+ }
614
+ }
615
+ return {
616
+ cliId,
617
+ adapter,
618
+ idp,
619
+ approval,
620
+ reason,
621
+ duration,
622
+ runAs,
623
+ wait,
624
+ resources,
625
+ selectors,
626
+ actions
627
+ };
628
+ }
629
+ async function waitForApproval2(grantsUrl, grantId) {
630
+ const maxWait = 3e5;
631
+ const interval = 3e3;
632
+ const start = Date.now();
633
+ while (Date.now() - start < maxWait) {
634
+ const grant = await apiFetch(`${grantsUrl}/${grantId}`);
635
+ if (grant.status === "approved") {
636
+ consola7.success("Grant approved!");
637
+ return;
638
+ }
639
+ if (grant.status === "denied") {
640
+ consola7.error("Grant denied.");
641
+ process.exit(1);
642
+ }
643
+ if (grant.status === "revoked") {
644
+ consola7.error("Grant revoked.");
645
+ process.exit(1);
646
+ }
647
+ await new Promise((resolve) => setTimeout(resolve, interval));
648
+ }
649
+ consola7.error("Timed out waiting for approval.");
650
+ process.exit(1);
651
+ }
652
+ var requestCapabilityCommand = defineCommand8({
653
+ meta: {
654
+ name: "request-capability",
655
+ description: "Request a structured CLI capability grant"
656
+ },
657
+ async run({ rawArgs }) {
658
+ const auth = loadAuth();
659
+ if (!auth) {
660
+ consola7.error("Not logged in. Run `apes login` first.");
661
+ return process.exit(1);
662
+ }
663
+ const parsed = parseCapabilityArgs(rawArgs);
664
+ const idp = getIdpUrl(parsed.idp);
665
+ if (!idp) {
666
+ consola7.error("No IdP URL configured. Use --idp or log in first.");
667
+ return process.exit(1);
668
+ }
669
+ const loaded = loadAdapter(parsed.cliId, parsed.adapter);
670
+ const resolved = resolveCapabilityRequest(loaded, {
671
+ resources: parsed.resources,
672
+ selectors: parsed.selectors,
673
+ actions: parsed.actions
674
+ });
675
+ const { request } = await buildStructuredCliGrantRequest(resolved, {
676
+ requester: auth.email,
677
+ target_host: hostname2(),
678
+ grant_type: parsed.approval,
679
+ ...parsed.reason ? { reason: parsed.reason } : {}
680
+ });
681
+ if (parsed.duration != null) {
682
+ request.duration = parsed.duration;
683
+ }
684
+ if (parsed.runAs) {
685
+ request.run_as = parsed.runAs;
686
+ }
687
+ const grantsUrl = await getGrantsEndpoint(idp);
688
+ const grant = await apiFetch(grantsUrl, {
689
+ method: "POST",
690
+ idp,
691
+ body: request
692
+ });
693
+ consola7.success(`Grant requested: ${grant.id} (status: ${grant.status})`);
694
+ if (parsed.wait) {
695
+ consola7.info("Waiting for approval...");
696
+ await waitForApproval2(grantsUrl, grant.id);
697
+ }
698
+ }
699
+ });
700
+
701
+ // src/commands/grants/approve.ts
702
+ import { defineCommand as defineCommand9 } from "citty";
703
+ import consola8 from "consola";
704
+ var approveCommand = defineCommand9({
705
+ meta: {
706
+ name: "approve",
707
+ description: "Approve a grant request"
708
+ },
709
+ args: {
710
+ id: {
711
+ type: "positional",
712
+ description: "Grant ID",
713
+ required: true
714
+ }
715
+ },
716
+ async run({ args }) {
717
+ const idp = getIdpUrl();
718
+ const grantsUrl = await getGrantsEndpoint(idp);
719
+ await apiFetch(`${grantsUrl}/${args.id}/approve`, {
720
+ method: "POST"
721
+ });
722
+ consola8.success(`Grant ${args.id} approved.`);
723
+ }
724
+ });
725
+
726
+ // src/commands/grants/deny.ts
727
+ import { defineCommand as defineCommand10 } from "citty";
728
+ import consola9 from "consola";
729
+ var denyCommand = defineCommand10({
730
+ meta: {
731
+ name: "deny",
732
+ description: "Deny a grant request"
733
+ },
734
+ args: {
735
+ id: {
736
+ type: "positional",
737
+ description: "Grant ID",
738
+ required: true
739
+ }
740
+ },
741
+ async run({ args }) {
742
+ const idp = getIdpUrl();
743
+ const grantsUrl = await getGrantsEndpoint(idp);
744
+ await apiFetch(`${grantsUrl}/${args.id}/deny`, {
745
+ method: "POST"
746
+ });
747
+ consola9.success(`Grant ${args.id} denied.`);
748
+ }
749
+ });
750
+
751
+ // src/commands/grants/revoke.ts
752
+ import { defineCommand as defineCommand11 } from "citty";
753
+ import consola10 from "consola";
754
+ var revokeCommand = defineCommand11({
755
+ meta: {
756
+ name: "revoke",
757
+ description: "Revoke a grant"
758
+ },
759
+ args: {
760
+ id: {
761
+ type: "positional",
762
+ description: "Grant ID",
763
+ required: true
764
+ }
765
+ },
766
+ async run({ args }) {
767
+ const idp = getIdpUrl();
768
+ const grantsUrl = await getGrantsEndpoint(idp);
769
+ await apiFetch(`${grantsUrl}/${args.id}/revoke`, {
770
+ method: "POST"
771
+ });
772
+ consola10.success(`Grant ${args.id} revoked.`);
773
+ }
774
+ });
775
+
776
+ // src/commands/grants/token.ts
777
+ import { defineCommand as defineCommand12 } from "citty";
778
+ import consola11 from "consola";
779
+ var tokenCommand = defineCommand12({
780
+ meta: {
781
+ name: "token",
782
+ description: "Get grant token JWT"
783
+ },
784
+ args: {
785
+ id: {
786
+ type: "positional",
787
+ description: "Grant ID",
788
+ required: true
789
+ }
790
+ },
791
+ async run({ args }) {
792
+ const idp = getIdpUrl();
793
+ const grantsUrl = await getGrantsEndpoint(idp);
794
+ const result = await apiFetch(`${grantsUrl}/${args.id}/token`, {
795
+ method: "POST"
796
+ });
797
+ if (!result.authz_jwt) {
798
+ consola11.error("No token received. Grant may not be approved.");
799
+ return process.exit(1);
800
+ }
801
+ process.stdout.write(result.authz_jwt);
802
+ }
803
+ });
804
+
805
+ // src/commands/grants/delegate.ts
806
+ import { defineCommand as defineCommand13 } from "citty";
807
+ import consola12 from "consola";
808
+ var delegateCommand = defineCommand13({
809
+ meta: {
810
+ name: "delegate",
811
+ description: "Create a delegation"
812
+ },
813
+ args: {
814
+ to: {
815
+ type: "string",
816
+ description: "Delegate email (who can act on your behalf)",
817
+ required: true
818
+ },
819
+ at: {
820
+ type: "string",
821
+ description: "Service/audience where delegation applies",
822
+ required: true
823
+ },
824
+ scopes: {
825
+ type: "string",
826
+ description: "Comma-separated scopes"
827
+ },
828
+ approval: {
829
+ type: "string",
830
+ description: "Approval type: once, timed, always",
831
+ default: "once"
832
+ },
833
+ expires: {
834
+ type: "string",
835
+ description: "Expiration date (ISO 8601)"
836
+ }
837
+ },
838
+ async run({ args }) {
839
+ const auth = loadAuth();
840
+ if (!auth) {
841
+ consola12.error("Not logged in. Run `apes login` first.");
842
+ return process.exit(1);
843
+ }
844
+ const idp = getIdpUrl();
845
+ const delegationsUrl = await getDelegationsEndpoint(idp);
846
+ const body = {
847
+ delegate: args.to,
848
+ audience: args.at,
849
+ approval: args.approval
850
+ };
851
+ if (args.scopes) {
852
+ body.scopes = args.scopes.split(",").map((s) => s.trim());
853
+ }
854
+ if (args.expires) {
855
+ body.expires_at = args.expires;
856
+ }
857
+ const result = await apiFetch(delegationsUrl, {
858
+ method: "POST",
859
+ body
860
+ });
861
+ consola12.success(`Delegation created: ${result.id}`);
862
+ console.log(` Delegate: ${args.to}`);
863
+ console.log(` Audience: ${args.at}`);
864
+ if (args.scopes)
865
+ console.log(` Scopes: ${args.scopes}`);
866
+ console.log(` Approval: ${args.approval}`);
867
+ if (args.expires)
868
+ console.log(` Expires: ${args.expires}`);
869
+ }
870
+ });
871
+
872
+ // src/commands/grants/delegations.ts
873
+ import { defineCommand as defineCommand14 } from "citty";
874
+ import consola13 from "consola";
875
+ var delegationsCommand = defineCommand14({
876
+ meta: {
877
+ name: "delegations",
878
+ description: "List delegations"
879
+ },
880
+ args: {
881
+ json: {
882
+ type: "boolean",
883
+ description: "Output as JSON",
884
+ default: false
885
+ }
886
+ },
887
+ async run({ args }) {
888
+ const idp = getIdpUrl();
889
+ const delegationsUrl = await getDelegationsEndpoint(idp);
890
+ const delegations = await apiFetch(delegationsUrl);
891
+ if (args.json) {
892
+ console.log(JSON.stringify(delegations, null, 2));
893
+ return;
894
+ }
895
+ if (delegations.length === 0) {
896
+ consola13.info("No delegations found.");
897
+ return;
898
+ }
899
+ for (const d of delegations) {
900
+ const scopes = d.scopes?.join(", ") || "(all)";
901
+ const expires = d.expires_at ? ` expires ${d.expires_at}` : "";
902
+ console.log(`${d.id} ${d.delegator} \u2192 ${d.delegate} at ${d.audience} [${scopes}]${expires}`);
903
+ }
904
+ }
905
+ });
906
+
907
+ // src/commands/adapter/index.ts
908
+ import { defineCommand as defineCommand15 } from "citty";
909
+ import consola14 from "consola";
910
+ import {
911
+ fetchRegistry,
912
+ findAdapter,
913
+ findConflictingAdapters,
914
+ getInstalledDigest,
915
+ installAdapter,
916
+ isInstalled,
917
+ loadAdapter as loadAdapter2,
918
+ removeAdapter,
919
+ searchAdapters
920
+ } from "@openape/shapes";
921
+ var adapterCommand = defineCommand15({
922
+ meta: {
923
+ name: "adapter",
924
+ description: "Manage CLI adapters"
925
+ },
926
+ subCommands: {
927
+ list: defineCommand15({
928
+ meta: {
929
+ name: "list",
930
+ description: "List available adapters"
931
+ },
932
+ args: {
933
+ remote: {
934
+ type: "boolean",
935
+ description: "List adapters from the remote registry",
936
+ default: false
937
+ },
938
+ json: {
939
+ type: "boolean",
940
+ description: "Output as JSON",
941
+ default: false
942
+ },
943
+ refresh: {
944
+ type: "boolean",
945
+ description: "Force refresh the registry cache",
946
+ default: false
947
+ }
948
+ },
949
+ async run({ args }) {
950
+ const forceRefresh = Boolean(args.refresh);
951
+ if (args.remote) {
952
+ const index2 = await fetchRegistry(forceRefresh);
953
+ if (args.json) {
954
+ process.stdout.write(`${JSON.stringify(index2.adapters, null, 2)}
955
+ `);
956
+ return;
957
+ }
958
+ consola14.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
959
+ for (const a of index2.adapters) {
960
+ const installed = isInstalled(a.id, false) ? " [installed]" : "";
961
+ console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
962
+ }
963
+ return;
964
+ }
965
+ const index = await fetchRegistry(forceRefresh);
966
+ const local = [];
967
+ for (const a of index.adapters) {
968
+ try {
969
+ const loaded = loadAdapter2(a.id);
970
+ local.push({ id: a.id, source: loaded.source, digest: loaded.digest });
971
+ } catch {
972
+ }
973
+ }
974
+ if (args.json) {
975
+ process.stdout.write(`${JSON.stringify(local, null, 2)}
976
+ `);
977
+ return;
978
+ }
979
+ if (local.length === 0) {
980
+ consola14.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
981
+ return;
982
+ }
983
+ for (const a of local) {
984
+ console.log(` ${a.id.padEnd(12)} ${a.source}`);
985
+ }
986
+ }
987
+ }),
988
+ install: defineCommand15({
989
+ meta: {
990
+ name: "install",
991
+ description: "Install an adapter from the registry"
992
+ },
993
+ args: {
994
+ id: {
995
+ type: "positional",
996
+ description: "Adapter ID to install",
997
+ required: true
998
+ },
999
+ local: {
1000
+ type: "boolean",
1001
+ description: "Install to project-local .openape/ instead of ~/.openape/",
1002
+ default: false
1003
+ },
1004
+ refresh: {
1005
+ type: "boolean",
1006
+ description: "Force refresh the registry cache",
1007
+ default: false
1008
+ }
1009
+ },
1010
+ async run({ args }) {
1011
+ const id = String(args.id);
1012
+ const local = Boolean(args.local);
1013
+ const index = await fetchRegistry(Boolean(args.refresh));
1014
+ const entry = findAdapter(index, id);
1015
+ if (!entry)
1016
+ throw new Error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1017
+ const conflicts = findConflictingAdapters(entry.executable, id);
1018
+ if (conflicts.length > 0) {
1019
+ for (const c of conflicts) {
1020
+ consola14.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1021
+ consola14.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1022
+ }
1023
+ }
1024
+ const result = await installAdapter(entry, { local });
1025
+ const verb = result.updated ? "Updated" : "Installed";
1026
+ consola14.success(`${verb} ${result.id} \u2192 ${result.path}`);
1027
+ consola14.info(`Digest: ${result.digest}`);
1028
+ }
1029
+ }),
1030
+ remove: defineCommand15({
1031
+ meta: {
1032
+ name: "remove",
1033
+ description: "Remove an installed adapter"
1034
+ },
1035
+ args: {
1036
+ id: {
1037
+ type: "positional",
1038
+ description: "Adapter ID to remove",
1039
+ required: true
1040
+ },
1041
+ local: {
1042
+ type: "boolean",
1043
+ description: "Remove from project-local .openape/",
1044
+ default: false
1045
+ }
1046
+ },
1047
+ async run({ args }) {
1048
+ const id = String(args.id);
1049
+ const local = Boolean(args.local);
1050
+ if (removeAdapter(id, local)) {
1051
+ consola14.success(`Removed adapter: ${id}`);
1052
+ } else {
1053
+ consola14.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1054
+ process.exit(1);
1055
+ }
1056
+ }
1057
+ }),
1058
+ info: defineCommand15({
1059
+ meta: {
1060
+ name: "info",
1061
+ description: "Show detailed adapter information"
1062
+ },
1063
+ args: {
1064
+ id: {
1065
+ type: "positional",
1066
+ description: "Adapter ID",
1067
+ required: true
1068
+ },
1069
+ refresh: {
1070
+ type: "boolean",
1071
+ description: "Force refresh the registry cache",
1072
+ default: false
1073
+ }
1074
+ },
1075
+ async run({ args }) {
1076
+ const id = String(args.id);
1077
+ const index = await fetchRegistry(Boolean(args.refresh));
1078
+ const entry = findAdapter(index, id);
1079
+ if (!entry)
1080
+ throw new Error(`Adapter "${id}" not found in registry`);
1081
+ console.log(`ID: ${entry.id}`);
1082
+ console.log(`Name: ${entry.name}`);
1083
+ console.log(`Description: ${entry.description}`);
1084
+ console.log(`Category: ${entry.category}`);
1085
+ console.log(`Tags: ${entry.tags.join(", ")}`);
1086
+ console.log(`Author: ${entry.author}`);
1087
+ console.log(`Executable: ${entry.executable}`);
1088
+ console.log(`Digest: ${entry.digest}`);
1089
+ console.log(`Min version: ${entry.min_shapes_version}`);
1090
+ console.log(`URL: ${entry.download_url}`);
1091
+ const localDigest = getInstalledDigest(id, false);
1092
+ if (localDigest) {
1093
+ const upToDate = localDigest === entry.digest;
1094
+ console.log(`Installed: yes${upToDate ? " (up to date)" : " (update available)"}`);
1095
+ } else {
1096
+ console.log(`Installed: no`);
1097
+ }
1098
+ }
1099
+ }),
1100
+ search: defineCommand15({
1101
+ meta: {
1102
+ name: "search",
1103
+ description: "Search adapters in the registry"
1104
+ },
1105
+ args: {
1106
+ query: {
1107
+ type: "positional",
1108
+ description: "Search query",
1109
+ required: true
1110
+ },
1111
+ json: {
1112
+ type: "boolean",
1113
+ description: "Output as JSON",
1114
+ default: false
1115
+ },
1116
+ refresh: {
1117
+ type: "boolean",
1118
+ description: "Force refresh the registry cache",
1119
+ default: false
1120
+ }
1121
+ },
1122
+ async run({ args }) {
1123
+ const query = String(args.query);
1124
+ const index = await fetchRegistry(Boolean(args.refresh));
1125
+ const results = searchAdapters(index, query);
1126
+ if (args.json) {
1127
+ process.stdout.write(`${JSON.stringify(results, null, 2)}
1128
+ `);
1129
+ return;
1130
+ }
1131
+ if (results.length === 0) {
1132
+ consola14.info(`No adapters matching "${query}"`);
1133
+ return;
1134
+ }
1135
+ for (const a of results) {
1136
+ console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}`);
1137
+ console.log(` ${a.description}`);
1138
+ }
1139
+ }
1140
+ }),
1141
+ update: defineCommand15({
1142
+ meta: {
1143
+ name: "update",
1144
+ description: "Update installed adapters"
1145
+ },
1146
+ args: {
1147
+ id: {
1148
+ type: "positional",
1149
+ description: "Adapter ID (omit to update all)"
1150
+ },
1151
+ yes: {
1152
+ type: "boolean",
1153
+ description: "Skip confirmation",
1154
+ default: false
1155
+ },
1156
+ refresh: {
1157
+ type: "boolean",
1158
+ description: "Force refresh the registry cache",
1159
+ default: true
1160
+ }
1161
+ },
1162
+ async run({ args }) {
1163
+ const index = await fetchRegistry(Boolean(args.refresh));
1164
+ const targetId = args.id ? String(args.id) : void 0;
1165
+ const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
1166
+ if (targets.length === 0) {
1167
+ consola14.info("No adapters installed to update.");
1168
+ return;
1169
+ }
1170
+ for (const id of targets) {
1171
+ const entry = findAdapter(index, id);
1172
+ if (!entry) {
1173
+ consola14.warn(`${id}: not found in registry, skipping`);
1174
+ continue;
1175
+ }
1176
+ const localDigest = getInstalledDigest(id, false);
1177
+ if (localDigest === entry.digest) {
1178
+ consola14.info(`${id}: already up to date`);
1179
+ continue;
1180
+ }
1181
+ if (localDigest && !args.yes) {
1182
+ consola14.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1183
+ consola14.info(` Old: ${localDigest}`);
1184
+ consola14.info(` New: ${entry.digest}`);
1185
+ consola14.info(" Use --yes to confirm");
1186
+ continue;
1187
+ }
1188
+ const result = await installAdapter(entry);
1189
+ consola14.success(`Updated ${result.id} \u2192 ${result.path}`);
1190
+ }
1191
+ }
1192
+ }),
1193
+ verify: defineCommand15({
1194
+ meta: {
1195
+ name: "verify",
1196
+ description: "Verify installed adapter against registry digest"
1197
+ },
1198
+ args: {
1199
+ id: {
1200
+ type: "positional",
1201
+ description: "Adapter ID",
1202
+ required: true
1203
+ },
1204
+ local: {
1205
+ type: "boolean",
1206
+ description: "Check project-local adapter",
1207
+ default: false
1208
+ },
1209
+ refresh: {
1210
+ type: "boolean",
1211
+ description: "Force refresh the registry cache",
1212
+ default: false
1213
+ }
1214
+ },
1215
+ async run({ args }) {
1216
+ const id = String(args.id);
1217
+ const local = Boolean(args.local);
1218
+ const index = await fetchRegistry(Boolean(args.refresh));
1219
+ const entry = findAdapter(index, id);
1220
+ if (!entry)
1221
+ throw new Error(`Adapter "${id}" not found in registry`);
1222
+ const localDigest = getInstalledDigest(id, local);
1223
+ if (!localDigest)
1224
+ throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1225
+ if (localDigest === entry.digest) {
1226
+ consola14.success(`${id}: digest matches registry`);
1227
+ } else {
1228
+ consola14.error(`${id}: digest mismatch`);
1229
+ console.log(` Local: ${localDigest}`);
1230
+ console.log(` Registry: ${entry.digest}`);
1231
+ process.exit(1);
1232
+ }
1233
+ }
1234
+ })
1235
+ }
1236
+ });
1237
+
1238
+ // src/commands/run.ts
1239
+ import { execFileSync } from "child_process";
1240
+ import { hostname as hostname3 } from "os";
1241
+ import { defineCommand as defineCommand16 } from "citty";
1242
+ import {
1243
+ createShapesGrant,
1244
+ extractOption,
1245
+ extractWrappedCommand,
1246
+ fetchGrantToken,
1247
+ findExistingGrant,
1248
+ loadAdapter as loadAdapter3,
1249
+ resolveCommand,
1250
+ verifyAndExecute,
1251
+ waitForGrantStatus
1252
+ } from "@openape/shapes";
1253
+ import consola15 from "consola";
1254
+ var runCommand = defineCommand16({
1255
+ meta: {
1256
+ name: "run",
1257
+ description: "Execute a grant-secured command"
1258
+ },
1259
+ args: {
1260
+ "approval": {
1261
+ type: "string",
1262
+ description: "Approval type: once, timed, always",
1263
+ default: "once"
1264
+ },
1265
+ "reason": {
1266
+ type: "string",
1267
+ description: "Reason for the grant request"
1268
+ },
1269
+ "adapter": {
1270
+ type: "string",
1271
+ description: "Explicit path to adapter TOML file"
1272
+ },
1273
+ "as": {
1274
+ type: "string",
1275
+ description: "Execute as this user (delegates to escapes)"
1276
+ },
1277
+ "host": {
1278
+ type: "string",
1279
+ description: "Target host (default: system hostname)"
1280
+ },
1281
+ "escapes-path": {
1282
+ type: "string",
1283
+ description: "Path to escapes binary",
1284
+ default: "escapes"
1285
+ },
1286
+ "idp": {
1287
+ type: "string",
1288
+ description: "IdP URL"
1289
+ },
1290
+ "_": {
1291
+ type: "positional",
1292
+ description: "Command to execute (after --)",
1293
+ required: false
1294
+ }
1295
+ },
1296
+ async run({ rawArgs, args }) {
1297
+ const wrappedCommand = extractWrappedCommand(rawArgs ?? []);
1298
+ if (wrappedCommand.length > 0) {
1299
+ await runAdapterMode(wrappedCommand, rawArgs ?? [], args);
1300
+ } else {
1301
+ const positionals = extractPositionals(rawArgs ?? []);
1302
+ if (positionals.length < 2)
1303
+ throw new Error("Usage: apes run -- <cli> <args...> OR apes run <audience> <action>");
1304
+ await runAudienceMode(positionals[0], positionals[1], args);
1305
+ }
1306
+ }
1307
+ });
1308
+ function extractPositionals(rawArgs) {
1309
+ const positionals = [];
1310
+ const delimiter = rawArgs.indexOf("--");
1311
+ const args = delimiter >= 0 ? rawArgs.slice(0, delimiter) : rawArgs;
1312
+ for (let i = 0; i < args.length; i++) {
1313
+ const arg = args[i];
1314
+ if (arg === "run")
1315
+ continue;
1316
+ if (arg.startsWith("--")) {
1317
+ i++;
1318
+ continue;
1319
+ }
1320
+ positionals.push(arg);
1321
+ }
1322
+ return positionals;
1323
+ }
1324
+ async function runAdapterMode(command, rawArgs, args) {
1325
+ const idp = getIdpUrl(args.idp);
1326
+ if (!idp)
1327
+ throw new Error("No IdP URL configured. Run `apes login` first or pass --idp.");
1328
+ const adapterOpt = extractOption(rawArgs, "adapter");
1329
+ const loaded = loadAdapter3(command[0], adapterOpt);
1330
+ const resolved = await resolveCommand(loaded, command);
1331
+ const approval = args.approval ?? "once";
1332
+ if (approval !== "once") {
1333
+ try {
1334
+ const existingGrantId = await findExistingGrant(resolved, idp);
1335
+ if (existingGrantId) {
1336
+ consola15.info(`Reusing existing grant: ${existingGrantId}`);
1337
+ const token2 = await fetchGrantToken(idp, existingGrantId);
1338
+ await verifyAndExecute(token2, resolved);
1339
+ return;
1340
+ }
1341
+ } catch {
1342
+ }
1343
+ }
1344
+ const grant = await createShapesGrant(resolved, {
1345
+ idp,
1346
+ approval,
1347
+ ...args.reason ? { reason: args.reason } : {}
1348
+ });
1349
+ consola15.info(`Grant requested: ${grant.id}`);
1350
+ consola15.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1351
+ const status = await waitForGrantStatus(idp, grant.id);
1352
+ if (status !== "approved")
1353
+ throw new Error(`Grant ${status}`);
1354
+ const token = await fetchGrantToken(idp, grant.id);
1355
+ await verifyAndExecute(token, resolved);
1356
+ }
1357
+ async function runAudienceMode(audience, action, args) {
1358
+ const auth = loadAuth();
1359
+ if (!auth) {
1360
+ consola15.error("Not logged in. Run `apes login` first.");
1361
+ return process.exit(1);
1362
+ }
1363
+ const idp = getIdpUrl(args.idp);
1364
+ const grantsUrl = await getGrantsEndpoint(idp);
1365
+ const command = action.split(" ");
1366
+ const targetHost = args.host || hostname3();
1367
+ consola15.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1368
+ const grant = await apiFetch(grantsUrl, {
1369
+ method: "POST",
1370
+ body: {
1371
+ requester: auth.email,
1372
+ target_host: targetHost,
1373
+ audience,
1374
+ grant_type: args.approval,
1375
+ command,
1376
+ reason: args.reason || command.join(" "),
1377
+ ...args.as ? { run_as: args.as } : {}
1378
+ }
1379
+ });
1380
+ consola15.success(`Grant requested: ${grant.id}`);
1381
+ consola15.info("Waiting for approval...");
1382
+ const maxWait = 3e5;
1383
+ const interval = 3e3;
1384
+ const start = Date.now();
1385
+ while (Date.now() - start < maxWait) {
1386
+ const status = await apiFetch(`${grantsUrl}/${grant.id}`);
1387
+ if (status.status === "approved") {
1388
+ consola15.success("Grant approved!");
1389
+ break;
1390
+ }
1391
+ if (status.status === "denied" || status.status === "revoked") {
1392
+ consola15.error(`Grant ${status.status}.`);
1393
+ return process.exit(1);
1394
+ }
1395
+ await new Promise((r) => setTimeout(r, interval));
1396
+ }
1397
+ consola15.info("Fetching grant token...");
1398
+ const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
1399
+ method: "POST"
1400
+ });
1401
+ if (audience === "escapes") {
1402
+ consola15.info(`Executing: ${command.join(" ")}`);
1403
+ try {
1404
+ execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
1405
+ stdio: "inherit"
1406
+ });
1407
+ } catch (err) {
1408
+ const exitCode = err.status || 1;
1409
+ process.exit(exitCode);
1410
+ }
1411
+ } else {
1412
+ process.stdout.write(authz_jwt);
1413
+ }
1414
+ }
1415
+
1416
+ // src/commands/explain.ts
1417
+ import { defineCommand as defineCommand17 } from "citty";
1418
+ import { extractOption as extractOption2, extractWrappedCommand as extractWrappedCommand2, loadAdapter as loadAdapter4, resolveCommand as resolveCommand2 } from "@openape/shapes";
1419
+ var explainCommand = defineCommand17({
1420
+ meta: {
1421
+ name: "explain",
1422
+ description: "Show what permission a command would need"
1423
+ },
1424
+ args: {
1425
+ adapter: {
1426
+ type: "string",
1427
+ description: "Explicit path to adapter TOML file"
1428
+ },
1429
+ _: {
1430
+ type: "positional",
1431
+ description: "Wrapped command (after --)",
1432
+ required: false
1433
+ }
1434
+ },
1435
+ async run({ rawArgs }) {
1436
+ const command = extractWrappedCommand2(rawArgs ?? []);
1437
+ if (command.length === 0)
1438
+ throw new Error("Missing wrapped command. Usage: apes explain [--adapter <file>] -- <cli> ...");
1439
+ const adapterOpt = extractOption2(rawArgs ?? [], "adapter");
1440
+ const loaded = loadAdapter4(command[0], adapterOpt);
1441
+ const resolved = await resolveCommand2(loaded, command);
1442
+ process.stdout.write(`${JSON.stringify({
1443
+ adapter: resolved.adapter.cli.id,
1444
+ source: resolved.source,
1445
+ operation: resolved.detail.operation_id,
1446
+ display: resolved.detail.display,
1447
+ permission: resolved.permission,
1448
+ resource_chain: resolved.detail.resource_chain,
1449
+ exact_command: resolved.detail.constraints?.exact_command ?? false,
1450
+ adapter_digest: resolved.digest
1451
+ }, null, 2)}
1452
+ `);
1453
+ }
1454
+ });
1455
+
1456
+ // src/commands/config/get.ts
1457
+ import { defineCommand as defineCommand18 } from "citty";
1458
+ import consola16 from "consola";
1459
+ var configGetCommand = defineCommand18({
1460
+ meta: {
1461
+ name: "get",
1462
+ description: "Get a configuration value"
1463
+ },
1464
+ args: {
1465
+ key: {
1466
+ type: "positional",
1467
+ description: "Config key: idp, email, defaults.idp, defaults.approval, agent.key, agent.email",
1468
+ required: true
1469
+ }
1470
+ },
1471
+ run({ args }) {
1472
+ const key = args.key;
1473
+ switch (key) {
1474
+ case "idp": {
1475
+ const idp = getIdpUrl();
1476
+ if (idp)
1477
+ console.log(idp);
1478
+ else
1479
+ consola16.info("No IdP configured.");
1480
+ break;
1481
+ }
1482
+ case "email": {
1483
+ const auth = loadAuth();
1484
+ if (auth?.email)
1485
+ console.log(auth.email);
1486
+ else
1487
+ consola16.info("Not logged in.");
1488
+ break;
1489
+ }
1490
+ default: {
1491
+ const config = loadConfig();
1492
+ const parts = key.split(".");
1493
+ if (parts.length === 2) {
1494
+ const section = parts[0];
1495
+ const field = parts[1];
1496
+ const sectionObj = config[section];
1497
+ if (sectionObj && field in sectionObj) {
1498
+ console.log(sectionObj[field]);
1499
+ } else {
1500
+ consola16.info(`Key "${key}" not set.`);
1501
+ }
1502
+ } else {
1503
+ consola16.error(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
1504
+ process.exit(1);
1505
+ }
1506
+ }
1507
+ }
1508
+ }
1509
+ });
1510
+
1511
+ // src/commands/config/set.ts
1512
+ import { defineCommand as defineCommand19 } from "citty";
1513
+ import consola17 from "consola";
1514
+ var configSetCommand = defineCommand19({
1515
+ meta: {
1516
+ name: "set",
1517
+ description: "Set a configuration value"
1518
+ },
1519
+ args: {
1520
+ key: {
1521
+ type: "positional",
1522
+ description: "Config key: defaults.idp, defaults.approval, agent.key, agent.email",
1523
+ required: true
1524
+ },
1525
+ value: {
1526
+ type: "positional",
1527
+ description: "Value to set",
1528
+ required: true
1529
+ }
1530
+ },
1531
+ run({ args }) {
1532
+ const key = args.key;
1533
+ const value = args.value;
1534
+ const config = loadConfig();
1535
+ const parts = key.split(".");
1536
+ if (parts.length !== 2) {
1537
+ consola17.error(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
1538
+ return process.exit(1);
1539
+ }
1540
+ const [section, field] = parts;
1541
+ if (section === "defaults") {
1542
+ config.defaults = config.defaults || {};
1543
+ config.defaults[field] = value;
1544
+ } else if (section === "agent") {
1545
+ config.agent = config.agent || {};
1546
+ config.agent[field] = value;
1547
+ } else {
1548
+ consola17.error(`Unknown section: "${section}". Use: defaults, agent`);
1549
+ return process.exit(1);
1550
+ }
1551
+ saveConfig(config);
1552
+ consola17.success(`Set ${key} = ${value}`);
1553
+ }
1554
+ });
1555
+
1556
+ // src/commands/fetch/index.ts
1557
+ import { defineCommand as defineCommand20 } from "citty";
1558
+ import consola18 from "consola";
1559
+ async function doRequest(method, url, body, contentType, raw, showHeaders) {
1560
+ const token = getAuthToken();
1561
+ if (!token) {
1562
+ consola18.error("Not authenticated. Run `apes login` first.");
1563
+ return process.exit(1);
1564
+ }
1565
+ const response = await fetch(url, {
1566
+ method,
1567
+ headers: {
1568
+ "Authorization": `Bearer ${token}`,
1569
+ "Content-Type": contentType
1570
+ },
1571
+ body: body || void 0
1572
+ });
1573
+ if (showHeaders) {
1574
+ console.log(`HTTP ${response.status} ${response.statusText}`);
1575
+ for (const [key, value] of response.headers.entries()) {
1576
+ console.log(`${key}: ${value}`);
1577
+ }
1578
+ console.log();
1579
+ }
1580
+ const respContentType = response.headers.get("content-type") || "";
1581
+ const text = await response.text();
1582
+ if (raw || !respContentType.includes("json")) {
1583
+ process.stdout.write(text);
1584
+ } else {
1585
+ try {
1586
+ console.log(JSON.stringify(JSON.parse(text), null, 2));
1587
+ } catch {
1588
+ process.stdout.write(text);
1589
+ }
1590
+ }
1591
+ if (!response.ok) {
1592
+ process.exit(1);
1593
+ }
1594
+ }
1595
+ var fetchCommand = defineCommand20({
1596
+ meta: {
1597
+ name: "fetch",
1598
+ description: "Make authenticated HTTP requests"
1599
+ },
1600
+ subCommands: {
1601
+ get: defineCommand20({
1602
+ meta: {
1603
+ name: "get",
1604
+ description: "GET request with auth token"
1605
+ },
1606
+ args: {
1607
+ url: {
1608
+ type: "positional",
1609
+ description: "URL to fetch",
1610
+ required: true
1611
+ },
1612
+ raw: {
1613
+ type: "boolean",
1614
+ description: "Output raw response body",
1615
+ default: false
1616
+ },
1617
+ headers: {
1618
+ type: "boolean",
1619
+ description: "Show response headers",
1620
+ default: false
1621
+ }
1622
+ },
1623
+ async run({ args }) {
1624
+ await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
1625
+ }
1626
+ }),
1627
+ post: defineCommand20({
1628
+ meta: {
1629
+ name: "post",
1630
+ description: "POST request with auth token"
1631
+ },
1632
+ args: {
1633
+ url: {
1634
+ type: "positional",
1635
+ description: "URL to fetch",
1636
+ required: true
1637
+ },
1638
+ body: {
1639
+ type: "string",
1640
+ description: "Request body (JSON string)"
1641
+ },
1642
+ "content-type": {
1643
+ type: "string",
1644
+ description: "Content-Type header",
1645
+ default: "application/json"
1646
+ },
1647
+ raw: {
1648
+ type: "boolean",
1649
+ description: "Output raw response body",
1650
+ default: false
1651
+ },
1652
+ headers: {
1653
+ type: "boolean",
1654
+ description: "Show response headers",
1655
+ default: false
1656
+ }
1657
+ },
1658
+ async run({ args }) {
1659
+ await doRequest("POST", String(args.url), args.body, String(args["content-type"] || "application/json"), Boolean(args.raw), Boolean(args.headers));
1660
+ }
1661
+ })
1662
+ }
1663
+ });
1664
+
1665
+ // src/commands/mcp/index.ts
1666
+ import { defineCommand as defineCommand21 } from "citty";
1667
+ var mcpCommand = defineCommand21({
1668
+ meta: {
1669
+ name: "mcp",
1670
+ description: "Start MCP server for AI agents"
1671
+ },
1672
+ args: {
1673
+ transport: {
1674
+ type: "string",
1675
+ description: "Transport type: stdio or sse",
1676
+ default: "stdio"
1677
+ },
1678
+ port: {
1679
+ type: "string",
1680
+ description: "Port for SSE transport",
1681
+ default: "3001"
1682
+ }
1683
+ },
1684
+ async run({ args }) {
1685
+ const transport = args.transport || "stdio";
1686
+ const port = Number.parseInt(String(args.port), 10);
1687
+ if (transport !== "stdio" && transport !== "sse") {
1688
+ throw new Error('Transport must be "stdio" or "sse"');
1689
+ }
1690
+ const { startMcpServer } = await import("./server-4ZIIWOOP.js");
1691
+ await startMcpServer(transport, port);
1692
+ }
1693
+ });
1694
+
1695
+ // src/cli.ts
1696
+ var debug = process.argv.includes("--debug");
1697
+ var grantsCommand = defineCommand22({
1698
+ meta: {
1699
+ name: "grants",
1700
+ description: "Grant management"
1701
+ },
1702
+ subCommands: {
1703
+ list: listCommand,
1704
+ inbox: inboxCommand,
1705
+ status: statusCommand,
1706
+ request: requestCommand,
1707
+ "request-capability": requestCapabilityCommand,
1708
+ approve: approveCommand,
1709
+ deny: denyCommand,
1710
+ revoke: revokeCommand,
1711
+ token: tokenCommand,
1712
+ delegate: delegateCommand,
1713
+ delegations: delegationsCommand
1714
+ }
1715
+ });
1716
+ var configCommand = defineCommand22({
1717
+ meta: {
1718
+ name: "config",
1719
+ description: "Configuration management"
1720
+ },
1721
+ subCommands: {
1722
+ get: configGetCommand,
1723
+ set: configSetCommand
1724
+ }
1725
+ });
1726
+ var main = defineCommand22({
1727
+ meta: {
1728
+ name: "apes",
1729
+ version: "0.2.0",
1730
+ description: "Unified CLI for OpenApe"
1731
+ },
1732
+ subCommands: {
1733
+ login: loginCommand,
1734
+ logout: logoutCommand,
1735
+ whoami: whoamiCommand,
1736
+ grants: grantsCommand,
1737
+ run: runCommand,
1738
+ explain: explainCommand,
1739
+ adapter: adapterCommand,
1740
+ config: configCommand,
1741
+ fetch: fetchCommand,
1742
+ mcp: mcpCommand
1743
+ }
1744
+ });
1745
+ runMain(main).catch((err) => {
1746
+ if (debug) {
1747
+ consola19.error(err);
1748
+ } else {
1749
+ consola19.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
1750
+ }
1751
+ process.exit(1);
1752
+ });
1753
+ //# sourceMappingURL=cli.js.map