@openape/apes 0.22.1 → 0.24.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
@@ -379,7 +379,7 @@ async function loginWithPKCE(idp) {
379
379
  consola2.success(`Logged in as ${payload.email || payload.sub}`);
380
380
  }
381
381
  async function loginWithKey(idp, keyPath, agentEmail) {
382
- const { readFileSync: readFileSync8 } = await import("fs");
382
+ const { readFileSync: readFileSync9 } = await import("fs");
383
383
  const { sign: sign3 } = await import("crypto");
384
384
  const { loadEd25519PrivateKey: loadEd25519PrivateKey2 } = await import("./ssh-key-YBNNG5K5.js");
385
385
  const challengeUrl = await getAgentChallengeEndpoint(idp);
@@ -392,7 +392,7 @@ async function loginWithKey(idp, keyPath, agentEmail) {
392
392
  throw new CliError(`Challenge failed: ${await challengeResp.text()}`);
393
393
  }
394
394
  const { challenge } = await challengeResp.json();
395
- const keyContent = readFileSync8(keyPath, "utf-8");
395
+ const keyContent = readFileSync9(keyPath, "utf-8");
396
396
  const privateKey = loadEd25519PrivateKey2(keyContent);
397
397
  const signature = sign3(null, Buffer2.from(challenge), privateKey).toString("base64");
398
398
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -1873,7 +1873,7 @@ mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
1873
1873
  cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
1874
1874
  cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
1875
1875
  cat > "$HOME_DIR/.config/apes/auth.json" ${shHeredoc(input.authJson)}
1876
- ${claudeBlock}${claudeTokenBlock}
1876
+ ${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}
1877
1877
  chown -R "$NAME:staff" "$HOME_DIR"
1878
1878
  chmod 700 "$HOME_DIR/.ssh"
1879
1879
  chmod 700 "$HOME_DIR/.config"
@@ -1886,6 +1886,31 @@ if [ -f "$HOME_DIR/.config/openape/claude-token.env" ]; then
1886
1886
  fi
1887
1887
 
1888
1888
  echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
1889
+ ${buildBridgeBootstrapBlock(input.bridge)}`;
1890
+ }
1891
+ function buildBridgeBlock(bridge) {
1892
+ if (!bridge) return "";
1893
+ return `
1894
+ mkdir -p "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs" "$HOME_DIR/.pi/agent"
1895
+ cat > "$HOME_DIR/.pi/agent/.env" ${shHeredoc(bridge.envFile)}
1896
+ cat > "$HOME_DIR/Library/Application Support/openape/bridge/start.sh" ${shHeredoc(bridge.startScript)}
1897
+ chmod 755 "$HOME_DIR/Library/Application Support/openape/bridge/start.sh"
1898
+ chmod 600 "$HOME_DIR/.pi/agent/.env"
1899
+
1900
+ # System-wide LaunchDaemon \u2014 root-owned, mode 644 (launchd refuses
1901
+ # group/world-writable plists). UserName in the plist makes launchd run
1902
+ # the binary as the agent, not root.
1903
+ cat > ${shQuote(bridge.plistPath)} ${shHeredoc(bridge.plistContent)}
1904
+ chown root:wheel ${shQuote(bridge.plistPath)}
1905
+ chmod 644 ${shQuote(bridge.plistPath)}
1906
+ `;
1907
+ }
1908
+ function buildBridgeBootstrapBlock(bridge) {
1909
+ if (!bridge) return "";
1910
+ return `
1911
+ launchctl bootout "system/${bridge.plistLabel}" 2>/dev/null || true
1912
+ launchctl bootstrap system ${shQuote(bridge.plistPath)} || \\
1913
+ echo "warn: bridge bootstrap into system domain failed; check ${bridge.plistPath}"
1889
1914
  `;
1890
1915
  }
1891
1916
  function buildDestroyTeardownScript(input) {
@@ -1916,6 +1941,16 @@ if [ -n "$UID_OF" ]; then
1916
1941
  pkill -9 -u "$UID_OF" 2>/dev/null || true
1917
1942
  fi
1918
1943
 
1944
+ # Per-agent system LaunchDaemon written by spawn --bridge. Bootout +
1945
+ # delete must come BEFORE we delete the user, otherwise launchd keeps a
1946
+ # zombie reference. No-op if the plist isn't there.
1947
+ BRIDGE_LABEL="eco.hofmann.apes.bridge.$NAME"
1948
+ BRIDGE_PLIST="/Library/LaunchDaemons/$BRIDGE_LABEL.plist"
1949
+ if [ -f "$BRIDGE_PLIST" ]; then
1950
+ launchctl bootout "system/$BRIDGE_LABEL" 2>/dev/null || true
1951
+ rm -f "$BRIDGE_PLIST"
1952
+ fi
1953
+
1919
1954
  if [ -d "$HOME_DIR" ] && [ "$HOME_DIR" != "/" ] && [ "$HOME_DIR" != "" ]; then
1920
1955
  rm -rf "$HOME_DIR"
1921
1956
  fi
@@ -2329,10 +2364,61 @@ var registerAgentCommand = defineCommand22({
2329
2364
  import { execFileSync as execFileSync4 } from "child_process";
2330
2365
  import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2331
2366
  import { tmpdir as tmpdir2 } from "os";
2332
- import { join as join3 } from "path";
2367
+ import { join as join4 } from "path";
2333
2368
  import { defineCommand as defineCommand23 } from "citty";
2334
2369
  import consola21 from "consola";
2335
2370
 
2371
+ // src/lib/chat-room.ts
2372
+ var DEFAULT_CHAT_ENDPOINT = "https://chat.openape.ai";
2373
+ function chatEndpoint() {
2374
+ return (process.env.APE_CHAT_ENDPOINT ?? DEFAULT_CHAT_ENDPOINT).replace(/\/$/, "");
2375
+ }
2376
+ async function chatFetch(bearer, path2, init) {
2377
+ const url = `${chatEndpoint()}${path2}`;
2378
+ const res = await fetch(url, {
2379
+ method: init?.method ?? "GET",
2380
+ headers: {
2381
+ Authorization: `Bearer ${bearer}`,
2382
+ "Content-Type": "application/json"
2383
+ },
2384
+ body: init?.body !== void 0 ? JSON.stringify(init.body) : void 0
2385
+ });
2386
+ if (!res.ok) {
2387
+ const detail = await res.text().catch(() => "");
2388
+ throw new Error(`chat.openape.ai ${init?.method ?? "GET"} ${path2} \u2192 ${res.status}: ${detail.slice(0, 200)}`);
2389
+ }
2390
+ return await res.json();
2391
+ }
2392
+ async function findRoomByName(bearer, name) {
2393
+ const rooms = await chatFetch(bearer, "/api/rooms");
2394
+ return rooms.find((r) => r.name === name) ?? null;
2395
+ }
2396
+ async function createRoom(bearer, name) {
2397
+ return chatFetch(bearer, "/api/rooms", {
2398
+ method: "POST",
2399
+ body: { name, kind: "channel", members: [] }
2400
+ });
2401
+ }
2402
+ async function addMember(bearer, roomId, email, role = "member") {
2403
+ await chatFetch(bearer, `/api/rooms/${encodeURIComponent(roomId)}/members`, {
2404
+ method: "POST",
2405
+ body: { email, role }
2406
+ });
2407
+ }
2408
+ async function ensureRoomMembership(opts) {
2409
+ const existing = await findRoomByName(opts.callerBearer, opts.roomName);
2410
+ let room;
2411
+ let created = false;
2412
+ if (existing) {
2413
+ room = existing;
2414
+ } else {
2415
+ room = await createRoom(opts.callerBearer, opts.roomName);
2416
+ created = true;
2417
+ }
2418
+ await addMember(opts.callerBearer, room.id, opts.agentEmail);
2419
+ return { roomId: room.id, created };
2420
+ }
2421
+
2336
2422
  // src/lib/keygen.ts
2337
2423
  import { Buffer as Buffer4 } from "buffer";
2338
2424
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
@@ -2389,6 +2475,158 @@ function generateKeyPairInMemory() {
2389
2475
  };
2390
2476
  }
2391
2477
 
2478
+ // src/lib/llm-bridge.ts
2479
+ import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2480
+ import { homedir as homedir5 } from "os";
2481
+ import { join as join3 } from "path";
2482
+ var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
2483
+ function readLitellmEnv(envPath = join3(homedir5(), "litellm", ".env")) {
2484
+ if (!existsSync6(envPath)) return null;
2485
+ try {
2486
+ const text = readFileSync5(envPath, "utf8");
2487
+ const out = {};
2488
+ for (const line of text.split("\n")) {
2489
+ const trimmed = line.trim();
2490
+ if (!trimmed || trimmed.startsWith("#")) continue;
2491
+ const eq = trimmed.indexOf("=");
2492
+ if (eq < 0) continue;
2493
+ const key = trimmed.slice(0, eq).trim();
2494
+ const value = trimmed.slice(eq + 1).trim();
2495
+ if (key === "LITELLM_MASTER_KEY" || key === "LITELLM_API_KEY") out.apiKey = value;
2496
+ if (key === "LITELLM_BASE_URL") out.baseUrl = value;
2497
+ }
2498
+ return out;
2499
+ } catch {
2500
+ return null;
2501
+ }
2502
+ }
2503
+ function resolveBridgeConfig(opts) {
2504
+ const env = readLitellmEnv(opts.envPath);
2505
+ const apiKey = opts.cliKey ?? env?.apiKey;
2506
+ const baseUrl = opts.cliBaseUrl ?? env?.baseUrl ?? "http://127.0.0.1:4000/v1";
2507
+ if (!apiKey) {
2508
+ throw new Error(
2509
+ "No LITELLM_API_KEY resolved. Pass --bridge-key sk-\u2026 or write LITELLM_MASTER_KEY into ~/litellm/.env first."
2510
+ );
2511
+ }
2512
+ return { baseUrl, apiKey };
2513
+ }
2514
+ function bridgePlistLabel(agentName) {
2515
+ return `${PLIST_LABEL_PREFIX}.${agentName}`;
2516
+ }
2517
+ function bridgePlistPath(agentName) {
2518
+ return `/Library/LaunchDaemons/${bridgePlistLabel(agentName)}.plist`;
2519
+ }
2520
+ function buildBridgeEnvFile(cfg) {
2521
+ return `# Auto-generated by 'apes agents spawn --bridge'.
2522
+ # Read by the chat-bridge daemon at boot to talk to the local LLM proxy.
2523
+ LITELLM_BASE_URL=${cfg.baseUrl}
2524
+ LITELLM_API_KEY=${cfg.apiKey}
2525
+ `;
2526
+ }
2527
+ function buildBridgeStartScript() {
2528
+ return `#!/usr/bin/env bash
2529
+ # Auto-generated by 'apes agents spawn --bridge'.
2530
+ # Idempotent installer + launcher.
2531
+ set -euo pipefail
2532
+
2533
+ export NPM_CONFIG_PREFIX="$HOME/.npm-global"
2534
+ export PATH="$NPM_CONFIG_PREFIX/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
2535
+ mkdir -p "$NPM_CONFIG_PREFIX"
2536
+
2537
+ if ! command -v openape-chat-bridge >/dev/null 2>&1; then
2538
+ npm install -g --silent @openape/chat-bridge
2539
+ fi
2540
+
2541
+ if ! command -v pi >/dev/null 2>&1; then
2542
+ npm install -g --silent @mariozechner/pi-coding-agent
2543
+ fi
2544
+
2545
+ EXT_DIR="$HOME/.pi/agent/extensions"
2546
+ mkdir -p "$EXT_DIR"
2547
+ if [ ! -f "$EXT_DIR/litellm.ts" ]; then
2548
+ cat > "$EXT_DIR/litellm.ts" <<'PI_EXT_EOF'
2549
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2550
+
2551
+ const BASE_URL = process.env.LITELLM_BASE_URL ?? "http://127.0.0.1:4000/v1";
2552
+
2553
+ export default async function (pi: ExtensionAPI) {
2554
+ pi.registerProvider("litellm", {
2555
+ baseUrl: BASE_URL,
2556
+ apiKey: "LITELLM_API_KEY",
2557
+ api: "openai-completions",
2558
+ models: [
2559
+ {
2560
+ id: "gpt-5.4",
2561
+ name: "ChatGPT 5.4 (Subscription)",
2562
+ reasoning: false,
2563
+ input: ["text", "image"],
2564
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2565
+ contextWindow: 200000,
2566
+ maxTokens: 8192,
2567
+ },
2568
+ {
2569
+ id: "gpt-5.3-codex",
2570
+ name: "ChatGPT 5.3 Codex (Subscription)",
2571
+ reasoning: false,
2572
+ input: ["text"],
2573
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2574
+ contextWindow: 200000,
2575
+ maxTokens: 8192,
2576
+ },
2577
+ ],
2578
+ });
2579
+ }
2580
+ PI_EXT_EOF
2581
+ fi
2582
+
2583
+ set -a
2584
+ . "$HOME/.pi/agent/.env"
2585
+ set +a
2586
+ exec openape-chat-bridge
2587
+ `;
2588
+ }
2589
+ function buildBridgePlist(agentName, homeDir) {
2590
+ const startScript = `${homeDir}/Library/Application Support/openape/bridge/start.sh`;
2591
+ const stdoutLog = `${homeDir}/Library/Logs/openape-chat-bridge.log`;
2592
+ const stderrLog = `${homeDir}/Library/Logs/openape-chat-bridge.err.log`;
2593
+ return `<?xml version="1.0" encoding="UTF-8"?>
2594
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2595
+ <plist version="1.0">
2596
+ <dict>
2597
+ <key>Label</key>
2598
+ <string>${bridgePlistLabel(agentName)}</string>
2599
+ <key>UserName</key>
2600
+ <string>${agentName}</string>
2601
+ <key>ProgramArguments</key>
2602
+ <array>
2603
+ <string>/bin/bash</string>
2604
+ <string>${startScript}</string>
2605
+ </array>
2606
+ <key>WorkingDirectory</key>
2607
+ <string>${homeDir}</string>
2608
+ <key>RunAtLoad</key>
2609
+ <true/>
2610
+ <key>KeepAlive</key>
2611
+ <true/>
2612
+ <key>ThrottleInterval</key>
2613
+ <integer>10</integer>
2614
+ <key>StandardOutPath</key>
2615
+ <string>${stdoutLog}</string>
2616
+ <key>StandardErrorPath</key>
2617
+ <string>${stderrLog}</string>
2618
+ <key>EnvironmentVariables</key>
2619
+ <dict>
2620
+ <key>HOME</key>
2621
+ <string>${homeDir}</string>
2622
+ <key>PATH</key>
2623
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
2624
+ </dict>
2625
+ </dict>
2626
+ </plist>
2627
+ `;
2628
+ }
2629
+
2392
2630
  // src/commands/agents/spawn.ts
2393
2631
  var spawnAgentCommand = defineCommand23({
2394
2632
  meta: {
@@ -2416,6 +2654,22 @@ var spawnAgentCommand = defineCommand23({
2416
2654
  "claude-token-stdin": {
2417
2655
  type: "boolean",
2418
2656
  description: "Read the Claude Code OAuth token from stdin (paranoid form of --claude-token)."
2657
+ },
2658
+ "bridge": {
2659
+ type: "boolean",
2660
+ description: "Install the openape-chat-bridge daemon for this agent: drops a launchd plist that runs `@openape/chat-bridge` so the agent answers chat.openape.ai messages. Reads LITELLM_API_KEY/BASE_URL defaults from ~/litellm/.env; override via --bridge-key / --bridge-base-url."
2661
+ },
2662
+ "bridge-key": {
2663
+ type: "string",
2664
+ description: "Override LITELLM_API_KEY for the bridge (default: read from ~/litellm/.env)."
2665
+ },
2666
+ "bridge-base-url": {
2667
+ type: "string",
2668
+ description: "Override LITELLM_BASE_URL for the bridge (default: read from ~/litellm/.env or http://127.0.0.1:4000/v1)."
2669
+ },
2670
+ "bridge-room": {
2671
+ type: "string",
2672
+ description: "After spawn, create (or find) a chat.openape.ai room with this name and add the new agent as a member. Uses the spawning user's IdP bearer."
2419
2673
  }
2420
2674
  },
2421
2675
  async run({ args }) {
@@ -2461,8 +2715,8 @@ and try again.`
2461
2715
  throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
2462
2716
  }
