@openape/apes 0.9.3 → 0.10.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.
@@ -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";
@@ -44,7 +44,23 @@ function notifyGrantPending(info) {
44
44
  }
45
45
  }
46
46
 
47
+ // src/shell/apes-self-dispatch.ts
48
+ import { basename } from "path";
49
+ var APES_GATED_SUBCOMMANDS = /* @__PURE__ */ new Set(["run", "fetch", "mcp"]);
50
+ function isApesSelfDispatch(parsed) {
51
+ if (!parsed || parsed.isCompound)
52
+ return false;
53
+ const invokedName = basename(parsed.executable);
54
+ if (invokedName !== "apes" && invokedName !== "apes.js")
55
+ return false;
56
+ const subCommand = parsed.argv[0];
57
+ if (!subCommand)
58
+ return false;
59
+ return !APES_GATED_SUBCOMMANDS.has(subCommand);
60
+ }
61
+
47
62
  export {
48
- notifyGrantPending
63
+ notifyGrantPending,
64
+ isApesSelfDispatch
49
65
  };
50
- //# sourceMappingURL=chunk-5FV5KXEX.js.map
66
+ //# sourceMappingURL=chunk-EAXBC4KC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/notifications.ts","../src/shell/apes-self-dispatch.ts"],"sourcesContent":["import { spawn } from 'node:child_process'\nimport consola from 'consola'\nimport { quote } from 'shell-quote'\nimport { loadConfig } from './config'\n\nexport interface PendingGrantInfo {\n grantId: string\n approveUrl: string\n command: string\n audience: string\n host: string\n}\n\n/**\n * Resolve the notification command for pending grants. Checks (in order):\n * 1. `APES_NOTIFY_PENDING_COMMAND` env var (highest priority — lets\n * parent programs like openclaw override per invocation)\n * 2. `[notifications] pending_command` in ~/.config/apes/config.toml\n *\n * Returns undefined if no notification command is configured.\n */\nfunction resolvePendingCommand(): string | undefined {\n if (process.env.APES_NOTIFY_PENDING_COMMAND)\n return process.env.APES_NOTIFY_PENDING_COMMAND\n\n const config = loadConfig()\n return config.notifications?.pending_command\n}\n\n/**\n * Escape a value for safe embedding inside a single-quoted shell string.\n * We use `shell-quote` to produce a safe literal, then strip the outer\n * quoting because the template substitution embeds the value inside the\n * user's command template which is itself passed to `sh -c`.\n */\nfunction shellEscape(value: string): string {\n // quote() wraps in single quotes and escapes internal single quotes\n // e.g. \"it's\" → \"'it'\\\\''s'\"\n // We return the raw escaped form so it's safe inside sh -c.\n return quote([value])\n}\n\n/**\n * Substitute template variables in the notification command.\n * All values are shell-escaped to prevent injection.\n */\nfunction renderTemplate(template: string, info: PendingGrantInfo): string {\n return template\n .replace(/\\{grant_id\\}/g, shellEscape(info.grantId))\n .replace(/\\{approve_url\\}/g, shellEscape(info.approveUrl))\n .replace(/\\{command\\}/g, shellEscape(info.command))\n .replace(/\\{audience\\}/g, shellEscape(info.audience))\n .replace(/\\{host\\}/g, shellEscape(info.host))\n}\n\n/**\n * Send a notification that a grant is awaiting human approval.\n *\n * This is **fire-and-forget**: the notification subprocess runs detached\n * and unref'd so it cannot block the grant flow. A 10-second timeout\n * kills it if it hangs (e.g. network issue reaching Telegram API).\n *\n * Only fires when a notification command is configured. Silently returns\n * if not — the grant flow must never depend on notifications.\n *\n * Only call this when the grant **actually requires waiting** (new grant\n * with pending status). Do NOT call when:\n * - An existing timed/always grant was reused (no human action needed)\n * - The grant was instantly approved (no waiting phase)\n */\nexport function notifyGrantPending(info: PendingGrantInfo): void {\n const template = resolvePendingCommand()\n if (!template)\n return\n\n const rendered = renderTemplate(template, info)\n\n try {\n const child = spawn('sh', ['-c', rendered], {\n detached: true,\n stdio: 'ignore',\n env: { ...process.env },\n })\n\n // Don't let the notification process keep the parent alive\n child.unref()\n\n // Kill after 10 seconds if it hasn't exited\n const timeout = setTimeout(() => {\n try {\n child.kill('SIGKILL')\n }\n catch {}\n }, 10_000)\n timeout.unref()\n\n child.on('exit', () => clearTimeout(timeout))\n }\n catch (err) {\n // Never let notification failure break the grant flow\n consola.debug('Notification command failed:', err)\n }\n}\n","import { basename } from 'node:path'\nimport type { ParsedShellCommand } from '../shapes/shell-parser.js'\n\n/**\n * Subset of `apes` subcommands that remain grant-gated even when invoked\n * as self-dispatches from inside an ape-shell context. These are the\n * three categories where the shell-grant layer adds real security value\n * that isn't duplicated by server-side auth gates or local-file-only\n * semantics:\n *\n * - `run` — spawns arbitrary executables, the core of the grant system\n * - `fetch` — forwards the bearer token to a user-specified URL\n * - `mcp` — binds a network port and serves a persistent API\n *\n * Every other `apes <subcmd>` either reads state, mutates the user's own\n * local config, or talks to the IdP through endpoints that are already\n * scoped by the auth token — gating them in the shell is redundant\n * friction, and under 0.9.0's async-default grant flow it actively\n * breaks `apes grants run <id>` via recursion (the polling call itself\n * creates a new grant, cascading indefinitely).\n *\n * This is the single source of truth shared by both dispatch paths:\n * - Interactive REPL: `shell/grant-dispatch.ts` → `requestGrantForShellLine`\n * - One-shot `ape-shell -c`: `commands/run.ts` → `runShellMode` (which\n * receives the bash-c-wrapped command after `rewriteApeShellArgs`\n * rewrites `ape-shell -c \"<cmd>\"` into `apes run --shell -- bash -c <cmd>`)\n *\n * Keep this list in sync with the blocklist snapshot test in\n * `shell-grant-dispatch.test.ts` — the tripwire that forces a review\n * decision whenever a new top-level apes subcommand is added.\n */\nexport const APES_GATED_SUBCOMMANDS = new Set(['run', 'fetch', 'mcp'])\n\n/**\n * Returns true if the parsed shell command is an `apes <subcmd>`\n * invocation that should bypass the grant flow entirely. Non-apes\n * binaries, compound lines (pipes, &&, etc.), and subcommands in\n * `APES_GATED_SUBCOMMANDS` all return false so they stay on the normal\n * grant path.\n *\n * The caller (either `requestGrantForShellLine` for the REPL path or\n * `runShellMode` for the one-shot path) is responsible for parsing the\n * input string and passing the resulting ParsedShellCommand here.\n */\nexport function isApesSelfDispatch(parsed: ParsedShellCommand | null | undefined): boolean {\n if (!parsed || parsed.isCompound)\n return false\n const invokedName = basename(parsed.executable)\n if (invokedName !== 'apes' && invokedName !== 'apes.js')\n return false\n const subCommand = parsed.argv[0]\n if (!subCommand)\n return false\n return !APES_GATED_SUBCOMMANDS.has(subCommand)\n}\n"],"mappings":";;;;;;AAAA,SAAS,aAAa;AACtB,OAAO,aAAa;AACpB,SAAS,aAAa;AAmBtB,SAAS,wBAA4C;AACnD,MAAI,QAAQ,IAAI;AACd,WAAO,QAAQ,IAAI;AAErB,QAAM,SAAS,WAAW;AAC1B,SAAO,OAAO,eAAe;AAC/B;AAQA,SAAS,YAAY,OAAuB;AAI1C,SAAO,MAAM,CAAC,KAAK,CAAC;AACtB;AAMA,SAAS,eAAe,UAAkB,MAAgC;AACxE,SAAO,SACJ,QAAQ,iBAAiB,YAAY,KAAK,OAAO,CAAC,EAClD,QAAQ,oBAAoB,YAAY,KAAK,UAAU,CAAC,EACxD,QAAQ,gBAAgB,YAAY,KAAK,OAAO,CAAC,EACjD,QAAQ,iBAAiB,YAAY,KAAK,QAAQ,CAAC,EACnD,QAAQ,aAAa,YAAY,KAAK,IAAI,CAAC;AAChD;AAiBO,SAAS,mBAAmB,MAA8B;AAC/D,QAAM,WAAW,sBAAsB;AACvC,MAAI,CAAC;AACH;AAEF,QAAM,WAAW,eAAe,UAAU,IAAI;AAE9C,MAAI;AACF,UAAM,QAAQ,MAAM,MAAM,CAAC,MAAM,QAAQ,GAAG;AAAA,MAC1C,UAAU;AAAA,MACV,OAAO;AAAA,MACP,KAAK,EAAE,GAAG,QAAQ,IAAI;AAAA,IACxB,CAAC;AAGD,UAAM,MAAM;AAGZ,UAAM,UAAU,WAAW,MAAM;AAC/B,UAAI;AACF,cAAM,KAAK,SAAS;AAAA,MACtB,QACM;AAAA,MAAC;AAAA,IACT,GAAG,GAAM;AACT,YAAQ,MAAM;AAEd,UAAM,GAAG,QAAQ,MAAM,aAAa,OAAO,CAAC;AAAA,EAC9C,SACO,KAAK;AAEV,YAAQ,MAAM,gCAAgC,GAAG;AAAA,EACnD;AACF;;;ACtGA,SAAS,gBAAgB;AA+BlB,IAAM,yBAAyB,oBAAI,IAAI,CAAC,OAAO,SAAS,KAAK,CAAC;AAa9D,SAAS,mBAAmB,QAAwD;AACzF,MAAI,CAAC,UAAU,OAAO;AACpB,WAAO;AACT,QAAM,cAAc,SAAS,OAAO,UAAU;AAC9C,MAAI,gBAAgB,UAAU,gBAAgB;AAC5C,WAAO;AACT,QAAM,aAAa,OAAO,KAAK,CAAC;AAChC,MAAI,CAAC;AACH,WAAO;AACT,SAAO,CAAC,uBAAuB,IAAI,UAAU;AAC/C;","names":[]}
@@ -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
@@ -9,8 +9,9 @@ import {
9
9
  readPublicKeyComment
10
10
  } from "./chunk-ION3CWD5.js";
