@openape/apes 0.5.5 → 0.6.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
@@ -1,7 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ CliError,
4
+ CliExit,
3
5
  parseDuration
4
- } from "./chunk-AGHP6MNV.js";
6
+ } from "./chunk-ZSJU7IXE.js";
5
7
  import {
6
8
  loadEd25519PrivateKey
7
9
  } from "./chunk-KVBHBOED.js";
@@ -22,8 +24,8 @@ import {
22
24
  } from "./chunk-KXESKY4X.js";
23
25
 
24
26
  // src/cli.ts
25
- import consola22 from "consola";
26
- import { defineCommand as defineCommand25, runMain } from "citty";
27
+ import consola21 from "consola";
28
+ import { defineCommand as defineCommand26, runMain } from "citty";
27
29
 
28
30
  // src/commands/auth/login.ts
29
31
  import { Buffer } from "buffer";
@@ -57,8 +59,7 @@ var loginCommand = defineCommand({
57
59
  const config = loadConfig();
58
60
  const idp = args.idp || process.env.APES_IDP || process.env.GRAPES_IDP || config.defaults?.idp;
59
61
  if (!idp) {
60
- consola.error("IdP URL required. Use --idp <url> or set APES_IDP.");
61
- return process.exit(1);
62
+ throw new CliError("IdP URL required. Use --idp <url> or set APES_IDP.");
62
63
  }
63
64
  if (args.key) {
64
65
  await loginWithKey(idp, args.key, args.email);
@@ -137,14 +138,12 @@ async function loginWithPKCE(idp) {
137
138
  });
138
139
  if (!tokenResponse.ok) {
139
140
  const text = await tokenResponse.text();
140
- consola.error(`Token exchange failed: ${text}`);
141
- return process.exit(1);
141
+ throw new CliError(`Token exchange failed: ${text}`);
142
142
  }
143
143
  const tokens = await tokenResponse.json();
144
144
  const accessToken = tokens.access_token || tokens.id_token || tokens.assertion;
145
145
  if (!accessToken) {
146
- consola.error("No access token received");
147
- return process.exit(1);
146
+ throw new CliError("No access token received");
148
147
  }
149
148
  const payload = JSON.parse(atob(accessToken.split(".")[1]));
150
149
  saveAuth({
@@ -162,8 +161,7 @@ async function loginWithKey(idp, keyPath, email) {
162
161
  const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-Q7KG4K25.js");
163
162
  const agentEmail = email;
164
163
  if (!agentEmail) {
165
- consola.error("Agent email required for key-based login. Use --email <agent-email>");
166
- return process.exit(1);
164
+ throw new CliError("Agent email required for key-based login. Use --email <agent-email>");
167
165
  }
168
166
  const challengeUrl = await getAgentChallengeEndpoint(idp);
169
167
  const challengeResp = await fetch(challengeUrl, {
@@ -172,8 +170,7 @@ async function loginWithKey(idp, keyPath, email) {
172
170
  body: JSON.stringify({ agent_id: agentEmail })
173
171
  });
174
172
  if (!challengeResp.ok) {
175
- consola.error(`Challenge failed: ${await challengeResp.text()}`);
176
- return process.exit(1);
173
+ throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
177
174
  }
178
175
  const { challenge } = await challengeResp.json();
179
176
  const keyContent = readFileSync2(keyPath, "utf-8");
@@ -190,8 +187,7 @@ async function loginWithKey(idp, keyPath, email) {
190
187
  })
191
188
  });
192
189
  if (!authResp.ok) {
193
- consola.error(`Authentication failed: ${await authResp.text()}`);
194
- return process.exit(1);
190
+ throw new CliError(`Authentication failed: ${await authResp.text()}`);
195
191
  }
196
192
  const { token, expires_in } = await authResp.json();
197
193
  saveAuth({
@@ -228,8 +224,7 @@ var whoamiCommand = defineCommand3({
228
224
  run() {
229
225
  const auth = loadAuth();
230
226
  if (!auth) {
231
- consola3.error("Not logged in. Run `apes login` first.");
232
- return process.exit(1);
227
+ throw new CliError("Not logged in. Run `apes login` first.");
233
228
  }
234
229
  const isAgent = auth.email.includes("agent+");
235
230
  const expiresAt = new Date(auth.expires_at * 1e3).toISOString();
@@ -275,8 +270,7 @@ var listCommand = defineCommand4({
275
270
  async run({ args }) {
276
271
  const idp = getIdpUrl();
277
272
  if (!idp) {
278
- consola4.error("No IdP URL configured. Run `apes login` first or pass --idp.");
279
- return process.exit(1);
273
+ throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
280
274
  }
281
275
  const auth = loadAuth();
282
276
  const grantsUrl = await getGrantsEndpoint(idp);
@@ -335,13 +329,11 @@ var inboxCommand = defineCommand5({
335
329
  async run({ args }) {
336
330
  const idp = getIdpUrl();
337
331
  if (!idp) {
338
- consola5.error("No IdP URL configured. Run `apes login` first.");
339
- return process.exit(1);
332
+ throw new CliError("No IdP URL configured. Run `apes login` first.");
340
333
  }
341
334
  const auth = loadAuth();
342
335
  if (!auth) {
343
- consola5.error("Not logged in. Run `apes login` first.");
344
- return process.exit(1);
336
+ throw new CliError("Not logged in. Run `apes login` first.");
345
337
  }
346
338
  const grantsUrl = await getGrantsEndpoint(idp);
347
339
  const params = new URLSearchParams();
@@ -477,8 +469,7 @@ var requestCommand = defineCommand7({
477
469
  async run({ args }) {
478
470
  const auth = loadAuth();
479
471
  if (!auth) {
480
- consola6.error("Not logged in. Run `apes login` first.");
481
- return process.exit(1);
472
+ throw new CliError("Not logged in. Run `apes login` first.");
482
473
  }
483
474
  const idp = getIdpUrl();
484
475
  const grantsUrl = await getGrantsEndpoint(idp);
@@ -516,17 +507,14 @@ async function waitForApproval(grantsUrl, grantId) {
516
507
  return;
517
508
  }
518
509
  if (grant.status === "denied") {
519
- consola6.error("Grant denied.");
520
- return process.exit(1);
510
+ throw new CliError("Grant denied.");
521
511
  }
522
512
  if (grant.status === "revoked") {
523
- consola6.error("Grant revoked.");
524
- return process.exit(1);
513
+ throw new CliError("Grant revoked.");
525
514
  }
526
515
  await new Promise((r) => setTimeout(r, interval));
527
516
  }
528
- consola6.error("Timed out waiting for approval.");
529
- return process.exit(1);
517
+ throw new CliError("Timed out waiting for approval.");
530
518
  }
531
519
 
532
520
  // src/commands/grants/request-capability.ts
@@ -644,17 +632,14 @@ async function waitForApproval2(grantsUrl, grantId) {
644
632
  return;
645
633
  }
646
634
  if (grant.status === "denied") {
647
- consola7.error("Grant denied.");
648
- process.exit(1);
635
+ throw new CliError("Grant denied.");
649
636
  }
650
637
  if (grant.status === "revoked") {
651
- consola7.error("Grant revoked.");
652
- process.exit(1);
638
+ throw new CliError("Grant revoked.");
653
639
  }
654
640
  await new Promise((resolve2) => setTimeout(resolve2, interval));
655
641
  }
656
- consola7.error("Timed out waiting for approval.");
657
- process.exit(1);
642
+ throw new CliError("Timed out waiting for approval.");
658
643
  }
659
644
  var requestCapabilityCommand = defineCommand8({
660
645
  meta: {
@@ -713,14 +698,12 @@ var requestCapabilityCommand = defineCommand8({
713
698
  async run({ rawArgs }) {
714
699
  const auth = loadAuth();
715
700
  if (!auth) {
716
- consola7.error("Not logged in. Run `apes login` first.");
717
- return process.exit(1);
701
+ throw new CliError("Not logged in. Run `apes login` first.");
718
702
  }
719
703
  const parsed = parseCapabilityArgs(rawArgs);
720
704
  const idp = getIdpUrl(parsed.idp);
721
705
  if (!idp) {
722
- consola7.error("No IdP URL configured. Use --idp or log in first.");
723
- return process.exit(1);
706
+ throw new CliError("No IdP URL configured. Use --idp or log in first.");
724
707
  }
725
708
  const loaded = loadAdapter(parsed.cliId, parsed.adapter);
726
709
  const resolved = resolveCapabilityRequest(loaded, {
@@ -822,6 +805,11 @@ var revokeCommand = defineCommand11({
822
805
  type: "boolean",
823
806
  description: "Revoke all own pending grants",
824
807
  default: false
808
+ },
809
+ debug: {
810
+ type: "boolean",
811
+ description: "Print debug information (does not include full tokens)",
812
+ default: false
825
813
  }
826
814
  },
827
815
  async run({ args }) {
@@ -829,25 +817,28 @@ var revokeCommand = defineCommand11({
829
817
  const token = getAuthToken();
830
818
  const idp = getIdpUrl();
831
819
  const grantsUrl = await getGrantsEndpoint(idp);
832
- if (process.argv.includes("--debug")) {
820
+ if (args.debug) {
833
821
  consola10.debug(`idp: ${idp}`);
834
822
  consola10.debug(`grantsUrl: ${grantsUrl}`);
835
823
  consola10.debug(`auth.email: ${auth?.email}`);
836
824
  consola10.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
837
825
  consola10.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
838
826
  }
827
+ if (!auth || !token) {
828
+ throw new CliError("Authentication required. Run `apes login` and try again.");
829
+ }
839
830
  const explicitIds = args.id ? [String(args.id), ...args._].filter(Boolean) : [];
840
831
  if (args.allPending && explicitIds.length > 0) {
841
- consola10.error("Use either --all-pending or grant IDs, not both.");
842
- return process.exit(1);
832
+ throw new CliError("Use either --all-pending or grant IDs, not both.");
843
833
  }
844
834
  let ids;
845
835
  if (args.allPending) {
846
836
  const auth2 = loadAuth();
847
837
  const response = await apiFetch(
848
- `${grantsUrl}?status=pending&limit=100`
838
+ `${grantsUrl}?status=pending&limit=100`,
839
+ { token }
849
840
  );
850
- const ownPending = auth2?.email ? response.data.filter((g) => g.requester === auth2.email) : response.data;
841
+ const ownPending = auth2?.email ? response.data.filter((g) => g.request?.requester === auth2.email) : response.data;
851
842
  if (ownPending.length === 0) {
852
843
  consola10.info("No pending grants to revoke.");
853
844
  return;
@@ -857,18 +848,17 @@ var revokeCommand = defineCommand11({
857
848
  } else if (explicitIds.length > 0) {
858
849
  ids = explicitIds;
859
850
  } else {
860
- consola10.error("Provide grant ID(s) or use --all-pending.");
861
- return process.exit(1);
851
+ throw new CliError("Provide grant ID(s) or use --all-pending.");
862
852
  }
863
853
  if (ids.length === 1) {
864
- await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token: token || void 0 });
854
+ await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token });
865
855
  consola10.success(`Grant ${ids[0]} revoked.`);
866
856
  return;
867
857
  }
868
858
  const operations = ids.map((id) => ({ id, action: "revoke" }));
869
859
  const { results } = await apiFetch(
870
860
  `${grantsUrl}/batch`,
871
- { method: "POST", body: { operations }, token: token || void 0 }
861
+ { method: "POST", body: { operations }, token }
872
862
  );
873
863
  let succeeded = 0;
874
864
  for (const r of results) {
@@ -880,8 +870,7 @@ var revokeCommand = defineCommand11({
880
870
  }
881
871
  }
882
872
  if (succeeded < results.length) {
883
- consola10.info(`Revoked ${succeeded} of ${results.length} grants.`);
884
- process.exit(1);
873
+ throw new CliError(`Revoked ${succeeded} of ${results.length} grants.`);
885
874
  } else {
886
875
  consola10.success(`All ${succeeded} grants revoked.`);
887
876
  }
@@ -890,7 +879,6 @@ var revokeCommand = defineCommand11({
890
879
 
891
880
  // src/commands/grants/token.ts
892
881
  import { defineCommand as defineCommand12 } from "citty";
893
- import consola11 from "consola";
894
882
  var tokenCommand = defineCommand12({
895
883
  meta: {
896
884
  name: "token",
@@ -910,8 +898,7 @@ var tokenCommand = defineCommand12({
910
898
  method: "POST"
911
899
  });
912
900
  if (!result.authz_jwt) {
913
- consola11.error("No token received. Grant may not be approved.");
914
- return process.exit(1);
901
+ throw new CliError("No token received. Grant may not be approved.");
915
902
  }
916
903
  process.stdout.write(result.authz_jwt);
917
904
  }
@@ -919,7 +906,7 @@ var tokenCommand = defineCommand12({
919
906
 
920
907
  // src/commands/grants/delegate.ts
921
908
  import { defineCommand as defineCommand13 } from "citty";
922
- import consola12 from "consola";
909
+ import consola11 from "consola";
923
910
  var delegateCommand = defineCommand13({
924
911
  meta: {
925
912
  name: "delegate",
@@ -953,8 +940,7 @@ var delegateCommand = defineCommand13({
953
940
  async run({ args }) {
954
941
  const auth = loadAuth();
955
942
  if (!auth) {
956
- consola12.error("Not logged in. Run `apes login` first.");
957
- return process.exit(1);
943
+ throw new CliError("Not logged in. Run `apes login` first.");
958
944
  }
959
945
  const idp = getIdpUrl();
960
946
  const delegationsUrl = await getDelegationsEndpoint(idp);
@@ -973,7 +959,7 @@ var delegateCommand = defineCommand13({
973
959
  method: "POST",
974
960
  body
975
961
  });
976
- consola12.success(`Delegation created: ${result.id}`);
962
+ consola11.success(`Delegation created: ${result.id}`);
977
963
  console.log(` Delegate: ${args.to}`);
978
964
  console.log(` Audience: ${args.at}`);
979
965
  if (args.scopes)
@@ -986,7 +972,7 @@ var delegateCommand = defineCommand13({
986
972
 
987
973
  // src/commands/grants/delegations.ts
988
974
  import { defineCommand as defineCommand14 } from "citty";
989
- import consola13 from "consola";
975
+ import consola12 from "consola";
990
976
  var delegationsCommand = defineCommand14({
991
977
  meta: {
992
978
  name: "delegations",
@@ -1008,7 +994,7 @@ var delegationsCommand = defineCommand14({
1008
994
  return;
1009
995
  }
1010
996
  if (delegations.length === 0) {
1011
- consola13.info("No delegations found.");
997
+ consola12.info("No delegations found.");
1012
998
  return;
1013
999
  }
1014
1000
  for (const d of delegations) {
@@ -1021,7 +1007,7 @@ var delegationsCommand = defineCommand14({
1021
1007
 
1022
1008
  // src/commands/adapter/index.ts
1023
1009
  import { defineCommand as defineCommand15 } from "citty";
1024
- import consola14 from "consola";
1010
+ import consola13 from "consola";
1025
1011
  import {
1026
1012
  fetchRegistry,
1027
1013
  findAdapter,
@@ -1070,7 +1056,7 @@ var adapterCommand = defineCommand15({
1070
1056
  `);
1071
1057
  return;
1072
1058
  }
1073
- consola14.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1059
+ consola13.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1074
1060
  for (const a of index2.adapters) {
1075
1061
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
1076
1062
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -1092,7 +1078,7 @@ var adapterCommand = defineCommand15({
1092
1078
  return;
1093
1079
  }
1094
1080
  if (local.length === 0) {
1095
- consola14.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1081
+ consola13.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
1096
1082
  return;
1097
1083
  }
1098
1084
  for (const a of local) {
@@ -1129,20 +1115,20 @@ var adapterCommand = defineCommand15({
1129
1115
  for (const id of ids) {
1130
1116
  const entry = findAdapter(index, id);
1131
1117
  if (!entry) {
1132
- consola14.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1118
+ consola13.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
1133
1119
  continue;
1134
1120
  }
1135
1121
  const conflicts = findConflictingAdapters(entry.executable, id);
1136
1122
  if (conflicts.length > 0) {
1137
1123
  for (const c of conflicts) {
1138
- consola14.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1139
- consola14.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1124
+ consola13.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1125
+ consola13.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
1140
1126
  }
1141
1127
  }
1142
1128
  const result = await installAdapter(entry, { local });
1143
1129
  const verb = result.updated ? "Updated" : "Installed";
1144
- consola14.success(`${verb} ${result.id} \u2192 ${result.path}`);
1145
- consola14.info(`Digest: ${result.digest}`);
1130
+ consola13.success(`${verb} ${result.id} \u2192 ${result.path}`);
1131
+ consola13.info(`Digest: ${result.digest}`);
1146
1132
  }
1147
1133
  }
1148
1134
  }),
@@ -1169,14 +1155,14 @@ var adapterCommand = defineCommand15({
1169
1155
  let failed = false;
1170
1156
  for (const id of ids) {
1171
1157
  if (removeAdapter(id, local)) {
1172
- consola14.success(`Removed adapter: ${id}`);
1158
+ consola13.success(`Removed adapter: ${id}`);
1173
1159
  } else {
1174
- consola14.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1160
+ consola13.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1175
1161
  failed = true;
1176
1162
  }
1177
1163
  }
1178
1164
  if (failed)
1179
- process.exit(1);
1165
+ throw new CliError("Some adapters could not be removed");
1180
1166
  }
1181
1167
  }),
1182
1168
  info: defineCommand15({
@@ -1253,7 +1239,7 @@ var adapterCommand = defineCommand15({
1253
1239
  return;
1254
1240
  }
1255
1241
  if (results.length === 0) {
1256
- consola14.info(`No adapters matching "${query}"`);
1242
+ consola13.info(`No adapters matching "${query}"`);
1257
1243
  return;
1258
1244
  }
1259
1245
  for (const a of results) {
@@ -1288,29 +1274,29 @@ var adapterCommand = defineCommand15({
1288
1274
  const targetId = args.id ? String(args.id) : void 0;
1289
1275
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
1290
1276
  if (targets.length === 0) {
1291
- consola14.info("No adapters installed to update.");
1277
+ consola13.info("No adapters installed to update.");
1292
1278
  return;
1293
1279
  }
1294
1280
  for (const id of targets) {
1295
1281
  const entry = findAdapter(index, id);
1296
1282
  if (!entry) {
1297
- consola14.warn(`${id}: not found in registry, skipping`);
1283
+ consola13.warn(`${id}: not found in registry, skipping`);
1298
1284
  continue;
1299
1285
  }
1300
1286
  const localDigest = getInstalledDigest(id, false);
1301
1287
  if (localDigest === entry.digest) {
1302
- consola14.info(`${id}: already up to date`);
1288
+ consola13.info(`${id}: already up to date`);
1303
1289
  continue;
1304
1290
  }
1305
1291
  if (localDigest && !args.yes) {
1306
- consola14.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1307
- consola14.info(` Old: ${localDigest}`);
1308
- consola14.info(` New: ${entry.digest}`);
1309
- consola14.info(" Use --yes to confirm");
1292
+ consola13.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1293
+ consola13.info(` Old: ${localDigest}`);
1294
+ consola13.info(` New: ${entry.digest}`);
1295
+ consola13.info(" Use --yes to confirm");
1310
1296
  continue;
1311
1297
  }
1312
1298
  const result = await installAdapter(entry);
1313
- consola14.success(`Updated ${result.id} \u2192 ${result.path}`);
1299
+ consola13.success(`Updated ${result.id} \u2192 ${result.path}`);
1314
1300
  }
1315
1301
  }
1316
1302
  }),
@@ -1347,12 +1333,11 @@ var adapterCommand = defineCommand15({
1347
1333
  if (!localDigest)
1348
1334
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1349
1335
  if (localDigest === entry.digest) {
1350
- consola14.success(`${id}: digest matches registry`);
1336
+ consola13.success(`${id}: digest matches registry`);
1351
1337
  } else {
1352
- consola14.error(`${id}: digest mismatch`);
1353
1338
  console.log(` Local: ${localDigest}`);
1354
1339
  console.log(` Registry: ${entry.digest}`);
1355
- process.exit(1);
1340
+ throw new CliError(`${id}: digest mismatch`);
1356
1341
  }
1357
1342
  }
1358
1343
  })
@@ -1374,7 +1359,7 @@ import {
1374
1359
  verifyAndExecute,
1375
1360
  waitForGrantStatus
1376
1361
  } from "@openape/shapes";
1377
- import consola15 from "consola";
1362
+ import consola14 from "consola";
1378
1363
  var runCommand = defineCommand16({
1379
1364
  meta: {
1380
1365
  name: "run",
@@ -1449,6 +1434,10 @@ async function runAdapterMode(command, rawArgs, args) {
1449
1434
  const idp = getIdpUrl(args.idp);
1450
1435
  if (!idp)
1451
1436
  throw new Error("No IdP URL configured. Run `apes login` first or pass --idp.");
1437
+ if (args.as) {
1438
+ await runAudienceMode("escapes", command.join(" "), args);
1439
+ return;
1440
+ }
1452
1441
  const adapterOpt = extractOption(rawArgs, "adapter");
1453
1442
  const loaded = loadAdapter3(command[0], adapterOpt);
1454
1443
  const resolved = await resolveCommand(loaded, command);
@@ -1456,7 +1445,7 @@ async function runAdapterMode(command, rawArgs, args) {
1456
1445
  try {
1457
1446
  const existingGrantId = await findExistingGrant(resolved, idp);
1458
1447
  if (existingGrantId) {
1459
- consola15.info(`Reusing existing grant: ${existingGrantId}`);
1448
+ consola14.info(`Reusing existing grant: ${existingGrantId}`);
1460
1449
  const token2 = await fetchGrantToken(idp, existingGrantId);
1461
1450
  await verifyAndExecute(token2, resolved);
1462
1451
  return;
@@ -1468,17 +1457,17 @@ async function runAdapterMode(command, rawArgs, args) {
1468
1457
  approval,
1469
1458
  ...args.reason ? { reason: args.reason } : {}
1470
1459
  });
1471
- consola15.info(`Grant requested: ${grant.id}`);
1472
- consola15.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1460
+ consola14.info(`Grant requested: ${grant.id}`);
1461
+ consola14.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1473
1462
  if (grant.similar_grants?.similar_grants?.length) {
1474
1463
  const n = grant.similar_grants.similar_grants.length;
1475
- consola15.info("");
1476
- consola15.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
1464
+ consola14.info("");
1465
+ consola14.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
1477
1466
  if (grant.similar_grants.widened_details?.length) {
1478
1467
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
1479
- consola15.info(` Broader scope: ${wider}`);
1468
+ consola14.info(` Broader scope: ${wider}`);
1480
1469
  }
1481
- consola15.info("");
1470
+ consola14.info("");
1482
1471
  }
1483
1472
  const status = await waitForGrantStatus(idp, grant.id);
1484
1473
  if (status !== "approved")
@@ -1489,14 +1478,13 @@ async function runAdapterMode(command, rawArgs, args) {
1489
1478
  async function runAudienceMode(audience, action, args) {
1490
1479
  const auth = loadAuth();
1491
1480
  if (!auth) {
1492
- consola15.error("Not logged in. Run `apes login` first.");
1493
- return process.exit(1);
1481
+ throw new CliError("Not logged in. Run `apes login` first.");
1494
1482
  }
1495
1483
  const idp = getIdpUrl(args.idp);
1496
1484
  const grantsUrl = await getGrantsEndpoint(idp);
1497
1485
  const command = action.split(" ");
1498
1486
  const targetHost = args.host || hostname3();
1499
- consola15.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1487
+ consola14.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1500
1488
  const grant = await apiFetch(grantsUrl, {
1501
1489
  method: "POST",
1502
1490
  body: {
@@ -1509,36 +1497,35 @@ async function runAudienceMode(audience, action, args) {
1509
1497
  ...args.as ? { run_as: args.as } : {}
1510
1498
  }
1511
1499
  });
1512
- consola15.success(`Grant requested: ${grant.id}`);
1513
- consola15.info("Waiting for approval...");
1500
+ consola14.success(`Grant requested: ${grant.id}`);
1501
+ consola14.info("Waiting for approval...");
1514
1502
  const maxWait = 3e5;
1515
1503
  const interval = 3e3;
1516
1504
  const start = Date.now();
1517
1505
  while (Date.now() - start < maxWait) {
1518
1506
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
1519
1507
  if (status.status === "approved") {
1520
- consola15.success("Grant approved!");
1508
+ consola14.success("Grant approved!");
1521
1509
  break;
1522
1510
  }
1523
1511
  if (status.status === "denied" || status.status === "revoked") {
1524
- consola15.error(`Grant ${status.status}.`);
1525
- return process.exit(1);
1512
+ throw new CliError(`Grant ${status.status}.`);
1526
1513
  }
1527
1514
  await new Promise((r) => setTimeout(r, interval));
1528
1515
  }
1529
- consola15.info("Fetching grant token...");
1516
+ consola14.info("Fetching grant token...");
1530
1517
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
1531
1518
  method: "POST"
1532
1519
  });
1533
1520
  if (audience === "escapes") {
1534
- consola15.info(`Executing: ${command.join(" ")}`);
1521
+ consola14.info(`Executing: ${command.join(" ")}`);
1535
1522
  try {
1536
1523
  execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
1537
1524
  stdio: "inherit"
1538
1525
  });
1539
1526
  } catch (err) {
1540
1527
  const exitCode = err.status || 1;
1541
- process.exit(exitCode);
1528
+ throw new CliExit(exitCode);
1542
1529
  }
1543
1530
  } else {
1544
1531
  process.stdout.write(authz_jwt);
@@ -1587,7 +1574,7 @@ var explainCommand = defineCommand17({
1587
1574
 
1588
1575
  // src/commands/config/get.ts
1589
1576
  import { defineCommand as defineCommand18 } from "citty";
1590
- import consola16 from "consola";
1577
+ import consola15 from "consola";
1591
1578
  var configGetCommand = defineCommand18({
1592
1579
  meta: {
1593
1580
  name: "get",
@@ -1608,7 +1595,7 @@ var configGetCommand = defineCommand18({
1608
1595
  if (idp)
1609
1596
  console.log(idp);
1610
1597
  else
1611
- consola16.info("No IdP configured.");
1598
+ consola15.info("No IdP configured.");
1612
1599
  break;
1613
1600
  }
1614
1601
  case "email": {
@@ -1616,7 +1603,7 @@ var configGetCommand = defineCommand18({
1616
1603
  if (auth?.email)
1617
1604
  console.log(auth.email);
1618
1605
  else
1619
- consola16.info("Not logged in.");
1606
+ consola15.info("Not logged in.");
1620
1607
  break;
1621
1608
  }
1622
1609
  default: {
@@ -1629,11 +1616,10 @@ var configGetCommand = defineCommand18({
1629
1616
  if (sectionObj && field in sectionObj) {
1630
1617
  console.log(sectionObj[field]);
1631
1618
  } else {
1632
- consola16.info(`Key "${key}" not set.`);
1619
+ consola15.info(`Key "${key}" not set.`);
1633
1620
  }
1634
1621
  } else {
1635
- consola16.error(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
1636
- process.exit(1);
1622
+ throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
1637
1623
  }
1638
1624
  }
1639
1625
  }
@@ -1642,7 +1628,7 @@ var configGetCommand = defineCommand18({
1642
1628
 
1643
1629
  // src/commands/config/set.ts
1644
1630
  import { defineCommand as defineCommand19 } from "citty";
1645
- import consola17 from "consola";
1631
+ import consola16 from "consola";
1646
1632
  var configSetCommand = defineCommand19({
1647
1633
  meta: {
1648
1634
  name: "set",
@@ -1666,8 +1652,7 @@ var configSetCommand = defineCommand19({
1666
1652
  const config = loadConfig();
1667
1653
  const parts = key.split(".");
1668
1654
  if (parts.length !== 2) {
1669
- consola17.error(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
1670
- return process.exit(1);
1655
+ throw new CliError(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
1671
1656
  }
1672
1657
  const [section, field] = parts;
1673
1658
  if (section === "defaults") {
@@ -1677,22 +1662,19 @@ var configSetCommand = defineCommand19({
1677
1662
  config.agent = config.agent || {};
1678
1663
  config.agent[field] = value;
1679
1664
  } else {
1680
- consola17.error(`Unknown section: "${section}". Use: defaults, agent`);
1681
- return process.exit(1);
1665
+ throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
1682
1666
  }
1683
1667
  saveConfig(config);
1684
- consola17.success(`Set ${key} = ${value}`);
1668
+ consola16.success(`Set ${key} = ${value}`);
1685
1669
  }
1686
1670
  });
1687
1671
 
1688
1672
  // src/commands/fetch/index.ts
1689
1673
  import { defineCommand as defineCommand20 } from "citty";
1690
- import consola18 from "consola";
1691
1674
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
1692
1675
  const token = getAuthToken();
1693
1676
  if (!token) {
1694
- consola18.error("Not authenticated. Run `apes login` first.");
1695
- return process.exit(1);
1677
+ throw new CliError("Not authenticated. Run `apes login` first.");
1696
1678
  }
1697
1679
  const response = await fetch(url, {
1698
1680
  method,
@@ -1721,7 +1703,7 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
1721
1703
  }
1722
1704
  }
1723
1705
  if (!response.ok) {
1724
- process.exit(1);
1706
+ throw new CliError(`HTTP ${response.status} ${response.statusText}`);
1725
1707
  }
1726
1708
  }
1727
1709
  var fetchCommand = defineCommand20({
@@ -1819,7 +1801,7 @@ var mcpCommand = defineCommand21({
1819
1801
  if (transport !== "stdio" && transport !== "sse") {
1820
1802
  throw new Error('Transport must be "stdio" or "sse"');
1821
1803
  }
1822
- const { startMcpServer } = await import("./server-R2EVEMKX.js");
1804
+ const { startMcpServer } = await import("./server-IYR5LM63.js");
1823
1805
  await startMcpServer(transport, port);
1824
1806
  }
1825
1807
  });
@@ -1830,7 +1812,7 @@ import { randomBytes } from "crypto";
1830
1812
  import { execFileSync as execFileSync2 } from "child_process";
1831
1813
  import { join } from "path";
1832
1814
  import { defineCommand as defineCommand22 } from "citty";
1833
- import consola19 from "consola";
1815
+ import consola17 from "consola";
1834
1816
  var DEFAULT_IDP_URL = "https://id.openape.at";
1835
1817
  async function downloadTemplate(repo, targetDir) {
1836
1818
  const { downloadTemplate: gigetDownload } = await import("giget");
@@ -1847,16 +1829,16 @@ function installDeps(dir) {
1847
1829
  }
1848
1830
  }
1849
1831
  async function promptChoice(message, choices) {
1850
- const result = await consola19.prompt(message, { type: "select", options: choices });
1832
+ const result = await consola17.prompt(message, { type: "select", options: choices });
1851
1833
  if (typeof result === "symbol") {
1852
- process.exit(0);
1834
+ throw new CliExit(0);
1853
1835
  }
1854
1836
  return result;
1855
1837
  }
1856
1838
  async function promptText(message, defaultValue) {
1857
- const result = await consola19.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
1839
+ const result = await consola17.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
1858
1840
  if (typeof result === "symbol") {
1859
- process.exit(0);
1841
+ throw new CliExit(0);
1860
1842
  }
1861
1843
  return result || defaultValue || "";
1862
1844
  }
@@ -1903,23 +1885,22 @@ var initCommand = defineCommand22({
1903
1885
  async function initSP(targetDir) {
1904
1886
  const dir = targetDir || "my-app";
1905
1887
  if (existsSync(join(dir, "package.json"))) {
1906
- consola19.error(`Directory "${dir}" already contains a project.`);
1907
- return process.exit(1);
1888
+ throw new CliError(`Directory "${dir}" already contains a project.`);
1908
1889
  }
1909
- consola19.start("Scaffolding SP starter...");
1890
+ consola17.start("Scaffolding SP starter...");
1910
1891
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
1911
- consola19.success("Scaffolded from openape-sp-starter");
1912
- consola19.start("Installing dependencies...");
1892
+ consola17.success("Scaffolded from openape-sp-starter");
1893
+ consola17.start("Installing dependencies...");
1913
1894
  installDeps(dir);
1914
- consola19.success("Dependencies installed");
1895
+ consola17.success("Dependencies installed");
1915
1896
  const envExample = join(dir, ".env.example");
1916
1897
  const envFile = join(dir, ".env");
1917
1898
  if (existsSync(envExample) && !existsSync(envFile)) {
1918
1899
  copyFileSync(envExample, envFile);
1919
- consola19.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1900
+ consola17.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1920
1901
  }
1921
1902
  console.log("");
1922
- consola19.box([
1903
+ consola17.box([
1923
1904
  `cd ${dir}`,
1924
1905
  "npm run dev",
1925
1906
  "",
@@ -1929,8 +1910,7 @@ async function initSP(targetDir) {
1929
1910
  async function initIdP(targetDir) {
1930
1911
  const dir = targetDir || "my-idp";
1931
1912
  if (existsSync(join(dir, "package.json"))) {
1932
- consola19.error(`Directory "${dir}" already contains a project.`);
1933
- return process.exit(1);
1913
+ throw new CliError(`Directory "${dir}" already contains a project.`);
1934
1914
  }
1935
1915
  const domain = await promptText("Domain for the IdP", "localhost");
1936
1916
  const storage = await promptChoice("Storage backend", [
@@ -1939,15 +1919,15 @@ async function initIdP(targetDir) {
1939
1919
  "s3 (S3-compatible)"
1940
1920
  ]);
1941
1921
  const adminEmail = await promptText("Admin email");
1942
- consola19.start("Scaffolding IdP starter...");
1922
+ consola17.start("Scaffolding IdP starter...");
1943
1923
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
1944
- consola19.success("Scaffolded from openape-idp-starter");
1945
- consola19.start("Installing dependencies...");
1924
+ consola17.success("Scaffolded from openape-idp-starter");
1925
+ consola17.start("Installing dependencies...");
1946
1926
  installDeps(dir);
1947
- consola19.success("Dependencies installed");
1927
+ consola17.success("Dependencies installed");
1948
1928
  const sessionSecret = randomBytes(32).toString("hex");
1949
1929
  const managementToken = randomBytes(32).toString("hex");
1950
- consola19.success("Secrets generated");
1930
+ consola17.success("Secrets generated");
1951
1931
  const isLocalhost = domain === "localhost";
1952
1932
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
1953
1933
  const envContent = [
@@ -1961,10 +1941,11 @@ async function initIdP(targetDir) {
1961
1941
  `NUXT_OPENAPE_RP_ID=${domain}`,
1962
1942
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
1963
1943
  ].join("\n");
1964
- writeFileSync(join(dir, ".env"), envContent + "\n", { mode: 384 });
1965
- consola19.success(".env created");
1944
+ writeFileSync(join(dir, ".env"), `${envContent}
1945
+ `, { mode: 384 });
1946
+ consola17.success(".env created");
1966
1947
  console.log("");
1967
- consola19.box([
1948
+ consola17.box([
1968
1949
  `cd ${dir}`,
1969
1950
  "npm run dev",
1970
1951
  "",
@@ -1987,7 +1968,7 @@ import { generateKeyPairSync, sign } from "crypto";
1987
1968
  import { dirname, resolve } from "path";
1988
1969
  import { homedir } from "os";
1989
1970
  import { defineCommand as defineCommand23 } from "citty";
1990
- import consola20 from "consola";
1971
+ import consola18 from "consola";
1991
1972
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
1992
1973
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
1993
1974
  var POLL_INTERVAL = 3e3;
@@ -2092,38 +2073,48 @@ var enrollCommand = defineCommand23({
2092
2073
  }
2093
2074
  },
2094
2075
  async run({ args }) {
2095
- const idp = args.idp || await consola20.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => typeof r === "symbol" ? process.exit(0) : r) || DEFAULT_IDP_URL2;
2096
- const agentName = args.name || await consola20.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => typeof r === "symbol" ? process.exit(0) : r);
2076
+ const idp = args.idp || await consola18.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
2077
+ if (typeof r === "symbol") throw new CliExit(0);
2078
+ return r;
2079
+ }) || DEFAULT_IDP_URL2;
2080
+ const agentName = args.name || await consola18.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
2081
+ if (typeof r === "symbol") throw new CliExit(0);
2082
+ return r;
2083
+ });
2097
2084
  if (!agentName) {
2098
- consola20.error("Agent name is required.");
2099
- return process.exit(1);
2085
+ throw new CliError("Agent name is required.");
2100
2086
  }
2101
- const keyPath = args.key || await consola20.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => typeof r === "symbol" ? process.exit(0) : r) || DEFAULT_KEY_PATH;
2087
+ const keyPath = args.key || await consola18.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
2088
+ if (typeof r === "symbol") throw new CliExit(0);
2089
+ return r;
2090
+ }) || DEFAULT_KEY_PATH;
2102
2091
  const resolvedKey = resolvePath(keyPath);
2103
2092
  let publicKey;
2104
2093
  if (existsSync2(resolvedKey)) {
2105
2094
  publicKey = readPublicKey(resolvedKey);
2106
- consola20.success(`Using existing key ${keyPath}`);
2095
+ consola18.success(`Using existing key ${keyPath}`);
2107
2096
  } else {
2108
- consola20.start(`Generating Ed25519 key pair at ${keyPath}...`);
2097
+ consola18.start(`Generating Ed25519 key pair at ${keyPath}...`);
2109
2098
  publicKey = generateAndSaveKey(keyPath);
2110
- consola20.success(`Key pair generated at ${keyPath}`);
2099
+ consola18.success(`Key pair generated at ${keyPath}`);
2111
2100
  }
2112
2101
  const encodedKey = encodeURIComponent(publicKey);
2113
2102
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
2114
- consola20.info("Opening browser for enrollment...");
2115
- consola20.info(`\u2192 ${idp}/enroll`);
2103
+ consola18.info("Opening browser for enrollment...");
2104
+ consola18.info(`\u2192 ${idp}/enroll`);
2116
2105
  openBrowser2(enrollUrl);
2117
2106
  console.log("");
2118
- const agentEmail = await consola20.prompt(
2107
+ const agentEmail = await consola18.prompt(
2119
2108
  "Agent email (shown in browser after enrollment)",
2120
2109
  { type: "text", placeholder: `agent+${agentName}@...` }
2121
- ).then((r) => typeof r === "symbol" ? process.exit(0) : r);
2110
+ ).then((r) => {
2111
+ if (typeof r === "symbol") throw new CliExit(0);
2112
+ return r;
2113
+ });
2122
2114
  if (!agentEmail) {
2123
- consola20.error("Agent email is required to verify enrollment.");
2124
- return process.exit(1);
2115
+ throw new CliError("Agent email is required to verify enrollment.");
2125
2116
  }
2126
- consola20.start("Verifying enrollment...");
2117
+ consola18.start("Verifying enrollment...");
2127
2118
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
2128
2119
  saveAuth({
2129
2120
  idp,
@@ -2135,16 +2126,16 @@ var enrollCommand = defineCommand23({
2135
2126
  config.defaults = { ...config.defaults, idp };
2136
2127
  config.agent = { key: keyPath, email: agentEmail };
2137
2128
  saveConfig(config);
2138
- consola20.success(`Agent enrolled as ${agentEmail}`);
2139
- consola20.success("Config saved to ~/.config/apes/");
2129
+ consola18.success(`Agent enrolled as ${agentEmail}`);
2130
+ consola18.success("Config saved to ~/.config/apes/");
2140
2131
  console.log("");
2141
- consola20.info("Verify with: apes whoami");
2132
+ consola18.info("Verify with: apes whoami");
2142
2133
  }
2143
2134
  });
2144
2135
 
2145
2136
  // src/commands/dns-check.ts
2146
2137
  import { defineCommand as defineCommand24 } from "citty";
2147
- import consola21 from "consola";
2138
+ import consola19 from "consola";
2148
2139
  import { resolveDDISA } from "@openape/core";
2149
2140
  var dnsCheckCommand = defineCommand24({
2150
2141
  meta: {
@@ -2160,17 +2151,16 @@ var dnsCheckCommand = defineCommand24({
2160
2151
  },
2161
2152
  async run({ args }) {
2162
2153
  const domain = args.domain;
2163
- consola21.start(`Checking _ddisa.${domain}...`);
2154
+ consola19.start(`Checking _ddisa.${domain}...`);
2164
2155
  try {
2165
2156
  const result = await resolveDDISA(domain);
2166
2157
  if (!result) {
2167
- consola21.error(`No DDISA record found for ${domain}`);
2168
2158
  console.log("");
2169
2159
  console.log("To set up DDISA, add a DNS TXT record:");
2170
2160
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
2171
- return process.exit(1);
2161
+ throw new CliError(`No DDISA record found for ${domain}`);
2172
2162
  }
2173
- consola21.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2163
+ consola19.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2174
2164
  console.log("");
2175
2165
  console.log(` Version: ${result.version || "ddisa1"}`);
2176
2166
  console.log(` IdP URL: ${result.idp}`);
@@ -2179,14 +2169,14 @@ var dnsCheckCommand = defineCommand24({
2179
2169
  if (result.priority !== void 0)
2180
2170
  console.log(` Priority: ${result.priority}`);
2181
2171
  console.log("");
2182
- consola21.start(`Verifying IdP at ${result.idp}...`);
2172
+ consola19.start(`Verifying IdP at ${result.idp}...`);
2183
2173
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
2184
2174
  if (!discoResp.ok) {
2185
- consola21.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2175
+ consola19.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
2186
2176
  return;
2187
2177
  }
2188
2178
  const disco = await discoResp.json();
2189
- consola21.success(`IdP is reachable`);
2179
+ consola19.success(`IdP is reachable`);
2190
2180
  console.log(` Issuer: ${disco.issuer}`);
2191
2181
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
2192
2182
  if (disco.ddisa_auth_methods_supported) {
@@ -2196,15 +2186,130 @@ var dnsCheckCommand = defineCommand24({
2196
2186
  console.log(` Grants: ${disco.openape_grant_types_supported.join(", ")}`);
2197
2187
  }
2198
2188
  } catch (err) {
2199
- consola21.error(`DNS check failed: ${err instanceof Error ? err.message : String(err)}`);
2200
- return process.exit(1);
2189
+ throw new CliError(`DNS check failed: ${err instanceof Error ? err.message : String(err)}`);
2190
+ }
2191
+ }
2192
+ });
2193
+
2194
+ // src/commands/workflows.ts
2195
+ import { defineCommand as defineCommand25 } from "citty";
2196
+ import consola20 from "consola";
2197
+
2198
+ // src/guides/index.ts
2199
+ var guides = [
2200
+ {
2201
+ id: "timed-session",
2202
+ title: "Timed maintenance session",
2203
+ description: "Request a timed grant for multiple commands without per-command approval.",
2204
+ steps: [
2205
+ { description: "Request a timed grant (e.g. 1 hour)", command: "apes run --approval timed -- <your-command>" },
2206
+ { description: "Approve the grant in the browser (link is printed)" },
2207
+ { description: "Subsequent commands reuse the timed grant until it expires" },
2208
+ { note: "Use --approval always for standing permissions (revoke manually when done)" }
2209
+ ]
2210
+ },
2211
+ {
2212
+ id: "agent-onboarding",
2213
+ title: "Onboard a new agent",
2214
+ description: "Register an AI agent with a DDISA identity in under 3 minutes.",
2215
+ steps: [
2216
+ { description: "Initialize a new project (optional)", command: "apes init --sp my-app" },
2217
+ { description: "Enroll the agent at an IdP", command: "apes enroll" },
2218
+ { description: "Verify enrollment", command: "apes whoami" },
2219
+ { description: "Check DNS discovery", command: "apes dns-check" }
2220
+ ]
2221
+ },
2222
+ {
2223
+ id: "delegation",
2224
+ title: "Delegate permissions",
2225
+ description: "Let an agent act on your behalf at a specific service.",
2226
+ steps: [
2227
+ { description: "Create a delegation", command: "apes grants delegate --to agent@example.com --at api.example.com" },
2228
+ { description: "List active delegations", command: "apes grants delegations" },
2229
+ { description: "Revoke when no longer needed", command: "apes grants revoke <delegation-id>" }
2230
+ ]
2231
+ },
2232
+ {
2233
+ id: "privilege-escalation",
2234
+ title: "Run commands as root (escapes)",
2235
+ description: "Execute privileged commands with grant-verified escalation.",
2236
+ steps: [
2237
+ { description: "Request a grant to run a command as root", command: "apes run --as root -- apt-get upgrade" },
2238
+ { description: "Approve the grant in the browser" },
2239
+ { description: "The command executes via escapes with verified authorization" },
2240
+ { note: "escapes must be installed on the target machine (cargo build && sudo make install)" }
2241
+ ]
2242
+ }
2243
+ ];
2244
+
2245
+ // src/commands/workflows.ts
2246
+ var workflowsCommand = defineCommand25({
2247
+ meta: {
2248
+ name: "workflows",
2249
+ description: "Discover workflow guides"
2250
+ },
2251
+ args: {
2252
+ id: {
2253
+ type: "positional",
2254
+ description: "Guide ID to show (omit for list)",
2255
+ required: false
2256
+ },
2257
+ json: {
2258
+ type: "boolean",
2259
+ description: "Output as JSON",
2260
+ default: false
2261
+ }
2262
+ },
2263
+ run({ args }) {
2264
+ if (args.id) {
2265
+ const guide = guides.find((g) => g.id === String(args.id));
2266
+ if (!guide) {
2267
+ consola20.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
2268
+ throw new CliError(`Guide not found: ${args.id}`);
2269
+ }
2270
+ if (args.json) {
2271
+ console.log(JSON.stringify(guide, null, 2));
2272
+ return;
2273
+ }
2274
+ console.log(`
2275
+ ${guide.title}`);
2276
+ console.log(` ${guide.description}
2277
+ `);
2278
+ for (let i = 0; i < guide.steps.length; i++) {
2279
+ const step = guide.steps[i];
2280
+ if (step.note) {
2281
+ console.log(` Note: ${step.note}`);
2282
+ } else {
2283
+ console.log(` ${i + 1}. ${step.description}`);
2284
+ if (step.command) {
2285
+ console.log(` $ ${step.command}`);
2286
+ }
2287
+ }
2288
+ }
2289
+ console.log();
2290
+ return;
2291
+ }
2292
+ if (args.json) {
2293
+ console.log(JSON.stringify(guides.map((g) => ({ id: g.id, title: g.title, description: g.description })), null, 2));
2294
+ return;
2295
+ }
2296
+ console.log("\n Workflow Guides\n");
2297
+ for (const guide of guides) {
2298
+ console.log(` ${guide.id.padEnd(24)} ${guide.title}`);
2201
2299
  }
2300
+ console.log(`
2301
+ Show a guide: apes workflows <id>
2302
+ `);
2202
2303
  }
2203
2304
  });
2204
2305
 
2205
2306
  // src/cli.ts
2307
+ process.stdout.on("error", (err) => {
2308
+ if (err.code === "EPIPE") process.exit(0);
2309
+ throw err;
2310
+ });
2206
2311
  var debug = process.argv.includes("--debug");
2207
- var grantsCommand = defineCommand25({
2312
+ var grantsCommand = defineCommand26({
2208
2313
  meta: {
2209
2314
  name: "grants",
2210
2315
  description: "Grant management"
@@ -2223,7 +2328,7 @@ var grantsCommand = defineCommand25({
2223
2328
  delegations: delegationsCommand
2224
2329
  }
2225
2330
  });
2226
- var configCommand = defineCommand25({
2331
+ var configCommand = defineCommand26({
2227
2332
  meta: {
2228
2333
  name: "config",
2229
2334
  description: "Configuration management"
@@ -2233,10 +2338,10 @@ var configCommand = defineCommand25({
2233
2338
  set: configSetCommand
2234
2339
  }
2235
2340
  });
2236
- var main = defineCommand25({
2341
+ var main = defineCommand26({
2237
2342
  meta: {
2238
2343
  name: "apes",
2239
- version: "0.5.5",
2344
+ version: "0.6.0",
2240
2345
  description: "Unified CLI for OpenApe"
2241
2346
  },
2242
2347
  subCommands: {
@@ -2252,14 +2357,22 @@ var main = defineCommand25({
2252
2357
  adapter: adapterCommand,
2253
2358
  config: configCommand,
2254
2359
  fetch: fetchCommand,
2255
- mcp: mcpCommand
2360
+ mcp: mcpCommand,
2361
+ workflows: workflowsCommand
2256
2362
  }
2257
2363
  });
2258
2364
  runMain(main).catch((err) => {
2365
+ if (err instanceof CliExit) {
2366
+ process.exit(err.exitCode);
2367
+ }
2368
+ if (err instanceof CliError) {
2369
+ consola21.error(err.message);
2370
+ process.exit(err.exitCode);
2371
+ }
2259
2372
  if (debug) {
2260
- consola22.error(err);
2373
+ consola21.error(err);
2261
2374
  } else {
2262
- consola22.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
2375
+ consola21.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
2263
2376
  }
2264
2377
  process.exit(1);
2265
2378
  });