@openape/apes 0.7.2 → 0.9.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 CHANGED
@@ -8,6 +8,9 @@ import {
8
8
  loadEd25519PrivateKey,
9
9
  readPublicKeyComment
10
10
  } from "./chunk-ION3CWD5.js";
11
+ import {
12
+ notifyGrantPending
13
+ } from "./chunk-LSKHTHUY.js";
11
14
  import {
12
15
  ApiError,
13
16
  apiFetch,
@@ -34,11 +37,14 @@ import {
34
37
  removeAdapter,
35
38
  resolveCapabilityRequest,
36
39
  resolveCommand,
40
+ resolveFromGrant,
37
41
  searchAdapters,
38
42
  verifyAndExecute,
39
43
  waitForGrantStatus
40
- } from "./chunk-B32ZQP5K.js";
44
+ } from "./chunk-UQ673USC.js";
41
45
  import {
46
+ AUTH_FILE,
47
+ CONFIG_DIR,
42
48
  clearAuth,
43
49
  getAuthToken,
44
50
  getIdpUrl,
@@ -46,10 +52,10 @@ import {
46
52
  loadConfig,
47
53
  saveAuth,
48
54
  saveConfig
49
- } from "./chunk-TBYYREL6.js";
55
+ } from "./chunk-AZVY3X7Q.js";
50
56
 
51
57
  // src/cli.ts
52
- import consola26 from "consola";
58
+ import consola27 from "consola";
53
59
 
54
60
  // src/ape-shell.ts
55
61
  import path from "path";
@@ -79,7 +85,7 @@ function rewriteApeShellArgs(argv, argv0) {
79
85
  }
80
86
 
81
87
  // src/cli.ts
82
- import { defineCommand as defineCommand31, runMain } from "citty";
88
+ import { defineCommand as defineCommand33, runMain } from "citty";
83
89
 
84
90
  // src/commands/auth/login.ts
85
91
  import { Buffer } from "buffer";
@@ -1036,9 +1042,83 @@ var revokeCommand = defineCommand11({
1036
1042
  }
1037
1043
  });
1038
1044
 
1039
- // src/commands/grants/token.ts
1045
+ // src/commands/grants/run.ts
1046
+ import { execFileSync } from "child_process";
1040
1047
  import { defineCommand as defineCommand12 } from "citty";