2463
2717
  const homeDir = `/Users/${name}`;
2464
- const scratch = mkdtempSync2(join3(tmpdir2(), `apes-spawn-${name}-`));
2465
- const scriptPath = join3(scratch, "setup.sh");
2718
+ const scratch = mkdtempSync2(join4(tmpdir2(), `apes-spawn-${name}-`));
2719
+ const scriptPath = join4(scratch, "setup.sh");
2466
2720
  try {
2467
2721
  consola21.start(`Generating keypair for ${name}\u2026`);
2468
2722
  const { privatePem, publicSshLine } = generateKeyPairInMemory();
@@ -2486,6 +2740,19 @@ and try again.`
2486
2740
  flag: typeof args["claude-token"] === "string" ? args["claude-token"] : void 0,
2487
2741
  fromStdin: !!args["claude-token-stdin"]
2488
2742
  });
2743
+ const bridge = args.bridge ? (() => {
2744
+ const cfg = resolveBridgeConfig({
2745
+ cliKey: typeof args["bridge-key"] === "string" ? args["bridge-key"] : void 0,
2746
+ cliBaseUrl: typeof args["bridge-base-url"] === "string" ? args["bridge-base-url"] : void 0
2747
+ });
2748
+ return {
2749
+ plistLabel: bridgePlistLabel(name),
2750
+ plistPath: bridgePlistPath(name),
2751
+ plistContent: buildBridgePlist(name, homeDir),
2752
+ startScript: buildBridgeStartScript(),
2753
+ envFile: buildBridgeEnvFile(cfg)
2754
+ };
2755
+ })() : null;
2489
2756
  const script = buildSpawnSetupScript({
2490
2757
  name,
2491
2758
  homeDir,
@@ -2495,13 +2762,32 @@ and try again.`
2495
2762
  authJson,
2496
2763
  claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
2497
2764
  hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null,
2498
- claudeOauthToken
2765
+ claudeOauthToken,
2766
+ bridge
2499
2767
  });
