@openape/apes 0.23.0 → 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
@@ -1891,23 +1891,26 @@ ${buildBridgeBootstrapBlock(input.bridge)}`;
1891
1891
  function buildBridgeBlock(bridge) {
1892
1892
  if (!bridge) return "";
1893
1893
  return `
1894
- mkdir -p "$HOME_DIR/Library/LaunchAgents" "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs" "$HOME_DIR/.pi/agent"
1894
+ mkdir -p "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs" "$HOME_DIR/.pi/agent"
1895
1895
  cat > "$HOME_DIR/.pi/agent/.env" ${shHeredoc(bridge.envFile)}
1896
1896
  cat > "$HOME_DIR/Library/Application Support/openape/bridge/start.sh" ${shHeredoc(bridge.startScript)}
1897
1897
  chmod 755 "$HOME_DIR/Library/Application Support/openape/bridge/start.sh"
1898
- cat > "$HOME_DIR/Library/LaunchAgents/${bridge.plistLabel}.plist" ${shHeredoc(bridge.plistContent)}
1899
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)}
1900
1906
  `;
1901
1907
  }
1902
1908
  function buildBridgeBootstrapBlock(bridge) {
1903
1909
  if (!bridge) return "";
1904
1910
  return `
1905
- # Load the bridge launchd job into the agent's gui domain. Runs as root
1906
- # from the spawn setup script so we target gui/<agent-uid> explicitly.
1907
- # Failure here is non-fatal \u2014 the plist still lands and launchd will pick
1908
- # it up next time the agent logs in.
1909
- launchctl bootstrap "gui/$NEXT_UID" "$HOME_DIR/Library/LaunchAgents/${bridge.plistLabel}.plist" || \\
1910
- echo "warn: bridge bootstrap failed for gui/$NEXT_UID; loads on next login"
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}"
1911
1914
  `;
1912
1915
  }
1913
1916
  function buildDestroyTeardownScript(input) {
@@ -1938,6 +1941,16 @@ if [ -n "$UID_OF" ]; then
1938
1941
  pkill -9 -u "$UID_OF" 2>/dev/null || true
1939
1942
  fi
1940
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
+
1941
1954
  if [ -d "$HOME_DIR" ] && [ "$HOME_DIR" != "/" ] && [ "$HOME_DIR" != "" ]; then
1942
1955
  rm -rf "$HOME_DIR"
1943
1956
  fi
@@ -2355,6 +2368,57 @@ import { join as join4 } from "path";
2355
2368
  import { defineCommand as defineCommand23 } from "citty";
2356
2369
  import consola21 from "consola";
2357
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
+
2358
2422
  // src/lib/keygen.ts
2359
2423
  import { Buffer as Buffer4 } from "buffer";
2360
2424
  import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
@@ -2415,7 +2479,7 @@ function generateKeyPairInMemory() {
2415
2479
  import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2416
2480
  import { homedir as homedir5 } from "os";
2417
2481
  import { join as join3 } from "path";
2418
- var BRIDGE_PLIST_LABEL = "eco.hofmann.apes.bridge";
2482
+ var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
2419
2483
  function readLitellmEnv(envPath = join3(homedir5(), "litellm", ".env")) {
2420
2484
  if (!existsSync6(envPath)) return null;
2421
2485
  try {
@@ -2447,6 +2511,12 @@ function resolveBridgeConfig(opts) {
2447
2511
  }
2448
2512
  return { baseUrl, apiKey };
2449
2513
  }
2514
+ function bridgePlistLabel(agentName) {
2515
+ return `${PLIST_LABEL_PREFIX}.${agentName}`;
2516
+ }
2517
+ function bridgePlistPath(agentName) {
2518
+ return `/Library/LaunchDaemons/${bridgePlistLabel(agentName)}.plist`;
2519
+ }
2450
2520
  function buildBridgeEnvFile(cfg) {
2451
2521
  return `# Auto-generated by 'apes agents spawn --bridge'.
2452
2522
  # Read by the chat-bridge daemon at boot to talk to the local LLM proxy.
@@ -2457,20 +2527,66 @@ LITELLM_API_KEY=${cfg.apiKey}
2457
2527
  function buildBridgeStartScript() {
2458
2528
  return `#!/usr/bin/env bash
2459
2529
  # Auto-generated by 'apes agents spawn --bridge'.
2460
- # Idempotent installer + launcher. First boot installs @openape/chat-bridge
2461
- # globally via bun (homebrew-provided); subsequent boots skip the install.
2530
+ # Idempotent installer + launcher.
2462
2531
  set -euo pipefail
2463
- export PATH="/opt/homebrew/bin:$HOME/.bun/bin:$PATH"
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
+
2464
2537
  if ! command -v openape-chat-bridge >/dev/null 2>&1; then
2465
- bun add -g @openape/chat-bridge
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
2466
2581
  fi
2582
+
2467
2583
  set -a
2468
2584
  . "$HOME/.pi/agent/.env"
2469
2585
  set +a
2470
2586
  exec openape-chat-bridge
2471
2587
  `;
2472
2588
  }
