@openape/apes 0.9.4 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR
4
- } from "./chunk-ILKZ5HGV.js";
4
+ } from "./chunk-6JSOSD7R.js";
5
5
 
6
6
  // src/auth-lock.ts
7
7
  import { open, rm, stat } from "fs/promises";
@@ -38,4 +38,4 @@ export {
38
38
  acquireAuthLock,
39
39
  releaseAuthLock
40
40
  };
41
- //# sourceMappingURL=auth-lock-6GGWZVOA.js.map
41
+ //# sourceMappingURL=auth-lock-O7BTENTJ.js.map
@@ -142,4 +142,4 @@ export {
142
142
  getAuthToken,
143
143
  getRequesterIdentity
144
144
  };
145
- //# sourceMappingURL=chunk-ILKZ5HGV.js.map
145
+ //# sourceMappingURL=chunk-6JSOSD7R.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\nexport interface AuthData {\n idp: string\n access_token: string\n refresh_token?: string\n email: string\n expires_at: number\n}\n\nexport interface ApesConfig {\n defaults?: {\n idp?: string\n approval?: string\n /**\n * Audience for the `apes run` async info block. `agent` (default)\n * emits verbose agent-facing instructions with a polling protocol;\n * `human` emits a short friendly block. Env var `APES_USER` wins.\n */\n user?: 'agent' | 'human'\n /**\n * Poll interval (seconds) embedded in the agent-mode instructions.\n * Default 10. Env var `APES_GRANT_POLL_INTERVAL` wins. Stored as a\n * string in TOML because the hand-rolled parser only handles quoted\n * values — casting to number happens at read time.\n */\n grant_poll_interval_seconds?: string\n /**\n * Maximum poll duration (minutes) embedded in the agent-mode\n * instructions. Default 5. Env var `APES_GRANT_POLL_MAX_MINUTES` wins.\n */\n grant_poll_max_minutes?: string\n /**\n * Exit code emitted by `apes run` / `ape-shell -c` when the async\n * default path creates a pending grant. Default `75` (`EX_TEMPFAIL`\n * from sysexits.h — \"temporary failure, retry later\"). Set to `0`\n * to restore the pre-0.10.0 exit-0 behaviour. Env var\n * `APES_ASYNC_EXIT_CODE` wins. Valid range 0–255.\n */\n async_exit_code?: string\n }\n agent?: {\n key?: string\n email?: string\n }\n notifications?: {\n pending_command?: string\n }\n}\n\nconst CONFIG_DIR = join(homedir(), '.config', 'apes')\nconst AUTH_FILE = join(CONFIG_DIR, 'auth.json')\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.toml')\n\nfunction ensureDir() {\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true })\n }\n}\n\nexport function loadAuth(): AuthData | null {\n if (!existsSync(AUTH_FILE))\n return null\n try {\n return JSON.parse(readFileSync(AUTH_FILE, 'utf-8'))\n }\n catch {\n return null\n }\n}\n\nexport function saveAuth(data: AuthData): void {\n ensureDir()\n writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 })\n}\n\nexport function clearAuth(): void {\n if (existsSync(AUTH_FILE)) {\n writeFileSync(AUTH_FILE, '', { mode: 0o600 })\n }\n // Also wipe the [agent] section from config.toml so logout disables\n // auto-refresh. Preserves [defaults] so the IdP URL stays configured.\n if (existsSync(CONFIG_FILE)) {\n const existing = loadConfig()\n if (existing.agent) {\n const { agent: _removed, ...rest } = existing\n saveConfig(rest)\n }\n }\n}\n\nexport function loadConfig(): ApesConfig {\n if (!existsSync(CONFIG_FILE))\n return {}\n try {\n return parseTOML(readFileSync(CONFIG_FILE, 'utf-8'))\n }\n catch {\n return {}\n }\n}\n\nfunction parseTOML(content: string): ApesConfig {\n const config: ApesConfig = {}\n let section = ''\n\n for (const line of content.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed || trimmed.startsWith('#'))\n continue\n\n const sectionMatch = trimmed.match(/^\\[(.+)\\]$/)\n if (sectionMatch) {\n section = sectionMatch[1]!\n continue\n }\n\n const kvMatch = trimmed.match(/^(\\w+)\\s*=\\s*\"(.+)\"$/)\n if (kvMatch) {\n const [, key, value] = kvMatch\n if (section === 'defaults') {\n config.defaults = config.defaults || {}\n ;(config.defaults as Record<string, string>)[key!] = value!\n }\n else if (section === 'agent') {\n config.agent = config.agent || {}\n ;(config.agent as Record<string, string>)[key!] = value!\n }\n else if (section === 'notifications') {\n config.notifications = config.notifications || {}\n ;(config.notifications as Record<string, string>)[key!] = value!\n }\n }\n }\n\n return config\n}\n\nexport function saveConfig(config: ApesConfig): void {\n ensureDir()\n const lines: string[] = []\n\n if (config.defaults) {\n lines.push('[defaults]')\n for (const [key, value] of Object.entries(config.defaults)) {\n if (value)\n lines.push(`${key} = \"${value}\"`)\n }\n lines.push('')\n }\n\n if (config.agent) {\n lines.push('[agent]')\n for (const [key, value] of Object.entries(config.agent)) {\n if (value)\n lines.push(`${key} = \"${value}\"`)\n }\n lines.push('')\n }\n\n if (config.notifications) {\n lines.push('[notifications]')\n for (const [key, value] of Object.entries(config.notifications)) {\n if (value)\n lines.push(`${key} = \"${value}\"`)\n }\n lines.push('')\n }\n\n writeFileSync(CONFIG_FILE, lines.join('\\n'), { mode: 0o600 })\n}\n\nexport function getIdpUrl(explicit?: string): string | null {\n if (explicit)\n return explicit\n if (process.env.APES_IDP)\n return process.env.APES_IDP\n\n const auth = loadAuth()\n if (auth?.idp)\n return auth.idp\n\n const config = loadConfig()\n if (config.defaults?.idp)\n return config.defaults.idp\n\n return null\n}\n\nexport function getAuthToken(): string | null {\n const auth = loadAuth()\n if (!auth)\n return null\n\n // Check expiry (with 30s buffer)\n if (auth.expires_at && Date.now() / 1000 > auth.expires_at - 30) {\n return null // expired\n }\n\n return auth.access_token\n}\n\nexport function getRequesterIdentity(): string | null {\n return loadAuth()?.email ?? null\n}\n\nexport { CONFIG_DIR, AUTH_FILE }\n"],"mappings":"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","names":[]}
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  loadConfig
4
- } from "./chunk-ILKZ5HGV.js";
4
+ } from "./chunk-6JSOSD7R.js";
5
5
 