2500
2768
  writeFileSync3(scriptPath, script, { mode: 448 });
2501
2769
  consola21.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
2502
2770
  consola21.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
2503
2771
  execFileSync4(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
2504
2772
  consola21.success(`Agent ${name} spawned.`);
2773
+ const bridgeRoom = typeof args["bridge-room"] === "string" ? args["bridge-room"] : void 0;
2774
+ if (args.bridge && bridgeRoom) {
2775
+ try {
2776
+ consola21.start(`Inviting agent into chat.openape.ai room "${bridgeRoom}"\u2026`);
2777
+ const result = await ensureRoomMembership({
2778
+ callerBearer: auth.access_token,
2779
+ roomName: bridgeRoom,
2780
+ agentEmail: registration.email
2781
+ });
2782
+ consola21.success(
2783
+ result.created ? `Created room ${result.roomId} and added ${registration.email}` : `Room ${result.roomId} already existed; added ${registration.email}`
2784
+ );
2785
+ } catch (err) {
2786
+ const msg = err instanceof Error ? err.message : String(err);
2787
+ consola21.warn(`Could not auto-create / invite to chat room: ${msg}`);
2788
+ consola21.info("Add the agent manually with: ape-chat members add <agent-email>");
2789
+ }
2790
+ }
2505
2791
  console.log("");
2506
2792
  console.log("Run as the agent with:");
2507
2793
  console.log(` apes run --as ${name} -- claude --session-name ${name} --dangerously-skip-permissions`);
@@ -3359,7 +3645,7 @@ import { spawn } from "child_process";
3359
3645
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
3360
3646
  import { createRequire } from "module";
3361
3647
  import { tmpdir as tmpdir3 } from "os";
3362
- import { dirname as dirname2, join as join4, resolve as resolve3 } from "path";
3648
+ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
3363
3649
  var require2 = createRequire(import.meta.url);
3364
3650
  function findProxyBin() {
3365
3651
  const pkgPath = require2.resolve("@openape/proxy/package.json");
@@ -3371,8 +3657,8 @@ function findProxyBin() {
3371
3657
  return resolve3(dirname2(pkgPath), binRel);
3372
3658
  }
3373
3659
  async function startEphemeralProxy(configToml) {
3374
- const tmpDir = mkdtempSync3(join4(tmpdir3(), "openape-proxy-"));
3375
- const configPath = join4(tmpDir, "config.toml");
3660
+ const tmpDir = mkdtempSync3(join5(tmpdir3(), "openape-proxy-"));
3661
+ const configPath = join5(tmpDir, "config.toml");
3376
3662
  writeFileSync4(configPath, configToml, { mode: 384 });
3377
3663
  const binPath = findProxyBin();
3378
3664
  const child = spawn(process.execPath, [binPath, "-c", configPath], {
@@ -3808,16 +4094,16 @@ var mcpCommand = defineCommand32({
3808
4094
  if (transport !== "stdio" && transport !== "sse") {
3809
4095
  throw new Error('Transport must be "stdio" or "sse"');
3810
4096
  }
3811
- const { startMcpServer } = await import("./server-WBR7VEDY.js");
4097
+ const { startMcpServer } = await import("./server-J4H4UV25.js");
3812
4098
  await startMcpServer(transport, port);
3813
4099
  }
3814
4100
  });
3815
4101
 
3816
4102
  // src/commands/init/index.ts
3817
- import { existsSync as existsSync6, copyFileSync, writeFileSync as writeFileSync5 } from "fs";
4103
+ import { existsSync as existsSync7, copyFileSync, writeFileSync as writeFileSync5 } from "fs";
3818
4104
  import { randomBytes } from "crypto";
3819
4105
  import { execFileSync as execFileSync6 } from "child_process";
3820
- import { join as join5 } from "path";
4106
+ import { join as join6 } from "path";
3821
4107
  import { defineCommand as defineCommand33 } from "citty";
3822
4108
  import consola27 from "consola";
3823
4109
  var DEFAULT_IDP_URL = "https://id.openape.at";
@@ -3826,7 +4112,7 @@ async function downloadTemplate(repo, targetDir) {
3826
4112
  await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
3827
4113
  }
3828
4114
  function installDeps(dir) {
3829
- const hasLockFile = (name) => existsSync6(join5(dir, name));
4115
+ const hasLockFile = (name) => existsSync7(join6(dir, name));
3830
4116
  if (hasLockFile("pnpm-lock.yaml")) {
3831
4117
  execFileSync6("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
3832
4118
  } else if (hasLockFile("bun.lockb")) {
@@ -3891,7 +4177,7 @@ var initCommand = defineCommand33({
3891
4177
  });
3892
4178
  async function initSP(targetDir) {
3893
4179
  const dir = targetDir || "my-app";
3894
- if (existsSync6(join5(dir, "package.json"))) {
4180
+ if (existsSync7(join6(dir, "package.json"))) {
3895
4181
  throw new CliError(`Directory "${dir}" already contains a project.`);
3896
4182
  }
3897
4183
  consola27.start("Scaffolding SP starter...");
@@ -3900,9 +4186,9 @@ async function initSP(targetDir) {
3900
4186
  consola27.start("Installing dependencies...");
3901
4187
  installDeps(dir);
3902
4188
  consola27.success("Dependencies installed");
3903
- const envExample = join5(dir, ".env.example");
3904
- const envFile = join5(dir, ".env");
3905
- if (existsSync6(envExample) && !existsSync6(envFile)) {
4189
+ const envExample = join6(dir, ".env.example");
4190
+ const envFile = join6(dir, ".env");
4191
+ if (existsSync7(envExample) && !existsSync7(envFile)) {
3906
4192
  copyFileSync(envExample, envFile);
3907
4193
  consola27.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
3908
4194
  }
@@ -3916,7 +4202,7 @@ async function initSP(targetDir) {
3916
4202
  }
3917
4203
  async function initIdP(targetDir) {
3918
4204
  const dir = targetDir || "my-idp";
3919
- if (existsSync6(join5(dir, "package.json"))) {
4205
+ if (existsSync7(join6(dir, "package.json"))) {
3920
4206
  throw new CliError(`Directory "${dir}" already contains a project.`);
3921
4207
  }
3922
4208
  const domain = await promptText("Domain for the IdP", "localhost");
@@ -3948,7 +4234,7 @@ async function initIdP(targetDir) {
3948
4234
  `NUXT_OPENAPE_RP_ID=${domain}`,
3949
4235
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
3950
4236
  ].join("\n");
3951
- writeFileSync5(join5(dir, ".env"), `${envContent}
4237
+ writeFileSync5(join6(dir, ".env"), `${envContent}
3952
4238
  `, { mode: 384 });
3953
4239
  consola27.success(".env created");
3954
4240
  console.log("");
@@ -3969,7 +4255,7 @@ async function initIdP(targetDir) {
3969
4255
 
3970
4256
  // src/commands/enroll.ts
3971
4257
  import { Buffer as Buffer5 } from "buffer";
3972
- import { existsSync as existsSync7, readFileSync as readFileSync5 } from "fs";
4258
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
3973
4259
  import { execFile as execFile2 } from "child_process";
3974
4260
  import { sign as sign2 } from "crypto";
3975
4261
  import { defineCommand as defineCommand34 } from "citty";
@@ -3985,7 +4271,7 @@ function openBrowser2(url) {
3985
4271
  }
3986
4272
  async function pollForEnrollment(idp, agentEmail, keyPath) {
3987
4273
  const resolvedKey = resolveKeyPath(keyPath);
3988
- const keyContent = readFileSync5(resolvedKey, "utf-8");
4274
+ const keyContent = readFileSync6(resolvedKey, "utf-8");
3989
4275
  const privateKey = loadEd25519PrivateKey(keyContent);
3990
4276
  const challengeUrl = await getAgentChallengeEndpoint(idp);
3991
4277
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -4053,7 +4339,7 @@ var enrollCommand = defineCommand34({
4053
4339
  }) || DEFAULT_KEY_PATH;
4054
4340
  const resolvedKey = resolveKeyPath(keyPath);
4055
4341
  let publicKey;
4056
- if (existsSync7(resolvedKey)) {
4342
+ if (existsSync8(resolvedKey)) {
4057
4343
  publicKey = readPublicKey(resolvedKey);
4058
4344
  consola28.success(`Using existing key ${keyPath}`);
4059
4345
  } else {
@@ -4097,7 +4383,7 @@ var enrollCommand = defineCommand34({
4097
4383
  });
4098
4384
 
4099
4385
  // src/commands/register-user.ts
4100
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
4386
+ import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
4101
4387
  import { defineCommand as defineCommand35 } from "citty";
4102
4388
  import consola29 from "consola";
4103
4389
  var registerUserCommand = defineCommand35({
@@ -4136,8 +4422,8 @@ var registerUserCommand = defineCommand35({
4136
4422
  throw new CliError("No IdP URL configured. Run `apes login` first.");
4137
4423
  }
4138
4424
  let publicKey = args.key;
4139
- if (existsSync8(args.key)) {
4140
- publicKey = readFileSync6(args.key, "utf-8").trim();
4425
+ if (existsSync9(args.key)) {
4426
+ publicKey = readFileSync7(args.key, "utf-8").trim();
4141
4427
  }
4142
4428
  if (!publicKey.startsWith("ssh-ed25519 ")) {
4143
4429
  throw new CliError("Public key must be in ssh-ed25519 format.");
@@ -4446,7 +4732,7 @@ async function bestEffortGrantCount(idp) {
4446
4732
  }
4447
4733
  }
4448
4734
  async function runHealth(args) {
4449
- const version = true ? "0.22.1" : "0.0.0";
4735
+ const version = true ? "0.24.0" : "0.0.0";
4450
4736
  const auth = loadAuth();
4451
4737
  if (!auth) {
4452
4738
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -4639,25 +4925,25 @@ var workflowsCommand = defineCommand43({
4639
4925
  });
4640
4926
 
4641
4927
  // src/version-check.ts
4642
- import { existsSync as existsSync9, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
4643
- import { homedir as homedir5 } from "os";
4644
- import { join as join6 } from "path";
4928
+ import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
4929
+ import { homedir as homedir6 } from "os";
4930
+ import { join as join7 } from "path";
4645
4931
  import consola35 from "consola";
4646
4932
  var PACKAGE_NAME = "@openape/apes";
4647
4933
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4648
- var CACHE_FILE = join6(homedir5(), ".config", "apes", ".version-check.json");
4934
+ var CACHE_FILE = join7(homedir6(), ".config", "apes", ".version-check.json");
4649
4935
  function readCache() {
4650
- if (!existsSync9(CACHE_FILE)) return null;
4936
+ if (!existsSync10(CACHE_FILE)) return null;
4651
4937
  try {
4652
- return JSON.parse(readFileSync7(CACHE_FILE, "utf-8"));
4938
+ return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
4653
4939
  } catch {
4654
4940
  return null;
4655
4941
  }
4656
4942
  }
4657
4943
  function writeCache(entry) {
4658
4944
  try {
4659
- const dir = join6(homedir5(), ".config", "apes");
4660
- if (!existsSync9(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
4945
+ const dir = join7(homedir6(), ".config", "apes");
4946
+ if (!existsSync10(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
4661
4947
  writeFileSync6(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
4662
4948
  } catch {
4663
4949
  }
@@ -4719,10 +5005,10 @@ if (shellRewrite) {
4719
5005
  if (shellRewrite.action === "rewrite") {
4720
5006
  process.argv = shellRewrite.argv;
4721
5007
  } else if (shellRewrite.action === "version") {
4722
- console.log(`ape-shell ${"0.22.1"} (OpenApe DDISA shell wrapper)`);
5008
+ console.log(`ape-shell ${"0.24.0"} (OpenApe DDISA shell wrapper)`);
4723
5009
  process.exit(0);
4724
5010
  } else if (shellRewrite.action === "help") {
4725
- console.log(`ape-shell ${"0.22.1"} \u2014 OpenApe DDISA shell wrapper`);
5011
+ console.log(`ape-shell ${"0.24.0"} \u2014 OpenApe DDISA shell wrapper`);
4726
5012
  console.log("");
4727
5013
  console.log("Usage:");
4728
5014
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -4780,7 +5066,7 @@ var configCommand = defineCommand44({
4780
5066
  var main = defineCommand44({
4781
5067
  meta: {
4782
5068
  name: "apes",
4783
- version: "0.22.1",
5069
+ version: "0.24.0",
4784
5070
  description: "Unified CLI for OpenApe"
4785
5071
  },
4786
5072
  subCommands: {
@@ -4835,7 +5121,7 @@ async function maybeRefreshAuth() {
4835
5121
  }
4836
5122
  }
4837
5123
  await maybeRefreshAuth();
4838
- await maybeWarnStaleVersion("0.22.1").catch(() => {
5124
+ await maybeWarnStaleVersion("0.24.0").catch(() => {
4839
5125
  });
4840
5126
  runMain(main).catch((err) => {
4841
5127
  if (err instanceof CliExit) {