2473
- function buildBridgePlist(homeDir) {
2589
+ function buildBridgePlist(agentName, homeDir) {
2474
2590
  const startScript = `${homeDir}/Library/Application Support/openape/bridge/start.sh`;
2475
2591
  const stdoutLog = `${homeDir}/Library/Logs/openape-chat-bridge.log`;
2476
2592
  const stderrLog = `${homeDir}/Library/Logs/openape-chat-bridge.err.log`;
@@ -2479,9 +2595,12 @@ function buildBridgePlist(homeDir) {
2479
2595
  <plist version="1.0">
2480
2596
  <dict>
2481
2597
  <key>Label</key>
2482
- <string>${BRIDGE_PLIST_LABEL}</string>
2598
+ <string>${bridgePlistLabel(agentName)}</string>
2599
+ <key>UserName</key>
2600
+ <string>${agentName}</string>
2483
2601
  <key>ProgramArguments</key>
2484
2602
  <array>
2603
+ <string>/bin/bash</string>
2485
2604
  <string>${startScript}</string>
2486
2605
  </array>
2487
2606
  <key>WorkingDirectory</key>
@@ -2501,7 +2620,7 @@ function buildBridgePlist(homeDir) {
2501
2620
  <key>HOME</key>
2502
2621
  <string>${homeDir}</string>
2503
2622
  <key>PATH</key>
2504
- <string>/opt/homebrew/bin:/usr/bin:/bin</string>
2623
+ <string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
2505
2624
  </dict>
2506
2625
  </dict>
2507
2626
  </plist>
@@ -2547,6 +2666,10 @@ var spawnAgentCommand = defineCommand23({
2547
2666
  "bridge-base-url": {
2548
2667
  type: "string",
2549
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."
2550
2673
  }
2551
2674
  },
2552
2675
  async run({ args }) {
@@ -2623,8 +2746,9 @@ and try again.`
2623
2746
  cliBaseUrl: typeof args["bridge-base-url"] === "string" ? args["bridge-base-url"] : void 0
2624
2747
  });
2625
2748
  return {
2626
- plistLabel: BRIDGE_PLIST_LABEL,
2627
- plistContent: buildBridgePlist(homeDir),
2749
+ plistLabel: bridgePlistLabel(name),
2750
+ plistPath: bridgePlistPath(name),
2751
+ plistContent: buildBridgePlist(name, homeDir),
2628
2752
  startScript: buildBridgeStartScript(),
2629
2753
  envFile: buildBridgeEnvFile(cfg)
2630
2754
  };
@@ -2646,6 +2770,24 @@ and try again.`
2646
2770
  consola21.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
2647
2771
  execFileSync4(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
2648
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
+ }
2649
2791
  console.log("");
2650
2792
  console.log("Run as the agent with:");
2651
2793
  console.log(` apes run --as ${name} -- claude --session-name ${name} --dangerously-skip-permissions`);
@@ -3952,7 +4094,7 @@ var mcpCommand = defineCommand32({
3952
4094
  if (transport !== "stdio" && transport !== "sse") {
3953
4095
  throw new Error('Transport must be "stdio" or "sse"');
3954
4096
  }
3955
- const { startMcpServer } = await import("./server-D5EQSCWF.js");
4097
+ const { startMcpServer } = await import("./server-J4H4UV25.js");
3956
4098
  await startMcpServer(transport, port);
3957
4099
  }
3958
4100
  });
@@ -4590,7 +4732,7 @@ async function bestEffortGrantCount(idp) {
4590
4732
  }
4591
4733
  }
4592
4734
  async function runHealth(args) {
4593
- const version = true ? "0.23.0" : "0.0.0";
4735
+ const version = true ? "0.24.0" : "0.0.0";
4594
4736
  const auth = loadAuth();
4595
4737
  if (!auth) {
4596
4738
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -4863,10 +5005,10 @@ if (shellRewrite) {
4863
5005
  if (shellRewrite.action === "rewrite") {
4864
5006
  process.argv = shellRewrite.argv;
4865
5007
  } else if (shellRewrite.action === "version") {
4866
- console.log(`ape-shell ${"0.23.0"} (OpenApe DDISA shell wrapper)`);
5008
+ console.log(`ape-shell ${"0.24.0"} (OpenApe DDISA shell wrapper)`);
4867
5009
  process.exit(0);
4868
5010
  } else if (shellRewrite.action === "help") {
4869
- console.log(`ape-shell ${"0.23.0"} \u2014 OpenApe DDISA shell wrapper`);
5011
+ console.log(`ape-shell ${"0.24.0"} \u2014 OpenApe DDISA shell wrapper`);
4870
5012
  console.log("");
4871
5013
  console.log("Usage:");
4872
5014
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -4924,7 +5066,7 @@ var configCommand = defineCommand44({
4924
5066
  var main = defineCommand44({
4925
5067
  meta: {
4926
5068
  name: "apes",
4927
- version: "0.23.0",
5069
+ version: "0.24.0",
4928
5070
  description: "Unified CLI for OpenApe"
4929
5071
  },
4930
5072
  subCommands: {
@@ -4979,7 +5121,7 @@ async function maybeRefreshAuth() {
4979
5121
  }
4980
5122
  }
4981
5123
  await maybeRefreshAuth();
4982
- await maybeWarnStaleVersion("0.23.0").catch(() => {
5124
+ await maybeWarnStaleVersion("0.24.0").catch(() => {
4983
5125
  });
4984
5126
  runMain(main).catch((err) => {
4985
5127
  if (err instanceof CliExit) {