11
11
  import {
12
+ isApesSelfDispatch,
12
13
  notifyGrantPending
13
- } from "./chunk-5FV5KXEX.js";
14
+ } from "./chunk-EAXBC4KC.js";
14
15
  import {
15
16
  ApiError,
16
17
  apiFetch,
@@ -41,7 +42,7 @@ import {
41
42
  searchAdapters,
42
43
  verifyAndExecute,
43
44
  waitForGrantStatus
44
- } from "./chunk-D3OMN7RV.js";
45
+ } from "./chunk-U4CI2RBO.js";
45
46
  import {
46
47
  AUTH_FILE,
47
48
  CONFIG_DIR,
@@ -52,7 +53,7 @@ import {
52
53
  loadConfig,
53
54
  saveAuth,
54
55
  saveConfig
55
- } from "./chunk-ILKZ5HGV.js";
56
+ } from "./chunk-6JSOSD7R.js";
56
57
 
57
58
  // src/cli.ts
58
59
  import consola27 from "consola";
@@ -1971,6 +1972,22 @@ function getPollMaxMinutes() {
1971
1972
  }
1972
1973
  return 5;
1973
1974
  }
1975
+ function getAsyncExitCode() {
1976
+ const envValue = process.env.APES_ASYNC_EXIT_CODE;
1977
+ if (envValue !== void 0 && envValue !== "") {
1978
+ const n = Number(envValue);
1979
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
1980
+ return Math.floor(n);
1981
+ }
1982
+ const cfg = loadConfig();
1983
+ const cfgValue = cfg.defaults?.async_exit_code;
1984
+ if (cfgValue !== void 0 && cfgValue !== "") {
1985
+ const n = Number(cfgValue);
1986
+ if (Number.isFinite(n) && n >= 0 && n <= 255)
1987
+ return Math.floor(n);
1988
+ }
1989
+ return 75;
1990
+ }
1974
1991
  function printPendingGrantInfo(grant, idp) {
1975
1992
  const mode = getUserMode();
1976
1993
  const approveUrl = `${idp}/grant-approval?grant_id=${grant.id}`;
@@ -2076,6 +2093,14 @@ async function runShellMode(command, args) {
2076
2093
  const idp = getIdpUrl(args.idp);
2077
2094
  if (!idp)
2078
2095
  throw new CliError("No IdP URL configured. Run `apes login` first or pass --idp.");
2096
+ const innerLine = extractShellCommandString(command);
2097
+ if (innerLine) {
2098
+ const parsedInner = parseShellCommand(innerLine);
2099
+ if (isApesSelfDispatch(parsedInner)) {
2100
+ execShellCommand(command);
2101
+ return;
2102
+ }
2103
+ }
2079
2104
  const adapterHandled = await tryAdapterModeFromShell(command, idp, args);
2080
2105
  if (adapterHandled) return;
2081
2106
  const grantsUrl = await getGrantsEndpoint(idp);
@@ -2130,6 +2155,7 @@ async function runShellMode(command, args) {
2130
2155
  return;
2131
2156
  }
2132
2157
  printPendingGrantInfo(grant, idp);
2158
+ throw new CliExit(getAsyncExitCode());
2133
2159
  }
2134
2160
  async function tryAdapterModeFromShell(command, idp, args) {
2135
2161
  const cmdString = extractShellCommandString(command);
@@ -2187,13 +2213,17 @@ async function tryAdapterModeFromShell(command, idp, args) {
2187
2213
  return true;
2188
2214
  }
2189
2215
  printPendingGrantInfo(grant, idp);
2190
- return true;
2216
+ throw new CliExit(getAsyncExitCode());
2191
2217
  }
2192
2218
  function execShellCommand(command) {
2193
2219
  if (command.length === 0)
2194
2220
  throw new CliError("No command to execute");
2195
2221
  try {
2196
- execFileSync2(command[0], command.slice(1), { stdio: "inherit" });
2222
+ const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
2223
+ execFileSync2(command[0], command.slice(1), {
2224
+ stdio: "inherit",
2225
+ env: inheritedEnv
2226
+ });
2197
2227
  } catch (err) {
2198
2228
  const exitCode = err.status || 1;
2199
2229
  throw new CliExit(exitCode);
@@ -2263,6 +2293,7 @@ async function runAdapterMode(command, rawArgs, args) {
2263
2293
  return;
2264
2294
  }
2265
2295
  printPendingGrantInfo(grant, idp);
2296
+ throw new CliExit(getAsyncExitCode());
2266
2297
  }
2267
2298
  async function runAudienceMode(audience, action, args) {
2268
2299
  const auth = loadAuth();
@@ -2288,7 +2319,7 @@ async function runAudienceMode(audience, action, args) {
2288
2319
  });
2289
2320
  if (!shouldWaitForGrant(args)) {
2290
2321
  printPendingGrantInfo(grant, idp);
2291
- return;
2322
+ throw new CliExit(getAsyncExitCode());
2292
2323
  }
2293
2324
  consola19.success(`Grant requested: ${grant.id}`);
2294
2325
  consola19.info("Waiting for approval...");
@@ -2313,8 +2344,10 @@ async function runAudienceMode(audience, action, args) {
2313
2344
  if (audience === "escapes") {
2314
2345
  consola19.info(`Executing: ${command.join(" ")}`);
2315
2346
  try {
2347
+ const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
2316
2348
  execFileSync2(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
2317
- stdio: "inherit"
2349
+ stdio: "inherit",
2350
+ env: inheritedEnv
2318
2351
  });
2319
2352
  } catch (err) {
2320
2353
  const exitCode = err.status || 1;
@@ -2593,7 +2626,7 @@ var mcpCommand = defineCommand26({
2593
2626
  if (transport !== "stdio" && transport !== "sse") {
2594
2627
  throw new Error('Transport must be "stdio" or "sse"');
2595
2628
  }
2596
- const { startMcpServer } = await import("./server-2IMH7YQX.js");
2629
+ const { startMcpServer } = await import("./server-N3DPYYBL.js");
2597
2630
  await startMcpServer(transport, port);
2598
2631
  }
2599
2632
  });
@@ -3085,7 +3118,7 @@ async function bestEffortGrantCount(idp) {
3085
3118
  }
3086
3119
  }
3087
3120
  async function runHealth(args) {
3088
- const version = true ? "0.9.3" : "0.0.0";
3121
+ const version = true ? "0.10.0" : "0.0.0";
3089
3122
  const auth = loadAuth();
3090
3123
  if (!auth) {
3091
3124
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -3287,10 +3320,10 @@ if (shellRewrite) {
3287
3320
  if (shellRewrite.action === "rewrite") {
3288
3321
  process.argv = shellRewrite.argv;
3289
3322
  } else if (shellRewrite.action === "version") {
3290
- console.log(`ape-shell ${"0.9.3"} (OpenApe DDISA shell wrapper)`);
3323
+ console.log(`ape-shell ${"0.10.0"} (OpenApe DDISA shell wrapper)`);
3291
3324
  process.exit(0);
3292
3325
  } else if (shellRewrite.action === "help") {
3293
- console.log(`ape-shell ${"0.9.3"} \u2014 OpenApe DDISA shell wrapper`);
3326
+ console.log(`ape-shell ${"0.10.0"} \u2014 OpenApe DDISA shell wrapper`);
3294
3327
  console.log("");
3295
3328
  console.log("Usage:");
3296
3329
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -3305,7 +3338,7 @@ if (shellRewrite) {
3305
3338
  console.log(" --help, -h Show this help message");
3306
3339
  process.exit(0);
3307
3340
  } else if (shellRewrite.action === "interactive") {
3308
- const { runInteractiveShell } = await import("./orchestrator-MVOSHEI2.js");
3341
+ const { runInteractiveShell } = await import("./orchestrator-QL3AT67U.js");
3309
3342
  await runInteractiveShell();
3310
3343
  process.exit(0);
3311
3344
  } else {
@@ -3348,7 +3381,7 @@ var configCommand = defineCommand33({
3348
3381
  var main = defineCommand33({
3349
3382
  meta: {
3350
3383
  name: "apes",
3351
- version: "0.9.3",
3384
+ version: "0.10.0",
3352
3385
  description: "Unified CLI for OpenApe"
3353
3386
  },
3354
3387
  subCommands: {