6
6
  // src/notifications.ts
7
7
  import { spawn } from "child_process";
@@ -63,4 +63,4 @@ export {
63
63
  notifyGrantPending,
64
64
  isApesSelfDispatch
65
65
  };
66
- //# sourceMappingURL=chunk-ZHTLP2DD.js.map
66
+ //# sourceMappingURL=chunk-EAXBC4KC.js.map
@@ -5,7 +5,7 @@ import {
5
5
  loadAuth,
6
6
  loadConfig,
7
7
  saveAuth
8
- } from "./chunk-ILKZ5HGV.js";
8
+ } from "./chunk-6JSOSD7R.js";
9
9
 
10
10
  // src/http.ts
11
11
  import consola from "consola";
@@ -104,7 +104,7 @@ async function refreshOAuthToken() {
104
104
  const auth = loadAuth();
105
105
  if (!auth?.refresh_token)
106
106
  return null;
107
- const { acquireAuthLock, releaseAuthLock } = await import("./auth-lock-6GGWZVOA.js");
107
+ const { acquireAuthLock, releaseAuthLock } = await import("./auth-lock-O7BTENTJ.js");
108
108
  const lock = await acquireAuthLock({ timeoutMs: 5e3 });
109
109
  if (!lock) {
110
110
  return getAuthToken();
@@ -1302,4 +1302,4 @@ export {
1302
1302
  buildExactCommandGrantRequest,
1303
1303
  buildStructuredCliGrantRequest
1304
1304
  };
1305
- //# sourceMappingURL=chunk-D3OMN7RV.js.map
1305
+ //# sourceMappingURL=chunk-U4CI2RBO.js.map
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  import {
12
12
  isApesSelfDispatch,
13
13
  notifyGrantPending
14
- } from "./chunk-ZHTLP2DD.js";
14
+ } from "./chunk-EAXBC4KC.js";
15
15
  import {
16
16
  ApiError,
17
17
  apiFetch,
@@ -42,7 +42,7 @@ import {
42
42
  searchAdapters,
43
43
  verifyAndExecute,
44
44
  waitForGrantStatus
45
- } from "./chunk-D3OMN7RV.js";
45
+ } from "./chunk-U4CI2RBO.js";
46
46
  import {
47
47
  AUTH_FILE,
48
48
  CONFIG_DIR,
@@ -53,7 +53,7 @@ import {
53
53
  loadConfig,
54
54
  saveAuth,
55
55
  saveConfig
56
- } from "./chunk-ILKZ5HGV.js";
56
+ } from "./chunk-6JSOSD7R.js";
57
57
 
58
58
  // src/cli.ts
59
59
  import consola27 from "consola";
@@ -1064,6 +1064,59 @@ var revokeCommand = defineCommand11({
1064
1064
  import { execFileSync } from "child_process";
1065
1065
  import { defineCommand as defineCommand12 } from "citty";
1066
1066
  import consola12 from "consola";
1067
+
1068
+ // src/grant-poll.ts
1069
+ function getPollIntervalSeconds() {
1070
+ const envValue = process.env.APES_GRANT_POLL_INTERVAL;
1071
+ if (envValue) {
1072
+ const n = Number(envValue);
1073
+ if (Number.isFinite(n) && n > 0)
1074
+ return Math.floor(n);
1075
+ }
1076
+ const cfg = loadConfig();
1077
+ const cfgValue = cfg.defaults?.grant_poll_interval_seconds;
1078
+ if (cfgValue) {
1079
+ const n = Number(cfgValue);
1080
+ if (Number.isFinite(n) && n > 0)
1081
+ return Math.floor(n);
1082
+ }
1083
+ return 10;
1084
+ }
1085
+ function getPollMaxMinutes() {
1086
+ const envValue = process.env.APES_GRANT_POLL_MAX_MINUTES;
1087
+ if (envValue) {
1088
+ const n = Number(envValue);
1089
+ if (Number.isFinite(n) && n > 0)
1090
+ return Math.floor(n);
1091
+ }
1092
+ const cfg = loadConfig();
1093
+ const cfgValue = cfg.defaults?.grant_poll_max_minutes;
1094
+ if (cfgValue) {
1095
+ const n = Number(cfgValue);
1096
+ if (Number.isFinite(n) && n > 0)
1097
+ return Math.floor(n);
1098
+ }
1099
+ return 5;
1100
+ }
1101
+ async function pollGrantUntilResolved(idp, grantId) {
1102
+ const grantsEndpoint = await getGrantsEndpoint(idp);
1103
+ const intervalSec = getPollIntervalSeconds();
1104
+ const maxMinutes = getPollMaxMinutes();
1105
+ const maxMs = maxMinutes * 6e4;
1106
+ const intervalMs = intervalSec * 1e3;
1107
+ const start = Date.now();
1108
+ while (Date.now() - start < maxMs) {
1109
+ const grant = await apiFetch(`${grantsEndpoint}/${grantId}`);
1110
+ if (grant.status === "approved")
1111
+ return { kind: "approved" };
1112
+ if (grant.status === "denied" || grant.status === "revoked" || grant.status === "used")
1113
+ return { kind: "terminal", status: grant.status };
1114
+ await new Promise((r) => setTimeout(r, intervalMs));
1115
+ }
1116
+ return { kind: "timeout" };
1117
+ }
1118
+
1119
+ // src/commands/grants/run.ts
1067
1120
  var runGrantCommand = defineCommand12({
1068
1121
  meta: {
1069
1122
  name: "run",
@@ -1079,6 +1132,11 @@ var runGrantCommand = defineCommand12({
1079
1132
  type: "string",
1080
1133
  description: "Path to escapes binary (audience=escapes only)",
1081
1134
  default: "escapes"
1135
+ },
1136
+ wait: {
1137
+ type: "boolean",
1138
+ description: "If the grant is pending, block and poll until approved (or denied/revoked/used/timeout). Reuses APES_GRANT_POLL_INTERVAL / APES_GRANT_POLL_MAX_MINUTES knobs.",
1139
+ default: false
1082
1140
  }
1083
1141
  },
1084
1142
  async run({ args }) {
@@ -1086,9 +1144,29 @@ var runGrantCommand = defineCommand12({
1086
1144
  if (!idp)
1087
1145
  throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
1088
1146
  const grantsUrl = await getGrantsEndpoint(idp);
1089
- const grant = await apiFetch(`${grantsUrl}/${args.id}`);
1090
- if (grant.status === "pending")
1091
- throw new CliError(`Grant ${grant.id} is still pending. Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
1147
+ let grant = await apiFetch(`${grantsUrl}/${args.id}`);
1148
+ if (grant.status === "pending") {
1149
+ if (!args.wait) {
1150
+ throw new CliError(
1151
+ `Grant ${grant.id} is still pending. Approve at: ${idp}/grant-approval?grant_id=${grant.id}`
1152
+ );
1153
+ }
1154
+ const maxMinutes = getPollMaxMinutes();
1155
+ consola12.info(`Waiting for grant ${grant.id} approval (up to ${maxMinutes} minute${maxMinutes === 1 ? "" : "s"})...`);
1156
+ const outcome = await pollGrantUntilResolved(idp, grant.id);
1157
+ if (outcome.kind === "timeout") {
1158
+ throw new CliError(
1159
+ `Grant ${grant.id} approval timed out after ${maxMinutes} minute${maxMinutes === 1 ? "" : "s"}. Re-run after approval, or extend the timeout via APES_GRANT_POLL_MAX_MINUTES.`
1160
+ );
1161
+ }
1162
+ if (outcome.kind === "terminal") {
1163
+ throw new CliError(
1164
+ `Grant ${grant.id} resolved to ${outcome.status}. Request a new one.`
1165
+ );
1166
+ }
1167
+ grant = await apiFetch(`${grantsUrl}/${args.id}`);
1168
+ consola12.info(`Grant ${grant.id} approved \u2014 continuing`);
1169
+ }
1092
1170
  if (grant.status === "denied" || grant.status === "revoked")
1093
1171
  throw new CliError(`Grant ${grant.id} is ${grant.status}. Request a new one.`);
1094
1172
  if (grant.status === "used")
@@ -1940,37 +2018,21 @@ function getUserMode() {
1940
2018
  return "human";
1941
2019
  return "agent";
1942
2020
  }
1943
- function getPollIntervalSeconds() {
1944
- const envValue = process.env.APES_GRANT_POLL_INTERVAL;
1945
- if (envValue) {
2021
+ function getAsyncExitCode() {
2022
+ const envValue = process.env.APES_ASYNC_EXIT_CODE;
2023
+ if (envValue !== void 0 && envValue !== "") {
1946
2024
  const n = Number(envValue);
1947
- if (Number.isFinite(n) && n > 0)
2025
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
1948
2026
  return Math.floor(n);
1949
2027
  }
1950
2028
  const cfg = loadConfig();
1951
- const cfgValue = cfg.defaults?.grant_poll_interval_seconds;
1952
- if (cfgValue) {
2029
+ const cfgValue = cfg.defaults?.async_exit_code;
2030
+ if (cfgValue !== void 0 && cfgValue !== "") {
1953
2031
  const n = Number(cfgValue);
1954
- if (Number.isFinite(n) && n > 0)
2032
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
1955
2033
  return Math.floor(n);
1956
2034
  }
1957
- return 10;
1958
- }
1959
- function getPollMaxMinutes() {
1960
- const envValue = process.env.APES_GRANT_POLL_MAX_MINUTES;
1961
- if (envValue) {
1962
- const n = Number(envValue);
1963
- if (Number.isFinite(n) && n > 0)
1964
- return Math.floor(n);
1965
- }
1966
- const cfg = loadConfig();
1967
- const cfgValue = cfg.defaults?.grant_poll_max_minutes;
1968
- if (cfgValue) {
1969
- const n = Number(cfgValue);
1970
- if (Number.isFinite(n) && n > 0)
1971
- return Math.floor(n);
1972
- }
1973
- return 5;
2035
+ return 75;
1974
2036
  }
1975
2037
  function printPendingGrantInfo(grant, idp) {
1976
2038
  const mode = getUserMode();
@@ -1987,17 +2049,25 @@ function printPendingGrantInfo(grant, idp) {
1987
2049
  console.log(" this grant without re-approval on the next invocation.");
1988
2050
  return;
1989
2051
  }
1990
- const pollSec = getPollIntervalSeconds();
1991
2052
  const maxMin = getPollMaxMinutes();
1992
2053
  consola19.success(`Grant ${grant.id} created (pending approval)`);
1993
2054
  console.log(` Approve: ${approveUrl}`);
1994
2055
  console.log(` Status: ${statusCmd} [--json]`);
1995
- console.log(` Execute: ${executeCmd}`);
2056
+ console.log(` Execute: ${executeCmd} --wait`);
1996
2057
  console.log("");
1997
- console.log(` For agents: poll \`${statusCmd} --json\` every ${pollSec}s, wait up to ${maxMin} minutes.`);
1998
- console.log(` When .status == "approved", run \`${executeCmd}\` to execute.`);
1999
- console.log(` On "denied" or "revoked", stop and report to the user.`);
2000
- console.log(` On timeout, stop and notify the user that approval has not happened.`);
2058
+ console.log(" For agents:");
2059
+ console.log(` 1. Tell the user about the pending grant and the approve URL above.`);
2060
+ console.log(` 2. Run \`${executeCmd} --wait\`. This blocks up to ${maxMin} minutes`);
2061
+ console.log(` until the user approves (or denies/timeout) and then executes`);
2062
+ console.log(` the command in a single step. The CLI handles the polling loop`);
2063
+ console.log(` internally \u2014 you do not need to poll the status yourself.`);
2064
+ console.log(` 3. Exit 0 means approved + executed; stdout is the command output.`);
2065
+ console.log(` Exit 75 (pending) only appears if you accidentally call this`);
2066
+ console.log(` without --wait. Any other non-zero exit means denied, revoked,`);
2067
+ console.log(` used, or timeout \u2014 report the reason to the user.`);
2068
+ console.log("");
2069
+ console.log(' Note: exit code 75 (EX_TEMPFAIL) from this command means "pending,');
2070
+ console.log(' retry later" \u2014 do not abort your workflow, follow the steps above.');
2001
2071
  console.log("");
2002
2072
  console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
2003
2073
  console.log(" grant be reused on subsequent invocations without re-approval.");
@@ -2139,6 +2209,7 @@ async function runShellMode(command, args) {
2139
2209
  return;
2140
2210
  }
2141
2211
  printPendingGrantInfo(grant, idp);
2212
+ throw new CliExit(getAsyncExitCode());
2142
2213
  }
2143
2214
  async function tryAdapterModeFromShell(command, idp, args) {
2144
2215
  const cmdString = extractShellCommandString(command);
@@ -2196,7 +2267,7 @@ async function tryAdapterModeFromShell(command, idp, args) {
2196
2267
  return true;
2197
2268
  }
2198
2269
  printPendingGrantInfo(grant, idp);
2199
- return true;
2270
+ throw new CliExit(getAsyncExitCode());
2200
2271
  }
2201
2272
  function execShellCommand(command) {
2202
2273
  if (command.length === 0)
@@ -2276,6 +2347,7 @@ async function runAdapterMode(command, rawArgs, args) {
2276
2347
  return;
2277
2348
  }
2278
2349
  printPendingGrantInfo(grant, idp);
2350
+ throw new CliExit(getAsyncExitCode());
2279
2351
  }
2280
2352
  async function runAudienceMode(audience, action, args) {
2281
2353
  const auth = loadAuth();
@@ -2301,7 +2373,7 @@ async function runAudienceMode(audience, action, args) {
2301
2373
  });
2302
2374
  if (!shouldWaitForGrant(args)) {
2303
2375
  printPendingGrantInfo(grant, idp);
2304
- return;
2376
+ throw new CliExit(getAsyncExitCode());
2305
2377
  }
2306
2378
  consola19.success(`Grant requested: ${grant.id}`);
2307
2379
  consola19.info("Waiting for approval...");
@@ -2608,7 +2680,7 @@ var mcpCommand = defineCommand26({
2608
2680
  if (transport !== "stdio" && transport !== "sse") {
2609
2681
  throw new Error('Transport must be "stdio" or "sse"');
2610
2682
  }
2611
- const { startMcpServer } = await import("./server-ED5MMYT3.js");
2683
+ const { startMcpServer } = await import("./server-GTATJDYX.js");
2612
2684
  await startMcpServer(transport, port);
2613
2685
  }
2614
2686
  });
@@ -3100,7 +3172,7 @@ async function bestEffortGrantCount(idp) {
3100
3172
  }
3101
3173
  }
3102
3174
  async function runHealth(args) {
3103
- const version = true ? "0.9.4" : "0.0.0";
3175
+ const version = true ? "0.10.1" : "0.0.0";
3104
3176
  const auth = loadAuth();
3105
3177
  if (!auth) {
3106
3178
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -3302,10 +3374,10 @@ if (shellRewrite) {
3302
3374
  if (shellRewrite.action === "rewrite") {
3303
3375
  process.argv = shellRewrite.argv;
3304
3376
  } else if (shellRewrite.action === "version") {
3305
- console.log(`ape-shell ${"0.9.4"} (OpenApe DDISA shell wrapper)`);
3377
+ console.log(`ape-shell ${"0.10.1"} (OpenApe DDISA shell wrapper)`);
3306
3378
  process.exit(0);
3307
3379
  } else if (shellRewrite.action === "help") {
3308
- console.log(`ape-shell ${"0.9.4"} \u2014 OpenApe DDISA shell wrapper`);
3380
+ console.log(`ape-shell ${"0.10.1"} \u2014 OpenApe DDISA shell wrapper`);
3309
3381
  console.log("");
3310
3382
  console.log("Usage:");
3311
3383
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -3320,7 +3392,7 @@ if (shellRewrite) {
3320
3392
  console.log(" --help, -h Show this help message");
3321
3393
  process.exit(0);
3322
3394
  } else if (shellRewrite.action === "interactive") {
3323
- const { runInteractiveShell } = await import("./orchestrator-5EZD7ZQE.js");
3395
+ const { runInteractiveShell } = await import("./orchestrator-QL3AT67U.js");
3324
3396
  await runInteractiveShell();
3325
3397
  process.exit(0);
3326
3398
  } else {
@@ -3363,7 +3435,7 @@ var configCommand = defineCommand33({
3363
3435
  var main = defineCommand33({
3364
3436
  meta: {
3365
3437
  name: "apes",
3366
- version: "0.9.4",
3438
+ version: "0.10.1",
3367
3439
  description: "Unified CLI for OpenApe"
3368
3440
  },
3369
3441
  subCommands: {