@openape/apes 0.31.3 → 0.32.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
@@ -63,7 +63,7 @@ import {
63
63
  } from "./chunk-OBF7IMQ2.js";
64
64
 
65
65
  // src/cli.ts
66
- import consola37 from "consola";
66
+ import consola39 from "consola";
67
67
 
68
68
  // src/ape-shell.ts
69
69
  import path from "path";
@@ -93,7 +93,7 @@ function rewriteApeShellArgs(argv, argv0) {
93
93
  }
94
94
 
95
95
  // src/cli.ts
96
- import { defineCommand as defineCommand45, runMain } from "citty";
96
+ import { defineCommand as defineCommand48, runMain } from "citty";
97
97
 
98
98
  // src/commands/auth/login.ts
99
99
  import { Buffer as Buffer2 } from "buffer";
@@ -306,7 +306,7 @@ async function loginWithPKCE(idp) {
306
306
  authUrl.searchParams.set("state", state);
307
307
  authUrl.searchParams.set("nonce", nonce);
308
308
  authUrl.searchParams.set("scope", "openid email profile offline_access");
309
- const code = await new Promise((resolve4, reject) => {
309
+ const code = await new Promise((resolve5, reject) => {
310
310
  const server = createServer((req, res) => {
311
311
  const url = new URL(req.url, `http://localhost:${CALLBACK_PORT}`);
312
312
  if (url.pathname === "/callback") {
@@ -323,7 +323,7 @@ async function loginWithPKCE(idp) {
323
323
  res.writeHead(200, { "Content-Type": "text/html" });
324
324
  res.end("<h1>Login successful!</h1><p>You can close this window.</p>");
325
325
  server.close();
326
- resolve4(authCode);
326
+ resolve5(authCode);
327
327
  return;
328
328
  }
329
329
  res.writeHead(400);
@@ -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: readFileSync9 } = await import("fs");
382
+ const { readFileSync: readFileSync14 } = 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 = readFileSync9(keyPath, "utf-8");
395
+ const keyContent = readFileSync14(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);
@@ -884,7 +884,7 @@ async function waitForApproval2(grantsUrl, grantId) {
884
884
  if (grant.status === "revoked") {
885
885
  throw new CliError("Grant revoked.");
886
886
  }
887
- await new Promise((resolve4) => setTimeout(resolve4, interval));
887
+ await new Promise((resolve5) => setTimeout(resolve5, interval));
888
888
  }
889
889
  throw new CliError("Timed out waiting for approval.");
890
890
  }
@@ -1744,7 +1744,7 @@ var adminCommand = defineCommand19({
1744
1744
  });
1745
1745
 
1746
1746
  // src/commands/agents/index.ts
1747
- import { defineCommand as defineCommand25 } from "citty";
1747
+ import { defineCommand as defineCommand28 } from "citty";
1748
1748
 
1749
1749
  // src/commands/agents/allow.ts
1750
1750
  import { execFileSync as execFileSync3 } from "child_process";
@@ -1871,7 +1871,7 @@ mkdir -p "$HOME_DIR/.ssh" "$HOME_DIR/.config/apes"
1871
1871
  cat > "$HOME_DIR/.ssh/id_ed25519" ${shHeredoc(privatePemForHeredoc.trimEnd())}
1872
1872
  cat > "$HOME_DIR/.ssh/id_ed25519.pub" ${shHeredoc(`${input.publicKeySshLine}`)}
1873
1873
  cat > "$HOME_DIR/.config/apes/auth.json" ${shHeredoc(input.authJson)}
1874
- ${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}
1874
+ ${claudeBlock}${claudeTokenBlock}${buildBridgeBlock(input.bridge)}${buildTribeBlock(input.tribe)}
1875
1875
  chown -R "$NAME:staff" "$HOME_DIR"
1876
1876
  chmod 700 "$HOME_DIR/.ssh"
1877
1877
  chmod 700 "$HOME_DIR/.config"
@@ -1884,16 +1884,16 @@ if [ -f "$HOME_DIR/.config/openape/claude-token.env" ]; then
1884
1884
  fi
1885
1885
 
1886
1886
  echo "OK $NAME uid=$NEXT_UID home=$HOME_DIR"
1887
- ${buildBridgeBootstrapBlock(input.bridge)}`;
1887
+ ${buildBridgeBootstrapBlock(input.bridge)}${buildTribeBootstrapBlock(input.tribe)}`;
1888
1888
  }
1889
1889
  function buildBridgeBlock(bridge) {
1890
1890
  if (!bridge) return "";
1891
1891
  return `
1892
- mkdir -p "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs" "$HOME_DIR/.pi/agent"
1893
- cat > "$HOME_DIR/.pi/agent/.env" ${shHeredoc(bridge.envFile)}
1892
+ mkdir -p "$HOME_DIR/Library/Application Support/openape/bridge" "$HOME_DIR/Library/Logs"
1893
+ cat > "$HOME_DIR/Library/Application Support/openape/bridge/.env" ${shHeredoc(bridge.envFile)}
1894
1894
  cat > "$HOME_DIR/Library/Application Support/openape/bridge/start.sh" ${shHeredoc(bridge.startScript)}
1895
1895
  chmod 755 "$HOME_DIR/Library/Application Support/openape/bridge/start.sh"
1896
- chmod 600 "$HOME_DIR/.pi/agent/.env"
1896
+ chmod 600 "$HOME_DIR/Library/Application Support/openape/bridge/.env"
1897
1897
 
1898
1898
  # System-wide LaunchDaemon \u2014 root-owned, mode 644 (launchd refuses
1899
1899
  # group/world-writable plists). UserName in the plist makes launchd run
@@ -1910,7 +1910,7 @@ echo "==> Installing bridge stack as $NAME via bun (one-time)\u2026"
1910
1910
  su - "$NAME" -c '
1911
1911
  set -euo pipefail
1912
1912
  export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:$HOME/.bun/install/global/bin"
1913
- bun add -g @openape/chat-bridge @openape/apes @mariozechner/pi-coding-agent
1913
+ bun add -g @openape/chat-bridge @openape/apes
1914
1914
  '
1915
1915
 
1916
1916
  # Bootstrap into the system domain. Spawn already runs as root via
@@ -1921,6 +1921,31 @@ launchctl bootstrap system ${shQuote(bridge.plistPath)} || \\
1921
1921
  echo "warn: bridge bootstrap into system domain failed; check ${bridge.plistPath}"
1922
1922
  `;
1923
1923
  }
1924
+ function buildTribeBlock(tribe) {
1925
+ if (!tribe) return "";
1926
+ return `
1927
+ mkdir -p "$HOME_DIR/Library/LaunchAgents" "$HOME_DIR/Library/Logs" "$HOME_DIR/.openape/agent/tasks"
1928
+ cat > ${shQuote(tribe.plistPath)} ${shHeredoc(tribe.plistContent)}
1929
+ chmod 644 ${shQuote(tribe.plistPath)}
1930
+ `;
1931
+ }
1932
+ function buildTribeBootstrapBlock(tribe) {
1933
+ if (!tribe) return "";
1934
+ return `
1935
+ # Bootstrap the tribe sync launchd into the agent's GUI domain so it
1936
+ # starts firing every 5 minutes. RunAtLoad in the plist also kicks
1937
+ # off an immediate first sync so the agent registers + appears in
1938
+ # the tribe SP within seconds of spawn finishing.
1939
+ echo "==> Installing tribe sync launchd as $NAME\u2026"
1940
+ su - "$NAME" -c '
1941
+ set -euo pipefail
1942
+ NAME_UID="$(id -u)"
1943
+ launchctl bootout "gui/$NAME_UID/${tribe.plistLabel}" 2>/dev/null || true
1944
+ launchctl bootstrap "gui/$NAME_UID" ${shQuote(tribe.plistPath)} || \\
1945
+ echo "warn: tribe sync bootstrap failed; run \\\`apes agents sync\\\` manually as $NAME to register at tribe.openape.ai"
1946
+ '
1947
+ `;
1948
+ }
1924
1949
  function buildDestroyTeardownScript(input) {
1925
1950
  const { name, homeDir, adminUser } = input;
1926
1951
  return `#!/bin/bash
@@ -2182,7 +2207,7 @@ function readPasswordSilent(prompt) {
2182
2207
  "No TTY available for the silent password prompt. Set APES_ADMIN_PASSWORD in the environment instead."
2183
2208
  ));
2184
2209
  }
2185
- return new Promise((resolve4, reject) => {
2210
+ return new Promise((resolve5, reject) => {
2186
2211
  process.stdout.write(prompt);
2187
2212
  const wasRaw = process.stdin.isRaw ?? false;
2188
2213
  process.stdin.setRawMode(true);
@@ -2197,7 +2222,7 @@ function readPasswordSilent(prompt) {
2197
2222
  if (ch === "\r" || ch === "\n") {
2198
2223
  cleanup();
2199
2224
  process.stdout.write("\n");
2200
- resolve4(buf);
2225
+ resolve5(buf);
2201
2226
  return;
2202
2227
  }
2203
2228
  if (code === 3) {
@@ -2492,22 +2517,814 @@ var registerAgentCommand = defineCommand23({
2492
2517
  }
2493
2518
  });
2494
2519
 
2495
- // src/commands/agents/spawn.ts
2496
- import { execFileSync as execFileSync5 } from "child_process";
2497
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
2498
- import { tmpdir as tmpdir2 } from "os";
2499
- import { join as join4 } from "path";
2520
+ // src/commands/agents/run.ts
2521
+ import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2522
+ import { homedir as homedir5 } from "os";
2523
+ import { join as join3 } from "path";
2500
2524
  import { defineCommand as defineCommand24 } from "citty";
2501
2525
  import consola22 from "consola";
2502
2526
 
2527
+ // src/lib/agent-tools/file.ts
2528
+ import { mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
2529
+ import { homedir as homedir4 } from "os";
2530
+ import { dirname, normalize, resolve as resolve2 } from "path";
2531
+ var MAX_BYTES = 1024 * 1024;
2532
+ function jailPath(input) {
2533
+ if (typeof input !== "string" || input === "") {
2534
+ throw new Error("path must be a non-empty string");
2535
+ }
2536
+ const home = homedir4();
2537
+ const candidate = input.startsWith("~/") ? resolve2(home, input.slice(2)) : input.startsWith("/") ? normalize(input) : resolve2(home, input);
2538
+ if (candidate !== home && !candidate.startsWith(`${home}/`)) {
2539
+ throw new Error(`path "${input}" resolves outside the agent's home`);
2540
+ }
2541
+ return candidate;
2542
+ }
2543
+ var fileTools = [
2544
+ {
2545
+ name: "file.read",
2546
+ description: "Read a UTF-8 file from the agent's home directory ($HOME). Capped at 1MB. Path traversal blocked.",
2547
+ parameters: {
2548
+ type: "object",
2549
+ properties: {
2550
+ path: { type: "string", description: "Path relative to $HOME (or absolute under $HOME). `..` segments are rejected." }
2551
+ },
2552
+ required: ["path"]
2553
+ },
2554
+ execute: async (args) => {
2555
+ const a = args;
2556
+ const p = jailPath(a.path);
2557
+ const content = readFileSync4(p, "utf8");
2558
+ if (Buffer.byteLength(content, "utf8") > MAX_BYTES) {
2559
+ return { path: p, truncated: true, content: content.slice(0, MAX_BYTES) };
2560
+ }
2561
+ return { path: p, truncated: false, content };
2562
+ }
2563
+ },
2564
+ {
2565
+ name: "file.write",
2566
+ description: "Write a UTF-8 file under the agent's home directory. Creates parent dirs as needed. 1MB max.",
2567
+ parameters: {
2568
+ type: "object",
2569
+ properties: {
2570
+ path: { type: "string", description: "Path relative to $HOME (or absolute under $HOME)." },
2571
+ content: { type: "string", description: "File body. Existing files are overwritten." }
2572
+ },
2573
+ required: ["path", "content"]
2574
+ },
2575
+ execute: async (args) => {
2576
+ const a = args;
2577
+ if (typeof a.content !== "string") throw new Error("content must be a string");
2578
+ if (Buffer.byteLength(a.content, "utf8") > MAX_BYTES) {
2579
+ throw new Error(`content exceeds ${MAX_BYTES} byte cap`);
2580
+ }
2581
+ const p = jailPath(a.path);
2582
+ mkdirSync(dirname(p), { recursive: true });
2583
+ writeFileSync2(p, a.content, { encoding: "utf8" });
2584
+ return { path: p, bytes: Buffer.byteLength(a.content, "utf8") };
2585
+ }
2586
+ }
2587
+ ];
2588
+
2589
+ // src/lib/agent-tools/http.ts
2590
+ var MAX_BYTES2 = 1024 * 1024;
2591
+ var FORBIDDEN_HEADERS = /* @__PURE__ */ new Set([
2592
+ "host",
2593
+ "authorization",
2594
+ "cookie",
2595
+ "connection",
2596
+ "transfer-encoding",
2597
+ "upgrade",
2598
+ "proxy-authorization"
2599
+ ]);
2600
+ function sanitizeHeaders(input) {
2601
+ if (!input || typeof input !== "object") return {};
2602
+ const out = {};
2603
+ for (const [k, v] of Object.entries(input)) {
2604
+ if (typeof v !== "string") continue;
2605
+ if (FORBIDDEN_HEADERS.has(k.toLowerCase())) continue;
2606
+ out[k] = v;
2607
+ }
2608
+ return out;
2609
+ }
2610
+ async function readCappedBody(res) {
2611
+ const buf = new Uint8Array(MAX_BYTES2 + 1);
2612
+ let written = 0;
2613
+ const reader = res.body?.getReader();
2614
+ if (!reader) return await res.text();
2615
+ while (true) {
2616
+ const { value, done } = await reader.read();
2617
+ if (done) break;
2618
+ if (written + value.byteLength > MAX_BYTES2) {
2619
+ buf.set(value.subarray(0, MAX_BYTES2 - written), written);
2620
+ written = MAX_BYTES2;
2621
+ try {
2622
+ await reader.cancel();
2623
+ } catch {
2624
+ }
2625
+ break;
2626
+ }
2627
+ buf.set(value, written);
2628
+ written += value.byteLength;
2629
+ }
2630
+ return new TextDecoder().decode(buf.subarray(0, written));
2631
+ }
2632
+ var httpTools = [
2633
+ {
2634
+ name: "http.get",
2635
+ description: "GET an HTTPS URL and return the response body (capped at 1MB). Useful for reading public APIs, RSS feeds, web pages.",
2636
+ parameters: {
2637
+ type: "object",
2638
+ properties: {
2639
+ url: { type: "string", description: "Absolute HTTPS URL." },
2640
+ headers: { type: "object", description: "Optional headers (Host, Authorization, Cookie are stripped)." }
2641
+ },
2642
+ required: ["url"]
2643
+ },
2644
+ execute: async (args) => {
2645
+ const a = args;
2646
+ if (typeof a.url !== "string" || !a.url.startsWith("http")) {
2647
+ throw new Error("url must be an http(s) URL");
2648
+ }
2649
+ const res = await fetch(a.url, { method: "GET", headers: sanitizeHeaders(a.headers) });
2650
+ const body = await readCappedBody(res);
2651
+ return { status: res.status, headers: Object.fromEntries(res.headers), body };
2652
+ }
2653
+ },
2654
+ {
2655
+ name: "http.post",
2656
+ description: "POST JSON to an HTTPS URL and return the response body (capped at 1MB).",
2657
+ parameters: {
2658
+ type: "object",
2659
+ properties: {
2660
+ url: { type: "string", description: "Absolute HTTPS URL." },
2661
+ body: { description: "JSON-serialisable payload." },
2662
+ headers: { type: "object", description: "Optional headers (Host, Authorization, Cookie are stripped)." }
2663
+ },
2664
+ required: ["url", "body"]
2665
+ },
2666
+ execute: async (args) => {
2667
+ const a = args;
2668
+ if (typeof a.url !== "string" || !a.url.startsWith("http")) {
2669
+ throw new Error("url must be an http(s) URL");
2670
+ }
2671
+ const res = await fetch(a.url, {
2672
+ method: "POST",
2673
+ headers: { "content-type": "application/json", ...sanitizeHeaders(a.headers) },
2674
+ body: JSON.stringify(a.body)
2675
+ });
2676
+ const body = await readCappedBody(res);
2677
+ return { status: res.status, headers: Object.fromEntries(res.headers), body };
2678
+ }
2679
+ }
2680
+ ];
2681
+
2682
+ // src/lib/agent-tools/mail.ts
2683
+ import { execFileSync as execFileSync5 } from "child_process";
2684
+ function o365(args) {
2685
+ try {
2686
+ return execFileSync5("o365-cli", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
2687
+ } catch (err) {
2688
+ const e = err;
2689
+ if (e.code === "ENOENT") {
2690
+ throw new Error("o365-cli is not installed on this agent host");
2691
+ }
2692
+ const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8");
2693
+ throw new Error(`o365-cli failed: ${stderr ?? e.message ?? err}`);
2694
+ }
2695
+ }
2696
+ var mailTools = [
2697
+ {
2698
+ name: "mail.list",
2699
+ description: "List recent inbox messages via o365-cli. Optional `unread_only` and `limit`.",
2700
+ parameters: {
2701
+ type: "object",
2702
+ properties: {
2703
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 20 },
2704
+ unread_only: { type: "boolean", default: false }
2705
+ },
2706
+ required: []
2707
+ },
2708
+ execute: async (args) => {
2709
+ const a = args ?? {};
2710
+ const argv = ["mail", "list", "--json", "--limit", String(a.limit ?? 20)];
2711
+ if (a.unread_only) argv.push("--unread");
2712
+ const out = o365(argv);
2713
+ try {
2714
+ return JSON.parse(out);
2715
+ } catch {
2716
+ return { raw: out };
2717
+ }
2718
+ }
2719
+ },
2720
+ {
2721
+ name: "mail.search",
2722
+ description: "Search the inbox via o365-cli using a free-form query string.",
2723
+ parameters: {
2724
+ type: "object",
2725
+ properties: {
2726
+ q: { type: "string" },
2727
+ limit: { type: "integer", minimum: 1, maximum: 100, default: 20 }
2728
+ },
2729
+ required: ["q"]
2730
+ },
2731
+ execute: async (args) => {
2732
+ const a = args;
2733
+ if (typeof a.q !== "string" || a.q.length === 0) throw new Error("q is required");
2734
+ const argv = ["mail", "search", a.q, "--json", "--limit", String(a.limit ?? 20)];
2735
+ const out = o365(argv);
2736
+ try {
2737
+ return JSON.parse(out);
2738
+ } catch {
2739
+ return { raw: out };
2740
+ }
2741
+ }
2742
+ }
2743
+ ];
2744
+
2745
+ // src/lib/agent-tools/tasks.ts
2746
+ import { execFileSync as execFileSync6 } from "child_process";
2747
+ function ape(args) {
2748
+ try {
2749
+ return execFileSync6("ape-tasks", args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] });
2750
+ } catch (err) {
2751
+ const e = err;
2752
+ const stderr = typeof e.stderr === "string" ? e.stderr : e.stderr?.toString("utf8");
2753
+ throw new Error(`ape-tasks failed: ${stderr ?? e.message ?? err}`);
2754
+ }
2755
+ }
2756
+ var tasksTools = [
2757
+ {
2758
+ name: "tasks.list",
2759
+ description: "List the owner's open ape-tasks (the user's personal task list at tasks.openape.ai).",
2760
+ parameters: {
2761
+ type: "object",
2762
+ properties: {
2763
+ status: { type: "string", enum: ["open", "doing", "done", "archived"] },
2764
+ team_id: { type: "string" }
2765
+ },
2766
+ required: []
2767
+ },
2768
+ execute: async (args) => {
2769
+ const a = args ?? {};
2770
+ const argv = ["list", "--json"];
2771
+ if (a.status) argv.push("--status", a.status);
2772
+ if (a.team_id) argv.push("--team", a.team_id);
2773
+ const out = ape(argv);
2774
+ try {
2775
+ return JSON.parse(out);
2776
+ } catch {
2777
+ return { raw: out };
2778
+ }
2779
+ }
2780
+ },
2781
+ {
2782
+ name: "tasks.create",
2783
+ description: "Create a new ape-task on the owner's task list at tasks.openape.ai.",
2784
+ parameters: {
2785
+ type: "object",
2786
+ properties: {
2787
+ title: { type: "string" },
2788
+ notes: { type: "string" },
2789
+ priority: { type: "string", enum: ["low", "med", "high"] },
2790
+ due_at: { type: "string", description: "ISO date or +Nh/+Nd shorthand." }
2791
+ },
2792
+ required: ["title"]
2793
+ },
2794
+ execute: async (args) => {
2795
+ const a = args;
2796
+ const argv = ["new", "--title", a.title, "--json"];
2797
+ if (a.notes) argv.push("--notes", a.notes);
2798
+ if (a.priority) argv.push("--priority", a.priority);
2799
+ if (a.due_at) argv.push("--due", a.due_at);
2800
+ const out = ape(argv);
2801
+ try {
2802
+ return JSON.parse(out);
2803
+ } catch {
2804
+ return { raw: out };
2805
+ }
2806
+ }
2807
+ }
2808
+ ];
2809
+
2810
+ // src/lib/agent-tools/time.ts
2811
+ var timeTools = [
2812
+ {
2813
+ name: "time.now",
2814
+ description: "Returns the current UTC date and time as ISO 8601 plus epoch seconds. No inputs.",
2815
+ parameters: { type: "object", properties: {}, required: [] },
2816
+ execute: async () => {
2817
+ const now = /* @__PURE__ */ new Date();
2818
+ return {
2819
+ iso: now.toISOString(),
2820
+ epoch_seconds: Math.floor(now.getTime() / 1e3),
2821
+ timezone_offset_minutes: -now.getTimezoneOffset()
2822
+ };
2823
+ }
2824
+ }
2825
+ ];
2826
+
2827
+ // src/lib/agent-tools/index.ts
2828
+ var ALL_TOOLS = [
2829
+ ...timeTools,
2830
+ ...httpTools,
2831
+ ...fileTools,
2832
+ ...tasksTools,
2833
+ ...mailTools
2834
+ ];
2835
+ var TOOLS = Object.fromEntries(
2836
+ ALL_TOOLS.map((t) => [t.name, t])
2837
+ );
2838
+ function taskTools(names) {
2839
+ const out = [];
2840
+ const missing = [];
2841
+ for (const name of names) {
2842
+ const tool = TOOLS[name];
2843
+ if (!tool) missing.push(name);
2844
+ else out.push(tool);
2845
+ }
2846
+ if (missing.length > 0) {
2847
+ throw new Error(`unknown tool(s): ${missing.join(", ")}`);
2848
+ }
2849
+ return out;
2850
+ }
2851
+ function asOpenAiTools(tools) {
2852
+ return tools.map((t) => ({
2853
+ type: "function",
2854
+ function: { name: t.name, description: t.description, parameters: t.parameters }
2855
+ }));
2856
+ }
2857
+
2858
+ // src/lib/agent-runtime.ts
2859
+ function previewJson(value, max = 500) {
2860
+ let s;
2861
+ try {
2862
+ s = JSON.stringify(value);
2863
+ } catch {
2864
+ s = String(value);
2865
+ }
2866
+ return s.length > max ? `${s.slice(0, max)}\u2026` : s;
2867
+ }
2868
+ async function runLoop(opts) {
2869
+ const fetchFn = opts.fetchImpl ?? fetch;
2870
+ const trace = [];
2871
+ const messages = [
2872
+ { role: "system", content: opts.systemPrompt },
2873
+ ...opts.history ?? [],
2874
+ { role: "user", content: opts.userMessage }
2875
+ ];
2876
+ const tools = asOpenAiTools(opts.tools);
2877
+ for (let step = 1; step <= opts.maxSteps; step++) {
2878
+ const res = await fetchFn(`${opts.config.apiBase}/chat/completions`, {
2879
+ method: "POST",
2880
+ headers: {
2881
+ "authorization": `Bearer ${opts.config.apiKey}`,
2882
+ "content-type": "application/json"
2883
+ },
2884
+ body: JSON.stringify({
2885
+ model: opts.config.model,
2886
+ messages,
2887
+ ...tools.length > 0 ? { tools, tool_choice: "auto" } : {}
2888
+ })
2889
+ });
2890
+ if (!res.ok) {
2891
+ const text = await res.text().catch(() => "");
2892
+ throw new Error(`LiteLLM ${res.status}: ${text.slice(0, 500)}`);
2893
+ }
2894
+ const data = await res.json();
2895
+ const choice = data.choices?.[0];
2896
+ if (!choice) throw new Error("LiteLLM response had no choices");
2897
+ const assistant = choice.message;
2898
+ messages.push(assistant);
2899
+ if (assistant.content) opts.handlers?.onTextDelta?.(assistant.content);
2900
+ trace.push({
2901
+ step,
2902
+ type: "assistant",
2903
+ preview: previewJson({ content: assistant.content, tool_calls: assistant.tool_calls?.length ?? 0 })
2904
+ });
2905
+ if (!assistant.tool_calls || assistant.tool_calls.length === 0) {
2906
+ const result2 = {
2907
+ status: "ok",
2908
+ finalMessage: assistant.content,
2909
+ stepCount: step,
2910
+ trace
2911
+ };
2912
+ opts.handlers?.onDone?.(result2);
2913
+ return result2;
2914
+ }
2915
+ for (const call of assistant.tool_calls) {
2916
+ const tool = opts.tools.find((t) => t.name === call.function.name);
2917
+ let parsedArgs;
2918
+ try {
2919
+ parsedArgs = JSON.parse(call.function.arguments);
2920
+ } catch {
2921
+ parsedArgs = {};
2922
+ }
2923
+ opts.handlers?.onToolCall?.({ name: call.function.name, args: parsedArgs });
2924
+ trace.push({ step, type: "tool_call", tool: call.function.name, preview: previewJson(parsedArgs) });
2925
+ let result2;
2926
+ let isError = false;
2927
+ if (!tool) {
2928
+ result2 = `unknown tool: ${call.function.name}`;
2929
+ isError = true;
2930
+ } else {
2931
+ try {
2932
+ result2 = await tool.execute(parsedArgs);
2933
+ } catch (err) {
2934
+ result2 = err?.message ?? String(err);
2935
+ isError = true;
2936
+ }
2937
+ }
2938
+ if (isError) {
2939
+ opts.handlers?.onToolError?.({ name: call.function.name, error: String(result2) });
2940
+ trace.push({ step, type: "tool_error", tool: call.function.name, preview: previewJson(result2) });
2941
+ } else {
2942
+ opts.handlers?.onToolResult?.({ name: call.function.name, result: result2 });
2943
+ trace.push({ step, type: "tool_result", tool: call.function.name, preview: previewJson(result2) });
2944
+ }
2945
+ messages.push({
2946
+ role: "tool",
2947
+ tool_call_id: call.id,
2948
+ name: call.function.name,
2949
+ content: typeof result2 === "string" ? result2 : JSON.stringify(result2)
2950
+ });
2951
+ }
2952
+ }
2953
+ const result = {
2954
+ status: "error",
2955
+ finalMessage: `max_steps (${opts.maxSteps}) reached without completion`,
2956
+ stepCount: opts.maxSteps,
2957
+ trace
2958
+ };
2959
+ opts.handlers?.onDone?.(result);
2960
+ return result;
2961
+ }
2962
+ var RPC_SESSION_TTL_MS = 60 * 60 * 1e3;
2963
+ var RpcSessionMap = class {
2964
+ sessions = /* @__PURE__ */ new Map();
2965
+ get(id) {
2966
+ const s = this.sessions.get(id);
2967
+ if (s) s.lastTouched = Date.now();
2968
+ return s;
2969
+ }
2970
+ put(id, s) {
2971
+ s.lastTouched = Date.now();
2972
+ this.sessions.set(id, s);
2973
+ }
2974
+ evictStale() {
2975
+ const cutoff = Date.now() - RPC_SESSION_TTL_MS;
2976
+ for (const [k, v] of this.sessions) {
2977
+ if (v.lastTouched < cutoff) this.sessions.delete(k);
2978
+ }
2979
+ }
2980
+ size() {
2981
+ return this.sessions.size;
2982
+ }
2983
+ };
2984
+
2985
+ // src/lib/tribe-client.ts
2986
+ var DEFAULT_TRIBE_URL = "https://tribe.openape.ai";
2987
+ var TribeClient = class {
2988
+ constructor(tribeUrl, agentJwt) {
2989
+ this.tribeUrl = tribeUrl;
2990
+ this.agentJwt = agentJwt;
2991
+ }
2992
+ async request(path2, init) {
2993
+ const res = await fetch(`${this.tribeUrl}${path2}`, {
2994
+ ...init,
2995
+ headers: {
2996
+ ...init?.headers ?? {},
2997
+ "Authorization": `Bearer ${this.agentJwt}`,
2998
+ "Content-Type": "application/json"
2999
+ }
3000
+ });
3001
+ if (!res.ok) {
3002
+ const text = await res.text().catch(() => "");
3003
+ throw new Error(`tribe ${init?.method ?? "GET"} ${path2} failed: ${res.status} ${text}`);
3004
+ }
3005
+ if (res.status === 204) return void 0;
3006
+ return await res.json();
3007
+ }
3008
+ sync(input) {
3009
+ return this.request("/api/agents/me/sync", {
3010
+ method: "POST",
3011
+ body: JSON.stringify({
3012
+ hostname: input.hostname,
3013
+ host_id: input.hostId,
3014
+ owner_email: input.ownerEmail,
3015
+ ...input.pubkeySsh ? { pubkey_ssh: input.pubkeySsh } : {}
3016
+ })
3017
+ });
3018
+ }
3019
+ listTasks() {
3020
+ return this.request("/api/agents/me/tasks");
3021
+ }
3022
+ startRun(taskId) {
3023
+ return this.request("/api/agents/me/runs", {
3024
+ method: "POST",
3025
+ body: JSON.stringify({ task_id: taskId })
3026
+ });
3027
+ }
3028
+ finaliseRun(id, payload) {
3029
+ return this.request(`/api/agents/me/runs/${id}`, {
3030
+ method: "PATCH",
3031
+ body: JSON.stringify(payload)
3032
+ });
3033
+ }
3034
+ };
3035
+ function resolveTribeUrl(override) {
3036
+ if (override) return override.replace(/\/$/, "");
3037
+ const fromEnv = process.env.OPENAPE_TRIBE_URL;
3038
+ if (fromEnv) return fromEnv.replace(/\/$/, "");
3039
+ return DEFAULT_TRIBE_URL;
3040
+ }
3041
+
3042
+ // src/commands/agents/run.ts
3043
+ var AUTH_PATH = join3(homedir5(), ".config", "apes", "auth.json");
3044
+ var TASK_CACHE_DIR = join3(homedir5(), ".openape", "agent", "tasks");
3045
+ function readAuth() {
3046
+ if (!existsSync5(AUTH_PATH)) {
3047
+ throw new CliError(`No agent auth found at ${AUTH_PATH}. Run \`apes agents spawn <name>\` first.`);
3048
+ }
3049
+ const parsed = JSON.parse(readFileSync5(AUTH_PATH, "utf8"));
3050
+ if (!parsed.access_token) throw new CliError("auth.json missing access_token");
3051
+ return parsed;
3052
+ }
3053
+ function readTaskSpec(taskId) {
3054
+ const path2 = join3(TASK_CACHE_DIR, `${taskId}.json`);
3055
+ if (!existsSync5(path2)) {
3056
+ throw new CliError(`No cached task spec at ${path2}. Run \`apes agents sync\` first to pull the task list from tribe.`);
3057
+ }
3058
+ return JSON.parse(readFileSync5(path2, "utf8"));
3059
+ }
3060
+ function readLitellmConfig(model) {
3061
+ const envPath = join3(homedir5(), "litellm", ".env");
3062
+ const env = {};
3063
+ if (existsSync5(envPath)) {
3064
+ for (const line of readFileSync5(envPath, "utf8").split(/\r?\n/)) {
3065
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
3066
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3067
+ }
3068
+ }
3069
+ for (const k of ["LITELLM_API_KEY", "LITELLM_MASTER_KEY", "LITELLM_BASE_URL"]) {
3070
+ if (process.env[k]) env[k] = process.env[k];
3071
+ }
3072
+ const apiKey = env.LITELLM_API_KEY || env.LITELLM_MASTER_KEY;
3073
+ const apiBase = (env.LITELLM_BASE_URL || "http://127.0.0.1:4000/v1").replace(/\/$/, "");
3074
+ if (!apiKey) {
3075
+ throw new CliError("No LITELLM_API_KEY / LITELLM_MASTER_KEY in ~/litellm/.env or env.");
3076
+ }
3077
+ return { apiBase, apiKey, model: model || "claude-haiku-4-5" };
3078
+ }
3079
+ var runAgentCommand = defineCommand24({
3080
+ meta: {
3081
+ name: "run",
3082
+ description: "Execute one task (typically launchd-invoked). Reports the run record to tribe."
3083
+ },
3084
+ args: {
3085
+ "task-id": {
3086
+ type: "positional",
3087
+ description: "Task ID (slug) to run. The cached spec at ~/.openape/agent/tasks/<id>.json is used.",
3088
+ required: true
3089
+ },
3090
+ "tribe-url": {
3091
+ type: "string",
3092
+ description: "Override tribe SP base URL."
3093
+ },
3094
+ "model": {
3095
+ type: "string",
3096
+ description: "Override the LLM model name. Default: claude-haiku-4-5."
3097
+ }
3098
+ },
3099
+ async run({ args }) {
3100
+ const taskId = args["task-id"];
3101
+ const auth = readAuth();
3102
+ const spec = readTaskSpec(taskId);
3103
+ const config = readLitellmConfig(args.model);
3104
+ let tools;
3105
+ try {
3106
+ tools = taskTools(spec.tools);
3107
+ } catch (err) {
3108
+ throw new CliError(`task ${taskId}: ${err.message}`);
3109
+ }
3110
+ const tribe = new TribeClient(resolveTribeUrl(args["tribe-url"]), auth.access_token);
3111
+ const { id: runId } = await tribe.startRun(taskId);
3112
+ consola22.info(`Run ${runId} started for task ${taskId}`);
3113
+ try {
3114
+ const result = await runLoop({
3115
+ config,
3116
+ systemPrompt: spec.systemPrompt,
3117
+ // Cron tasks have no prior user message — the task fires by
3118
+ // schedule. We use a synthetic kick-off message; tasks can
3119
+ // ignore it via their system prompt.
3120
+ userMessage: "It is time to run this task. Use your tools as needed and report when done.",
3121
+ tools,
3122
+ maxSteps: spec.maxSteps
3123
+ });
3124
+ await tribe.finaliseRun(runId, {
3125
+ status: result.status,
3126
+ final_message: result.finalMessage,
3127
+ step_count: result.stepCount,
3128
+ trace: result.trace
3129
+ });
3130
+ consola22.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3131
+ if (result.status === "error") process.exit(1);
3132
+ } catch (err) {
3133
+ const message = err?.message ?? String(err);
3134
+ await tribe.finaliseRun(runId, {
3135
+ status: "error",
3136
+ final_message: message.slice(0, 4e3),
3137
+ step_count: 0,
3138
+ trace: []
3139
+ }).catch(() => {
3140
+ });
3141
+ throw new CliError(`Run ${runId} crashed: ${message}`);
3142
+ }
3143
+ }
3144
+ });
3145
+
3146
+ // src/commands/agents/serve.ts
3147
+ import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
3148
+ import { homedir as homedir6 } from "os";
3149
+ import { join as join4 } from "path";
3150
+ import { createInterface } from "readline";
3151
+ import { defineCommand as defineCommand25 } from "citty";
3152
+ var AUTH_PATH2 = join4(homedir6(), ".config", "apes", "auth.json");
3153
+ function readLitellmConfig2(model) {
3154
+ const envPath = join4(homedir6(), "litellm", ".env");
3155
+ const env = {};
3156
+ if (existsSync6(envPath)) {
3157
+ for (const line of readFileSync6(envPath, "utf8").split(/\r?\n/)) {
3158
+ const m = line.match(/^([A-Z_]+)=(.*)$/);
3159
+ if (m) env[m[1]] = m[2].replace(/^["']|["']$/g, "");
3160
+ }
3161
+ }
3162
+ for (const k of ["LITELLM_API_KEY", "LITELLM_MASTER_KEY", "LITELLM_BASE_URL"]) {
3163
+ if (process.env[k]) env[k] = process.env[k];
3164
+ }
3165
+ const apiKey = env.LITELLM_API_KEY || env.LITELLM_MASTER_KEY;
3166
+ const apiBase = (env.LITELLM_BASE_URL || "http://127.0.0.1:4000/v1").replace(/\/$/, "");
3167
+ if (!apiKey) throw new CliError("No LITELLM_API_KEY / LITELLM_MASTER_KEY in ~/litellm/.env or env.");
3168
+ return { apiBase, apiKey, model: model || "claude-haiku-4-5" };
3169
+ }
3170
+ function emit(event) {
3171
+ process.stdout.write(`${JSON.stringify(event)}
3172
+ `);
3173
+ }
3174
+ var serveAgentCommand = defineCommand25({
3175
+ meta: {
3176
+ name: "serve",
3177
+ description: "Long-running stdio RPC server for chat-bridge subprocess use."
3178
+ },
3179
+ args: {
3180
+ rpc: {
3181
+ type: "boolean",
3182
+ description: "Use the line-delimited JSON RPC protocol on stdio (the only mode for now)."
3183
+ }
3184
+ },
3185
+ async run({ args }) {
3186
+ if (!args.rpc) {
3187
+ throw new CliError("apes agents serve currently only supports --rpc mode");
3188
+ }
3189
+ if (existsSync6(AUTH_PATH2)) {
3190
+ try {
3191
+ JSON.parse(readFileSync6(AUTH_PATH2, "utf8"));
3192
+ } catch {
3193
+ }
3194
+ }
3195
+ const sessions = new RpcSessionMap();
3196
+ const evictTimer = setInterval(() => sessions.evictStale(), 5 * 60 * 1e3);
3197
+ process.on("exit", () => clearInterval(evictTimer));
3198
+ const rl = createInterface({ input: process.stdin, terminal: false });
3199
+ rl.on("line", async (line) => {
3200
+ const trimmed = line.trim();
3201
+ if (!trimmed) return;
3202
+ let msg;
3203
+ try {
3204
+ msg = JSON.parse(trimmed);
3205
+ } catch (err) {
3206
+ emit({ type: "error", message: `invalid JSON: ${err.message}` });
3207
+ return;
3208
+ }
3209
+ if (!msg.session_id || !msg.user_msg) {
3210
+ emit({ type: "error", message: "session_id and user_msg are required" });
3211
+ return;
3212
+ }
3213
+ try {
3214
+ await handleInbound(msg, sessions);
3215
+ } catch (err) {
3216
+ emit({ type: "error", session_id: msg.session_id, message: err?.message ?? String(err) });
3217
+ emit({ type: "done", session_id: msg.session_id, step_count: 0, status: "error" });
3218
+ }
3219
+ });
3220
+ rl.on("close", () => process.exit(0));
3221
+ }
3222
+ });
3223
+ async function handleInbound(msg, sessions) {
3224
+ const config = readLitellmConfig2(msg.model);
3225
+ const tools = taskTools(msg.tools ?? []);
3226
+ const maxSteps = msg.max_steps ?? 10;
3227
+ let session = sessions.get(msg.session_id);
3228
+ if (!session) {
3229
+ session = {
3230
+ systemPrompt: msg.system_prompt,
3231
+ tools,
3232
+ maxSteps,
3233
+ messages: [],
3234
+ lastTouched: Date.now()
3235
+ };
3236
+ sessions.put(msg.session_id, session);
3237
+ }
3238
+ const result = await runLoop({
3239
+ config,
3240
+ systemPrompt: session.systemPrompt,
3241
+ userMessage: msg.user_msg,
3242
+ tools: session.tools,
3243
+ maxSteps: session.maxSteps,
3244
+ history: session.messages,
3245
+ handlers: {
3246
+ onTextDelta: (delta) => emit({ type: "text_delta", session_id: msg.session_id, delta }),
3247
+ onToolCall: ({ name, args }) => emit({ type: "tool_call", session_id: msg.session_id, name, args }),
3248
+ onToolResult: ({ name, result: result2 }) => emit({ type: "tool_result", session_id: msg.session_id, name, result: result2 }),
3249
+ onToolError: ({ name, error }) => emit({ type: "tool_error", session_id: msg.session_id, name, error })
3250
+ }
3251
+ });
3252
+ session.messages.push({ role: "user", content: msg.user_msg });
3253
+ if (result.finalMessage) {
3254
+ session.messages.push({ role: "assistant", content: result.finalMessage });
3255
+ }
3256
+ emit({
3257
+ type: "done",
3258
+ session_id: msg.session_id,
3259
+ step_count: result.stepCount,
3260
+ status: result.status,
3261
+ final_message: result.finalMessage
3262
+ });
3263
+ }
3264
+
3265
+ // src/commands/agents/spawn.ts
3266
+ import { execFileSync as execFileSync7 } from "child_process";
3267
+ import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
3268
+ import { tmpdir as tmpdir2 } from "os";
3269
+ import { join as join6 } from "path";
3270
+ import { defineCommand as defineCommand26 } from "citty";
3271
+ import consola23 from "consola";
3272
+
3273
+ // src/lib/tribe-bootstrap.ts
3274
+ var SYNC_LABEL_PREFIX = "openape.tribe.sync";
3275
+ var SYNC_INTERVAL_SECONDS = 300;
3276
+ function syncPlistLabel(agentName) {
3277
+ return `${SYNC_LABEL_PREFIX}.${agentName}`;
3278
+ }
3279
+ function escape(s) {
3280
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3281
+ }
3282
+ function buildSyncPlist(input) {
3283
+ const envBlock = input.tribeUrl ? ` <key>EnvironmentVariables</key>
3284
+ <dict>
3285
+ <key>HOME</key><string>${escape(input.homeDir)}</string>
3286
+ <key>OPENAPE_TRIBE_URL</key><string>${escape(input.tribeUrl)}</string>
3287
+ </dict>
3288
+ ` : ` <key>EnvironmentVariables</key>
3289
+ <dict>
3290
+ <key>HOME</key><string>${escape(input.homeDir)}</string>
3291
+ </dict>
3292
+ `;
3293
+ return `<?xml version="1.0" encoding="UTF-8"?>
3294
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3295
+ <plist version="1.0">
3296
+ <dict>
3297
+ <key>Label</key>
3298
+ <string>${escape(syncPlistLabel(input.agentName))}</string>
3299
+ <key>ProgramArguments</key>
3300
+ <array>
3301
+ <string>${escape(input.apesBin)}</string>
3302
+ <string>agents</string>
3303
+ <string>sync</string>
3304
+ </array>
3305
+ <key>WorkingDirectory</key>
3306
+ <string>${escape(input.homeDir)}</string>
3307
+ ${envBlock} <key>StartInterval</key>
3308
+ <integer>${SYNC_INTERVAL_SECONDS}</integer>
3309
+ <key>RunAtLoad</key>
3310
+ <true/>
3311
+ <key>StandardOutPath</key>
3312
+ <string>${escape(input.homeDir)}/Library/Logs/openape-tribe-sync.log</string>
3313
+ <key>StandardErrorPath</key>
3314
+ <string>${escape(input.homeDir)}/Library/Logs/openape-tribe-sync.log</string>
3315
+ </dict>
3316
+ </plist>
3317
+ `;
3318
+ }
3319
+
2503
3320
  // src/lib/keygen.ts
2504
3321
  import { Buffer as Buffer4 } from "buffer";
2505
- import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
3322
+ import { existsSync as existsSync7, mkdirSync as mkdirSync2, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
2506
3323
  import { generateKeyPairSync } from "crypto";
2507
- import { homedir as homedir4 } from "os";
2508
- import { dirname, resolve as resolve2 } from "path";
3324
+ import { homedir as homedir7 } from "os";
3325
+ import { dirname as dirname2, resolve as resolve3 } from "path";
2509
3326
  function resolveKeyPath(p) {
2510
- return resolve2(p.replace(/^~/, homedir4()));
3327
+ return resolve3(p.replace(/^~/, homedir7()));
2511
3328
  }
2512
3329
  function buildSshEd25519Line(rawPub) {
2513
3330
  const keyTypeStr = "ssh-ed25519";
@@ -2520,10 +3337,10 @@ function buildSshEd25519Line(rawPub) {
2520
3337
  }
2521
3338
  function readPublicKey(keyPath) {
2522
3339
  const pubPath = `${keyPath}.pub`;
2523
- if (existsSync5(pubPath)) {
2524
- return readFileSync4(pubPath, "utf-8").trim();
3340
+ if (existsSync7(pubPath)) {
3341
+ return readFileSync7(pubPath, "utf-8").trim();
2525
3342
  }
2526
- const keyContent = readFileSync4(keyPath, "utf-8");
3343
+ const keyContent = readFileSync7(keyPath, "utf-8");
2527
3344
  const privateKey = loadEd25519PrivateKey(keyContent);
2528
3345
  const jwk = privateKey.export({ format: "jwk" });
2529
3346
  const pubBytes = Buffer4.from(jwk.x, "base64url");
@@ -2531,17 +3348,17 @@ function readPublicKey(keyPath) {
2531
3348
  }
2532
3349
  function generateAndSaveKey(keyPath) {
2533
3350
  const resolved = resolveKeyPath(keyPath);
2534
- const dir = dirname(resolved);
2535
- if (!existsSync5(dir)) {
2536
- mkdirSync(dir, { recursive: true });
3351
+ const dir = dirname2(resolved);
3352
+ if (!existsSync7(dir)) {
3353
+ mkdirSync2(dir, { recursive: true });
2537
3354
  }
2538
3355
  const { publicKey, privateKey } = generateKeyPairSync("ed25519");
2539
3356
  const privatePem = privateKey.export({ type: "pkcs8", format: "pem" });
2540
- writeFileSync2(resolved, privatePem, { mode: 384 });
3357
+ writeFileSync3(resolved, privatePem, { mode: 384 });
2541
3358
  const jwk = publicKey.export({ format: "jwk" });
2542
3359
  const pubBytes = Buffer4.from(jwk.x, "base64url");
2543
3360
  const pubKeyStr = buildSshEd25519Line(pubBytes);
2544
- writeFileSync2(`${resolved}.pub`, `${pubKeyStr}
3361
+ writeFileSync3(`${resolved}.pub`, `${pubKeyStr}
2545
3362
  `, { mode: 420 });
2546
3363
  return pubKeyStr;
2547
3364
  }
@@ -2557,14 +3374,14 @@ function generateKeyPairInMemory() {
2557
3374
  }
2558
3375
 
2559
3376
  // src/lib/llm-bridge.ts
2560
- import { existsSync as existsSync6, readFileSync as readFileSync5 } from "fs";
2561
- import { homedir as homedir5 } from "os";
2562
- import { join as join3 } from "path";
3377
+ import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
3378
+ import { homedir as homedir8 } from "os";
3379
+ import { join as join5 } from "path";
2563
3380
  var PLIST_LABEL_PREFIX = "eco.hofmann.apes.bridge";
2564
- function readLitellmEnv(envPath = join3(homedir5(), "litellm", ".env")) {
2565
- if (!existsSync6(envPath)) return null;
3381
+ function readLitellmEnv(envPath = join5(homedir8(), "litellm", ".env")) {
3382
+ if (!existsSync8(envPath)) return null;
2566
3383
  try {
2567
- const text = readFileSync5(envPath, "utf8");
3384
+ const text = readFileSync8(envPath, "utf8");
2568
3385
  const out = {};
2569
3386
  for (const line of text.split("\n")) {
2570
3387
  const trimmed = line.trim();
@@ -2618,46 +3435,13 @@ export PATH="$HOME/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
2618
3435
  # at boot \u2014 keeping start.sh slim avoids the rate-limit dance the old
2619
3436
  # refresh hit when KeepAlive crash-restarted the daemon every 1h.
2620
3437
 
2621
- EXT_DIR="$HOME/.pi/agent/extensions"
2622
- mkdir -p "$EXT_DIR"
2623
- if [ ! -f "$EXT_DIR/litellm.ts" ]; then
2624
- cat > "$EXT_DIR/litellm.ts" <<'PI_EXT_EOF'
2625
- import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
2626
-
2627
- const BASE_URL = process.env.LITELLM_BASE_URL ?? "http://127.0.0.1:4000/v1";
2628
-
2629
- export default async function (pi: ExtensionAPI) {
2630
- pi.registerProvider("litellm", {
2631
- baseUrl: BASE_URL,
2632
- apiKey: "LITELLM_API_KEY",
2633
- api: "openai-completions",
2634
- models: [
2635
- {
2636
- id: "gpt-5.4",
2637
- name: "ChatGPT 5.4 (Subscription)",
2638
- reasoning: false,
2639
- input: ["text", "image"],
2640
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2641
- contextWindow: 200000,
2642
- maxTokens: 8192,
2643
- },
2644
- {
2645
- id: "gpt-5.3-codex",
2646
- name: "ChatGPT 5.3 Codex (Subscription)",
2647
- reasoning: false,
2648
- input: ["text"],
2649
- cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
2650
- contextWindow: 200000,
2651
- maxTokens: 8192,
2652
- },
2653
- ],
2654
- });
2655
- }
2656
- PI_EXT_EOF
2657
- fi
3438
+ # M6 dropped the third-party LLM-runtime install + extension write
3439
+ # that used to live here. The bridge now spawns
3440
+ # \`apes agents serve --rpc\` directly (M8) which calls LiteLLM
3441
+ # itself, so no third-party extension config is needed.
2658
3442
 
2659
3443
  set -a
2660
- . "$HOME/.pi/agent/.env"
3444
+ . "$HOME/Library/Application Support/openape/bridge/.env"
2661
3445
  set +a
2662
3446
  exec openape-chat-bridge
2663
3447
  `;
@@ -2706,7 +3490,7 @@ function buildBridgePlist(agentName, homeDir, ownerEmail) {
2706
3490
  }
2707
3491
 
2708
3492
  // src/commands/agents/spawn.ts
2709
- var spawnAgentCommand = defineCommand24({
3493
+ var spawnAgentCommand = defineCommand26({
2710
3494
  meta: {
2711
3495
  name: "spawn",
2712
3496
  description: "Provision a local macOS agent end-to-end (OS user, keypair, IdP agent, Claude hook)"
@@ -2789,15 +3573,15 @@ and try again.`
2789
3573
  throw new CliError(`macOS user "${name}" already exists (uid=${existing.uid ?? "?"}). Refusing to overwrite.`);
2790
3574
  }
2791
3575
  const homeDir = `/Users/${name}`;
2792
- const scratch = mkdtempSync2(join4(tmpdir2(), `apes-spawn-${name}-`));
2793
- const scriptPath = join4(scratch, "setup.sh");
3576
+ const scratch = mkdtempSync2(join6(tmpdir2(), `apes-spawn-${name}-`));
3577
+ const scriptPath = join6(scratch, "setup.sh");
2794
3578
  try {
2795
- consola22.start(`Generating keypair for ${name}\u2026`);
3579
+ consola23.start(`Generating keypair for ${name}\u2026`);
2796
3580
  const { privatePem, publicSshLine } = generateKeyPairInMemory();
2797
- consola22.start(`Registering agent at ${idp}\u2026`);
3581
+ consola23.start(`Registering agent at ${idp}\u2026`);
2798
3582
  const registration = await registerAgentAtIdp({ name, publicKey: publicSshLine, idp });
2799
- consola22.success(`Registered as ${registration.email}`);
2800
- consola22.start("Issuing agent access token\u2026");
3583
+ consola23.success(`Registered as ${registration.email}`);
3584
+ consola23.start("Issuing agent access token\u2026");
2801
3585
  const { token, expiresIn } = await issueAgentToken({
2802
3586
  idp,
2803
3587
  agentEmail: registration.email,
@@ -2829,6 +3613,13 @@ and try again.`
2829
3613
  envFile: buildBridgeEnvFile(cfg)
2830
3614
  };
2831
3615
  })() : null;
3616
+ const tribePlistLabel = `openape.tribe.sync.${name}`;
3617
+ const tribePlistPath = `${homeDir}/Library/LaunchAgents/${tribePlistLabel}.plist`;
3618
+ const tribe = {
3619
+ plistLabel: tribePlistLabel,
3620
+ plistPath: tribePlistPath,
3621
+ plistContent: buildSyncPlist({ agentName: name, apesBin: apes, homeDir })
3622
+ };
2832
3623
  const script = buildSpawnSetupScript({
2833
3624
  name,
2834
3625
  homeDir,
@@ -2839,16 +3630,18 @@ and try again.`
2839
3630
  claudeSettingsJson: includeClaudeHook ? CLAUDE_SETTINGS_JSON : null,
2840
3631
  hookScriptSource: includeClaudeHook ? BASH_VIA_APE_SHELL_HOOK_SOURCE : null,
2841
3632
  claudeOauthToken,
2842
- bridge
3633
+ bridge,
3634
+ tribe
2843
3635
  });
2844
- writeFileSync3(scriptPath, script, { mode: 448 });
2845
- consola22.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
2846
- consola22.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
2847
- execFileSync5(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
2848
- consola22.success(`Agent ${name} spawned.`);
3636
+ writeFileSync4(scriptPath, script, { mode: 448 });
3637
+ consola23.start("Running privileged setup as root via `apes run --as root --wait`\u2026");
3638
+ consola23.info("You will be asked to approve the as=root grant in your DDISA inbox; this command blocks until you do.");
3639
+ execFileSync7(apes, ["run", "--as", "root", "--wait", "--", "bash", scriptPath], { stdio: "inherit" });
3640
+ consola23.success(`Agent ${name} spawned.`);
3641
+ consola23.info(`\u{1F517} Tribe: https://tribe.openape.ai/agents/${name}`);
2849
3642
  if (args.bridge) {
2850
- consola22.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
2851
- consola22.info("Open chat.openape.ai and accept it to start chatting with the agent.");
3643
+ consola23.info(`On first boot, the bridge will send you a contact request from ${registration.email}.`);
3644
+ consola23.info("Open chat.openape.ai and accept it to start chatting with the agent.");
2852
3645
  }
2853
3646
  console.log("");
2854
3647
  console.log("Run as the agent with:");
@@ -2881,31 +3674,344 @@ async function resolveClaudeToken(opts) {
2881
3674
  return raw;
2882
3675
  }
2883
3676
 
3677
+ // src/commands/agents/sync.ts
3678
+ import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
3679
+ import { homedir as homedir10 } from "os";
3680
+ import { join as join8 } from "path";
3681
+ import { defineCommand as defineCommand27 } from "citty";
3682
+ import consola24 from "consola";
3683
+
3684
+ // src/lib/launchd-reconcile.ts
3685
+ import { execFileSync as execFileSync8 } from "child_process";
3686
+ import { mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
3687
+ import { homedir as homedir9, userInfo as userInfo2 } from "os";
3688
+ import { join as join7 } from "path";
3689
+ var PLIST_PREFIX = "openape.tribe.";
3690
+ function plistDir() {
3691
+ return join7(homedir9(), "Library", "LaunchAgents");
3692
+ }
3693
+ function plistPath(agentName, taskId) {
3694
+ return join7(plistDir(), `${PLIST_PREFIX}${agentName}.${taskId}.plist`);
3695
+ }
3696
+ function plistLabel(agentName, taskId) {
3697
+ return `${PLIST_PREFIX}${agentName}.${taskId}`;
3698
+ }
3699
+ function fieldValues(token, range) {
3700
+ const [min, max] = range;
3701
+ if (token === "*") return null;
3702
+ if (token.startsWith("*/")) {
3703
+ const step = Number(token.slice(2));
3704
+ if (!Number.isInteger(step) || step < 1) return [];
3705
+ const values = [];
3706
+ for (let i = min; i <= max; i++) {
3707
+ if (i % step === 0) values.push(i);
3708
+ }
3709
+ return values;
3710
+ }
3711
+ const n = Number(token);
3712
+ return Number.isInteger(n) ? [n] : [];
3713
+ }
3714
+ function cronToSchedule(expr) {
3715
+ const parts = expr.trim().split(/\s+/);
3716
+ if (parts.length !== 5) return [];
3717
+ const [m, h, dom, mo, dow] = parts;
3718
+ const minutes = fieldValues(m, [0, 59]);
3719
+ const hours = fieldValues(h, [0, 23]);
3720
+ const doms = fieldValues(dom, [1, 31]);
3721
+ const months = fieldValues(mo, [1, 12]);
3722
+ const dows = fieldValues(dow, [0, 7]);
3723
+ const slots = [];
3724
+ const minList = minutes ?? [null];
3725
+ const hourList = hours ?? [null];
3726
+ const domList = doms ?? [null];
3727
+ const monthList = months ?? [null];
3728
+ const dowList = dows ?? [null];
3729
+ for (const M of minList) {
3730
+ for (const H of hourList) {
3731
+ for (const D of domList) {
3732
+ for (const Mo of monthList) {
3733
+ for (const W of dowList) {
3734
+ const slot = {};
3735
+ if (M !== null) slot.Minute = M;
3736
+ if (H !== null) slot.Hour = H;
3737
+ if (D !== null) slot.Day = D;
3738
+ if (Mo !== null) slot.Month = Mo;
3739
+ if (W !== null) slot.Weekday = W === 7 ? 0 : W;
3740
+ slots.push(slot);
3741
+ }
3742
+ }
3743
+ }
3744
+ }
3745
+ }
3746
+ return slots;
3747
+ }
3748
+ function escape2(s) {
3749
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
3750
+ }
3751
+ function plistBody(input) {
3752
+ const calendarBlocks = input.schedule.map((slot) => {
3753
+ const lines = [];
3754
+ if (slot.Minute !== void 0) lines.push(` <key>Minute</key><integer>${slot.Minute}</integer>`);
3755
+ if (slot.Hour !== void 0) lines.push(` <key>Hour</key><integer>${slot.Hour}</integer>`);
3756
+ if (slot.Day !== void 0) lines.push(` <key>Day</key><integer>${slot.Day}</integer>`);
3757
+ if (slot.Month !== void 0) lines.push(` <key>Month</key><integer>${slot.Month}</integer>`);
3758
+ if (slot.Weekday !== void 0) lines.push(` <key>Weekday</key><integer>${slot.Weekday}</integer>`);
3759
+ return ` <dict>
3760
+ ${lines.join("\n")}
3761
+ </dict>`;
3762
+ }).join("\n");
3763
+ const calendarKey = input.schedule.length === 1 ? ` <key>StartCalendarInterval</key>
3764
+ <dict>
3765
+ ${calendarBlocks.replace(/^ {4}/gm, " ").replace(/^ {2}<dict>\n|\n {2}<\/dict>$/g, "")}
3766
+ </dict>` : ` <key>StartCalendarInterval</key>
3767
+ <array>
3768
+ ${calendarBlocks}
3769
+ </array>`;
3770
+ return `<?xml version="1.0" encoding="UTF-8"?>
3771
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3772
+ <plist version="1.0">
3773
+ <dict>
3774
+ <key>Label</key>
3775
+ <string>${escape2(input.label)}</string>
3776
+ <key>ProgramArguments</key>
3777
+ <array>
3778
+ <string>${escape2(input.apesBin)}</string>
3779
+ <string>agents</string>
3780
+ <string>run</string>
3781
+ <string>${escape2(input.taskId)}</string>
3782
+ </array>
3783
+ <key>WorkingDirectory</key>
3784
+ <string>${escape2(input.homeDir)}</string>
3785
+ <key>EnvironmentVariables</key>
3786
+ <dict>
3787
+ <key>HOME</key>
3788
+ <string>${escape2(input.homeDir)}</string>
3789
+ </dict>
3790
+ ${calendarKey}
3791
+ <key>StandardOutPath</key>
3792
+ <string>${escape2(input.homeDir)}/Library/Logs/openape-tribe-${escape2(input.taskId)}.log</string>
3793
+ <key>StandardErrorPath</key>
3794
+ <string>${escape2(input.homeDir)}/Library/Logs/openape-tribe-${escape2(input.taskId)}.log</string>
3795
+ </dict>
3796
+ </plist>
3797
+ `;
3798
+ }
3799
+ function buildPlistContent(args) {
3800
+ return plistBody({
3801
+ label: plistLabel(args.agentName, args.task.taskId),
3802
+ apesBin: args.apesBin,
3803
+ taskId: args.task.taskId,
3804
+ schedule: cronToSchedule(args.task.cron),
3805
+ homeDir: args.homeDir
3806
+ });
3807
+ }
3808
+ function uid() {
3809
+ return userInfo2().uid;
3810
+ }
3811
+ function bootstrap(label, path2) {
3812
+ try {
3813
+ execFileSync8("/bin/launchctl", ["bootout", `gui/${uid()}/${label}`], { stdio: "ignore" });
3814
+ } catch {
3815
+ }
3816
+ execFileSync8("/bin/launchctl", ["bootstrap", `gui/${uid()}`, path2], { stdio: "ignore" });
3817
+ }
3818
+ function bootout(label) {
3819
+ try {
3820
+ execFileSync8("/bin/launchctl", ["bootout", `gui/${uid()}/${label}`], { stdio: "ignore" });
3821
+ } catch {
3822
+ }
3823
+ }
3824
+ function reconcile(input) {
3825
+ mkdirSync3(plistDir(), { recursive: true });
3826
+ const present = readdirSync(plistDir()).filter((f) => f.startsWith(`${PLIST_PREFIX}${input.agentName}.`) && f.endsWith(".plist"));
3827
+ const presentTaskIds = new Set(
3828
+ present.map((f) => f.slice(`${PLIST_PREFIX}${input.agentName}.`.length, -".plist".length))
3829
+ );
3830
+ const desiredById = new Map(input.desired.map((t) => [t.taskId, t]));
3831
+ const result = { added: [], updated: [], removed: [], unchanged: [] };
3832
+ for (const taskId of presentTaskIds) {
3833
+ if (!desiredById.has(taskId)) {
3834
+ const path2 = plistPath(input.agentName, taskId);
3835
+ bootout(plistLabel(input.agentName, taskId));
3836
+ try {
3837
+ unlinkSync(path2);
3838
+ } catch {
3839
+ }
3840
+ result.removed.push(taskId);
3841
+ }
3842
+ }
3843
+ for (const task of input.desired) {
3844
+ const path2 = plistPath(input.agentName, task.taskId);
3845
+ const desiredContent = buildPlistContent({
3846
+ agentName: input.agentName,
3847
+ apesBin: input.apesBin,
3848
+ homeDir: input.homeDir,
3849
+ task
3850
+ });
3851
+ let existingContent = "";
3852
+ try {
3853
+ existingContent = readFileSync9(path2, "utf8");
3854
+ } catch {
3855
+ }
3856
+ if (existingContent === desiredContent) {
3857
+ if (task.enabled) bootstrap(plistLabel(input.agentName, task.taskId), path2);
3858
+ else bootout(plistLabel(input.agentName, task.taskId));
3859
+ result.unchanged.push(task.taskId);
3860
+ continue;
3861
+ }
3862
+ writeFileSync5(path2, desiredContent, { mode: 420 });
3863
+ if (task.enabled) bootstrap(plistLabel(input.agentName, task.taskId), path2);
3864
+ else bootout(plistLabel(input.agentName, task.taskId));
3865
+ if (presentTaskIds.has(task.taskId)) result.updated.push(task.taskId);
3866
+ else result.added.push(task.taskId);
3867
+ }
3868
+ return result;
3869
+ }
3870
+
3871
+ // src/lib/macos-host.ts
3872
+ import { execFileSync as execFileSync9 } from "child_process";
3873
+ import { hostname as hostname3 } from "os";
3874
+ function getHostId() {
3875
+ try {
3876
+ const output = execFileSync9(
3877
+ "/usr/sbin/ioreg",
3878
+ ["-d2", "-c", "IOPlatformExpertDevice"],
3879
+ { encoding: "utf8", timeout: 2e3 }
3880
+ );
3881
+ const match = output.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
3882
+ return match ? match[1].trim().toLowerCase() : "";
3883
+ } catch {
3884
+ return "";
3885
+ }
3886
+ }
3887
+ function getHostname() {
3888
+ try {
3889
+ return hostname3();
3890
+ } catch {
3891
+ return "";
3892
+ }
3893
+ }
3894
+
3895
+ // src/commands/agents/sync.ts
3896
+ var AUTH_PATH3 = join8(homedir10(), ".config", "apes", "auth.json");
3897
+ var TASK_CACHE_DIR2 = join8(homedir10(), ".openape", "agent", "tasks");
3898
+ function readAuthJson() {
3899
+ if (!existsSync9(AUTH_PATH3)) {
3900
+ throw new CliError(
3901
+ `No agent auth found at ${AUTH_PATH3}. Run \`apes agents spawn <name>\` to provision an agent first.`
3902
+ );
3903
+ }
3904
+ const raw = readFileSync10(AUTH_PATH3, "utf8");
3905
+ let parsed;
3906
+ try {
3907
+ parsed = JSON.parse(raw);
3908
+ } catch (err) {
3909
+ throw new CliError(`${AUTH_PATH3} is not valid JSON: ${err.message}`);
3910
+ }
3911
+ if (!parsed.access_token) throw new CliError(`${AUTH_PATH3} is missing access_token`);
3912
+ if (!parsed.email) throw new CliError(`${AUTH_PATH3} is missing email`);
3913
+ if (!parsed.email.startsWith("agent+")) {
3914
+ throw new CliError(
3915
+ `${AUTH_PATH3} email is "${parsed.email}" \u2014 expected an agent+name+domain@idp address. Run \`apes agents spawn\` rather than calling sync from a human user.`
3916
+ );
3917
+ }
3918
+ return parsed;
3919
+ }
3920
+ function agentNameFromEmail(email) {
3921
+ const m = email.match(/^agent\+([^+]+)\+/);
3922
+ if (!m) throw new CliError(`agent email "${email}" does not match agent+name+domain pattern`);
3923
+ return m[1];
3924
+ }
3925
+ function findApesBin() {
3926
+ const argv1 = process.argv[1];
3927
+ if (argv1 && existsSync9(argv1)) return argv1;
3928
+ for (const candidate of ["/opt/homebrew/bin/apes", "/usr/local/bin/apes"]) {
3929
+ if (existsSync9(candidate)) return candidate;
3930
+ }
3931
+ throw new CliError("Could not locate the apes binary path for launchd. Set APES_BIN env var to point at it.");
3932
+ }
3933
+ var syncAgentCommand = defineCommand27({
3934
+ meta: {
3935
+ name: "sync",
3936
+ description: "Pull this agent's task list from tribe.openape.ai and reconcile launchd plists"
3937
+ },
3938
+ args: {
3939
+ "tribe-url": {
3940
+ type: "string",
3941
+ description: "Override tribe SP base URL (default: $OPENAPE_TRIBE_URL or https://tribe.openape.ai)"
3942
+ }
3943
+ },
3944
+ async run({ args }) {
3945
+ const auth = readAuthJson();
3946
+ const agentName = agentNameFromEmail(auth.email);
3947
+ const tribeUrl = resolveTribeUrl(args["tribe-url"]);
3948
+ const client = new TribeClient(tribeUrl, auth.access_token);
3949
+ const hostId = getHostId();
3950
+ const host = getHostname();
3951
+ if (!hostId) {
3952
+ throw new CliError("Could not read IOPlatformUUID \u2014 is this macOS? Tribe sync only works on macOS for v1.");
3953
+ }
3954
+ if (!auth.owner_email) {
3955
+ throw new CliError(`${AUTH_PATH3} is missing owner_email \u2014 re-run \`apes agents spawn\` to update.`);
3956
+ }
3957
+ consola24.start(`Syncing ${agentName} (${host}, hostId ${hostId.slice(0, 8)}\u2026) with ${tribeUrl}`);
3958
+ const sync = await client.sync({
3959
+ hostname: host,
3960
+ hostId,
3961
+ ownerEmail: auth.owner_email
3962
+ });
3963
+ consola24.info(sync.first_sync ? "\u2713 first sync \u2014 agent registered" : "\u2713 presence updated");
3964
+ const tasks = await client.listTasks();
3965
+ consola24.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
3966
+ mkdirSync4(TASK_CACHE_DIR2, { recursive: true });
3967
+ for (const task of tasks) {
3968
+ const path2 = join8(TASK_CACHE_DIR2, `${task.taskId}.json`);
3969
+ writeFileSync6(path2, `${JSON.stringify(task, null, 2)}
3970
+ `, { mode: 384 });
3971
+ }
3972
+ const apesBin = findApesBin();
3973
+ const result = reconcile({
3974
+ agentName,
3975
+ apesBin,
3976
+ homeDir: homedir10(),
3977
+ desired: tasks
3978
+ });
3979
+ if (result.added.length) consola24.success(`launchd: added ${result.added.join(", ")}`);
3980
+ if (result.updated.length) consola24.success(`launchd: updated ${result.updated.join(", ")}`);
3981
+ if (result.removed.length) consola24.warn(`launchd: removed ${result.removed.join(", ")}`);
3982
+ if (result.unchanged.length) consola24.info(`launchd: ${result.unchanged.length} unchanged`);
3983
+ consola24.success("Sync complete.");
3984
+ }
3985
+ });
3986
+
2884
3987
  // src/commands/agents/index.ts
2885
- var agentsCommand = defineCommand25({
3988
+ var agentsCommand = defineCommand28({
2886
3989
  meta: {
2887
3990
  name: "agents",
2888
- description: "Manage owned agents (register, spawn, list, destroy, allow)"
3991
+ description: "Manage owned agents (register, spawn, list, destroy, allow, sync, run, serve)"
2889
3992
  },
2890
3993
  subCommands: {
2891
3994
  register: registerAgentCommand,
2892
3995
  spawn: spawnAgentCommand,
2893
3996
  list: listAgentsCommand,
2894
3997
  destroy: destroyAgentCommand,
2895
- allow: allowAgentCommand
3998
+ allow: allowAgentCommand,
3999
+ sync: syncAgentCommand,
4000
+ run: runAgentCommand,
4001
+ serve: serveAgentCommand
2896
4002
  }
2897
4003
  });
2898
4004
 
2899
4005
  // src/commands/adapter/index.ts
2900
- import { defineCommand as defineCommand26 } from "citty";
2901
- import consola23 from "consola";
2902
- var adapterCommand = defineCommand26({
4006
+ import { defineCommand as defineCommand29 } from "citty";
4007
+ import consola25 from "consola";
4008
+ var adapterCommand = defineCommand29({
2903
4009
  meta: {
2904
4010
  name: "adapter",
2905
4011
  description: "Manage CLI adapters"
2906
4012
  },
2907
4013
  subCommands: {
2908
- list: defineCommand26({
4014
+ list: defineCommand29({
2909
4015
  meta: {
2910
4016
  name: "list",
2911
4017
  description: "List available adapters"
@@ -2936,7 +4042,7 @@ var adapterCommand = defineCommand26({
2936
4042
  `);
2937
4043
  return;
2938
4044
  }
2939
- consola23.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
4045
+ consola25.info(`Registry: ${index2.adapters.length} adapters (${index2.generated_at})`);
2940
4046
  for (const a of index2.adapters) {
2941
4047
  const installed = isInstalled(a.id, false) ? " [installed]" : "";
2942
4048
  console.log(` ${a.id.padEnd(12)} ${a.name.padEnd(24)} ${a.category}${installed}`);
@@ -2958,7 +4064,7 @@ var adapterCommand = defineCommand26({
2958
4064
  return;
2959
4065
  }
2960
4066
  if (local.length === 0) {
2961
- consola23.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
4067
+ consola25.info("No adapters installed. Use `apes adapter list --remote` to see available adapters.");
2962
4068
  return;
2963
4069
  }
2964
4070
  for (const a of local) {
@@ -2966,7 +4072,7 @@ var adapterCommand = defineCommand26({
2966
4072
  }
2967
4073
  }
2968
4074
  }),
2969
- install: defineCommand26({
4075
+ install: defineCommand29({
2970
4076
  meta: {
2971
4077
  name: "install",
2972
4078
  description: "Install an adapter from the registry"
@@ -2995,24 +4101,24 @@ var adapterCommand = defineCommand26({
2995
4101
  for (const id of ids) {
2996
4102
  const entry = findAdapter(index, id);
2997
4103
  if (!entry) {
2998
- consola23.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
4104
+ consola25.error(`Adapter "${id}" not found in registry. Use \`apes adapter search ${id}\` to search.`);
2999
4105
  continue;
3000
4106
  }
3001
4107
  const conflicts = findConflictingAdapters(entry.executable, id);
3002
4108
  if (conflicts.length > 0) {
3003
4109
  for (const c of conflicts) {
3004
- consola23.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
3005
- consola23.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
4110
+ consola25.warn(`Conflicting adapter found: ${c.path} (id: ${c.adapterId}, executable: ${c.executable})`);
4111
+ consola25.warn(` Remove it with: apes adapter remove ${c.adapterId}`);
3006
4112
  }
3007
4113
  }
3008
4114
  const result = await installAdapter(entry, { local });
3009
4115
  const verb = result.updated ? "Updated" : "Installed";
3010
- consola23.success(`${verb} ${result.id} \u2192 ${result.path}`);
3011
- consola23.info(`Digest: ${result.digest}`);
4116
+ consola25.success(`${verb} ${result.id} \u2192 ${result.path}`);
4117
+ consola25.info(`Digest: ${result.digest}`);
3012
4118
  }
3013
4119
  }
3014
4120
  }),
3015
- remove: defineCommand26({
4121
+ remove: defineCommand29({
3016
4122
  meta: {
3017
4123
  name: "remove",
3018
4124
  description: "Remove an installed adapter"
@@ -3035,9 +4141,9 @@ var adapterCommand = defineCommand26({
3035
4141
  let failed = false;
3036
4142
  for (const id of ids) {
3037
4143
  if (removeAdapter(id, local)) {
3038
- consola23.success(`Removed adapter: ${id}`);
4144
+ consola25.success(`Removed adapter: ${id}`);
3039
4145
  } else {
3040
- consola23.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
4146
+ consola25.error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
3041
4147
  failed = true;
3042
4148
  }
3043
4149
  }
@@ -3045,7 +4151,7 @@ var adapterCommand = defineCommand26({
3045
4151
  throw new CliError("Some adapters could not be removed");
3046
4152
  }
3047
4153
  }),
3048
- info: defineCommand26({
4154
+ info: defineCommand29({
3049
4155
  meta: {
3050
4156
  name: "info",
3051
4157
  description: "Show detailed adapter information"
@@ -3087,7 +4193,7 @@ var adapterCommand = defineCommand26({
3087
4193
  }
3088
4194
  }
3089
4195
  }),
3090
- search: defineCommand26({
4196
+ search: defineCommand29({
3091
4197
  meta: {
3092
4198
  name: "search",
3093
4199
  description: "Search adapters in the registry"
@@ -3119,7 +4225,7 @@ var adapterCommand = defineCommand26({
3119
4225
  return;
3120
4226
  }
3121
4227
  if (results.length === 0) {
3122
- consola23.info(`No adapters matching "${query}"`);
4228
+ consola25.info(`No adapters matching "${query}"`);
3123
4229
  return;
3124
4230
  }
3125
4231
  for (const a of results) {
@@ -3128,7 +4234,7 @@ var adapterCommand = defineCommand26({
3128
4234
  }
3129
4235
  }
3130
4236
  }),
3131
- update: defineCommand26({
4237
+ update: defineCommand29({
3132
4238
  meta: {
3133
4239
  name: "update",
3134
4240
  description: "Update installed adapters"
@@ -3154,33 +4260,33 @@ var adapterCommand = defineCommand26({
3154
4260
  const targetId = args.id ? String(args.id) : void 0;
3155
4261
  const targets = targetId ? [targetId] : index.adapters.map((a) => a.id).filter((id) => isInstalled(id, false));
3156
4262
  if (targets.length === 0) {
3157
- consola23.info("No adapters installed to update.");
4263
+ consola25.info("No adapters installed to update.");
3158
4264
  return;
3159
4265
  }
3160
4266
  for (const id of targets) {
3161
4267
  const entry = findAdapter(index, id);
3162
4268
  if (!entry) {
3163
- consola23.warn(`${id}: not found in registry, skipping`);
4269
+ consola25.warn(`${id}: not found in registry, skipping`);
3164
4270
  continue;
3165
4271
  }
3166
4272
  const localDigest = getInstalledDigest(id, false);
3167
4273
  if (localDigest === entry.digest) {
3168
- consola23.info(`${id}: already up to date`);
4274
+ consola25.info(`${id}: already up to date`);
3169
4275
  continue;
3170
4276
  }
3171
4277
  if (localDigest && !args.yes) {
3172
- consola23.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
3173
- consola23.info(` Old: ${localDigest}`);
3174
- consola23.info(` New: ${entry.digest}`);
3175
- consola23.info(" Use --yes to confirm");
4278
+ consola25.warn(`${id}: digest will change \u2014 existing grants for this adapter will be invalidated`);
4279
+ consola25.info(` Old: ${localDigest}`);
4280
+ consola25.info(` New: ${entry.digest}`);
4281
+ consola25.info(" Use --yes to confirm");
3176
4282
  continue;
3177
4283
  }
3178
4284
  const result = await installAdapter(entry);
3179
- consola23.success(`Updated ${result.id} \u2192 ${result.path}`);
4285
+ consola25.success(`Updated ${result.id} \u2192 ${result.path}`);
3180
4286
  }
3181
4287
  }
3182
4288
  }),
3183
- verify: defineCommand26({
4289
+ verify: defineCommand29({
3184
4290
  meta: {
3185
4291
  name: "verify",
3186
4292
  description: "Verify installed adapter against registry digest"
@@ -3213,7 +4319,7 @@ var adapterCommand = defineCommand26({
3213
4319
  if (!localDigest)
3214
4320
  throw new Error(`Adapter "${id}" is not installed${local ? " locally" : ""}`);
3215
4321
  if (localDigest === entry.digest) {
3216
- consola23.success(`${id}: digest matches registry`);
4322
+ consola25.success(`${id}: digest matches registry`);
3217
4323
  } else {
3218
4324
  console.log(` Local: ${localDigest}`);
3219
4325
  console.log(` Registry: ${entry.digest}`);
@@ -3225,11 +4331,11 @@ var adapterCommand = defineCommand26({
3225
4331
  });
3226
4332
 
3227
4333
  // src/commands/run.ts
3228
- import { execFileSync as execFileSync6 } from "child_process";
3229
- import { hostname as hostname3 } from "os";
4334
+ import { execFileSync as execFileSync10 } from "child_process";
4335
+ import { hostname as hostname4 } from "os";
3230
4336
  import { basename } from "path";
3231
- import { defineCommand as defineCommand27 } from "citty";
3232
- import consola24 from "consola";
4337
+ import { defineCommand as defineCommand30 } from "citty";
4338
+ import consola26 from "consola";
3233
4339
  function shouldWaitForGrant(args) {
3234
4340
  return args.wait === true || process.env.APE_WAIT === "1";
3235
4341
  }
@@ -3266,7 +4372,7 @@ function printPendingGrantInfo(grant, idp) {
3266
4372
  const statusCmd = `apes grants status ${grant.id}`;
3267
4373
  const executeCmd = `apes grants run ${grant.id}`;
3268
4374
  if (mode === "human") {
3269
- consola24.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
4375
+ consola26.success(`Grant ${grant.id} created \u2014 awaiting your approval`);
3270
4376
  console.log(` Approve in browser: ${approveUrl}`);
3271
4377
  console.log(` Check status: ${statusCmd}`);
3272
4378
  console.log(` Run after approval: ${executeCmd}`);
@@ -3276,7 +4382,7 @@ function printPendingGrantInfo(grant, idp) {
3276
4382
  return;
3277
4383
  }
3278
4384
  const maxMin = getPollMaxMinutes();
3279
- consola24.success(`Grant ${grant.id} created (pending approval)`);
4385
+ consola26.success(`Grant ${grant.id} created (pending approval)`);
3280
4386
  console.log(` Approve: ${approveUrl}`);
3281
4387
  console.log(` Status: ${statusCmd} [--json]`);
3282
4388
  console.log(` Execute: ${executeCmd} --wait`);
@@ -3298,7 +4404,7 @@ function printPendingGrantInfo(grant, idp) {
3298
4404
  console.log(' Tip: Approve as "timed" or "always" in the browser to let this');
3299
4405
  console.log(" grant be reused on subsequent invocations without re-approval.");
3300
4406
  }
3301
- var runCommand = defineCommand27({
4407
+ var runCommand = defineCommand30({
3302
4408
  meta: {
3303
4409
  name: "run",
3304
4410
  description: "Execute a grant-secured command"
@@ -3387,7 +4493,7 @@ async function runShellMode(command, args) {
3387
4493
  const adapterHandled = await tryAdapterModeFromShell(command, idp, args);
3388
4494
  if (adapterHandled) return;
3389
4495
  const grantsUrl = await getGrantsEndpoint(idp);
3390
- const targetHost = args.host || hostname3();
4496
+ const targetHost = args.host || hostname4();
3391
4497
  try {
3392
4498
  const grants = await apiFetch(
3393
4499
  `${grantsUrl}?requester=${encodeURIComponent(auth.email)}&status=approved&limit=20`
@@ -3401,7 +4507,7 @@ async function runShellMode(command, args) {
3401
4507
  }
3402
4508
  } catch {
3403
4509
  }
3404
- consola24.info(`Requesting ape-shell session grant on ${targetHost}`);
4510
+ consola26.info(`Requesting ape-shell session grant on ${targetHost}`);
3405
4511
  const grant = await apiFetch(grantsUrl, {
3406
4512
  method: "POST",
3407
4513
  body: {
@@ -3421,8 +4527,8 @@ async function runShellMode(command, args) {
3421
4527
  host: targetHost
3422
4528
  });
3423
4529
  if (shouldWaitForGrant(args)) {
3424
- consola24.info(`Grant requested: ${grant.id}`);
3425
- consola24.info("Waiting for approval...");
4530
+ consola26.info(`Grant requested: ${grant.id}`);
4531
+ consola26.info("Waiting for approval...");
3426
4532
  const maxWait = 3e5;
3427
4533
  const interval = 3e3;
3428
4534
  const start = Date.now();
@@ -3453,13 +4559,13 @@ async function tryAdapterModeFromShell(command, idp, args) {
3453
4559
  try {
3454
4560
  resolved = await resolveCommand(loaded, [normalizedExecutable, ...parsed.argv]);
3455
4561
  } catch (err) {
3456
- consola24.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
4562
+ consola26.debug(`ape-shell: adapter resolve failed for "${parsed.raw}":`, err);
3457
4563
  return false;
3458
4564
  }
3459
4565
  try {
3460
4566
  const existingGrantId = await findExistingGrant(resolved, idp);
3461
4567
  if (existingGrantId) {
3462
- consola24.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
4568
+ consola26.info(`Reusing grant ${existingGrantId} for: ${resolved.detail.display}`);
3463
4569
  const token = await fetchGrantToken(idp, existingGrantId);
3464
4570
  await verifyAndExecute(token, resolved, existingGrantId);
3465
4571
  return true;
@@ -3467,7 +4573,7 @@ async function tryAdapterModeFromShell(command, idp, args) {
3467
4573
  } catch {
3468
4574
  }
3469
4575
  const approval = args.approval ?? "once";
3470
- consola24.info(`Requesting grant for: ${resolved.detail.display}`);
4576
+ consola26.info(`Requesting grant for: ${resolved.detail.display}`);
3471
4577
  const grant = await createShapesGrant(resolved, {
3472
4578
  idp,
3473
4579
  approval,
@@ -3475,19 +4581,19 @@ async function tryAdapterModeFromShell(command, idp, args) {
3475
4581
  });
3476
4582
  if (grant.similar_grants?.similar_grants?.length) {
3477
4583
  const n = grant.similar_grants.similar_grants.length;
3478
- consola24.info("");
3479
- consola24.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
4584
+ consola26.info("");
4585
+ consola26.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
3480
4586
  }
3481
4587
  notifyGrantPending({
3482
4588
  grantId: grant.id,
3483
4589
  approveUrl: `${idp}/grant-approval?grant_id=${grant.id}`,
3484
4590
  command: resolved.detail?.display || parsed?.raw || "unknown",
3485
4591
  audience: resolved.adapter?.cli?.audience ?? "shapes",
3486
- host: args.host || hostname3()
4592
+ host: args.host || hostname4()
3487
4593
  });
3488
4594
  if (shouldWaitForGrant(args)) {
3489
- consola24.info(`Grant requested: ${grant.id}`);
3490
- consola24.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
4595
+ consola26.info(`Grant requested: ${grant.id}`);
4596
+ consola26.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
3491
4597
  const status = await waitForGrantStatus(idp, grant.id);
3492
4598
  if (status !== "approved")
3493
4599
  throw new CliError(`Grant ${status}`);
@@ -3503,7 +4609,7 @@ function execShellCommand(command) {
3503
4609
  throw new CliError("No command to execute");
3504
4610
  try {
3505
4611
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
3506
- execFileSync6(command[0], command.slice(1), {
4612
+ execFileSync10(command[0], command.slice(1), {
3507
4613
  stdio: "inherit",
3508
4614
  env: inheritedEnv
3509
4615
  });
@@ -3561,7 +4667,7 @@ async function runAdapterMode(command, rawArgs, args) {
3561
4667
  try {
3562
4668
  const existingGrantId = await findExistingGrant(resolved, idp);
3563
4669
  if (existingGrantId) {
3564
- consola24.info(`Reusing existing grant: ${existingGrantId}`);
4670
+ consola26.info(`Reusing existing grant: ${existingGrantId}`);
3565
4671
  const token = await fetchGrantToken(idp, existingGrantId);
3566
4672
  await verifyAndExecute(token, resolved, existingGrantId);
3567
4673
  return;
@@ -3575,17 +4681,17 @@ async function runAdapterMode(command, rawArgs, args) {
3575
4681
  });
3576
4682
  if (grant.similar_grants?.similar_grants?.length) {
3577
4683
  const n = grant.similar_grants.similar_grants.length;
3578
- consola24.info("");
3579
- consola24.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
4684
+ consola26.info("");
4685
+ consola26.info(` Similar grant(s) found (${n}). Your approver can extend an existing grant to cover this request.`);
3580
4686
  if (grant.similar_grants.widened_details?.length) {
3581
4687
  const wider = grant.similar_grants.widened_details.map((d) => d.permission).join(", ");
3582
- consola24.info(` Broader scope: ${wider}`);
4688
+ consola26.info(` Broader scope: ${wider}`);
3583
4689
  }
3584
- consola24.info("");
4690
+ consola26.info("");
3585
4691
  }
3586
4692
  if (shouldWaitForGrant(args)) {
3587
- consola24.info(`Grant requested: ${grant.id}`);
3588
- consola24.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
4693
+ consola26.info(`Grant requested: ${grant.id}`);
4694
+ consola26.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
3589
4695
  const status = await waitForGrantStatus(idp, grant.id);
3590
4696
  if (status !== "approved")
3591
4697
  throw new Error(`Grant ${status}`);
@@ -3604,8 +4710,8 @@ async function runAudienceMode(audience, action, args) {
3604
4710
  const idp = getIdpUrl(args.idp);
3605
4711
  const grantsUrl = await getGrantsEndpoint(idp);
3606
4712
  const command = action.split(" ");
3607
- const targetHost = args.host || hostname3();
3608
- consola24.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
4713
+ const targetHost = args.host || hostname4();
4714
+ consola26.info(`Requesting ${audience} grant on ${targetHost}: ${command.join(" ")}`);
3609
4715
  const grant = await apiFetch(grantsUrl, {
3610
4716
  method: "POST",
3611
4717
  body: {
@@ -3622,9 +4728,9 @@ async function runAudienceMode(audience, action, args) {
3622
4728
  printPendingGrantInfo(grant, idp);
3623
4729
  throw new CliExit(getAsyncExitCode());
3624
4730
  }
3625
- consola24.success(`Grant requested: ${grant.id}`);
3626
- consola24.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
3627
- consola24.info("Waiting for approval...");
4731
+ consola26.success(`Grant requested: ${grant.id}`);
4732
+ consola26.info(`Approve at: ${idp}/grant-approval?grant_id=${grant.id}`);
4733
+ consola26.info("Waiting for approval...");
3628
4734
  const maxWait = 15 * 60 * 1e3;
3629
4735
  const interval = 3e3;
3630
4736
  const start = Date.now();
@@ -3632,7 +4738,7 @@ async function runAudienceMode(audience, action, args) {
3632
4738
  while (Date.now() - start < maxWait) {
3633
4739
  const status = await apiFetch(`${grantsUrl}/${grant.id}`);
3634
4740
  if (status.status === "approved") {
3635
- consola24.success("Grant approved!");
4741
+ consola26.success("Grant approved!");
3636
4742
  approved = true;
3637
4743
  break;
3638
4744
  }
@@ -3647,15 +4753,15 @@ async function runAudienceMode(audience, action, args) {
3647
4753
  `Grant approval timed out after ${minutes} min (still pending). Check your DDISA inbox at ${idp}/grant-approval?grant_id=${grant.id} \u2014 if approved later, re-run the same \`apes run\` command and it will reuse the grant.`
3648
4754
  );
3649
4755
  }
3650
- consola24.info("Fetching grant token...");
4756
+ consola26.info("Fetching grant token...");
3651
4757
  const { authz_jwt } = await apiFetch(`${grantsUrl}/${grant.id}/token`, {
3652
4758
  method: "POST"
3653
4759
  });
3654
4760
  if (audience === "escapes") {
3655
- consola24.info(`Executing: ${command.join(" ")}`);
4761
+ consola26.info(`Executing: ${command.join(" ")}`);
3656
4762
  try {
3657
4763
  const { APES_SHELL_WRAPPER: _wrapperMarker, ...inheritedEnv } = process.env;
3658
- execFileSync6(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
4764
+ execFileSync10(args["escapes-path"] || "escapes", ["--grant", authz_jwt, "--", ...command], {
3659
4765
  stdio: "inherit",
3660
4766
  env: inheritedEnv
3661
4767
  });
@@ -3670,8 +4776,8 @@ async function runAudienceMode(audience, action, args) {
3670
4776
 
3671
4777
  // src/commands/proxy.ts
3672
4778
  import { spawn as spawn2 } from "child_process";
3673
- import { defineCommand as defineCommand28 } from "citty";
3674
- import consola25 from "consola";
4779
+ import { defineCommand as defineCommand31 } from "citty";
4780
+ import consola27 from "consola";
3675
4781
 
3676
4782
  // src/proxy/config.ts
3677
4783
  function buildDefaultProxyConfigToml(opts) {
@@ -3705,10 +4811,10 @@ note = "VPC-internal hostname suffix"
3705
4811
 
3706
4812
  // src/proxy/local-proxy.ts
3707
4813
  import { spawn } from "child_process";
3708
- import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
4814
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync3, writeFileSync as writeFileSync7 } from "fs";
3709
4815
  import { createRequire } from "module";
3710
4816
  import { tmpdir as tmpdir3 } from "os";
3711
- import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
4817
+ import { dirname as dirname3, join as join9, resolve as resolve4 } from "path";
3712
4818
  var require2 = createRequire(import.meta.url);
3713
4819
  function findProxyBin() {
3714
4820
  const pkgPath = require2.resolve("@openape/proxy/package.json");
@@ -3717,12 +4823,12 @@ function findProxyBin() {
3717
4823
  if (!binRel) {
3718
4824
  throw new Error("@openape/proxy is missing the openape-proxy bin entry");
3719
4825
  }
3720
- return resolve3(dirname2(pkgPath), binRel);
4826
+ return resolve4(dirname3(pkgPath), binRel);
3721
4827
  }
3722
4828
  async function startEphemeralProxy(configToml) {
3723
- const tmpDir = mkdtempSync3(join5(tmpdir3(), "openape-proxy-"));
3724
- const configPath = join5(tmpDir, "config.toml");
3725
- writeFileSync4(configPath, configToml, { mode: 384 });
4829
+ const tmpDir = mkdtempSync3(join9(tmpdir3(), "openape-proxy-"));
4830
+ const configPath = join9(tmpDir, "config.toml");
4831
+ writeFileSync7(configPath, configToml, { mode: 384 });
3726
4832
  const binPath = findProxyBin();
3727
4833
  const child = spawn(process.execPath, [binPath, "-c", configPath], {
3728
4834
  stdio: ["ignore", "pipe", "pipe"],
@@ -3814,10 +4920,10 @@ function resolveProxyConfigOptions() {
3814
4920
  77
3815
4921
  );
3816
4922
  }
3817
- consola25.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
4923
+ consola27.info(`[apes proxy] IdP-mediated mode \u2014 agent=${auth.email}, idp=${auth.idp}`);
3818
4924
  return { agentEmail: auth.email, idpUrl: auth.idp, mediated: true };
3819
4925
  }
3820
- var proxyCommand = defineCommand28({
4926
+ var proxyCommand = defineCommand31({
3821
4927
  meta: {
3822
4928
  name: "proxy",
3823
4929
  description: "Run a command with HTTPS_PROXY routed through the OpenApe egress proxy."
@@ -3839,12 +4945,12 @@ var proxyCommand = defineCommand28({
3839
4945
  let close = null;
3840
4946
  if (reuseUrl) {
3841
4947
  proxyUrl = reuseUrl;
3842
- consola25.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
4948
+ consola27.info(`[apes proxy] reusing existing proxy at ${proxyUrl}`);
3843
4949
  } else {
3844
4950
  const ephemeral = await startEphemeralProxy(buildDefaultProxyConfigToml(resolveProxyConfigOptions()));
3845
4951
  proxyUrl = ephemeral.url;
3846
4952
  close = ephemeral.close;
3847
- consola25.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
4953
+ consola27.info(`[apes proxy] started ephemeral proxy at ${proxyUrl}`);
3848
4954
  }
3849
4955
  const noProxy = process.env.NO_PROXY ?? process.env.no_proxy ?? "127.0.0.1,localhost";
3850
4956
  const childEnv = {
@@ -3876,7 +4982,7 @@ var proxyCommand = defineCommand28({
3876
4982
  else resolveExit(code ?? 0);
3877
4983
  });
3878
4984
  child.once("error", (err) => {
3879
- consola25.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
4985
+ consola27.error(`[apes proxy] failed to spawn '${wrapped[0]}':`, err.message);
3880
4986
  resolveExit(127);
3881
4987
  });
3882
4988
  });
@@ -3890,8 +4996,8 @@ function signalNumber(signal) {
3890
4996
  }
3891
4997
 
3892
4998
  // src/commands/explain.ts
3893
- import { defineCommand as defineCommand29 } from "citty";
3894
- var explainCommand = defineCommand29({
4999
+ import { defineCommand as defineCommand32 } from "citty";
5000
+ var explainCommand = defineCommand32({
3895
5001
  meta: {
3896
5002
  name: "explain",
3897
5003
  description: "Show what permission a command would need"
@@ -3929,9 +5035,9 @@ var explainCommand = defineCommand29({
3929
5035
  });
3930
5036
 
3931
5037
  // src/commands/config/get.ts
3932
- import { defineCommand as defineCommand30 } from "citty";
3933
- import consola26 from "consola";
3934
- var configGetCommand = defineCommand30({
5038
+ import { defineCommand as defineCommand33 } from "citty";
5039
+ import consola28 from "consola";
5040
+ var configGetCommand = defineCommand33({
3935
5041
  meta: {
3936
5042
  name: "get",
3937
5043
  description: "Get a configuration value"
@@ -3951,7 +5057,7 @@ var configGetCommand = defineCommand30({
3951
5057
  if (idp)
3952
5058
  console.log(idp);
3953
5059
  else
3954
- consola26.info("No IdP configured.");
5060
+ consola28.info("No IdP configured.");
3955
5061
  break;
3956
5062
  }
3957
5063
  case "email": {
@@ -3959,7 +5065,7 @@ var configGetCommand = defineCommand30({
3959
5065
  if (auth?.email)
3960
5066
  console.log(auth.email);
3961
5067
  else
3962
- consola26.info("Not logged in.");
5068
+ consola28.info("Not logged in.");
3963
5069
  break;
3964
5070
  }
3965
5071
  default: {
@@ -3972,7 +5078,7 @@ var configGetCommand = defineCommand30({
3972
5078
  if (sectionObj && field in sectionObj) {
3973
5079
  console.log(sectionObj[field]);
3974
5080
  } else {
3975
- consola26.info(`Key "${key}" not set.`);
5081
+ consola28.info(`Key "${key}" not set.`);
3976
5082
  }
3977
5083
  } else {
3978
5084
  throw new CliError(`Unknown key: "${key}". Use: idp, email, defaults.idp, defaults.approval, agent.key, agent.email`);
@@ -3983,9 +5089,9 @@ var configGetCommand = defineCommand30({
3983
5089
  });
3984
5090
 
3985
5091
  // src/commands/config/set.ts
3986
- import { defineCommand as defineCommand31 } from "citty";
3987
- import consola27 from "consola";
3988
- var configSetCommand = defineCommand31({
5092
+ import { defineCommand as defineCommand34 } from "citty";
5093
+ import consola29 from "consola";
5094
+ var configSetCommand = defineCommand34({
3989
5095
  meta: {
3990
5096
  name: "set",
3991
5097
  description: "Set a configuration value"
@@ -4021,12 +5127,12 @@ var configSetCommand = defineCommand31({
4021
5127
  throw new CliError(`Unknown section: "${section}". Use: defaults, agent`);
4022
5128
  }
4023
5129
  saveConfig(config);
4024
- consola27.success(`Set ${key} = ${value}`);
5130
+ consola29.success(`Set ${key} = ${value}`);
4025
5131
  }
4026
5132
  });
4027
5133
 
4028
5134
  // src/commands/fetch/index.ts
4029
- import { defineCommand as defineCommand32 } from "citty";
5135
+ import { defineCommand as defineCommand35 } from "citty";
4030
5136
  async function doRequest(method, url, body, contentType, raw, showHeaders) {
4031
5137
  const token = getAuthToken();
4032
5138
  if (!token) {
@@ -4062,13 +5168,13 @@ async function doRequest(method, url, body, contentType, raw, showHeaders) {
4062
5168
  throw new CliError(`HTTP ${response.status} ${response.statusText}`);
4063
5169
  }
4064
5170
  }
4065
- var fetchCommand = defineCommand32({
5171
+ var fetchCommand = defineCommand35({
4066
5172
  meta: {
4067
5173
  name: "fetch",
4068
5174
  description: "Make authenticated HTTP requests"
4069
5175
  },
4070
5176
  subCommands: {
4071
- get: defineCommand32({
5177
+ get: defineCommand35({
4072
5178
  meta: {
4073
5179
  name: "get",
4074
5180
  description: "GET request with auth token"
@@ -4094,7 +5200,7 @@ var fetchCommand = defineCommand32({
4094
5200
  await doRequest("GET", String(args.url), void 0, "application/json", Boolean(args.raw), Boolean(args.headers));
4095
5201
  }
4096
5202
  }),
4097
- post: defineCommand32({
5203
+ post: defineCommand35({
4098
5204
  meta: {
4099
5205
  name: "post",
4100
5206
  description: "POST request with auth token"
@@ -4133,8 +5239,8 @@ var fetchCommand = defineCommand32({
4133
5239
  });
4134
5240
 
4135
5241
  // src/commands/mcp/index.ts
4136
- import { defineCommand as defineCommand33 } from "citty";
4137
- var mcpCommand = defineCommand33({
5242
+ import { defineCommand as defineCommand36 } from "citty";
5243
+ var mcpCommand = defineCommand36({
4138
5244
  meta: {
4139
5245
  name: "mcp",
4140
5246
  description: "Start MCP server for AI agents"
@@ -4157,48 +5263,48 @@ var mcpCommand = defineCommand33({
4157
5263
  if (transport !== "stdio" && transport !== "sse") {
4158
5264
  throw new Error('Transport must be "stdio" or "sse"');
4159
5265
  }
4160
- const { startMcpServer } = await import("./server-E26WAGHT.js");
5266
+ const { startMcpServer } = await import("./server-TU2WH6QN.js");
4161
5267
  await startMcpServer(transport, port);
4162
5268
  }
4163
5269
  });
4164
5270
 
4165
5271
  // src/commands/init/index.ts
4166
- import { existsSync as existsSync7, copyFileSync, writeFileSync as writeFileSync5 } from "fs";
5272
+ import { existsSync as existsSync10, copyFileSync, writeFileSync as writeFileSync8 } from "fs";
4167
5273
  import { randomBytes } from "crypto";
4168
- import { execFileSync as execFileSync7 } from "child_process";
4169
- import { join as join6 } from "path";
4170
- import { defineCommand as defineCommand34 } from "citty";
4171
- import consola28 from "consola";
5274
+ import { execFileSync as execFileSync11 } from "child_process";
5275
+ import { join as join10 } from "path";
5276
+ import { defineCommand as defineCommand37 } from "citty";
5277
+ import consola30 from "consola";
4172
5278
  var DEFAULT_IDP_URL = "https://id.openape.at";
4173
5279
  async function downloadTemplate(repo, targetDir) {
4174
5280
  const { downloadTemplate: gigetDownload } = await import("giget");
4175
5281
  await gigetDownload(`gh:${repo}`, { dir: targetDir, force: false });
4176
5282
  }
4177
5283
  function installDeps(dir) {
4178
- const hasLockFile = (name) => existsSync7(join6(dir, name));
5284
+ const hasLockFile = (name) => existsSync10(join10(dir, name));
4179
5285
  if (hasLockFile("pnpm-lock.yaml")) {
4180
- execFileSync7("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
5286
+ execFileSync11("pnpm", ["install"], { cwd: dir, stdio: "inherit" });
4181
5287
  } else if (hasLockFile("bun.lockb")) {
4182
- execFileSync7("bun", ["install"], { cwd: dir, stdio: "inherit" });
5288
+ execFileSync11("bun", ["install"], { cwd: dir, stdio: "inherit" });
4183
5289
  } else {
4184
- execFileSync7("npm", ["install"], { cwd: dir, stdio: "inherit" });
5290
+ execFileSync11("npm", ["install"], { cwd: dir, stdio: "inherit" });
4185
5291
  }
4186
5292
  }
4187
5293
  async function promptChoice(message, choices) {
4188
- const result = await consola28.prompt(message, { type: "select", options: choices });
5294
+ const result = await consola30.prompt(message, { type: "select", options: choices });
4189
5295
  if (typeof result === "symbol") {
4190
5296
  throw new CliExit(0);
4191
5297
  }
4192
5298
  return result;
4193
5299
  }
4194
5300
  async function promptText(message, defaultValue) {
4195
- const result = await consola28.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
5301
+ const result = await consola30.prompt(message, { type: "text", default: defaultValue, placeholder: defaultValue });
4196
5302
  if (typeof result === "symbol") {
4197
5303
  throw new CliExit(0);
4198
5304
  }
4199
5305
  return result || defaultValue || "";
4200
5306
  }
4201
- var initCommand = defineCommand34({
5307
+ var initCommand = defineCommand37({
4202
5308
  meta: {
4203
5309
  name: "init",
4204
5310
  description: "Scaffold a new OpenApe project"
@@ -4240,23 +5346,23 @@ var initCommand = defineCommand34({
4240
5346
  });
4241
5347
  async function initSP(targetDir) {
4242
5348
  const dir = targetDir || "my-app";
4243
- if (existsSync7(join6(dir, "package.json"))) {
5349
+ if (existsSync10(join10(dir, "package.json"))) {
4244
5350
  throw new CliError(`Directory "${dir}" already contains a project.`);
4245
5351
  }
4246
- consola28.start("Scaffolding SP starter...");
5352
+ consola30.start("Scaffolding SP starter...");
4247
5353
  await downloadTemplate("openape-ai/openape-sp-starter", dir);
4248
- consola28.success("Scaffolded from openape-sp-starter");
4249
- consola28.start("Installing dependencies...");
5354
+ consola30.success("Scaffolded from openape-sp-starter");
5355
+ consola30.start("Installing dependencies...");
4250
5356
  installDeps(dir);
4251
- consola28.success("Dependencies installed");
4252
- const envExample = join6(dir, ".env.example");
4253
- const envFile = join6(dir, ".env");
4254
- if (existsSync7(envExample) && !existsSync7(envFile)) {
5357
+ consola30.success("Dependencies installed");
5358
+ const envExample = join10(dir, ".env.example");
5359
+ const envFile = join10(dir, ".env");
5360
+ if (existsSync10(envExample) && !existsSync10(envFile)) {
4255
5361
  copyFileSync(envExample, envFile);
4256
- consola28.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
5362
+ consola30.success(`\`.env\` created (using Free IdP at ${DEFAULT_IDP_URL})`);
4257
5363
  }
4258
5364
  console.log("");
4259
- consola28.box([
5365
+ consola30.box([
4260
5366
  `cd ${dir}`,
4261
5367
  "npm run dev",
4262
5368
  "",
@@ -4265,7 +5371,7 @@ async function initSP(targetDir) {
4265
5371
  }
4266
5372
  async function initIdP(targetDir) {
4267
5373
  const dir = targetDir || "my-idp";
4268
- if (existsSync7(join6(dir, "package.json"))) {
5374
+ if (existsSync10(join10(dir, "package.json"))) {
4269
5375
  throw new CliError(`Directory "${dir}" already contains a project.`);
4270
5376
  }
4271
5377
  const domain = await promptText("Domain for the IdP", "localhost");
@@ -4275,15 +5381,15 @@ async function initIdP(targetDir) {
4275
5381
  "s3 (S3-compatible)"
4276
5382
  ]);
4277
5383
  const adminEmail = await promptText("Admin email");
4278
- consola28.start("Scaffolding IdP starter...");
5384
+ consola30.start("Scaffolding IdP starter...");
4279
5385
  await downloadTemplate("openape-ai/openape-idp-starter", dir);
4280
- consola28.success("Scaffolded from openape-idp-starter");
4281
- consola28.start("Installing dependencies...");
5386
+ consola30.success("Scaffolded from openape-idp-starter");
5387
+ consola30.start("Installing dependencies...");
4282
5388
  installDeps(dir);
4283
- consola28.success("Dependencies installed");
5389
+ consola30.success("Dependencies installed");
4284
5390
  const sessionSecret = randomBytes(32).toString("hex");
4285
5391
  const managementToken = randomBytes(32).toString("hex");
4286
- consola28.success("Secrets generated");
5392
+ consola30.success("Secrets generated");
4287
5393
  const isLocalhost = domain === "localhost";
4288
5394
  const origin = isLocalhost ? "http://localhost:3000" : `https://${domain}`;
4289
5395
  const envContent = [
@@ -4297,11 +5403,11 @@ async function initIdP(targetDir) {
4297
5403
  `NUXT_OPENAPE_RP_ID=${domain}`,
4298
5404
  `NUXT_OPENAPE_RP_ORIGIN=${origin}`
4299
5405
  ].join("\n");
4300
- writeFileSync5(join6(dir, ".env"), `${envContent}
5406
+ writeFileSync8(join10(dir, ".env"), `${envContent}
4301
5407
  `, { mode: 384 });
4302
- consola28.success(".env created");
5408
+ consola30.success(".env created");
4303
5409
  console.log("");
4304
- consola28.box([
5410
+ consola30.box([
4305
5411
  `cd ${dir}`,
4306
5412
  "npm run dev",
4307
5413
  "",
@@ -4318,11 +5424,11 @@ async function initIdP(targetDir) {
4318
5424
 
4319
5425
  // src/commands/enroll.ts
4320
5426
  import { Buffer as Buffer5 } from "buffer";
4321
- import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
5427
+ import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
4322
5428
  import { execFile as execFile2 } from "child_process";
4323
5429
  import { sign as sign2 } from "crypto";
4324
- import { defineCommand as defineCommand35 } from "citty";
4325
- import consola29 from "consola";
5430
+ import { defineCommand as defineCommand38 } from "citty";
5431
+ import consola31 from "consola";
4326
5432
  var DEFAULT_IDP_URL2 = "https://id.openape.at";
4327
5433
  var DEFAULT_KEY_PATH = "~/.ssh/id_ed25519";
4328
5434
  var POLL_INTERVAL = 3e3;
@@ -4334,7 +5440,7 @@ function openBrowser2(url) {
4334
5440
  }
4335
5441
  async function pollForEnrollment(idp, agentEmail, keyPath) {
4336
5442
  const resolvedKey = resolveKeyPath(keyPath);
4337
- const keyContent = readFileSync6(resolvedKey, "utf-8");
5443
+ const keyContent = readFileSync11(resolvedKey, "utf-8");
4338
5444
  const privateKey = loadEd25519PrivateKey(keyContent);
4339
5445
  const challengeUrl = await getAgentChallengeEndpoint(idp);
4340
5446
  const authenticateUrl = await getAgentAuthenticateEndpoint(idp);
@@ -4361,11 +5467,11 @@ async function pollForEnrollment(idp, agentEmail, keyPath) {
4361
5467
  }
4362
5468
  } catch {
4363
5469
  }
4364
- await new Promise((resolve4) => setTimeout(resolve4, POLL_INTERVAL));
5470
+ await new Promise((resolve5) => setTimeout(resolve5, POLL_INTERVAL));
4365
5471
  }
4366
5472
  throw new Error("Enrollment timed out. Please check the browser and try again.");
4367
5473
  }
4368
- var enrollCommand = defineCommand35({
5474
+ var enrollCommand = defineCommand38({
4369
5475
  meta: {
4370
5476
  name: "enroll",
4371
5477
  description: "Enroll an agent with an Identity Provider"
@@ -4385,38 +5491,38 @@ var enrollCommand = defineCommand35({
4385
5491
  }
4386
5492
  },
4387
5493
  async run({ args }) {
4388
- const idp = args.idp || await consola29.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
5494
+ const idp = args.idp || await consola31.prompt("IdP URL", { type: "text", default: DEFAULT_IDP_URL2, placeholder: DEFAULT_IDP_URL2 }).then((r) => {
4389
5495
  if (typeof r === "symbol") throw new CliExit(0);
4390
5496
  return r;
4391
5497
  }) || DEFAULT_IDP_URL2;
4392
- const agentName = args.name || await consola29.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
5498
+ const agentName = args.name || await consola31.prompt("Agent name", { type: "text", placeholder: "deploy-bot" }).then((r) => {
4393
5499
  if (typeof r === "symbol") throw new CliExit(0);
4394
5500
  return r;
4395
5501
  });
4396
5502
  if (!agentName) {
4397
5503
  throw new CliError("Agent name is required.");
4398
5504
  }
4399
- const keyPath = args.key || await consola29.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
5505
+ const keyPath = args.key || await consola31.prompt("Ed25519 key", { type: "text", default: DEFAULT_KEY_PATH, placeholder: DEFAULT_KEY_PATH }).then((r) => {
4400
5506
  if (typeof r === "symbol") throw new CliExit(0);
4401
5507
  return r;
4402
5508
  }) || DEFAULT_KEY_PATH;
4403
5509
  const resolvedKey = resolveKeyPath(keyPath);
4404
5510
  let publicKey;
4405
- if (existsSync8(resolvedKey)) {
5511
+ if (existsSync11(resolvedKey)) {
4406
5512
  publicKey = readPublicKey(resolvedKey);
4407
- consola29.success(`Using existing key ${keyPath}`);
5513
+ consola31.success(`Using existing key ${keyPath}`);
4408
5514
  } else {
4409
- consola29.start(`Generating Ed25519 key pair at ${keyPath}...`);
5515
+ consola31.start(`Generating Ed25519 key pair at ${keyPath}...`);
4410
5516
  publicKey = generateAndSaveKey(keyPath);
4411
- consola29.success(`Key pair generated at ${keyPath}`);
5517
+ consola31.success(`Key pair generated at ${keyPath}`);
4412
5518
  }
4413
5519
  const encodedKey = encodeURIComponent(publicKey);
4414
5520
  const enrollUrl = `${idp}/enroll?name=${encodeURIComponent(agentName)}&key=${encodedKey}`;
4415
- consola29.info("Opening browser for enrollment...");
4416
- consola29.info(`\u2192 ${idp}/enroll`);
5521
+ consola31.info("Opening browser for enrollment...");
5522
+ consola31.info(`\u2192 ${idp}/enroll`);
4417
5523
  openBrowser2(enrollUrl);
4418
5524
  console.log("");
4419
- const agentEmail = await consola29.prompt(
5525
+ const agentEmail = await consola31.prompt(
4420
5526
  "Agent email (shown in browser after enrollment)",
4421
5527
  { type: "text", placeholder: `agent+${agentName}@...` }
4422
5528
  ).then((r) => {
@@ -4426,7 +5532,7 @@ var enrollCommand = defineCommand35({
4426
5532
  if (!agentEmail) {
4427
5533
  throw new CliError("Agent email is required to verify enrollment.");
4428
5534
  }
4429
- consola29.start("Verifying enrollment...");
5535
+ consola31.start("Verifying enrollment...");
4430
5536
  const { token, expiresIn } = await pollForEnrollment(idp, agentEmail, keyPath);
4431
5537
  saveAuth({
4432
5538
  idp,
@@ -4438,18 +5544,18 @@ var enrollCommand = defineCommand35({
4438
5544
  config.defaults = { ...config.defaults, idp };
4439
5545
  config.agent = { key: keyPath, email: agentEmail };
4440
5546
  saveConfig(config);
4441
- consola29.success(`Agent enrolled as ${agentEmail}`);
4442
- consola29.success("Config saved to ~/.config/apes/");
5547
+ consola31.success(`Agent enrolled as ${agentEmail}`);
5548
+ consola31.success("Config saved to ~/.config/apes/");
4443
5549
  console.log("");
4444
- consola29.info("Verify with: apes whoami");
5550
+ consola31.info("Verify with: apes whoami");
4445
5551
  }
4446
5552
  });
4447
5553
 
4448
5554
  // src/commands/register-user.ts
4449
- import { existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
4450
- import { defineCommand as defineCommand36 } from "citty";
4451
- import consola30 from "consola";
4452
- var registerUserCommand = defineCommand36({
5555
+ import { existsSync as existsSync12, readFileSync as readFileSync12 } from "fs";
5556
+ import { defineCommand as defineCommand39 } from "citty";
5557
+ import consola32 from "consola";
5558
+ var registerUserCommand = defineCommand39({
4453
5559
  meta: {
4454
5560
  name: "register-user",
4455
5561
  description: "Register a sub-user with SSH key"
@@ -4485,8 +5591,8 @@ var registerUserCommand = defineCommand36({
4485
5591
  throw new CliError("No IdP URL configured. Run `apes login` first.");
4486
5592
  }
4487
5593
  let publicKey = args.key;
4488
- if (existsSync9(args.key)) {
4489
- publicKey = readFileSync7(args.key, "utf-8").trim();
5594
+ if (existsSync12(args.key)) {
5595
+ publicKey = readFileSync12(args.key, "utf-8").trim();
4490
5596
  }
4491
5597
  if (!publicKey.startsWith("ssh-ed25519 ")) {
4492
5598
  throw new CliError("Public key must be in ssh-ed25519 format.");
@@ -4504,18 +5610,18 @@ var registerUserCommand = defineCommand36({
4504
5610
  ...userType ? { type: userType } : {}
4505
5611
  }
4506
5612
  });
4507
- consola30.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
5613
+ consola32.success(`User registered: ${result.email} (type: ${result.type}, owner: ${result.owner})`);
4508
5614
  }
4509
5615
  });
4510
5616
 
4511
5617
  // src/commands/utils/index.ts
4512
- import { defineCommand as defineCommand38 } from "citty";
5618
+ import { defineCommand as defineCommand41 } from "citty";
4513
5619
 
4514
5620
  // src/commands/utils/dig.ts
4515
- import { defineCommand as defineCommand37 } from "citty";
4516
- import consola31 from "consola";
5621
+ import { defineCommand as defineCommand40 } from "citty";
5622
+ import consola33 from "consola";
4517
5623
  import { resolveDDISA as resolveDDISA2 } from "@openape/core";
4518
- var digCommand = defineCommand37({
5624
+ var digCommand = defineCommand40({
4519
5625
  meta: {
4520
5626
  name: "dig",
4521
5627
  description: "Resolve DDISA IdP for a domain or email (admin/diag tool)"
@@ -4588,12 +5694,12 @@ var digCommand = defineCommand37({
4588
5694
  console.log(` domain: ${domain}`);
4589
5695
  console.log("");
4590
5696
  if (!result.ddisa.found) {
4591
- consola31.warn(`No DDISA record at _ddisa.${domain}`);
5697
+ consola33.warn(`No DDISA record at _ddisa.${domain}`);
4592
5698
  if (result.hint) console.log(`
4593
5699
  ${result.hint}`);
4594
5700
  throw new CliError(`No DDISA record found for ${domain}`);
4595
5701
  }
4596
- consola31.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
5702
+ consola33.success(`_ddisa.${domain} \u2192 ${result.ddisa.idp}`);
4597
5703
  console.log(` Version: ${result.ddisa.version || "ddisa1"}`);
4598
5704
  console.log(` IdP URL: ${result.ddisa.idp}`);
4599
5705
  if (result.ddisa.mode) console.log(` Mode: ${result.ddisa.mode}`);
@@ -4603,13 +5709,13 @@ ${result.hint}`);
4603
5709
  return;
4604
5710
  }
4605
5711
  if (result.idpDiscovery.ok) {
4606
- consola31.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
5712
+ consola33.success(`IdP reachable (${result.idpDiscovery.status ?? 200})`);
4607
5713
  if (result.idpDiscovery.issuer) console.log(` Issuer: ${result.idpDiscovery.issuer}`);
4608
5714
  if (result.idpDiscovery.ddisaVersion) console.log(` DDISA: v${result.idpDiscovery.ddisaVersion}`);
4609
5715
  if (result.idpDiscovery.authMethods?.length) console.log(` Auth: ${result.idpDiscovery.authMethods.join(", ")}`);
4610
5716
  if (result.idpDiscovery.grantTypes?.length) console.log(` Grants: ${result.idpDiscovery.grantTypes.join(", ")}`);
4611
5717
  } else {
4612
- consola31.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
5718
+ consola33.warn(`IdP discovery failed${result.idpDiscovery.status ? ` (HTTP ${result.idpDiscovery.status})` : ""}`);
4613
5719
  if (result.hint) console.log(`
4614
5720
  ${result.hint}`);
4615
5721
  throw new CliError(`IdP at ${result.ddisa.idp} not reachable`);
@@ -4618,7 +5724,7 @@ ${result.hint}`);
4618
5724
  });
4619
5725
 
4620
5726
  // src/commands/utils/index.ts
4621
- var utilsCommand = defineCommand38({
5727
+ var utilsCommand = defineCommand41({
4622
5728
  meta: {
4623
5729
  name: "utils",
4624
5730
  description: "Admin/diagnostic utilities (dig, \u2026)"
@@ -4629,12 +5735,12 @@ var utilsCommand = defineCommand38({
4629
5735
  });
4630
5736
 
4631
5737
  // src/commands/sessions/index.ts
4632
- import { defineCommand as defineCommand41 } from "citty";
5738
+ import { defineCommand as defineCommand44 } from "citty";
4633
5739
 
4634
5740
  // src/commands/sessions/list.ts
4635
- import { defineCommand as defineCommand39 } from "citty";
4636
- import consola32 from "consola";
4637
- var sessionsListCommand = defineCommand39({
5741
+ import { defineCommand as defineCommand42 } from "citty";
5742
+ import consola34 from "consola";
5743
+ var sessionsListCommand = defineCommand42({
4638
5744
  meta: {
4639
5745
  name: "list",
4640
5746
  description: "List your active refresh-token families (one per logged-in device)."
@@ -4652,7 +5758,7 @@ var sessionsListCommand = defineCommand39({
4652
5758
  return;
4653
5759
  }
4654
5760
  if (result.data.length === 0) {
4655
- consola32.info("No active sessions.");
5761
+ consola34.info("No active sessions.");
4656
5762
  return;
4657
5763
  }
4658
5764
  for (const f of result.data) {
@@ -4664,9 +5770,9 @@ var sessionsListCommand = defineCommand39({
4664
5770
  });
4665
5771
 
4666
5772
  // src/commands/sessions/remove.ts
4667
- import { defineCommand as defineCommand40 } from "citty";
4668
- import consola33 from "consola";
4669
- var sessionsRemoveCommand = defineCommand40({
5773
+ import { defineCommand as defineCommand43 } from "citty";
5774
+ import consola35 from "consola";
5775
+ var sessionsRemoveCommand = defineCommand43({
4670
5776
  meta: {
4671
5777
  name: "remove",
4672
5778
  description: "Revoke one of your active refresh-token families by id."
@@ -4682,12 +5788,12 @@ var sessionsRemoveCommand = defineCommand40({
4682
5788
  const id = String(args.familyId).trim();
4683
5789
  if (!id) throw new CliError("familyId required");
4684
5790
  await apiFetch(`/api/me/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
4685
- consola33.success(`Session ${id} revoked. The device using it will need to \`apes login\` again on its next refresh.`);
5791
+ consola35.success(`Session ${id} revoked. The device using it will need to \`apes login\` again on its next refresh.`);
4686
5792
  }
4687
5793
  });
4688
5794
 
4689
5795
  // src/commands/sessions/index.ts
4690
- var sessionsCommand = defineCommand41({
5796
+ var sessionsCommand = defineCommand44({
4691
5797
  meta: {
4692
5798
  name: "sessions",
4693
5799
  description: "Manage your active refresh-token sessions across devices"
@@ -4699,10 +5805,10 @@ var sessionsCommand = defineCommand41({
4699
5805
  });
4700
5806
 
4701
5807
  // src/commands/dns-check.ts
4702
- import { defineCommand as defineCommand42 } from "citty";
4703
- import consola34 from "consola";
5808
+ import { defineCommand as defineCommand45 } from "citty";
5809
+ import consola36 from "consola";
4704
5810
  import { resolveDDISA as resolveDDISA3 } from "@openape/core";
4705
- var dnsCheckCommand = defineCommand42({
5811
+ var dnsCheckCommand = defineCommand45({
4706
5812
  meta: {
4707
5813
  name: "dns-check",
4708
5814
  description: "Validate DDISA DNS TXT records for a domain"
@@ -4716,7 +5822,7 @@ var dnsCheckCommand = defineCommand42({
4716
5822
  },
4717
5823
  async run({ args }) {
4718
5824
  const domain = args.domain;
4719
- consola34.start(`Checking _ddisa.${domain}...`);
5825
+ consola36.start(`Checking _ddisa.${domain}...`);
4720
5826
  try {
4721
5827
  const result = await resolveDDISA3(domain);
4722
5828
  if (!result) {
@@ -4725,7 +5831,7 @@ var dnsCheckCommand = defineCommand42({
4725
5831
  console.log(` _ddisa.${domain} TXT "v=ddisa1 idp=https://id.${domain}"`);
4726
5832
  throw new CliError(`No DDISA record found for ${domain}`);
4727
5833
  }
4728
- consola34.success(`_ddisa.${domain} \u2192 ${result.idp}`);
5834
+ consola36.success(`_ddisa.${domain} \u2192 ${result.idp}`);
4729
5835
  console.log("");
4730
5836
  console.log(` Version: ${result.version || "ddisa1"}`);
4731
5837
  console.log(` IdP URL: ${result.idp}`);
@@ -4734,14 +5840,14 @@ var dnsCheckCommand = defineCommand42({
4734
5840
  if (result.priority !== void 0)
4735
5841
  console.log(` Priority: ${result.priority}`);
4736
5842
  console.log("");
4737
- consola34.start(`Verifying IdP at ${result.idp}...`);
5843
+ consola36.start(`Verifying IdP at ${result.idp}...`);
4738
5844
  const discoResp = await fetch(`${result.idp}/.well-known/openid-configuration`);
4739
5845
  if (!discoResp.ok) {
4740
- consola34.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
5846
+ consola36.warn(`IdP discovery failed (${discoResp.status}). Is the IdP running at ${result.idp}?`);
4741
5847
  return;
4742
5848
  }
4743
5849
  const disco = await discoResp.json();
4744
- consola34.success(`IdP is reachable`);
5850
+ consola36.success(`IdP is reachable`);
4745
5851
  console.log(` Issuer: ${disco.issuer}`);
4746
5852
  console.log(` DDISA: v${disco.ddisa_version || "?"}`);
4747
5853
  if (disco.ddisa_auth_methods_supported) {
@@ -4759,7 +5865,7 @@ var dnsCheckCommand = defineCommand42({
4759
5865
  // src/commands/health.ts
4760
5866
  import { exec } from "child_process";
4761
5867
  import { promisify } from "util";
4762
- import { defineCommand as defineCommand43 } from "citty";
5868
+ import { defineCommand as defineCommand46 } from "citty";
4763
5869
  var execAsync = promisify(exec);
4764
5870
  async function resolveApeShellPath() {
4765
5871
  try {
@@ -4795,7 +5901,7 @@ async function bestEffortGrantCount(idp) {
4795
5901
  }
4796
5902
  }
4797
5903
  async function runHealth(args) {
4798
- const version = true ? "0.31.3" : "0.0.0";
5904
+ const version = true ? "0.32.0" : "0.0.0";
4799
5905
  const auth = loadAuth();
4800
5906
  if (!auth) {
4801
5907
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -4858,7 +5964,7 @@ async function runHealth(args) {
4858
5964
  throw new CliError(`IdP ${auth.idp} unreachable: ${idpProbe.error}`, 1);
4859
5965
  }
4860
5966
  }
4861
- var healthCommand = defineCommand43({
5967
+ var healthCommand = defineCommand46({
4862
5968
  meta: {
4863
5969
  name: "health",
4864
5970
  description: "Report CLI diagnostic state (auth, IdP, grants, binaries)"
@@ -4876,8 +5982,8 @@ var healthCommand = defineCommand43({
4876
5982
  });
4877
5983
 
4878
5984
  // src/commands/workflows.ts
4879
- import { defineCommand as defineCommand44 } from "citty";
4880
- import consola35 from "consola";
5985
+ import { defineCommand as defineCommand47 } from "citty";
5986
+ import consola37 from "consola";
4881
5987
 
4882
5988
  // src/guides/index.ts
4883
5989
  var guides = [
@@ -4927,7 +6033,7 @@ var guides = [
4927
6033
  ];
4928
6034
 
4929
6035
  // src/commands/workflows.ts
4930
- var workflowsCommand = defineCommand44({
6036
+ var workflowsCommand = defineCommand47({
4931
6037
  meta: {
4932
6038
  name: "workflows",
4933
6039
  description: "Discover workflow guides"
@@ -4948,7 +6054,7 @@ var workflowsCommand = defineCommand44({
4948
6054
  if (args.id) {
4949
6055
  const guide = guides.find((g) => g.id === String(args.id));
4950
6056
  if (!guide) {
4951
- consola35.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
6057
+ consola37.info(`Available: ${guides.map((g) => g.id).join(", ")}`);
4952
6058
  throw new CliError(`Guide not found: ${args.id}`);
4953
6059
  }
4954
6060
  if (args.json) {
@@ -4988,26 +6094,26 @@ var workflowsCommand = defineCommand44({
4988
6094
  });
4989
6095
 
4990
6096
  // src/version-check.ts
4991
- import { existsSync as existsSync10, mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
4992
- import { homedir as homedir6 } from "os";
4993
- import { join as join7 } from "path";
4994
- import consola36 from "consola";
6097
+ import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
6098
+ import { homedir as homedir11 } from "os";
6099
+ import { join as join11 } from "path";
6100
+ import consola38 from "consola";
4995
6101
  var PACKAGE_NAME = "@openape/apes";
4996
6102
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
4997
- var CACHE_FILE = join7(homedir6(), ".config", "apes", ".version-check.json");
6103
+ var CACHE_FILE = join11(homedir11(), ".config", "apes", ".version-check.json");
4998
6104
  function readCache() {
4999
- if (!existsSync10(CACHE_FILE)) return null;
6105
+ if (!existsSync13(CACHE_FILE)) return null;
5000
6106
  try {
5001
- return JSON.parse(readFileSync8(CACHE_FILE, "utf-8"));
6107
+ return JSON.parse(readFileSync13(CACHE_FILE, "utf-8"));
5002
6108
  } catch {
5003
6109
  return null;
5004
6110
  }
5005
6111
  }
5006
6112
  function writeCache(entry) {
5007
6113
  try {
5008
- const dir = join7(homedir6(), ".config", "apes");
5009
- if (!existsSync10(dir)) mkdirSync2(dir, { recursive: true, mode: 448 });
5010
- writeFileSync6(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
6114
+ const dir = join11(homedir11(), ".config", "apes");
6115
+ if (!existsSync13(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
6116
+ writeFileSync9(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
5011
6117
  } catch {
5012
6118
  }
5013
6119
  }
@@ -5036,7 +6142,7 @@ async function fetchLatestVersion() {
5036
6142
  }
5037
6143
  function warnIfBehind(currentVersion, latest) {
5038
6144
  if (compareSemver(currentVersion, latest) < 0) {
5039
- consola36.warn(
6145
+ consola38.warn(
5040
6146
  `apes ${currentVersion} is behind latest @openape/apes@${latest}. Run \`npm i -g @openape/apes@latest\` to update. (Suppress with APES_NO_UPDATE_CHECK=1.)`
5041
6147
  );
5042
6148
  }
@@ -5068,10 +6174,10 @@ if (shellRewrite) {
5068
6174
  if (shellRewrite.action === "rewrite") {
5069
6175
  process.argv = shellRewrite.argv;
5070
6176
  } else if (shellRewrite.action === "version") {
5071
- console.log(`ape-shell ${"0.31.3"} (OpenApe DDISA shell wrapper)`);
6177
+ console.log(`ape-shell ${"0.32.0"} (OpenApe DDISA shell wrapper)`);
5072
6178
  process.exit(0);
5073
6179
  } else if (shellRewrite.action === "help") {
5074
- console.log(`ape-shell ${"0.31.3"} \u2014 OpenApe DDISA shell wrapper`);
6180
+ console.log(`ape-shell ${"0.32.0"} \u2014 OpenApe DDISA shell wrapper`);
5075
6181
  console.log("");
5076
6182
  console.log("Usage:");
5077
6183
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -5095,7 +6201,7 @@ if (shellRewrite) {
5095
6201
  }
5096
6202
  }
5097
6203
  var debug = process.argv.includes("--debug");
5098
- var grantsCommand = defineCommand45({
6204
+ var grantsCommand = defineCommand48({
5099
6205
  meta: {
5100
6206
  name: "grants",
5101
6207
  description: "Grant management"
@@ -5116,7 +6222,7 @@ var grantsCommand = defineCommand45({
5116
6222
  "delegation-revoke": delegationRevokeCommand
5117
6223
  }
5118
6224
  });
5119
- var configCommand = defineCommand45({
6225
+ var configCommand = defineCommand48({
5120
6226
  meta: {
5121
6227
  name: "config",
5122
6228
  description: "Configuration management"
@@ -5126,10 +6232,10 @@ var configCommand = defineCommand45({
5126
6232
  set: configSetCommand
5127
6233
  }
5128
6234
  });
5129
- var main = defineCommand45({
6235
+ var main = defineCommand48({
5130
6236
  meta: {
5131
6237
  name: "apes",
5132
- version: "0.31.3",
6238
+ version: "0.32.0",
5133
6239
  description: "Unified CLI for OpenApe"
5134
6240
  },
5135
6241
  subCommands: {
@@ -5184,20 +6290,20 @@ async function maybeRefreshAuth() {
5184
6290
  }
5185
6291
  }
5186
6292
  await maybeRefreshAuth();
5187
- await maybeWarnStaleVersion("0.31.3").catch(() => {
6293
+ await maybeWarnStaleVersion("0.32.0").catch(() => {
5188
6294
  });
5189
6295
  runMain(main).catch((err) => {
5190
6296
  if (err instanceof CliExit) {
5191
6297
  process.exit(err.exitCode);
5192
6298
  }
5193
6299
  if (err instanceof CliError) {
5194
- consola37.error(err.message);
6300
+ consola39.error(err.message);
5195
6301
  process.exit(err.exitCode);
5196
6302
  }
5197
6303
  if (debug) {
5198
- consola37.error(err);
6304
+ consola39.error(err);
5199
6305
  } else {
5200
- consola37.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
6306
+ consola39.error(err instanceof ApiError ? err.message : err instanceof Error ? err.message : String(err));
5201
6307
  }
5202
6308
  process.exit(1);
5203
6309
  });