1041
- var tokenCommand = defineCommand12({
1048
+ import consola12 from "consola";
1049
+ var runGrantCommand = defineCommand12({
1050
+ meta: {
1051
+ name: "run",
1052
+ description: "Execute a previously-approved grant by ID"
1053
+ },
1054
+ args: {
1055
+ id: {
1056
+ type: "positional",
1057
+ description: "Grant ID",
1058
+ required: true
1059
+ },
1060
+ "escapes-path": {
1061
+ type: "string",
1062
+ description: "Path to escapes binary (audience=escapes only)",
1063
+ default: "escapes"
1064
+ }
1065
+ },
1066
+ async run({ args }) {
1067
+ const idp = getIdpUrl();
1068
+ if (!idp)
1069
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1070
+ const grantsUrl = await getGrantsEndpoint(idp);
1071
+ const grant = await apiFetch(`${grantsUrl}/${args.id}`);
1072
+ if (grant.status === "pending")
1073
+ throw new CliError(`Grant ${grant.id} is still pending. Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1074
+ if (grant.status === "denied" || grant.status === "revoked")
1075
+ throw new CliError(`Grant ${grant.id} is ${grant.status}. Request a new one.`);
1076
+ if (grant.status === "used")
1077
+ throw new CliError(`Grant ${grant.id} has already been used. Request a new one (single-use grants cannot be re-executed).`);
1078
+ if (grant.status !== "approved")
1079
+ throw new CliError(`Grant ${grant.id} has unexpected status: ${grant.status}`);
1080
+ const audience = grant.request?.audience;
1081
+ const authDetails = grant.request?.authorization_details ?? [];
1082
+ const hasOpenApeCliDetail = authDetails.some((d) => d?.type === "openape_cli");
1083
+ const isShapesGrant = hasOpenApeCliDetail || audience === "shapes";
1084
+ if (isShapesGrant) {
1085
+ let resolved;
1086
+ try {
1087
+ resolved = await resolveFromGrant(grant);
1088
+ } catch (err) {
1089
+ const msg = err instanceof Error ? err.message : String(err);
1090
+ throw new CliError(`Cannot re-resolve grant: ${msg}`);
1091
+ }
1092
+ const token = await fetchGrantToken(idp, grant.id);
1093
+ await verifyAndExecute(token, resolved);
1094
+ return;
1095
+ }
1096
+ if (audience === "escapes") {
1097
+ const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, { method: "POST" });
1098
+ const command = grant.request?.command ?? [];
1099
+ if (command.length === 0)
1100
+ throw new CliError(`Grant ${grant.id} has no command to execute.`);
1101
+ consola12.info(`Executing via escapes: ${command.join(" ")}`);
1102
+ try {
1103
+ execFileSync(args["escapes-path"], ["--grant", authz_jwt, "--", ...command], { stdio: "inherit" });
1104
+ } catch (err) {
1105
+ const exitCode = err.status || 1;
1106
+ throw new CliExit(exitCode);
1107
+ }
1108
+ return;
1109
+ }
1110
+ if (audience === "ape-shell") {
1111
+ throw new CliError(
1112
+ `Grant ${grant.id} is an ape-shell session grant and cannot be re-executed via \`apes grants run\`. Re-run the original command \u2014 if the grant was approved as timed/always, the REPL will reuse it automatically.`
1113
+ );
1114
+ }
1115
+ throw new CliError(`Grant ${grant.id} has unsupported audience "${audience}" \u2014 no execution path available.`);
1116
+ }
1117
+ });
1118
+
1119
+ // src/commands/grants/token.ts
1120
+ import { defineCommand as defineCommand13 } from "citty";
1121
+ var tokenCommand = defineCommand13({
1042
1122
  meta: {
1043
1123
  name: "token",
1044
1124
  description: "Get grant token JWT"
@@ -1064,9 +1144,9 @@ var tokenCommand = defineCommand12({
1064
1144
  });
1065
1145
 
1066
1146
  // src/commands/grants/delegate.ts
1067
- import { defineCommand as defineCommand13 } from "citty";
1068
- import consola12 from "consola";
1069
- var delegateCommand = defineCommand13({
1147
+ import { defineCommand as defineCommand14 } from "citty";
1148
+ import consola13 from "consola";
1149
+ var delegateCommand = defineCommand14({
1070
1150
  meta: {
1071
1151
  name: "delegate",
1072
1152
  description: "Create a delegation"
@@ -1118,7 +1198,7 @@ var delegateCommand = defineCommand13({
1118
1198
  method: "POST",
1119
1199
  body
1120
1200
  });
1121
- consola12.success(`Delegation created: ${result.id}`);
1201
+ consola13.success(`Delegation created: ${result.id}`);
1122
1202
  console.log(` Delegate: ${args.to}`);
1123
1203
  console.log(` Audience: ${args.at}`);
1124
1204
  if (args.scopes)
@@ -1130,9 +1210,9 @@ var delegateCommand = defineCommand13({
1130
1210
  });
1131
1211
 
1132
1212
  // src/commands/grants/delegations.ts
1133
- import { defineCommand as defineCommand14 } from "citty";
1134
- import consola13 from "consola";
1135
- var delegationsCommand = defineCommand14({
1213
+ import { defineCommand as defineCommand15 } from "citty";
1214
+ import consola14 from "consola";
1215
+ var delegationsCommand = defineCommand15({
1136
1216
  meta: {
1137
1217
  name: "delegations",
1138
1218
  description: "List delegations"
@@ -1154,7 +1234,7 @@ var delegationsCommand = defineCommand14({
1154
1234
  return;
1155
1235
  }
1156
1236
  if (delegations.length === 0) {
1157
- consola13.info("No delegations found.");
1237
+ consola14.info("No delegations found.");
1158
1238
  return;
1159
1239
  }
1160
1240
  for (const d of delegations) {
@@ -1166,9 +1246,9 @@ var delegationsCommand = defineCommand14({
1166
1246
  });
1167
1247
 
1168
1248
  // src/commands/grants/delegation-revoke.ts
1169
- import { defineCommand as defineCommand15 } from "citty";
1170
- import consola14 from "consola";
1171
- var delegationRevokeCommand = defineCommand15({
1249
+ import { defineCommand as defineCommand16 } from "citty";
1250
+ import consola15 from "consola";
1251
+ var delegationRevokeCommand = defineCommand16({
1172
1252
  meta: {
1173
1253
  name: "delegation-revoke",
1174
1254
  description: "Revoke a delegation"
@@ -1191,16 +1271,16 @@ var delegationRevokeCommand = defineCommand15({
1191
1271
  `${delegationsUrl}/${id}`,
1192
1272
  { method: "DELETE" }
1193
1273
  );
1194
- consola14.success(`Delegation ${result.id} revoked.`);
1274
+ consola15.success(`Delegation ${result.id} revoked.`);
1195
1275
  }
1196
1276
  });
1197
1277
 
1198
1278
  // src/commands/admin/index.ts
1199
- import { defineCommand as defineCommand18 } from "citty";
1279
+ import { defineCommand as defineCommand19 } from "citty";
1200
1280
 
1201
1281
  // src/commands/admin/users.ts
1202
- import { defineCommand as defineCommand16 } from "citty";
1203
- import consola15 from "consola";
1282
+ import { defineCommand as defineCommand17 } from "citty";
1283
+ import consola16 from "consola";
1204
1284
  function getManagementToken() {
1205
1285
  const token = process.env.APES_MANAGEMENT_TOKEN;
1206
1286
  if (!token) {
@@ -1208,7 +1288,7 @@ function getManagementToken() {
1208
1288
  }
1209
1289
  return token;
1210
1290
  }
1211
- var usersListCommand = defineCommand16({
1291
+ var usersListCommand = defineCommand17({
1212
1292
  meta: {
1213
1293
  name: "list",
1214
1294
  description: "List all users"
@@ -1250,7 +1330,7 @@ var usersListCommand = defineCommand16({
1250
1330
  return;
1251
1331
  }
1252
1332
  if (result.data.length === 0) {
1253
- consola15.info("No users found.");
1333
+ consola16.info("No users found.");
1254
1334
  return;
1255
1335
  }
1256
1336
  for (const u of result.data) {
@@ -1259,11 +1339,11 @@ var usersListCommand = defineCommand16({
1259
1339
  console.log(`${u.email} ${u.name}${owner}${active}`);
1260
1340
  }
1261
1341
  if (result.pagination.has_more) {
1262
- consola15.info(`More results available. Use --cursor="${result.pagination.cursor}" to see next page.`);
1342
+ consola16.info(`More results available. Use --cursor="${result.pagination.cursor}" to see next page.`);
1263
1343
  }
1264
1344
  }
1265
1345
  });
1266
- var usersCreateCommand = defineCommand16({
1346
+ var usersCreateCommand = defineCommand17({
1267
1347
  meta: {
1268
1348
  name: "create",
1269
1349
  description: "Create a user"
@@ -1294,10 +1374,10 @@ var usersCreateCommand = defineCommand16({
1294
1374
  token
1295
1375
  }
1296
1376
  );
1297
- consola15.success(`User created: ${result.email} (${result.name})`);
1377
+ consola16.success(`User created: ${result.email} (${result.name})`);
1298
1378
  }
1299
1379
  });
1300
- var usersDeleteCommand = defineCommand16({
1380
+ var usersDeleteCommand = defineCommand17({
1301
1381
  meta: {
1302
1382
  name: "delete",
1303
1383
  description: "Delete a user"
@@ -1320,7 +1400,7 @@ var usersDeleteCommand = defineCommand16({
1320
1400
  method: "DELETE",
1321
1401
  token
1322
1402
  });
1323
- consola15.success(`User deleted: ${email}`);
1403
+ consola16.success(`User deleted: ${email}`);
1324
1404
  }
1325
1405
  });
1326
1406
 
@@ -1328,8 +1408,8 @@ var usersDeleteCommand = defineCommand16({
1328
1408
  import { existsSync as existsSync2, readFileSync } from "fs";
1329
1409
  import { resolve } from "path";
1330
1410
  import { homedir as homedir3 } from "os";
1331
- import { defineCommand as defineCommand17 } from "citty";
1332
- import consola16 from "consola";
1411
+ import { defineCommand as defineCommand18 } from "citty";
1412
+ import consola17 from "consola";
1333
1413
  function getManagementToken2() {
1334
1414
  const token = process.env.APES_MANAGEMENT_TOKEN;
1335
1415
  if (!token) {
@@ -1337,7 +1417,7 @@ function getManagementToken2() {
1337
1417
  }
1338
1418
  return token;
1339
1419
  }
1340
- var sshKeysListCommand = defineCommand17({
1420
+ var sshKeysListCommand = defineCommand18({
1341
1421
  meta: {
1342
1422
  name: "list",
1343
1423
  description: "List SSH keys for a user"
@@ -1370,7 +1450,7 @@ var sshKeysListCommand = defineCommand17({
1370
1450
  return;
1371
1451
  }
1372
1452
  if (keys.length === 0) {
1373
- consola16.info(`No SSH keys found for ${email}.`);
1453
+ consola17.info(`No SSH keys found for ${email}.`);
1374
1454
  return;
1375
1455
  }
1376
1456
  for (const k of keys) {
@@ -1378,7 +1458,7 @@ var sshKeysListCommand = defineCommand17({
1378
1458
  }
1379
1459
  }
1380
1460
  });
1381
- var sshKeysAddCommand = defineCommand17({
1461
+ var sshKeysAddCommand = defineCommand18({
1382
1462
  meta: {
1383
1463
  name: "add",
1384
1464
  description: "Add an SSH key for a user"
@@ -1422,10 +1502,10 @@ var sshKeysAddCommand = defineCommand17({
1422
1502
  token
1423
1503
  }
1424
1504
  );
1425
- consola16.success(`SSH key added: ${result.keyId} (${result.name})`);
1505
+ consola17.success(`SSH key added: ${result.keyId} (${result.name})`);
1426
1506
  }
1427
1507
  });
1428
- var sshKeysDeleteCommand = defineCommand17({
1508
+ var sshKeysDeleteCommand = defineCommand18({
1429
1509
  meta: {
1430
1510
  name: "delete",
1431
1511
  description: "Delete an SSH key"
@@ -1456,12 +1536,12 @@ var sshKeysDeleteCommand = defineCommand17({
1456
1536
  token
1457
1537
  }
1458
1538
  );
1459
- consola16.success(`SSH key deleted: ${keyId}`);
1539
+ consola17.success(`SSH key deleted: ${keyId}`);
1460
1540
  }
1461
1541
  });
1462
1542
 
1463
1543
  // src/commands/admin/index.ts
1464
- var usersCommand = defineCommand18({
1544
+ var usersCommand = defineCommand19({
1465
1545
  meta: {
1466
1546
  name: "users",
1467
1547
  description: "Manage users"
@@ -1472,7 +1552,7 @@ var usersCommand = defineCommand18({
1472
1552
  delete: usersDeleteCommand
1473
1553
  }
1474
1554
  });
1475
- var sshKeysCommand = defineCommand18({
1555
+ var sshKeysCommand = defineCommand19({
1476
1556
  meta: {
1477
1557
  name: "ssh-keys",
1478
1558
  description: "Manage SSH keys"
@@ -1483,7 +1563,7 @@ var sshKeysCommand = defineCommand18({
1483
1563
  delete: sshKeysDeleteCommand
1484
1564
  }
1485
1565
  });
1486
- var adminCommand = defineCommand18({
1566
+ var adminCommand = defineCommand19({
1487
1567
  meta: {
1488
1568
  name: "admin",
1489
1569
  description: "Admin commands (requires APES_MANAGEMENT_TOKEN)"
@@ -1495,15 +1575,15 @@ var adminCommand = defineCommand18({
1495
1575
  });
1496
1576
 
1497
1577
  // src/commands/adapter/index.ts
1498
- import { defineCommand as defineCommand19 } from "citty";
1499
- import consola17 from "consola";
1500
- var adapterCommand = defineCommand19({
1578
+ import { defineCommand as defineCommand20 } from "citty";
1579
+ import consola18 from "consola";
1580
+ var adapterCommand = defineCommand20({
1501
1581
  meta: {
1502
1582
  name: "adapter",
1503
1583
  description: "Manage CLI adapters"
1504
1584
  },
1505
1585
  subCommands: {
1506
- list: defineCommand19({
1586
+ list: defineCommand20({
1507
1587
  meta: {
1508
1588
  name: "list",
1509
1589
  description: "List available adapters"
@@ -1534,7 +1614,7 @@ var adapterCommand = defineCommand19({
1534
1614
  `);
1535
1615
  return;
1536
1616
  }
1537
- consola17.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1617
+ consola18.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1538
1618
  for (const a of index2.adapters) {
1539
1619
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
1540
1620
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -1556,7 +1636,7 @@ var adapterCommand = defineCommand19({
1556
1636
  return;
1557
1637
  }
1558
1638
  if (local.length === 0) {
1559
- consola17.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1639
+ consola18.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1560
1640
  return;
1561
1641
  }
1562
1642
  for (const a of local) {
@@ -1564,7 +1644,7 @@ var adapterCommand = defineCommand19({
1564
1644
  }
1565
1645
  }
1566
1646
  }),
1567
- install: defineCommand19({
1647
+ install: defineCommand20({
1568
1648
  meta: {
1569
1649
  name: "install",
1570
1650
  description: "Install an adapter from the registry"
@@ -1593,24 +1673,24 @@ var adapterCommand = defineCommand19({
1593
1673
  for (const id of ids) {
1594
1674
  const entry = findAdapter(index, id);
1595
1675
  if (!entry) {
1596
- consola17.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1676
+ consola18.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1597
1677
  continue;
1598
1678
  }
1599
1679
  const conflicts = findConflictingAdapters(entry.executable, id);
1600
1680
  if (conflicts.length > 0) {
1601
1681
  for (const c of conflicts) {
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}`);
1682
+ consola18.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1683
+ consola18.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1604
1684
  }
1605
1685
  }
1606
1686
  const result = await installAdapter(entry, { local });
1607
1687
  const verb = result.updated ? "Updated" : "Installed";
1608
- consola17.success(`${verb} ${result.id} \u2192 ${result.path}`);
1609
- consola17.info(`Digest: ${result.digest}`);
1688
+ consola18.success(`${verb} ${result.id} \u2192 ${result.path}`);
1689
+ consola18.info(`Digest: ${result.digest}`);
1610
1690
  }
1611
1691
  }
1612
1692
  }),
1613
- remove: defineCommand19({
1693
+ remove: defineCommand20({
1614
1694
  meta: {
1615
1695
  name: "remove",
1616
1696
  description: "Remove an installed adapter"
@@ -1633,9 +1713,9 @@ var adapterCommand = defineCommand19({
1633
1713
  let failed = false;
1634
1714
  for (const id of ids) {
1635
1715
  if (removeAdapter(id, local)) {
1636
- consola17.success(`Removed adapter: ${id}`);
1716
+ consola18.success(`Removed adapter: ${id}`);
1637
1717
  } else {
1638
- consola17.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1718
+ consola18.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1639
1719
  failed = true;
1640
1720
  }
1641
1721
  }
@@ -1643,7 +1723,7 @@ var adapterCommand = defineCommand19({
1643
1723
  throw new CliError("Some adapters could not be removed");
1644
1724
  }
1645
1725
  }),
1646
- info: defineCommand19({
1726
+ info: defineCommand20({
1647
1727
  meta: {
1648
1728
  name: "info",
1649
1729
  description: "Show detailed adapter information"
@@ -1685,7 +1765,7 @@ var adapterCommand = defineCommand19({
1685
1765
  }
1686
1766
  }
1687
1767
  }),
1688
- search: defineCommand19({
1768
+ search: defineCommand20({
1689
1769
  meta: {
1690
1770
  name: "search",
1691
1771
  description: "Search adapters in the registry"
@@ -1717,7 +1797,7 @@ var adapterCommand = defineCommand19({
1717
1797
  return;
1718
1798
  }
1719
1799
  if (results.length === 0) {
1720
- consola17.info(`No adapters matching "${query}"`);
1800
+ consola18.info(`No adapters matching "${query}"`);
1721
1801
  return;
1722
1802
  }
1723
1803
  for (const a of results) {
@@ -1726,7 +1806,7 @@ var adapterCommand = defineCommand19({
1726
1806
  }
1727
1807
  }
1728
1808
  }),
1729
- update: defineCommand19({
1809
+ update: defineCommand20({
1730
1810
  meta: {
1731
1811
  name: "update",
1732
1812
  description: "Update installed adapters"
@@ -1752,33 +1832,33 @@ var adapterCommand = defineCommand19({
1752
1832
  const targetId = args.id ? String(args.id) : void 0;
1753
1833
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
1754
1834
  if (targets.length === 0) {
1755
- consola17.info("No adapters installed to update.");
1835
+ consola18.info("No adapters installed to update.");
1756
1836
  return;
1757
1837
  }
1758
1838
  for (const id of targets) {
1759
1839
  const entry = findAdapter(index, id);
1760
1840
  if (!entry) {
1761
- consola17.warn(`${id}: not found in registry, skipping`);
1841
+ consola18.warn(`${id}: not found in registry, skipping`);
1762
1842
  continue;
1763
1843
  }
1764
1844
  const localDigest = getInstalledDigest(id, false);
1765
1845
  if (localDigest === entry.digest) {
1766
- consola17.info(`${id}: already up to date`);
1846
+ consola18.info(`${id}: already up to date`);
1767
1847
  continue;
1768
1848
  }
1769
1849
  if (localDigest && !args.yes) {
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");
1850
+ consola18.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1851
+ consola18.info(` Old: ${localDigest}`);
1852
+ consola18.info(` New: ${entry.digest}`);
1853
+ consola18.info(" Use --yes to confirm");
1774
1854
  continue;
1775
1855
  }
1776
1856
  const result = await installAdapter(entry);
1777
- consola17.success(`Updated ${result.id} \u2192 ${result.path}`);
1857
+ consola18.success(`Updated ${result.id} \u2192 ${result.path}`);
1778
1858
  }
1779
1859
  }
1780
1860
  }),
1781
- verify: defineCommand19({
1861
+ verify: defineCommand20({
1782
1862
  meta: {
1783
1863
  name: "verify",
1784
1864
  description: "Verify installed adapter against registry digest"
@@ -1811,7 +1891,7 @@ var adapterCommand = defineCommand19({
1811
1891
  if (!localDigest)
1812
1892
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1813
1893
  if (localDigest === entry.digest) {
1814
- consola17.success(`${id}: digest matches registry`);
1894
+ consola18.success(`${id}: digest matches registry`);
1815
1895
  } else {
1816
1896
  console.log(` Local: ${localDigest}`);
1817
1897
  console.log(` Registry: ${entry.digest}`);
@@ -1823,12 +1903,24 @@ var adapterCommand = defineCommand19({
1823
1903
  });
1824
1904
 
1825
1905
  // src/commands/run.ts
1826
- import { execFileSync } from "child_process";
1906
+ import { execFileSync as execFileSync2 } from "child_process";
1827
1907
  import { hostname as hostname3 } from "os";
1828
1908
  import { basename } from "path";
1829
- import { defineCommand as defineCommand20 } from "citty";
1830
- import consola18 from "consola";
1831
- var runCommand = defineCommand20({
1909
+ import { defineCommand as defineCommand21 } from "citty";
1910
+ import consola19 from "consola";
1911
+ function shouldWaitForGrant(args) {
1912
+ return args.wait === true || process.env.APE_WAIT === "1";
1913
+ }
1914
+ function printPendingGrantInfo(grant, idp) {
1915
+ consola19.success(`Grant ${grant.id} erstellt`);
1916
+ console.log(` Approve: ${idp}/grant-approval?grant_id=${grant.id}`);
1917
+ console.log(` Status: apes grants status ${grant.id}`);
1918
+ console.log(` Ausf\xFChren: apes grants run ${grant.id}`);
1919
+ console.log("");
1920
+ console.log(' Tipp: Im Browser "als timed/always approven" w\xE4hlen, um das');
1921
+ console.log(" Kommando ohne erneuten Approval wiederzuverwenden.");
1922
+ }
1923
+ var runCommand = defineCommand21({
1832
1924
  meta: {
1833
1925
  name: "run",
1834
1926
  description: "Execute a grant-secured command"
@@ -1869,6 +1961,11 @@ var runCommand = defineCommand20({
1869
1961
  description: "Shell mode: use session grant with audience ape-shell",
1870
1962
  default: false
1871
1963
  },
1964
+ "wait": {
1965
+ type: "boolean",
1966
+ description: "Block until grant is approved (default: async, print grant info and exit 0). Equivalent to APE_WAIT=1.",
1967
+ default: false
1968
+ },
1872
1969
  "_": {
1873
1970
  type: "positional",
1874
1971
  description: "Command to execute (after --)",
@@ -1915,7 +2012,7 @@ async function runShellMode(command, args) {
1915
2012
  }
1916
2013
  } catch {
1917
2014
  }
1918
- consola18.info(`Requesting ape-shell session grant on ${targetHost}`);
2015
+ consola19.info(`Requesting ape-shell session grant on ${targetHost}`);
1919
2016
  const grant = await apiFetch(grantsUrl, {
1920
2017
  method: "POST",
1921
2018
  body: {
@@ -1927,20 +2024,31 @@ async function runShellMode(command, args) {
1927
2024
  reason: `Shell session: ${command.join(" ").slice(0, 100)}`
1928
2025
  }
1929
2026
  });
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));
2027
+ notifyGrantPending({
2028
+ grantId: grant.id,
2029
+ approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,
2030
+ command: command.join(" ").slice(0, 200),
2031
+ audience: "ape-shell",
2032
+ host: targetHost
2033
+ });
2034
+ if (shouldWaitForGrant(args)) {
2035
+ consola19.info(`Grant requested: ${grant.id}`);
2036
+ consola19.info("Waiting for approval...");
2037
+ const maxWait = 3e5;
2038
+ const interval = 3e3;
2039
+ const start = Date.now();
2040
+ while (Date.now() - start < maxWait) {
2041
+ const status = await apiFetch(`${grantsUrl}/${grant.id}`);
2042
+ if (status.status === "approved")
2043
+ break;
2044
+ if (status.status === "denied" || status.status === "revoked")
2045
+ throw new CliError(`Grant ${status.status}.`);
2046
+ await new Promise((r) => setTimeout(r, interval));
2047
+ }
2048
+ execShellCommand(command);
2049
+ return;
1942
2050
  }
1943
- execShellCommand(command);
2051
+ printPendingGrantInfo(grant, idp);
1944
2052
  }
1945
2053
  async function tryAdapterModeFromShell(command, idp, args) {
1946
2054
  const cmdString = extractShellCommandString(command);
@@ -1955,45 +2063,56 @@ async function tryAdapterModeFromShell(command, idp, args) {
1955
2063
  try {
1956
2064
  resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
1957
2065
  } catch (err) {
1958
- consola18.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
2066
+ consola19.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
1959
2067
  return false;
1960
2068
  }
1961
2069
  try {
1962
2070
  const existingGrantId = await findExistingGrant(resolved, idp);
1963
2071
  if (existingGrantId) {
1964
- consola18.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
1965
- const token2 = await fetchGrantToken(idp, existingGrantId);
1966
- await verifyAndExecute(token2, resolved);
2072
+ consola19.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
2073
+ const token = await fetchGrantToken(idp, existingGrantId);
2074
+ await verifyAndExecute(token, resolved);
1967
2075
  return true;
1968
2076
  }
1969
2077
  } catch {
1970
2078
  }
1971
2079
  const approval = args.approval ?? "once";
1972
- consola18.info(`Requesting grant for: ${resolved.detail.display}`);
2080
+ consola19.info(`Requesting grant for: ${resolved.detail.display}`);
1973
2081
  const grant = await createShapesGrant(resolved, {
1974
2082
  idp,
1975
2083
  approval,
1976
2084
  reason: args.reason || `ape-shell: ${resolved.detail.display}`
1977
2085
  });
1978
- consola18.info(`Grant requested: ${grant.id}`);
1979
- consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1980
2086
  if (grant.similar_grants?.similar_grants?.length) {
1981
2087
  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);
2088
+ consola19.info("");
2089
+ consola19.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
2090
+ }
2091
+ notifyGrantPending({
2092
+ grantId: grant.id,
2093
+ approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,
2094
+ command: resolved.detail?.display || parsed?.raw || "unknown",
2095
+ audience: resolved.adapter?.cli?.audience ?? "shapes",
2096
+ host: args.host || hostname3()
2097
+ });
2098
+ if (shouldWaitForGrant(args)) {
2099
+ consola19.info(`Grant requested: ${grant.id}`);
2100
+ consola19.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
2101
+ const status = await waitForGrantStatus(idp, grant.id);
2102
+ if (status !== "approved")
2103
+ throw new CliError(`Grant ${status}`);
2104
+ const token = await fetchGrantToken(idp, grant.id);
2105
+ await verifyAndExecute(token, resolved);
2106
+ return true;
2107
+ }
2108
+ printPendingGrantInfo(grant, idp);
1990
2109
  return true;
1991
2110
  }
1992
2111
  function execShellCommand(command) {
1993
2112
  if (command.length === 0)
1994
2113
  throw new CliError("No command to execute");
1995
2114
  try {
1996
- execFileSync(command[0], command.slice(1), { stdio: "inherit" });
2115
+ execFileSync2(command[0], command.slice(1), { stdio: "inherit" });
1997
2116
  } catch (err) {
1998
2117
  const exitCode = err.status || 1;
1999
2118
  throw new CliExit(exitCode);
@@ -2030,9 +2149,9 @@ async function runAdapterMode(command, rawArgs, args) {
2030
2149
  try {
2031
2150
  const existingGrantId = await findExistingGrant(resolved, idp);
2032
2151
  if (existingGrantId) {
2033
- consola18.info(`Reusing existing grant: ${existingGrantId}`);
2034
- const token2 = await fetchGrantToken(idp, existingGrantId);
2035
- await verifyAndExecute(token2, resolved);
2152
+ consola19.info(`Reusing existing grant: ${existingGrantId}`);
2153
+ const token = await fetchGrantToken(idp, existingGrantId);
2154
+ await verifyAndExecute(token, resolved);
2036
2155
  return;
2037
2156
  }
2038
2157
  } catch {
@@ -2042,23 +2161,27 @@ async function runAdapterMode(command, rawArgs, args) {
2042
2161
  approval,
2043
2162
  ...args.reason ? { reason: args.reason } : {}
2044
2163
  });
2045
- consola18.info(`Grant requested: ${grant.id}`);
2046
- consola18.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
2047
2164
  if (grant.similar_grants?.similar_grants?.length) {
2048
2165
  const n = grant.similar_grants.similar_grants.length;
2049
- consola18.info("");
2050
- consola18.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
2166
+ consola19.info("");
2167
+ consola19.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
2051
2168
  if (grant.similar_grants.widened_details?.length) {
2052
2169
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
2053
- consola18.info(` Broader scope: ${wider}`);
2054
- }
2055
- consola18.info("");
2170
+ consola19.info(` Broader scope: ${wider}`);
2171
+ }
2172
+ consola19.info("");
2173
+ }
2174
+ if (shouldWaitForGrant(args)) {
2175
+ consola19.info(`Grant requested: ${grant.id}`);
2176
+ consola19.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
2177
+ const status = await waitForGrantStatus(idp, grant.id);
2178
+ if (status !== "approved")
2179
+ throw new Error(`Grant ${status}`);
2180
+ const token = await fetchGrantToken(idp, grant.id);
2181
+ await verifyAndExecute(token, resolved);
2182
+ return;
2056
2183
  }
2057
- const status = await waitForGrantStatus(idp, grant.id);
2058
- if (status !== "approved")
2059
- throw new Error(`Grant ${status}`);
2060
- const token = await fetchGrantToken(idp, grant.id);
2061
- await verifyAndExecute(token, resolved);
2184
+ printPendingGrantInfo(grant, idp);
2062
2185
  }
2063
2186
  async function runAudienceMode(audience, action, args) {
2064
2187
  const auth = loadAuth();
@@ -2069,7 +2192,7 @@ async function runAudienceMode(audience, action, args) {
2069
2192
  const grantsUrl = await getGrantsEndpoint(idp);
2070
2193
  const command = action.split(" ");
2071
2194
  const targetHost = args.host || hostname3();
2072
- consola18.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
2195
+ consola19.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
2073
2196
  const grant = await apiFetch(grantsUrl, {
2074
2197
  method: "POST",
2075
2198
  body: {
@@ -2082,15 +2205,19 @@ async function runAudienceMode(audience, action, args) {
2082
2205
  ...args.as ? { run_as: args.as } : {}
2083
2206
  }
2084
2207
  });
2085
- consola18.success(`Grant requested: ${grant.id}`);
2086
- consola18.info("Waiting for approval...");
2208
+ if (!shouldWaitForGrant(args)) {
2209
+ printPendingGrantInfo(grant, idp);
2210
+ return;
2211
+ }
2212
+ consola19.success(`Grant requested: ${grant.id}`);
2213
+ consola19.info("Waiting for approval...");
2087
2214
  const maxWait = 3e5;
2088
2215
  const interval = 3e3;
2089
2216
  const start = Date.now();
2090
2217
  while (Date.now() - start < maxWait) {
2091
2218
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
2092
2219
  if (status.status === "approved") {
2093
- consola18.success("Grant approved!");
2220
+ consola19.success("Grant approved!");
2094
2221
  break;
2095
2222
  }
2096
2223
  if (status.status === "denied" || status.status === "revoked") {
@@ -2098,14 +2225,14 @@ async function runAudienceMode(audience, action, args) {
2098
2225
  }
2099
2226
  await new Promise((r) => setTimeout(r, interval));
2100
2227
  }
2101
- consola18.info("Fetching grant token...");
2228
+ consola19.info("Fetching grant token...");
2102
2229
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
2103
2230
  method: "POST"
2104
2231
  });
2105
2232
  if (audience === "escapes") {
2106
- consola18.info(`Executing: ${command.join(" ")}`);
2233
+ consola19.info(`Executing: ${command.join(" ")}`);
2107
2234
  try {
2108
- execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
2235
+ execFileSync2(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
2109
2236
  stdio: "inherit"
2110
2237
  });
2111
2238
  } catch (err) {
@@ -2118,8 +2245,8 @@ async function runAudienceMode(audience, action, args) {
2118
2245
  }
2119
2246
 
2120
2247
  // src/commands/explain.ts
2121
- import { defineCommand as defineCommand21 } from "citty";
2122
- var explainCommand = defineCommand21({
2248
+ import { defineCommand as defineCommand22 } from "citty";
2249
+ var explainCommand = defineCommand22({
2123
2250
  meta: {
2124
2251
  name: "explain",
2125
2252
  description: "Show what permission a command would need"
@@ -2157,9 +2284,9 @@ var explainCommand = defineCommand21({
2157
2284
  });
2158
2285
 
2159
2286
  // src/commands/config/get.ts
2160
- import { defineCommand as defineCommand22 } from "citty";
2161
- import consola19 from "consola";
2162
- var configGetCommand = defineCommand22({
2287
+ import { defineCommand as defineCommand23 } from "citty";
2288
+ import consola20 from "consola";
2289
+ var configGetCommand = defineCommand23({
2163
2290
  meta: {
2164
2291
  name: "get",
2165
2292
  description: "Get a configuration value"
@@ -2179,7 +2306,7 @@ var configGetCommand = defineCommand22({
2179
2306
  if (idp)
2180
2307
  console.log(idp);
2181
2308
  else
2182
- consola19.info("No IdP configured.");
2309
+ consola20.info("No IdP configured.");
2183
2310
  break;
2184
2311
  }
2185
2312
  case "email": {
@@ -2187,7 +2314,7 @@ var configGetCommand = defineCommand22({
2187
2314
  if (auth?.email)
2188
2315
  console.log(auth.email);
2189
2316
  else
2190
- consola19.info("Not logged in.");
2317
+ consola20.info("Not logged in.");
2191
2318
  break;
2192
2319
  }
2193
2320
  default: {
@@ -2200,7 +2327,7 @@ var configGetCommand = defineCommand22({
2200
2327
  if (sectionObj && field in sectionObj) {
2201
2328
  console.log(sectionObj[field]);
2202
2329
  } else {
2203
- consola19.info(`Key "${key}" not set.`);
2330
+ consola20.info(`Key "${key}" not set.`);
2204
2331
  }
2205
2332
  } else {
2206
2333
  throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
@@ -2211,9 +2338,9 @@ var configGetCommand = defineCommand22({
2211
2338
  });
2212
2339
 
2213
2340
  // src/commands/config/set.ts
2214
- import { defineCommand as defineCommand23 } from "citty";
2215
- import consola20 from "consola";
2216
- var configSetCommand = defineCommand23({
2341
+ import { defineCommand as defineCommand24 } from "citty";
2342
+ import consola21 from "consola";
2343
+ var configSetCommand = defineCommand24({
2217
2344
  meta: {
2218
2345
  name: "set",
2219
2346
  description: "Set a configuration value"
@@ -2249,12 +2376,12 @@ var configSetCommand = defineCommand23({
2249
2376
  throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
2250
2377
  }
2251
2378
  saveConfig(config);
2252
- consola20.success(`Set ${key} = ${value}`);
2379
+ consola21.success(`Set ${key} = ${value}`);
2253
2380
  }
2254
2381
  });
2255
2382
 
2256
2383
  // src/commands/fetch/index.ts
2257
- import { defineCommand as defineCommand24 } from "citty";
2384
+ import { defineCommand as defineCommand25 } from "citty";
2258
2385
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
2259
2386
  const token = getAuthToken();
2260
2387
  if (!token) {
@@ -2290,13 +2417,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
2290
2417
  throw new CliError(`HTTP ${response.status} ${response.statusText}`);
2291
2418
  }
2292
2419
  }
2293
- var fetchCommand = defineCommand24({
2420
+ var fetchCommand = defineCommand25({
2294
2421
  meta: {
2295
2422
  name: "fetch",
2296
2423
  description: "Make authenticated HTTP requests"
2297
2424
  },
2298
2425
  subCommands: {
2299
- get: defineCommand24({
2426
+ get: defineCommand25({
2300
2427
  meta: {
2301
2428
  name: "get",
2302
2429
  description: "GET request with auth token"
@@ -2322,7 +2449,7 @@ var fetchCommand = defineCommand24({
2322
2449
  await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
2323
2450
  }
2324
2451
  }),
2325
- post: defineCommand24({
2452
+ post: defineCommand25({
2326
2453
  meta: {
2327
2454
  name: "post",
2328
2455
  description: "POST request with auth token"
@@ -2361,8 +2488,8 @@ var fetchCommand = defineCommand24({
2361
2488
  });
2362
2489
 
2363
2490
  // src/commands/mcp/index.ts
2364
- import { defineCommand as defineCommand25 } from "citty";
2365
- var mcpCommand = defineCommand25({
2491
+ import { defineCommand as defineCommand26 } from "citty";
2492
+ var mcpCommand = defineCommand26({
2366
2493
  meta: {
2367
2494
  name: "mcp",
2368
2495
  description: "Start MCP server for AI agents"
@@ -2385,7 +2512,7 @@ var mcpCommand = defineCommand25({
2385
2512
  if (transport !== "stdio" && transport !== "sse") {
2386
2513
  throw new Error('Transport must be "stdio" or "sse"');
2387
2514
  }
2388
- const { startMcpServer } = await import("./server-GPETT3ON.js");
2515
+ const { startMcpServer } = await import("./server-FFOPFICW.js");
2389
2516
  await startMcpServer(transport, port);
2390
2517
  }
2391
2518
  });
@@ -2393,10 +2520,10 @@ var mcpCommand = defineCommand25({
2393
2520
  // src/commands/init/index.ts
2394
2521
  import { existsSync as existsSync3, copyFileSync, writeFileSync } from "fs";
2395
2522
  import { randomBytes } from "crypto";
2396
- import { execFileSync as execFileSync2 } from "child_process";
2523
+ import { execFileSync as execFileSync3 } from "child_process";
2397
2524
  import { join as join2 } from "path";
2398
- import { defineCommand as defineCommand26 } from "citty";
2399
- import consola21 from "consola";
2525
+ import { defineCommand as defineCommand27 } from "citty";
2526
+ import consola22 from "consola";
2400
2527
  var DEFAULT_IDP_URL = "https://id.openape.at";
2401
2528
  async function downloadTemplate(repo, targetDir) {
2402
2529
  const { downloadTemplate: gigetDownload } = await import("giget");
@@ -2405,28 +2532,28 @@ async function downloadTemplate(repo, targetDir) {
2405
2532
  function installDeps(dir) {
2406
2533
  const hasLockFile = (name) => existsSync3(join2(dir, name));
2407
2534
  if (hasLockFile("pnpm-lock.yaml")) {
2408
- execFileSync2("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
2535
+ execFileSync3("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
2409
2536
  } else if (hasLockFile("bun.lockb")) {
2410
- execFileSync2("bun", ["install"], { cwd: dir, stdio: "inherit" });
2537
+ execFileSync3("bun", ["install"], { cwd: dir, stdio: "inherit" });
2411
2538
  } else {
2412
- execFileSync2("npm", ["install"], { cwd: dir, stdio: "inherit" });
2539
+ execFileSync3("npm", ["install"], { cwd: dir, stdio: "inherit" });
2413
2540
  }
2414
2541
  }
2415
2542
  async function promptChoice(message, choices) {
2416
- const result = await consola21.prompt(message, { type: "select", options: choices });
2543
+ const result = await consola22.prompt(message, { type: "select", options: choices });
2417
2544
  if (typeof result === "symbol") {
2418
2545
  throw new CliExit(0);
2419
2546
  }
2420
2547
  return result;
2421
2548
  }
2422
2549
  async function promptText(message, defaultValue) {
2423
- const result = await consola21.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
2550
+ const result = await consola22.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
2424
2551
  if (typeof result === "symbol") {
2425
2552
  throw new CliExit(0);
2426
2553
  }
2427
2554
  return result || defaultValue || "";
2428
2555
  }
2429
- var initCommand = defineCommand26({
2556
+ var initCommand = defineCommand27({
2430
2557
  meta: {
2431
2558
  name: "init",
2432
2559
  description: "Scaffold a new OpenApe project"
@@ -2471,20 +2598,20 @@ async function initSP(targetDir) {
2471
2598
  if (existsSync3(join2(dir, "package.json"))) {
2472
2599
  throw new CliError(`Directory "${dir}" already contains a project.`);
2473
2600
  }
2474
- consola21.start("Scaffolding SP starter...");
2601
+ consola22.start("Scaffolding SP starter...");
2475
2602
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
2476
- consola21.success("Scaffolded from openape-sp-starter");
2477
- consola21.start("Installing dependencies...");
2603
+ consola22.success("Scaffolded from openape-sp-starter");
2604
+ consola22.start("Installing dependencies...");
2478
2605
  installDeps(dir);
2479
- consola21.success("Dependencies installed");
2606
+ consola22.success("Dependencies installed");
2480
2607
  const envExample = join2(dir, ".env.example");
2481
2608
  const envFile = join2(dir, ".env");
2482
2609
  if (existsSync3(envExample) && !existsSync3(envFile)) {
2483
2610
  copyFileSync(envExample, envFile);
2484
- consola21.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
2611
+ consola22.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
2485
2612
  }
2486
2613
  console.log("");
2487
- consola21.box([
2614
+ consola22.box([
2488
2615
  `cd ${dir}`,
2489
2616
  "npm run dev",
2490
2617
  "",
@@ -2503,15 +2630,15 @@ async function initIdP(targetDir) {
2503
2630
  "s3 (S3-compatible)"
2504
2631
  ]);
2505
2632
  const adminEmail = await promptText("Admin email");
2506
- consola21.start("Scaffolding IdP starter...");
2633
+ consola22.start("Scaffolding IdP starter...");
2507
2634
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
2508
- consola21.success("Scaffolded from openape-idp-starter");
2509
- consola21.start("Installing dependencies...");
2635
+ consola22.success("Scaffolded from openape-idp-starter");
2636
+ consola22.start("Installing dependencies...");
2510
2637
  installDeps(dir);
2511
- consola21.success("Dependencies installed");
2638
+ consola22.success("Dependencies installed");
2512
2639
  const sessionSecret = randomBytes(32).toString("hex");
2513
2640
  const managementToken = randomBytes(32).toString("hex");
2514
- consola21.success("Secrets generated");
2641
+ consola22.success("Secrets generated");
2515
2642
  const isLocalhost = domain === "localhost";
2516
2643
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
2517
2644
  const envContent = [
@@ -2527,9 +2654,9 @@ async function initIdP(targetDir) {
2527
2654
  ].join("\n");
2528
2655
  writeFileSync(join2(dir, ".env"), `${envContent}
2529
2656
  `, { mode: 384 });
2530
- consola21.success(".env created");
2657
+ consola22.success(".env created");
2531
2658
  console.log("");
2532
- consola21.box([
2659
+ consola22.box([
2533
2660
  `cd ${dir}`,
2534
2661
  "npm run dev",
2535
2662
  "",
@@ -2551,8 +2678,8 @@ import { execFile as execFile2 } from "child_process";
2551
2678
  import { generateKeyPairSync, sign } from "crypto";
2552
2679
  import { dirname, resolve as resolve2 } from "path";
2553
2680
  import { homedir as homedir4 } from "os";
2554
- import { defineCommand as defineCommand27 } from "citty";
2555
- import consola22 from "consola";
2681
+ import { defineCommand as defineCommand28 } from "citty";
2682
+ import consola23 from "consola";
2556
2683
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
2557
2684
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
2558
2685
  var POLL_INTERVAL = 3e3;
@@ -2637,7 +2764,7 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
2637
2764
  }
2638
2765
  throw new Error("Enrollment timed out. Please check the browser and try again.");
2639
2766
  }
2640
- var enrollCommand = defineCommand27({
2767
+ var enrollCommand = defineCommand28({
2641
2768
  meta: {
2642
2769
  name: "enroll",
2643
2770
  description: "Enroll an agent with an Identity Provider"
@@ -2657,18 +2784,18 @@ var enrollCommand = defineCommand27({
2657
2784
  }
2658
2785
  },
2659
2786
  async run({ args }) {
2660
- const idp = args.idp || await consola22.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
2787
+ const idp = args.idp || await consola23.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
2661
2788
  if (typeof r === "symbol") throw new CliExit(0);
2662
2789
  return r;
2663
2790
  }) || DEFAULT_IDP_URL2;
2664
- const agentName = args.name || await consola22.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
2791
+ const agentName = args.name || await consola23.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
2665
2792
  if (typeof r === "symbol") throw new CliExit(0);
2666
2793
  return r;
2667
2794
  });
2668
2795
  if (!agentName) {
2669
2796
  throw new CliError("Agent name is required.");
2670
2797
  }
2671
- const keyPath = args.key || await consola22.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
2798
+ const keyPath = args.key || await consola23.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
2672
2799
  if (typeof r === "symbol") throw new CliExit(0);
2673
2800
  return r;
2674
2801
  }) || DEFAULT_KEY_PATH;
@@ -2676,19 +2803,19 @@ var enrollCommand = defineCommand27({
2676
2803
  let publicKey;
2677
2804
  if (existsSync4(resolvedKey)) {
2678
2805
  publicKey = readPublicKey(resolvedKey);
2679
- consola22.success(`Using existing key ${keyPath}`);
2806
+ consola23.success(`Using existing key ${keyPath}`);
2680
2807
  } else {
2681
- consola22.start(`Generating Ed25519 key pair at ${keyPath}...`);
2808
+ consola23.start(`Generating Ed25519 key pair at ${keyPath}...`);
2682
2809
  publicKey = generateAndSaveKey(keyPath);
2683
- consola22.success(`Key pair generated at ${keyPath}`);
2810
+ consola23.success(`Key pair generated at ${keyPath}`);
2684
2811
  }
2685
2812
  const encodedKey = encodeURIComponent(publicKey);
2686
2813
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
2687
- consola22.info("Opening browser for enrollment...");
2688
- consola22.info(`\u2192 ${idp}/enroll`);
2814
+ consola23.info("Opening browser for enrollment...");
2815
+ consola23.info(`\u2192 ${idp}/enroll`);
2689
2816
  openBrowser2(enrollUrl);
2690
2817
  console.log("");
2691
- const agentEmail = await consola22.prompt(
2818
+ const agentEmail = await consola23.prompt(
2692
2819
  "Agent email (shown in browser after enrollment)",
2693
2820
  { type: "text", placeholder: `agent+${agentName}@...` }
2694
2821
  ).then((r) => {
@@ -2698,7 +2825,7 @@ var enrollCommand = defineCommand27({
2698
2825
  if (!agentEmail) {
2699
2826
  throw new CliError("Agent email is required to verify enrollment.");
2700
2827
  }
2701
- consola22.start("Verifying enrollment...");
2828
+ consola23.start("Verifying enrollment...");
2702
2829
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
2703
2830
  saveAuth({
2704
2831
  idp,
@@ -2710,18 +2837,18 @@ var enrollCommand = defineCommand27({
2710
2837
  config.defaults = { ...config.defaults, idp };
2711
2838
  config.agent = { key: keyPath, email: agentEmail };
2712
2839
  saveConfig(config);
2713
- consola22.success(`Agent enrolled as ${agentEmail}`);
2714
- consola22.success("Config saved to ~/.config/apes/");
2840
+ consola23.success(`Agent enrolled as ${agentEmail}`);
2841
+ consola23.success("Config saved to ~/.config/apes/");
2715
2842
  console.log("");
2716
- consola22.info("Verify with: apes whoami");
2843
+ consola23.info("Verify with: apes whoami");
2717
2844
  }
2718
2845
  });
2719
2846
 
2720
2847
  // src/commands/register-user.ts
2721
2848
  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({
2849
+ import { defineCommand as defineCommand29 } from "citty";
2850
+ import consola24 from "consola";
2851
+ var registerUserCommand = defineCommand29({
2725
2852
  meta: {
2726
2853
  name: "register-user",
2727
2854
  description: "Register a sub-user with SSH key"
@@ -2776,15 +2903,15 @@ var registerUserCommand = defineCommand28({
2776
2903
  ...userType ? { type: userType } : {}
2777
2904
  }
2778
2905
  });
2779
- consola23.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
2906
+ consola24.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
2780
2907
  }
2781
2908
  });
2782
2909
 
2783
2910
  // src/commands/dns-check.ts
2784
- import { defineCommand as defineCommand29 } from "citty";
2785
- import consola24 from "consola";
2911
+ import { defineCommand as defineCommand30 } from "citty";
2912
+ import consola25 from "consola";
2786
2913
  import { resolveDDISA as resolveDDISA2 } from "@openape/core";
2787
- var dnsCheckCommand = defineCommand29({
2914
+ var dnsCheckCommand = defineCommand30({
2788
2915
  meta: {
2789
2916
  name: "dns-check",
2790
2917
  description: "Validate DDISA DNS TXT records for a domain"
@@ -2798,7 +2925,7 @@ var dnsCheckCommand = defineCommand29({
2798
2925
  },
2799
2926
  async run({ args }) {
2800
2927
  const domain = args.domain;
2801
- consola24.start(`Checking _ddisa.${domain}...`);
2928
+ consola25.start(`Checking _ddisa.${domain}...`);
2802
2929
  try {
2803
2930
  const result = await resolveDDISA2(domain);
2804
2931
  if (!result) {
@@ -2807,7 +2934,7 @@ var dnsCheckCommand = defineCommand29({
2807
2934
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
2808
2935
  throw new CliError(`No DDISA record found for ${domain}`);
2809
2936
  }
2810
- consola24.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2937
+ consola25.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2811
2938
  console.log("");
2812
2939
  console.log(` Version: ${result.version || "ddisa1"}`);
2813
2940
  console.log(` IdP URL: ${result.idp}`);
@@ -2816,14 +2943,14 @@ var dnsCheckCommand = defineCommand29({
2816
2943
  if (result.priority !== void 0)
2817
2944
  console.log(` Priority: ${result.priority}`);
2818
2945
  console.log("");
2819
- consola24.start(`Verifying IdP at ${result.idp}...`);
2946
+ consola25.start(`Verifying IdP at ${result.idp}...`);
2820
2947
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
2821
2948
  if (!discoResp.ok) {
2822
- consola24.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2949
+ consola25.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2823
2950
  return;
2824
2951
  }
2825
2952
  const disco = await discoResp.json();
2826
- consola24.success(`IdP is reachable`);
2953
+ consola25.success(`IdP is reachable`);
2827
2954
  console.log(` Issuer: ${disco.issuer}`);
2828
2955
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
2829
2956
  if (disco.ddisa_auth_methods_supported) {
@@ -2838,9 +2965,128 @@ var dnsCheckCommand = defineCommand29({
2838
2965
  }
2839
2966
  });
2840
2967
 
2968
+ // src/commands/health.ts
2969
+ import { exec } from "child_process";
2970
+ import { promisify } from "util";
2971
+ import { defineCommand as defineCommand31 } from "citty";
2972
+ var execAsync = promisify(exec);
2973
+ async function resolveApeShellPath() {
2974
+ try {
2975
+ const { stdout } = await execAsync("command -v ape-shell", { shell: "/bin/bash" });
2976
+ const trimmed = stdout.trim();
2977
+ return trimmed.length > 0 ? trimmed : null;
2978
+ } catch {
2979
+ return null;
2980
+ }
2981
+ }
2982
+ async function probeIdp(url) {
2983
+ const ctrl = new AbortController();
2984
+ const timeout = setTimeout(() => ctrl.abort(), 3e3);
2985
+ try {
2986
+ await fetch(url, { method: "GET", signal: ctrl.signal });
2987
+ return { reachable: true };
2988
+ } catch (err) {
2989
+ const message = err instanceof Error ? err.message : String(err);
2990
+ return { reachable: false, error: message };
2991
+ } finally {
2992
+ clearTimeout(timeout);
2993
+ }
2994
+ }
2995
+ async function bestEffortGrantCount(idp) {
2996
+ try {
2997
+ const grantsUrl = await getGrantsEndpoint(idp);
2998
+ const res = await apiFetch(`${grantsUrl}?limit=1`);
2999
+ const count = Array.isArray(res?.data) ? res.data.length : 0;
3000
+ return { count };
3001
+ } catch (err) {
3002
+ const message = err instanceof Error ? err.message : String(err);
3003
+ return { error: message };
3004
+ }
3005
+ }
3006
+ async function runHealth(args) {
3007
+ const version = true ? "0.9.0" : "0.0.0";
3008
+ const auth = loadAuth();
3009
+ if (!auth) {
3010
+ throw new CliError("Not logged in. Run `apes login` first.", 1);
3011
+ }
3012
+ const isAgent = auth.email.includes("agent+");
3013
+ const expiresDate = new Date(auth.expires_at * 1e3);
3014
+ const isExpired = Date.now() / 1e3 > auth.expires_at;
3015
+ if (isExpired) {
3016
+ throw new CliError(`Token expired at ${expiresDate.toISOString()}. Run \`apes login\`.`, 1);
3017
+ }
3018
+ const idpProbe = await probeIdp(auth.idp);
3019
+ const grantInfo = await bestEffortGrantCount(auth.idp);
3020
+ const apeShellPath = await resolveApeShellPath();
3021
+ const report = {
3022
+ version,
3023
+ config: { dir: CONFIG_DIR },
3024
+ auth: {
3025
+ file: AUTH_FILE,
3026
+ present: true,
3027
+ email: auth.email,
3028
+ type: isAgent ? "agent" : "human",
3029
+ idp: auth.idp,
3030
+ expires_at_iso: expiresDate.toISOString(),
3031
+ expires_at_local: expiresDate.toLocaleString(),
3032
+ expired: false
3033
+ },
3034
+ idp: {
3035
+ url: auth.idp,
3036
+ reachable: idpProbe.reachable,
3037
+ ..."error" in idpProbe ? { error: idpProbe.error } : {}
3038
+ },
3039
+ grants: "count" in grantInfo ? { count: grantInfo.count } : { error: grantInfo.error },
3040
+ ape_shell_binary: apeShellPath,
3041
+ ok: idpProbe.reachable
3042
+ };
3043
+ if (args.json) {
3044
+ console.log(JSON.stringify(report, null, 2));
3045
+ } else {
3046
+ console.log(`apes ${version}`);
3047
+ console.log("");
3048
+ console.log(`Config: ${CONFIG_DIR}`);
3049
+ console.log(`Auth: ${AUTH_FILE}`);
3050
+ console.log(` ${auth.email} (${isAgent ? "agent" : "human"})`);
3051
+ console.log(` IdP: ${auth.idp}`);
3052
+ console.log(` Token: valid until ${expiresDate.toISOString()} (local: ${expiresDate.toLocaleString()})`);
3053
+ console.log("");
3054
+ if (idpProbe.reachable) {
3055
+ console.log("IdP: reachable");
3056
+ } else {
3057
+ console.log(`IdP: <unreachable: ${idpProbe.error}>`);
3058
+ }
3059
+ if ("count" in grantInfo) {
3060
+ console.log(`Grants: ${grantInfo.count}`);
3061
+ } else {
3062
+ console.log(`Grants: <unreachable: ${grantInfo.error}>`);
3063
+ }
3064
+ console.log(`ape-shell: ${apeShellPath ?? "(not on PATH)"}`);
3065
+ }
3066
+ if (!idpProbe.reachable) {
3067
+ throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
3068
+ }
3069
+ }
3070
+ var healthCommand = defineCommand31({
3071
+ meta: {
3072
+ name: "health",
3073
+ description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
3074
+ },
3075
+ args: {
3076
+ json: {
3077
+ type: "boolean",
3078
+ description: "Emit a machine-readable JSON report",
3079
+ default: false
3080
+ }
3081
+ },
3082
+ async run({ args }) {
3083
+ await runHealth({ json: Boolean(args.json) });
3084
+ }
3085
+ });
3086
+
2841
3087
  // src/commands/workflows.ts
2842
- import { defineCommand as defineCommand30 } from "citty";
2843
- import consola25 from "consola";
3088
+ import { defineCommand as defineCommand32 } from "citty";
3089
+ import consola26 from "consola";
2844
3090
 
2845
3091
  // src/guides/index.ts
2846
3092
  var guides = [
@@ -2890,7 +3136,7 @@ var guides = [
2890
3136
  ];
2891
3137
 
2892
3138
  // src/commands/workflows.ts
2893
- var workflowsCommand = defineCommand30({
3139
+ var workflowsCommand = defineCommand32({
2894
3140
  meta: {
2895
3141
  name: "workflows",
2896
3142
  description: "Discover workflow guides"
@@ -2911,7 +3157,7 @@ var workflowsCommand = defineCommand30({
2911
3157
  if (args.id) {
2912
3158
  const guide = guides.find((g) => g.id === String(args.id));
2913
3159
  if (!guide) {
2914
- consola25.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
3160
+ consola26.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
2915
3161
  throw new CliError(`Guide not found: ${args.id}`);
2916
3162
  }
2917
3163
  if (args.json) {
@@ -2960,10 +3206,10 @@ if (shellRewrite) {
2960
3206
  if (shellRewrite.action === "rewrite") {
2961
3207
  process.argv = shellRewrite.argv;
2962
3208
  } else if (shellRewrite.action === "version") {
2963
- console.log(`ape-shell ${"0.7.2"} (OpenApe DDISA shell wrapper)`);
3209
+ console.log(`ape-shell ${"0.9.0"} (OpenApe DDISA shell wrapper)`);
2964
3210
  process.exit(0);
2965
3211
  } else if (shellRewrite.action === "help") {
2966
- console.log(`ape-shell ${"0.7.2"} \u2014 OpenApe DDISA shell wrapper`);
3212
+ console.log(`ape-shell ${"0.9.0"} \u2014 OpenApe DDISA shell wrapper`);
2967
3213
  console.log("");
2968
3214
  console.log("Usage:");
2969
3215
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -2978,7 +3224,7 @@ if (shellRewrite) {
2978
3224
  console.log(" --help, -h Show this help message");
2979
3225
  process.exit(0);
2980
3226
  } else if (shellRewrite.action === "interactive") {
2981
- const { runInteractiveShell } = await import("./orchestrator-JAMWD6DD.js");
3227
+ const { runInteractiveShell } = await import("./orchestrator-GPNL543L.js");
2982
3228
  await runInteractiveShell();
2983
3229
  process.exit(0);
2984
3230
  } else {
@@ -2987,7 +3233,7 @@ if (shellRewrite) {
2987
3233
  }
2988
3234
  }
2989
3235
  var debug = process.argv.includes("--debug");
2990
- var grantsCommand = defineCommand31({
3236
+ var grantsCommand = defineCommand33({
2991
3237
  meta: {
2992
3238
  name: "grants",
2993
3239
  description: "Grant management"
@@ -3001,13 +3247,14 @@ var grantsCommand = defineCommand31({
3001
3247
  approve: approveCommand,
3002
3248
  deny: denyCommand,
3003
3249
  revoke: revokeCommand,
3250
+ run: runGrantCommand,
3004
3251
  token: tokenCommand,
3005
3252
  delegate: delegateCommand,
3006
3253
  delegations: delegationsCommand,
3007
3254
  "delegation-revoke": delegationRevokeCommand
3008
3255
  }
3009
3256
  });
3010
- var configCommand = defineCommand31({
3257
+ var configCommand = defineCommand33({
3011
3258
  meta: {
3012
3259
  name: "config",
3013
3260
  description: "Configuration management"
@@ -3017,10 +3264,10 @@ var configCommand = defineCommand31({
3017
3264
  set: configSetCommand
3018
3265
  }
3019
3266
  });
3020
- var main = defineCommand31({
3267
+ var main = defineCommand33({
3021
3268
  meta: {
3022
3269
  name: "apes",
3023
- version: "0.7.2",
3270
+ version: "0.9.0",
3024
3271
  description: "Unified CLI for OpenApe"
3025
3272
  },
3026
3273
  subCommands: {
@@ -3031,6 +3278,7 @@ var main = defineCommand31({
3031
3278
  login: loginCommand,
3032
3279
  logout: logoutCommand,
3033
3280
  whoami: whoamiCommand,
3281
+ health: healthCommand,
3034
3282
  grants: grantsCommand,
3035
3283
  admin: adminCommand,
3036
3284
  run: runCommand,
@@ -3047,13 +3295,13 @@ runMain(main).catch((err) => {
3047
3295
  process.exit(err.exitCode);
3048
3296
  }
3049
3297
  if (err instanceof CliError) {
3050
- consola26.error(err.message);
3298
+ consola27.error(err.message);
3051
3299
  process.exit(err.exitCode);
3052
3300
  }
3053
3301
  if (debug) {
3054
- consola26.error(err);
3302
+ consola27.error(err);
3055
3303
  } else {
3056
- consola26.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
3304
+ consola27.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
3057
3305
  }
3058
3306
  process.exit(1);
3059
3307
  });