@openape/apes 0.5.4 → 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,23 +805,40 @@ 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 }) {
816
+ const auth = loadAuth();
817
+ const token = getAuthToken();
828
818
  const idp = getIdpUrl();
829
819
  const grantsUrl = await getGrantsEndpoint(idp);
820
+ if (args.debug) {
821
+ consola10.debug(`idp: ${idp}`);
822
+ consola10.debug(`grantsUrl: ${grantsUrl}`);
823
+ consola10.debug(`auth.email: ${auth?.email}`);
824
+ consola10.debug(`auth.expires_at: ${auth?.expires_at} (now: ${Math.floor(Date.now() / 1e3)})`);
825
+ consola10.debug(`getAuthToken(): ${token ? `${token.substring(0, 20)}...` : "NULL"}`);
826
+ }
827
+ if (!auth || !token) {
828
+ throw new CliError("Authentication required. Run `apes login` and try again.");
829
+ }
830
830
  const explicitIds = args.id ? [String(args.id), ...args._].filter(Boolean) : [];
831
831
  if (args.allPending && explicitIds.length > 0) {
832
- consola10.error("Use either --all-pending or grant IDs, not both.");
833
- return process.exit(1);
832
+ throw new CliError("Use either --all-pending or grant IDs, not both.");
834
833
  }
835
834
  let ids;
836
835
  if (args.allPending) {
837
- const auth = loadAuth();
836
+ const auth2 = loadAuth();
838
837
  const response = await apiFetch(
839
- `${grantsUrl}?status=pending&limit=100`
838
+ `${grantsUrl}?status=pending&limit=100`,
839
+ { token }
840
840
  );
841
- const ownPending = auth?.email ? response.data.filter((g) => g.requester === auth.email) : response.data;
841
+ const ownPending = auth2?.email ? response.data.filter((g) => g.request?.requester === auth2.email) : response.data;
842
842
  if (ownPending.length === 0) {
843
843
  consola10.info("No pending grants to revoke.");
844
844
  return;
@@ -848,18 +848,17 @@ var revokeCommand = defineCommand11({
848
848
  } else if (explicitIds.length > 0) {
849
849
  ids = explicitIds;
850
850
  } else {
851
- consola10.error("Provide grant ID(s) or use --all-pending.");
852
- return process.exit(1);
851
+ throw new CliError("Provide grant ID(s) or use --all-pending.");
853
852
  }
854
853
  if (ids.length === 1) {
855
- await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST" });
854
+ await apiFetch(`${grantsUrl}/${ids[0]}/revoke`, { method: "POST", token });
856
855
  consola10.success(`Grant ${ids[0]} revoked.`);
857
856
  return;
858
857
  }
859
858
  const operations = ids.map((id) => ({ id, action: "revoke" }));
860
859
  const { results } = await apiFetch(
861
860
  `${grantsUrl}/batch`,
862
- { method: "POST", body: { operations } }
861
+ { method: "POST", body: { operations }, token }
863
862
  );
864
863
  let succeeded = 0;
865
864
  for (const r of results) {
@@ -871,8 +870,7 @@ var revokeCommand = defineCommand11({
871
870
  }
872
871
  }
873
872
  if (succeeded < results.length) {
874
- consola10.info(`Revoked ${succeeded} of ${results.length} grants.`);
875
- process.exit(1);
873
+ throw new CliError(`Revoked ${succeeded} of ${results.length} grants.`);
876
874
  } else {
877
875
  consola10.success(`All ${succeeded} grants revoked.`);
878
876
  }
@@ -881,7 +879,6 @@ var revokeCommand = defineCommand11({
881
879
 
882
880
  // src/commands/grants/token.ts
883
881
  import { defineCommand as defineCommand12 } from "citty";
884
- import consola11 from "consola";
885
882
  var tokenCommand = defineCommand12({
886
883
  meta: {
887
884
  name: "token",
@@ -901,8 +898,7 @@ var tokenCommand = defineCommand12({
901
898
  method: "POST"
902
899
  });
903
900
  if (!result.authz_jwt) {
904
- consola11.error("No token received. Grant may not be approved.");
905
- return process.exit(1);
901
+ throw new CliError("No token received. Grant may not be approved.");
906
902
  }
907
903
  process.stdout.write(result.authz_jwt);
908
904
  }
@@ -910,7 +906,7 @@ var tokenCommand = defineCommand12({
910
906
 
911
907
  // src/commands/grants/delegate.ts
912
908
  import { defineCommand as defineCommand13 } from "citty";
913
- import consola12 from "consola";
909
+ import consola11 from "consola";
914
910
  var delegateCommand = defineCommand13({
915
911
  meta: {
916
912
  name: "delegate",
@@ -944,8 +940,7 @@ var delegateCommand = defineCommand13({
944
940
  async run({ args }) {
945
941
  const auth = loadAuth();
946
942
  if (!auth) {
947
- consola12.error("Not logged in. Run `apes login` first.");
948
- return process.exit(1);
943
+ throw new CliError("Not logged in. Run `apes login` first.");
949
944
  }
950
945
  const idp = getIdpUrl();
951
946
  const delegationsUrl = await getDelegationsEndpoint(idp);
@@ -964,7 +959,7 @@ var delegateCommand = defineCommand13({
964
959
  method: "POST",
965
960
  body
966
961
  });
967
- consola12.success(`Delegation created: ${result.id}`);
962
+ consola11.success(`Delegation created: ${result.id}`);
968
963
  console.log(` Delegate: ${args.to}`);
969
964
  console.log(` Audience: ${args.at}`);
970
965
  if (args.scopes)
@@ -977,7 +972,7 @@ var delegateCommand = defineCommand13({
977
972
 
978
973
  // src/commands/grants/delegations.ts
979
974
  import { defineCommand as defineCommand14 } from "citty";
980
- import consola13 from "consola";
975
+ import consola12 from "consola";
981
976
  var delegationsCommand = defineCommand14({
982
977
  meta: {
983
978
  name: "delegations",
@@ -999,7 +994,7 @@ var delegationsCommand = defineCommand14({
999
994
  return;
1000
995
  }
1001
996
  if (delegations.length === 0) {
1002
- consola13.info("No delegations found.");
997
+ consola12.info("No delegations found.");
1003
998
  return;
1004
999
  }
1005
1000
  for (const d of delegations) {
@@ -1012,7 +1007,7 @@ var delegationsCommand = defineCommand14({
1012
1007
 
1013
1008
  // src/commands/adapter/index.ts
1014
1009
  import { defineCommand as defineCommand15 } from "citty";
1015
- import consola14 from "consola";
1010
+ import consola13 from "consola";
1016
1011
  import {
1017
1012
  fetchRegistry,
1018
1013
  findAdapter,
@@ -1061,7 +1056,7 @@ var adapterCommand = defineCommand15({
1061
1056
  `);
1062
1057
  return;
1063
1058
  }
1064
- consola14.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1059
+ consola13.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
1065
1060
  for (const a of index2.adapters) {
1066
1061
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
1067
1062
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -1083,7 +1078,7 @@ var adapterCommand = defineCommand15({
1083
1078
  return;
1084
1079
  }
1085
1080
  if (local.length === 0) {
1086
- 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.");
1087
1082
  return;
1088
1083
  }
1089
1084
  for (const a of local) {
@@ -1120,20 +1115,20 @@ var adapterCommand = defineCommand15({
1120
1115
  for (const id of ids) {
1121
1116
  const entry = findAdapter(index, id);
1122
1117
  if (!entry) {
1123
- 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.`);
1124
1119
  continue;
1125
1120
  }
1126
1121
  const conflicts = findConflictingAdapters(entry.executable, id);
1127
1122
  if (conflicts.length > 0) {
1128
1123
  for (const c of conflicts) {
1129
- consola14.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
1130
- 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}`);
1131
1126
  }
1132
1127
  }
1133
1128
  const result = await installAdapter(entry, { local });
1134
1129
  const verb = result.updated ? "Updated" : "Installed";
1135
- consola14.success(`${verb} ${result.id} \u2192 ${result.path}`);
1136
- consola14.info(`Digest: ${result.digest}`);
1130
+ consola13.success(`${verb} ${result.id} \u2192 ${result.path}`);
1131
+ consola13.info(`Digest: ${result.digest}`);
1137
1132
  }
1138
1133
  }
1139
1134
  }),
@@ -1160,14 +1155,14 @@ var adapterCommand = defineCommand15({
1160
1155
  let failed = false;
1161
1156
  for (const id of ids) {
1162
1157
  if (removeAdapter(id, local)) {
1163
- consola14.success(`Removed adapter: ${id}`);
1158
+ consola13.success(`Removed adapter: ${id}`);
1164
1159
  } else {
1165
- consola14.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1160
+ consola13.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1166
1161
  failed = true;
1167
1162
  }
1168
1163
  }
1169
1164
  if (failed)
1170
- process.exit(1);
1165
+ throw new CliError("Some adapters could not be removed");
1171
1166
  }
1172
1167
  }),
1173
1168
  info: defineCommand15({
@@ -1244,7 +1239,7 @@ var adapterCommand = defineCommand15({
1244
1239
  return;
1245
1240
  }
1246
1241
  if (results.length === 0) {
1247
- consola14.info(`No adapters matching "${query}"`);
1242
+ consola13.info(`No adapters matching "${query}"`);
1248
1243
  return;
1249
1244
  }
1250
1245
  for (const a of results) {
@@ -1279,29 +1274,29 @@ var adapterCommand = defineCommand15({
1279
1274
  const targetId = args.id ? String(args.id) : void 0;
1280
1275
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
1281
1276
  if (targets.length === 0) {
1282
- consola14.info("No adapters installed to update.");
1277
+ consola13.info("No adapters installed to update.");
1283
1278
  return;
1284
1279
  }
1285
1280
  for (const id of targets) {
1286
1281
  const entry = findAdapter(index, id);
1287
1282
  if (!entry) {
1288
- consola14.warn(`${id}: not found in registry, skipping`);
1283
+ consola13.warn(`${id}: not found in registry, skipping`);
1289
1284
  continue;
1290
1285
  }
1291
1286
  const localDigest = getInstalledDigest(id, false);
1292
1287
  if (localDigest === entry.digest) {
1293
- consola14.info(`${id}: already up to date`);
1288
+ consola13.info(`${id}: already up to date`);
1294
1289
  continue;
1295
1290
  }
1296
1291
  if (localDigest && !args.yes) {
1297
- consola14.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
1298
- consola14.info(` Old: ${localDigest}`);
1299
- consola14.info(` New: ${entry.digest}`);
1300
- 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");
1301
1296
  continue;
1302
1297
  }
1303
1298
  const result = await installAdapter(entry);
1304
- consola14.success(`Updated ${result.id} \u2192 ${result.path}`);
1299
+ consola13.success(`Updated ${result.id} \u2192 ${result.path}`);
1305
1300
  }
1306
1301
  }
1307
1302
  }),
@@ -1338,12 +1333,11 @@ var adapterCommand = defineCommand15({
1338
1333
  if (!localDigest)
1339
1334
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
1340
1335
  if (localDigest === entry.digest) {
1341
- consola14.success(`${id}: digest matches registry`);
1336
+ consola13.success(`${id}: digest matches registry`);
1342
1337
  } else {
1343
- consola14.error(`${id}: digest mismatch`);
1344
1338
  console.log(` Local: ${localDigest}`);
1345
1339
  console.log(` Registry: ${entry.digest}`);
1346
- process.exit(1);
1340
+ throw new CliError(`${id}: digest mismatch`);
1347
1341
  }
1348
1342
  }
1349
1343
  })
@@ -1365,7 +1359,7 @@ import {
1365
1359
  verifyAndExecute,
1366
1360
  waitForGrantStatus
1367
1361
  } from "@openape/shapes";
1368
- import consola15 from "consola";
1362
+ import consola14 from "consola";
1369
1363
  var runCommand = defineCommand16({
1370
1364
  meta: {
1371
1365
  name: "run",
@@ -1440,6 +1434,10 @@ async function runAdapterMode(command, rawArgs, args) {
1440
1434
  const idp = getIdpUrl(args.idp);
1441
1435
  if (!idp)
1442
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
+ }
1443
1441
  const adapterOpt = extractOption(rawArgs, "adapter");
1444
1442
  const loaded = loadAdapter3(command[0], adapterOpt);
1445
1443
  const resolved = await resolveCommand(loaded, command);
@@ -1447,7 +1445,7 @@ async function runAdapterMode(command, rawArgs, args) {
1447
1445
  try {
1448
1446
  const existingGrantId = await findExistingGrant(resolved, idp);
1449
1447
  if (existingGrantId) {
1450
- consola15.info(`Reusing existing grant: ${existingGrantId}`);
1448
+ consola14.info(`Reusing existing grant: ${existingGrantId}`);
1451
1449
  const token2 = await fetchGrantToken(idp, existingGrantId);
1452
1450
  await verifyAndExecute(token2, resolved);
1453
1451
  return;
@@ -1459,17 +1457,17 @@ async function runAdapterMode(command, rawArgs, args) {
1459
1457
  approval,
1460
1458
  ...args.reason ? { reason: args.reason } : {}
1461
1459
  });
1462
- consola15.info(`Grant requested: ${grant.id}`);
1463
- 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}`);
1464
1462
  if (grant.similar_grants?.similar_grants?.length) {
1465
1463
  const n = grant.similar_grants.similar_grants.length;
1466
- consola15.info("");
1467
- 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.`);
1468
1466
  if (grant.similar_grants.widened_details?.length) {
1469
1467
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
1470
- consola15.info(` Broader scope: ${wider}`);
1468
+ consola14.info(` Broader scope: ${wider}`);
1471
1469
  }
1472
- consola15.info("");
1470
+ consola14.info("");
1473
1471
  }
1474
1472
  const status = await waitForGrantStatus(idp, grant.id);
1475
1473
  if (status !== "approved")
@@ -1480,14 +1478,13 @@ async function runAdapterMode(command, rawArgs, args) {
1480
1478
  async function runAudienceMode(audience, action, args) {
1481
1479
  const auth = loadAuth();
1482
1480
  if (!auth) {
1483
- consola15.error("Not logged in. Run `apes login` first.");
1484
- return process.exit(1);
1481
+ throw new CliError("Not logged in. Run `apes login` first.");
1485
1482
  }
1486
1483
  const idp = getIdpUrl(args.idp);
1487
1484
  const grantsUrl = await getGrantsEndpoint(idp);
1488
1485
  const command = action.split(" ");
1489
1486
  const targetHost = args.host || hostname3();
1490
- consola15.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1487
+ consola14.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
1491
1488
  const grant = await apiFetch(grantsUrl, {
1492
1489
  method: "POST",
1493
1490
  body: {
@@ -1500,36 +1497,35 @@ async function runAudienceMode(audience, action, args) {
1500
1497
  ...args.as ? { run_as: args.as } : {}
1501
1498
  }
1502
1499
  });
1503
- consola15.success(`Grant requested: ${grant.id}`);
1504
- consola15.info("Waiting for approval...");
1500
+ consola14.success(`Grant requested: ${grant.id}`);
1501
+ consola14.info("Waiting for approval...");
1505
1502
  const maxWait = 3e5;
1506
1503
  const interval = 3e3;
1507
1504
  const start = Date.now();
1508
1505
  while (Date.now() - start < maxWait) {
1509
1506
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
1510
1507
  if (status.status === "approved") {
1511
- consola15.success("Grant approved!");
1508
+ consola14.success("Grant approved!");
1512
1509
  break;
1513
1510
  }
1514
1511
  if (status.status === "denied" || status.status === "revoked") {
1515
- consola15.error(`Grant ${status.status}.`);
1516
- return process.exit(1);
1512
+ throw new CliError(`Grant ${status.status}.`);
1517
1513
  }
1518
1514
  await new Promise((r) => setTimeout(r, interval));
1519
1515
  }
1520
- consola15.info("Fetching grant token...");
1516
+ consola14.info("Fetching grant token...");
1521
1517
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
1522
1518
  method: "POST"
1523
1519
  });
1524
1520
  if (audience === "escapes") {
1525
- consola15.info(`Executing: ${command.join(" ")}`);
1521
+ consola14.info(`Executing: ${command.join(" ")}`);
1526
1522
  try {
1527
1523
  execFileSync(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
1528
1524
  stdio: "inherit"
1529
1525
  });
1530
1526
  } catch (err) {
1531
1527
  const exitCode = err.status || 1;
1532
- process.exit(exitCode);
1528
+ throw new CliExit(exitCode);
1533
1529
  }
1534
1530
  } else {
1535
1531
  process.stdout.write(authz_jwt);
@@ -1578,7 +1574,7 @@ var explainCommand = defineCommand17({
1578
1574
 
1579
1575
  // src/commands/config/get.ts
1580
1576
  import { defineCommand as defineCommand18 } from "citty";
1581
- import consola16 from "consola";
1577
+ import consola15 from "consola";
1582
1578
  var configGetCommand = defineCommand18({
1583
1579
  meta: {
1584
1580
  name: "get",
@@ -1599,7 +1595,7 @@ var configGetCommand = defineCommand18({
1599
1595
  if (idp)
1600
1596
  console.log(idp);
1601
1597
  else
1602
- consola16.info("No IdP configured.");
1598
+ consola15.info("No IdP configured.");
1603
1599
  break;
1604
1600
  }
1605
1601
  case "email": {
@@ -1607,7 +1603,7 @@ var configGetCommand = defineCommand18({
1607
1603
  if (auth?.email)
1608
1604
  console.log(auth.email);
1609
1605
  else
1610
- consola16.info("Not logged in.");
1606
+ consola15.info("Not logged in.");
1611
1607
  break;
1612
1608
  }
1613
1609
  default: {
@@ -1620,11 +1616,10 @@ var configGetCommand = defineCommand18({
1620
1616
  if (sectionObj && field in sectionObj) {
1621
1617
  console.log(sectionObj[field]);
1622
1618
  } else {
1623
- consola16.info(`Key "${key}" not set.`);
1619
+ consola15.info(`Key "${key}" not set.`);
1624
1620
  }
1625
1621
  } else {
1626
- consola16.error(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
1627
- process.exit(1);
1622
+ throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
1628
1623
  }
1629
1624
  }
1630
1625
  }
@@ -1633,7 +1628,7 @@ var configGetCommand = defineCommand18({
1633
1628
 
1634
1629
  // src/commands/config/set.ts
1635
1630
  import { defineCommand as defineCommand19 } from "citty";
1636
- import consola17 from "consola";
1631
+ import consola16 from "consola";
1637
1632
  var configSetCommand = defineCommand19({
1638
1633
  meta: {
1639
1634
  name: "set",
@@ -1657,8 +1652,7 @@ var configSetCommand = defineCommand19({
1657
1652
  const config = loadConfig();
1658
1653
  const parts = key.split(".");
1659
1654
  if (parts.length !== 2) {
1660
- consola17.error(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
1661
- return process.exit(1);
1655
+ throw new CliError(`Invalid key: "${key}". Use: defaults.idp, defaults.approval, agent.key, agent.email`);
1662
1656
  }
1663
1657
  const [section, field] = parts;
1664
1658
  if (section === "defaults") {
@@ -1668,22 +1662,19 @@ var configSetCommand = defineCommand19({
1668
1662
  config.agent = config.agent || {};
1669
1663
  config.agent[field] = value;
1670
1664
  } else {
1671
- consola17.error(`Unknown section: "${section}". Use: defaults, agent`);
1672
- return process.exit(1);
1665
+ throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
1673
1666
  }
1674
1667
  saveConfig(config);
1675
- consola17.success(`Set ${key} = ${value}`);
1668
+ consola16.success(`Set ${key} = ${value}`);
1676
1669
  }
1677
1670
  });
1678
1671
 
1679
1672
  // src/commands/fetch/index.ts
1680
1673
  import { defineCommand as defineCommand20 } from "citty";
1681
- import consola18 from "consola";
1682
1674
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
1683
1675
  const token = getAuthToken();
1684
1676
  if (!token) {
1685
- consola18.error("Not authenticated. Run `apes login` first.");
1686
- return process.exit(1);
1677
+ throw new CliError("Not authenticated. Run `apes login` first.");
1687
1678
  }
1688
1679
  const response = await fetch(url, {
1689
1680
  method,
@@ -1712,7 +1703,7 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
1712
1703
  }
1713
1704
  }
1714
1705
  if (!response.ok) {
1715
- process.exit(1);
1706
+ throw new CliError(`HTTP ${response.status} ${response.statusText}`);
1716
1707
  }
1717
1708
  }
1718
1709
  var fetchCommand = defineCommand20({
@@ -1810,7 +1801,7 @@ var mcpCommand = defineCommand21({
1810
1801
  if (transport !== "stdio" && transport !== "sse") {
1811
1802
  throw new Error('Transport must be "stdio" or "sse"');
1812
1803
  }
1813
- const { startMcpServer } = await import("./server-5ZRR26S4.js");
1804
+ const { startMcpServer } = await import("./server-IYR5LM63.js");
1814
1805
  await startMcpServer(transport, port);
1815
1806
  }
1816
1807
  });
@@ -1821,7 +1812,7 @@ import { randomBytes } from "crypto";
1821
1812
  import { execFileSync as execFileSync2 } from "child_process";
1822
1813
  import { join } from "path";
1823
1814
  import { defineCommand as defineCommand22 } from "citty";
1824
- import consola19 from "consola";
1815
+ import consola17 from "consola";
1825
1816
  var DEFAULT_IDP_URL = "https://id.openape.at";
1826
1817
  async function downloadTemplate(repo, targetDir) {
1827
1818
  const { downloadTemplate: gigetDownload } = await import("giget");
@@ -1838,16 +1829,16 @@ function installDeps(dir) {
1838
1829
  }
1839
1830
  }
1840
1831
  async function promptChoice(message, choices) {
1841
- const result = await consola19.prompt(message, { type: "select", options: choices });
1832
+ const result = await consola17.prompt(message, { type: "select", options: choices });
1842
1833
  if (typeof result === "symbol") {
1843
- process.exit(0);
1834
+ throw new CliExit(0);
1844
1835
  }
1845
1836
  return result;
1846
1837
  }
1847
1838
  async function promptText(message, defaultValue) {
1848
- 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 });
1849
1840
  if (typeof result === "symbol") {
1850
- process.exit(0);
1841
+ throw new CliExit(0);
1851
1842
  }
1852
1843
  return result || defaultValue || "";
1853
1844
  }
@@ -1894,23 +1885,22 @@ var initCommand = defineCommand22({
1894
1885
  async function initSP(targetDir) {
1895
1886
  const dir = targetDir || "my-app";
1896
1887
  if (existsSync(join(dir, "package.json"))) {
1897
- consola19.error(`Directory "${dir}" already contains a project.`);
1898
- return process.exit(1);
1888
+ throw new CliError(`Directory "${dir}" already contains a project.`);
1899
1889
  }
1900
- consola19.start("Scaffolding SP starter...");
1890
+ consola17.start("Scaffolding SP starter...");
1901
1891
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
1902
- consola19.success("Scaffolded from openape-sp-starter");
1903
- consola19.start("Installing dependencies...");
1892
+ consola17.success("Scaffolded from openape-sp-starter");
1893
+ consola17.start("Installing dependencies...");
1904
1894
  installDeps(dir);
1905
- consola19.success("Dependencies installed");
1895
+ consola17.success("Dependencies installed");
1906
1896
  const envExample = join(dir, ".env.example");
1907
1897
  const envFile = join(dir, ".env");
1908
1898
  if (existsSync(envExample) && !existsSync(envFile)) {
1909
1899
  copyFileSync(envExample, envFile);
1910
- consola19.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1900
+ consola17.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
1911
1901
  }
1912
1902
  console.log("");
1913
- consola19.box([
1903
+ consola17.box([
1914
1904
  `cd ${dir}`,
1915
1905
  "npm run dev",
1916
1906
  "",
@@ -1920,8 +1910,7 @@ async function initSP(targetDir) {
1920
1910
  async function initIdP(targetDir) {
1921
1911
  const dir = targetDir || "my-idp";
1922
1912
  if (existsSync(join(dir, "package.json"))) {
1923
- consola19.error(`Directory "${dir}" already contains a project.`);
1924
- return process.exit(1);
1913
+ throw new CliError(`Directory "${dir}" already contains a project.`);
1925
1914
  }
1926
1915
  const domain = await promptText("Domain for the IdP", "localhost");
1927
1916
  const storage = await promptChoice("Storage backend", [
@@ -1930,15 +1919,15 @@ async function initIdP(targetDir) {
1930
1919
  "s3 (S3-compatible)"
1931
1920
  ]);
1932
1921
  const adminEmail = await promptText("Admin email");
1933
- consola19.start("Scaffolding IdP starter...");
1922
+ consola17.start("Scaffolding IdP starter...");
1934
1923
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
1935
- consola19.success("Scaffolded from openape-idp-starter");
1936
- consola19.start("Installing dependencies...");
1924
+ consola17.success("Scaffolded from openape-idp-starter");
1925
+ consola17.start("Installing dependencies...");
1937
1926
  installDeps(dir);
1938
- consola19.success("Dependencies installed");
1927
+ consola17.success("Dependencies installed");
1939
1928
  const sessionSecret = randomBytes(32).toString("hex");
1940
1929
  const managementToken = randomBytes(32).toString("hex");
1941
- consola19.success("Secrets generated");
1930
+ consola17.success("Secrets generated");
1942
1931
  const isLocalhost = domain === "localhost";
1943
1932
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
1944
1933
  const envContent = [
@@ -1952,10 +1941,11 @@ async function initIdP(targetDir) {
1952
1941
  `NUXT_OPENAPE_RP_ID=${domain}`,
1953
1942
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
1954
1943
  ].join("\n");
1955
- writeFileSync(join(dir, ".env"), envContent + "\n", { mode: 384 });
1956
- consola19.success(".env created");
1944
+ writeFileSync(join(dir, ".env"), `${envContent}
1945
+ `, { mode: 384 });
1946
+ consola17.success(".env created");
1957
1947
  console.log("");
1958
- consola19.box([
1948
+ consola17.box([
1959
1949
  `cd ${dir}`,
1960
1950
  "npm run dev",
1961
1951
  "",
@@ -1978,7 +1968,7 @@ import { generateKeyPairSync, sign } from "crypto";
1978
1968
  import { dirname, resolve } from "path";
1979
1969
  import { homedir } from "os";
1980
1970
  import { defineCommand as defineCommand23 } from "citty";
1981
- import consola20 from "consola";
1971
+ import consola18 from "consola";
1982
1972
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
1983
1973
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
1984
1974
  var POLL_INTERVAL = 3e3;
@@ -2083,38 +2073,48 @@ var enrollCommand = defineCommand23({
2083
2073
  }
2084
2074
  },
2085
2075
  async run({ args }) {
2086
- 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;
2087
- 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
+ });
2088
2084
  if (!agentName) {
2089
- consola20.error("Agent name is required.");
2090
- return process.exit(1);
2085
+ throw new CliError("Agent name is required.");
2091
2086
  }
2092
- 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;
2093
2091
  const resolvedKey = resolvePath(keyPath);
2094
2092
  let publicKey;
2095
2093
  if (existsSync2(resolvedKey)) {
2096
2094
  publicKey = readPublicKey(resolvedKey);
2097
- consola20.success(`Using existing key ${keyPath}`);
2095
+ consola18.success(`Using existing key ${keyPath}`);
2098
2096
  } else {
2099
- consola20.start(`Generating Ed25519 key pair at ${keyPath}...`);
2097
+ consola18.start(`Generating Ed25519 key pair at ${keyPath}...`);
2100
2098
  publicKey = generateAndSaveKey(keyPath);
2101
- consola20.success(`Key pair generated at ${keyPath}`);
2099
+ consola18.success(`Key pair generated at ${keyPath}`);
2102
2100
  }
2103
2101
  const encodedKey = encodeURIComponent(publicKey);
2104
2102
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
2105
- consola20.info("Opening browser for enrollment...");
2106
- consola20.info(`\u2192 ${idp}/enroll`);
2103
+ consola18.info("Opening browser for enrollment...");
2104
+ consola18.info(`\u2192 ${idp}/enroll`);
2107
2105
  openBrowser2(enrollUrl);
2108
2106
  console.log("");
2109
- const agentEmail = await consola20.prompt(
2107
+ const agentEmail = await consola18.prompt(
2110
2108
  "Agent email (shown in browser after enrollment)",
2111
2109
  { type: "text", placeholder: `agent+${agentName}@...` }
2112
- ).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
+ });
2113
2114
  if (!agentEmail) {
2114
- consola20.error("Agent email is required to verify enrollment.");
2115
- return process.exit(1);
2115
+ throw new CliError("Agent email is required to verify enrollment.");
2116
2116
  }
2117
- consola20.start("Verifying enrollment...");
2117
+ consola18.start("Verifying enrollment...");
2118
2118
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
2119
2119
  saveAuth({
2120
2120
  idp,
@@ -2126,16 +2126,16 @@ var enrollCommand = defineCommand23({
2126
2126
  config.defaults = { ...config.defaults, idp };
2127
2127
  config.agent = { key: keyPath, email: agentEmail };
2128
2128
  saveConfig(config);
2129
- consola20.success(`Agent enrolled as ${agentEmail}`);
2130
- consola20.success("Config saved to ~/.config/apes/");
2129
+ consola18.success(`Agent enrolled as ${agentEmail}`);
2130
+ consola18.success("Config saved to ~/.config/apes/");
2131
2131
  console.log("");
2132
- consola20.info("Verify with: apes whoami");
2132
+ consola18.info("Verify with: apes whoami");
2133
2133
  }
2134
2134
  });
2135
2135
 
2136
2136
  // src/commands/dns-check.ts
2137
2137
  import { defineCommand as defineCommand24 } from "citty";
2138
- import consola21 from "consola";
2138
+ import consola19 from "consola";
2139
2139
  import { resolveDDISA } from "@openape/core";
2140
2140
  var dnsCheckCommand = defineCommand24({
2141
2141
  meta: {
@@ -2151,17 +2151,16 @@ var dnsCheckCommand = defineCommand24({
2151
2151
  },
2152
2152
  async run({ args }) {
2153
2153
  const domain = args.domain;
2154
- consola21.start(`Checking _ddisa.${domain}...`);
2154
+ consola19.start(`Checking _ddisa.${domain}...`);
2155
2155
  try {
2156
2156
  const result = await resolveDDISA(domain);
2157
2157
  if (!result) {
2158
- consola21.error(`No DDISA record found for ${domain}`);
2159
2158
  console.log("");
2160
2159
  console.log("To set up DDISA, add a DNS TXT record:");
2161
2160
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
2162
- return process.exit(1);
2161
+ throw new CliError(`No DDISA record found for ${domain}`);
2163
2162
  }
2164
- consola21.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2163
+ consola19.success(`_ddisa.${domain} \u2192 ${result.idp}`);
2165
2164
  console.log("");
2166
2165
  console.log(` Version: ${result.version || "ddisa1"}`);
2167
2166
  console.log(` IdP URL: ${result.idp}`);
@@ -2170,14 +2169,14 @@ var dnsCheckCommand = defineCommand24({
2170
2169
  if (result.priority !== void 0)
2171
2170
  console.log(` Priority: ${result.priority}`);
2172
2171
  console.log("");
2173
- consola21.start(`Verifying IdP at ${result.idp}...`);
2172
+ consola19.start(`Verifying IdP at ${result.idp}...`);
2174
2173
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
2175
2174
  if (!discoResp.ok) {
2176
- 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}?`);
2177
2176
  return;
2178
2177
  }
2179
2178
  const disco = await discoResp.json();
2180
- consola21.success(`IdP is reachable`);
2179
+ consola19.success(`IdP is reachable`);
2181
2180
  console.log(` Issuer: ${disco.issuer}`);
2182
2181
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
2183
2182
  if (disco.ddisa_auth_methods_supported) {
@@ -2187,15 +2186,130 @@ var dnsCheckCommand = defineCommand24({
2187
2186
  console.log(` Grants: ${disco.openape_grant_types_supported.join(", ")}`);
2188
2187
  }
2189
2188
  } catch (err) {
2190
- consola21.error(`DNS check failed: ${err instanceof Error ? err.message : String(err)}`);
2191
- 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
2192
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}`);
2299
+ }
2300
+ console.log(`
2301
+ Show a guide: apes workflows <id>
2302
+ `);
2193
2303
  }
2194
2304
  });
2195
2305
 
2196
2306
  // src/cli.ts
2307
+ process.stdout.on("error", (err) => {
2308
+ if (err.code === "EPIPE") process.exit(0);
2309
+ throw err;
2310
+ });
2197
2311
  var debug = process.argv.includes("--debug");
2198
- var grantsCommand = defineCommand25({
2312
+ var grantsCommand = defineCommand26({
2199
2313
  meta: {
2200
2314
  name: "grants",
2201
2315
  description: "Grant management"
@@ -2214,7 +2328,7 @@ var grantsCommand = defineCommand25({
2214
2328
  delegations: delegationsCommand
2215
2329
  }
2216
2330
  });
2217
- var configCommand = defineCommand25({
2331
+ var configCommand = defineCommand26({
2218
2332
  meta: {
2219
2333
  name: "config",
2220
2334
  description: "Configuration management"
@@ -2224,10 +2338,10 @@ var configCommand = defineCommand25({
2224
2338
  set: configSetCommand
2225
2339
  }
2226
2340
  });
2227
- var main = defineCommand25({
2341
+ var main = defineCommand26({
2228
2342
  meta: {
2229
2343
  name: "apes",
2230
- version: "0.5.4",
2344
+ version: "0.6.0",
2231
2345
  description: "Unified CLI for OpenApe"
2232
2346
  },
2233
2347
  subCommands: {
@@ -2243,14 +2357,22 @@ var main = defineCommand25({
2243
2357
  adapter: adapterCommand,
2244
2358
  config: configCommand,
2245
2359
  fetch: fetchCommand,
2246
- mcp: mcpCommand
2360
+ mcp: mcpCommand,
2361
+ workflows: workflowsCommand
2247
2362
  }
2248
2363
  });
2249
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
+ }
2250
2372
  if (debug) {
2251
- consola22.error(err);
2373
+ consola21.error(err);
2252
2374
  } else {
2253
- 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));
2254
2376
  }
2255
2377
  process.exit(1);
2256
2378
  });