#!/usr/bin/env node
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __esm = (fn, res) => function __init() {
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
};
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};

// cli/chat.ts
var chat_exports = {};
__export(chat_exports, {
  chatCommand: () => chatCommand
});
import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
function dataDir() {
  return process.env.FORGE_DATA_DIR || join(homedir(), ".forge", "data");
}
function activeFile() {
  return join(dataDir(), "cli-chat-session.txt");
}
function loadActive() {
  try {
    const f = activeFile();
    if (!existsSync(f)) return null;
    const v = readFileSync(f, "utf-8").trim();
    return v || null;
  } catch {
    return null;
  }
}
function saveActive(id) {
  try {
    mkdirSync(dataDir(), { recursive: true });
    writeFileSync(activeFile(), id);
  } catch {
  }
}
function clearActive() {
  try {
    rmSync(activeFile(), { force: true });
  } catch {
  }
}
async function api(path, init) {
  let res;
  try {
    res = await fetch(BASE + path, init);
  } catch (e) {
    throw new Error(`chat service unreachable on ${BASE}: ${e.message}`);
  }
  if (!res.ok && res.status !== 202) {
    throw new Error(`HTTP ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
  }
  const ct = res.headers.get("content-type") || "";
  if (!ct.includes("application/json")) return null;
  return res.json();
}
async function openSse(sessionId) {
  const res = await fetch(`${BASE}/api/sessions/${encodeURIComponent(sessionId)}/events`, {
    headers: { accept: "text/event-stream" }
  });
  if (!res.ok) throw new Error(`SSE open failed: HTTP ${res.status}`);
  if (!res.body) throw new Error("SSE response has no body");
  const reader = res.body.getReader();
  await reader.read();
  return reader;
}
async function consumeSse(reader) {
  const decoder = new TextDecoder();
  let buf = "";
  let inText = false;
  while (true) {
    const { value, done } = await reader.read();
    if (done) return;
    buf += decoder.decode(value, { stream: true });
    let idx;
    while ((idx = buf.indexOf("\n\n")) >= 0) {
      const frame = buf.slice(0, idx);
      buf = buf.slice(idx + 2);
      const dataLine = frame.split("\n").find((l) => l.startsWith("data: "));
      if (!dataLine) continue;
      let evt;
      try {
        evt = JSON.parse(dataLine.slice(6));
      } catch {
        continue;
      }
      switch (evt.type) {
        case "text_delta": {
          inText = true;
          process.stdout.write(String(evt.data?.delta || ""));
          break;
        }
        case "tool_use": {
          if (inText) {
            process.stdout.write("\n");
            inText = false;
          }
          const name = String(evt.data?.name || "?");
          let inp = "";
          try {
            inp = JSON.stringify(evt.data?.input ?? {});
          } catch {
            inp = "";
          }
          if (inp.length > 60) inp = inp.slice(0, 60) + "\u2026";
          process.stdout.write(dim(`  \u2192 ${name}(${inp}) \u2026
`));
          break;
        }
        case "tool_result": {
          const name = String(evt.data?.name || "?");
          const r = evt.data?.result;
          const text = typeof r === "string" ? r : r?.content ?? JSON.stringify(r ?? "");
          const head = String(text).split("\n", 1)[0] || "";
          const short = head.length > 80 ? head.slice(0, 80) + "\u2026" : head;
          process.stdout.write(dim(`  \u2190 ${name}: ${short}
`));
          break;
        }
        case "turn_done": {
          if (inText) process.stdout.write("\n");
          try {
            await reader.cancel();
          } catch {
          }
          return;
        }
        case "error": {
          if (inText) process.stdout.write("\n");
          process.stderr.write(red(`error: ${String(evt.data?.error || "unknown")}
`));
          try {
            await reader.cancel();
          } catch {
          }
          return;
        }
      }
    }
  }
}
async function createSession() {
  const r = await api("/api/sessions", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ title: "CLI chat" })
  });
  const id = r?.session?.id;
  if (!id) throw new Error("create session: no id in response");
  return id;
}
async function chatCommand(args2) {
  if (args2.length === 0 || args2[0] === "--help" || args2[0] === "-h") {
    return printHelp();
  }
  const sub = args2[0];
  if (sub === "sessions" || sub === "ls") return cmdSessions();
  if (sub === "history" || sub === "h") return cmdHistory(args2[1]);
  if (sub === "watch") return cmdWatch(args2[1]);
  if (sub === "new") return cmdNew();
  if (sub === "rm") return cmdRm(args2[1]);
  if (sub === "use") return cmdUse(args2[1]);
  let forceNew = false;
  let explicit = null;
  const parts = [];
  for (let i = 0; i < args2.length; i++) {
    const a = args2[i];
    if (a === "-n" || a === "--new") {
      forceNew = true;
      continue;
    }
    if (a === "-s" || a === "--session") {
      explicit = args2[++i] || null;
      continue;
    }
    parts.push(a);
  }
  const text = parts.join(" ").trim();
  if (!text) {
    console.error('No message provided. Try: forge chat "your message"');
    process.exit(1);
  }
  let sessionId = explicit;
  if (!sessionId && !forceNew) sessionId = loadActive();
  if (!sessionId) {
    sessionId = await createSession();
    process.stderr.write(dim(`(new session ${sessionId.slice(0, 8)})
`));
  }
  saveActive(sessionId);
  const reader = await openSse(sessionId);
  await api(`/api/sessions/${encodeURIComponent(sessionId)}/messages`, {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ text })
  });
  await consumeSse(reader);
}
async function cmdSessions() {
  const j = await api("/api/sessions?limit=50");
  if (!j?.sessions?.length) {
    console.log("(no sessions)");
    return;
  }
  const active = loadActive();
  for (const s of j.sessions) {
    const marker = s.id === active ? "*" : " ";
    const ts = new Date(s.updated_at || s.created_at).toISOString().slice(0, 19).replace("T", " ");
    console.log(`${marker} ${cyan(s.id.slice(0, 8))}  ${dim(ts)}  ${s.title || "(untitled)"}`);
  }
}
async function cmdHistory(id) {
  const sessionId = id || loadActive();
  if (!sessionId) {
    console.error("No session. Pick one with: forge chat sessions");
    process.exit(1);
  }
  const j = await api(`/api/sessions/${encodeURIComponent(sessionId)}`);
  if (!j?.messages?.length) {
    console.log("(empty)");
    return;
  }
  for (const m of j.messages) {
    const role = m.role === "user" ? "U" : "A";
    for (const b of m.blocks || []) {
      if (b.type === "text" && b.text) {
        console.log(`[${role}] ${b.text}`);
      } else if (b.type === "tool_use") {
        let inp = "";
        try {
          inp = JSON.stringify(b.input ?? {});
        } catch {
        }
        if (inp.length > 80) inp = inp.slice(0, 80) + "\u2026";
        console.log(dim(`    \u2192 ${b.name}(${inp})`));
      } else if (b.type === "tool_result") {
        const head = String(b.content || "").split("\n", 1)[0] || "";
        console.log(dim(`    \u2190 ${head.slice(0, 100)}`));
      }
    }
  }
}
async function cmdWatch(id) {
  const sessionId = id || loadActive();
  if (!sessionId) {
    console.error("No session. Pick one with: forge chat sessions");
    process.exit(1);
  }
  process.stderr.write(dim(`watching ${sessionId} (Ctrl+C to exit)
`));
  const reader = await openSse(sessionId);
  await consumeSse(reader);
}
async function cmdNew() {
  const id = await createSession();
  saveActive(id);
  console.log(id);
}
async function cmdRm(id) {
  if (!id) {
    console.error("Usage: forge chat rm <id>");
    process.exit(1);
  }
  await api(`/api/sessions/${encodeURIComponent(id)}`, { method: "DELETE" });
  if (loadActive() === id) clearActive();
  console.log("deleted");
}
async function cmdUse(id) {
  if (!id) {
    console.error("Usage: forge chat use <id>");
    process.exit(1);
  }
  await api(`/api/sessions/${encodeURIComponent(id)}`);
  saveActive(id);
  console.log(`active: ${id}`);
}
function printHelp() {
  console.log(`forge chat \u2014 chat with your Forge agent from the terminal

Usage:
  forge chat "<prompt>"           Send to active session (creates one if none).
  forge chat -n "<prompt>"        Force a fresh session for this turn.
  forge chat -s <id> "<prompt>"   Send to a specific session.

  forge chat sessions             List recent sessions ('*' = active).
  forge chat history [<id>]       Print message history (default: active).
  forge chat watch [<id>]         Subscribe to the live event stream.
  forge chat new                  Create a fresh session and set as active.
  forge chat use <id>             Set <id> as the active session.
  forge chat rm <id>              Delete a session.

Env:
  FORGE_CHAT_URL=http://127.0.0.1:8408   chat-standalone endpoint
  CHAT_PORT=8408                         shorthand for the port`);
}
var CHAT_PORT, BASE, isTty, dim, red, cyan;
var init_chat = __esm({
  "cli/chat.ts"() {
    "use strict";
    CHAT_PORT = process.env.CHAT_PORT || "8408";
    BASE = (process.env.FORGE_CHAT_URL || `http://127.0.0.1:${CHAT_PORT}`).replace(/\/+$/, "");
    isTty = !!process.stdout.isTTY;
    dim = (s) => isTty ? `\x1B[2m${s}\x1B[0m` : s;
    red = (s) => isTty ? `\x1B[31m${s}\x1B[0m` : s;
    cyan = (s) => isTty ? `\x1B[36m${s}\x1B[0m` : s;
  }
});

// cli/jobs.ts
var jobs_exports = {};
__export(jobs_exports, {
  jobsCommand: () => jobsCommand
});
function getBase() {
  const portArgIdx = process.argv.findIndex((a) => a === "--port");
  const port = portArgIdx >= 0 ? process.argv[portArgIdx + 1] : void 0;
  return process.env.MW_URL || `http://localhost:${port || "8403"}`;
}
async function api2(path, init) {
  const base = getBase();
  let res;
  try {
    res = await fetch(base + path, init);
  } catch (e) {
    throw new Error(`forge unreachable on ${base}: ${e.message}`);
  }
  if (!res.ok && res.status !== 202) {
    throw new Error(`HTTP ${res.status}: ${(await res.text().catch(() => "")).slice(0, 200)}`);
  }
  return res.status === 204 ? null : res.json();
}
function statusBadge(s) {
  if (s === "ok") return grn("\u2713 ok");
  if (s === "error") return red2("\u2717 error");
  if (s === "running") return cyan2("\u2026 running");
  return s;
}
async function jobsCommand(args2) {
  const sub = args2[0] || "list";
  switch (sub) {
    case "list":
    case "ls":
      return cmdList();
    case "show":
      return cmdShow(args2[1]);
    case "runs":
      return cmdRuns(args2[1]);
    case "run":
      return cmdRun(args2[1], ...args2.slice(2));
    case "dispatches":
    case "d":
      return cmdDispatches(args2[1], args2[2]);
    case "enable":
    case "disable":
      return cmdToggle(args2[1], sub === "enable");
    case "rm":
      return cmdRm2(args2[1]);
    case "reset":
      return cmdReset(args2[1]);
    case "--help":
    case "-h":
    default:
      return printHelp2();
  }
}
async function cmdList() {
  const j = await api2("/api/jobs");
  if (!j?.jobs?.length) {
    console.log("(no jobs)");
    return;
  }
  for (const job of j.jobs) {
    const status = job.enabled ? grn("\u25CF") : dim2("\u25CB");
    const next = job.next_run_at ? dim2(`next ${job.next_run_at}`) : dim2("next: now");
    const last = job.last_run_at ? dim2(`last ${job.last_run_at}`) : dim2("never run");
    console.log(`${status} ${cyan2(job.id)}  ${job.name}`);
    console.log(`   ${dim2(`${job.source_connector}.${job.source_tool}`)} \u2192 ${job.dispatch_type} \xB7 every ${job.schedule_interval_minutes}m`);
    console.log(`   ${last}  ${next}`);
  }
}
async function cmdShow(id) {
  if (!id) {
    console.error("Usage: forge jobs show <id>");
    process.exit(1);
  }
  const j = await api2(`/api/jobs/${encodeURIComponent(id)}`);
  console.log(JSON.stringify(j, null, 2));
}
async function cmdRuns(id) {
  if (!id) {
    console.error("Usage: forge jobs runs <id>");
    process.exit(1);
  }
  const j = await api2(`/api/jobs/${encodeURIComponent(id)}/runs?limit=20`);
  if (!j?.runs?.length) {
    console.log("(no runs)");
    return;
  }
  for (const r of j.runs) {
    const dur = r.finished_at && r.started_at ? `${Math.round((new Date(r.finished_at).getTime() - new Date(r.started_at).getTime()) / 100) / 10}s` : "\u2026";
    console.log(`${cyan2(r.id)} ${r.started_at}  ${statusBadge(r.status)}  ${dim2(`(${dur})`)}`);
    console.log(`   seen=${r.items_seen} new=${r.items_new} dispatched=${r.items_dispatched}${r.error ? "  " + red2(r.error.slice(0, 100)) : ""}`);
  }
}
async function cmdDispatches(jobId, runId) {
  if (!jobId || !runId) {
    console.error("Usage: forge jobs dispatches <jobId> <runId>");
    process.exit(1);
  }
  const j = await api2(`/api/jobs/${encodeURIComponent(jobId)}/runs/${encodeURIComponent(runId)}`);
  console.log(`run ${cyan2(j.run.id)}  ${statusBadge(j.run.status)}  seen=${j.run.items_seen} new=${j.run.items_new} dispatched=${j.run.items_dispatched}`);
  if (j.run.error) console.log(red2("  error: " + j.run.error));
  if (!j.dispatches?.length) {
    console.log(dim2("  (no dispatches)"));
    return;
  }
  for (const d of j.dispatches) {
    const label = d.status === "dispatched" ? grn("\u2713") : red2("\u2717");
    console.log(`  ${label} ${cyan2(d.item_key)} ${dim2("\u2192")} ${d.dispatch_type}:${d.dispatch_target_id || "(none)"}`);
    if (d.item_preview) console.log(dim2("    " + d.item_preview.slice(0, 100)));
    if (d.error) console.log(red2("    error: " + d.error.slice(0, 200)));
  }
}
async function cmdRun(id, ...rest) {
  if (!id) {
    console.error("Usage: forge jobs run <id> [--force]");
    process.exit(1);
  }
  const force = rest.includes("--force") || rest.includes("-f");
  const r = await api2(`/api/jobs/${encodeURIComponent(id)}/run${force ? "?reset_dedup=1" : ""}`, { method: "POST" });
  console.log(`accepted \u2014 run_id=${r.run_id}` + (r.dedup_reset ? ` (dedup reset, removed ${r.removed_dedup_keys} keys)` : ""));
  console.log(dim2(`watch: forge jobs runs ${id}`));
}
async function cmdToggle(id, enabled) {
  if (!id) {
    console.error(`Usage: forge jobs ${enabled ? "enable" : "disable"} <id>`);
    process.exit(1);
  }
  await api2(`/api/jobs/${encodeURIComponent(id)}`, {
    method: "PATCH",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ enabled })
  });
  console.log(`${enabled ? "enabled" : "disabled"} ${id}`);
}
async function cmdRm2(id) {
  if (!id) {
    console.error("Usage: forge jobs rm <id>");
    process.exit(1);
  }
  await api2(`/api/jobs/${encodeURIComponent(id)}`, { method: "DELETE" });
  console.log("deleted");
}
async function cmdReset(id) {
  if (!id) {
    console.error("Usage: forge jobs reset <id>");
    process.exit(1);
  }
  const r = await api2(`/api/jobs/${encodeURIComponent(id)}/reset_dedup`, { method: "POST" });
  console.log(`dedup wiped \u2014 removed ${r.removed} keys`);
}
function printHelp2() {
  console.log(`forge jobs \u2014 scheduled connector polls

Usage:
  forge jobs                       list jobs
  forge jobs show <id>             full JSON
  forge jobs runs <id>             recent runs (summary line per tick)
  forge jobs dispatches <id> <run> per-item dispatches for one run (target ids)
  forge jobs run <id> [--force]    fire now (manual trigger; --force resets dedup first)
  forge jobs enable <id>           re-enable
  forge jobs disable <id>          stop scheduling
  forge jobs reset <id>            wipe dedup state (re-process everything next tick)
  forge jobs rm <id>               delete

To create a job, use the Forge extension Jobs tab or POST /api/jobs directly
(the YAML-vs-form authoring story lives in the extension).`);
}
var isTty2, dim2, red2, grn, cyan2;
var init_jobs = __esm({
  "cli/jobs.ts"() {
    "use strict";
    isTty2 = !!process.stdout.isTTY;
    dim2 = (s) => isTty2 ? `\x1B[2m${s}\x1B[0m` : s;
    red2 = (s) => isTty2 ? `\x1B[31m${s}\x1B[0m` : s;
    grn = (s) => isTty2 ? `\x1B[32m${s}\x1B[0m` : s;
    cyan2 = (s) => isTty2 ? `\x1B[36m${s}\x1B[0m` : s;
  }
});

// cli/worktree.ts
var worktree_exports = {};
__export(worktree_exports, {
  worktreeCommand: () => worktreeCommand
});
import { execSync, spawnSync } from "node:child_process";
import { existsSync as existsSync2, readdirSync, statSync } from "node:fs";
import { join as join2 } from "node:path";
function getBase2() {
  const portArgIdx = process.argv.findIndex((a) => a === "--port");
  const port = portArgIdx >= 0 ? process.argv[portArgIdx + 1] : void 0;
  return process.env.MW_URL || `http://localhost:${port || "8403"}`;
}
async function fetchProjects() {
  const base = getBase2();
  try {
    const res = await fetch(base + "/api/projects");
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    return await res.json();
  } catch (e) {
    throw new Error(`forge unreachable on ${base}: ${e.message}`);
  }
}
function dirSizeBytes(p) {
  try {
    const out = execSync(`du -sk "${p}" 2>/dev/null | awk '{print $1}'`, { encoding: "utf8" }).trim();
    return Number(out || 0) * 1024;
  } catch {
    return 0;
  }
}
function fmtSize(bytes) {
  if (bytes >= 1024 ** 3) return `${(bytes / 1024 ** 3).toFixed(1)}G`;
  if (bytes >= 1024 ** 2) return `${(bytes / 1024 ** 2).toFixed(1)}M`;
  if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)}K`;
  return `${bytes}B`;
}
function scanProject(p) {
  const root = p.path;
  const wtRoot = join2(root, ".forge", "worktrees");
  if (!existsSync2(wtRoot)) return [];
  const registered = /* @__PURE__ */ new Map();
  try {
    const porc = execSync("git worktree list --porcelain", { cwd: root, encoding: "utf8" });
    let cur = {};
    for (const line of porc.split("\n")) {
      if (line.startsWith("worktree ")) {
        if (cur.path) registered.set(cur.path, cur.branch ?? null);
        cur = { path: line.slice("worktree ".length).trim() };
      } else if (line.startsWith("branch ")) {
        cur.branch = line.slice("branch ".length).replace(/^refs\/heads\//, "").trim();
      } else if (line === "" && cur.path) {
        registered.set(cur.path, cur.branch ?? null);
        cur = {};
      }
    }
    if (cur.path) registered.set(cur.path, cur.branch ?? null);
  } catch {
  }
  const out = [];
  let names;
  try {
    names = readdirSync(wtRoot);
  } catch {
    return [];
  }
  for (const name of names) {
    const wt = join2(wtRoot, name);
    let isDir = false;
    try {
      isDir = statSync(wt).isDirectory();
    } catch {
      continue;
    }
    if (!isDir) continue;
    out.push({
      project: p.name,
      projectPath: root,
      worktreeDir: wt,
      name,
      branch: registered.get(wt) ?? null,
      registered: registered.has(wt),
      bytes: dirSizeBytes(wt)
    });
  }
  return out;
}
function removeOne(e) {
  const r = spawnSync(
    "git",
    ["worktree", "remove", "--force", e.worktreeDir],
    { cwd: e.projectPath, encoding: "utf8" }
  );
  if (r.status === 0) {
    if (e.branch) {
      spawnSync("git", ["branch", "-D", e.branch], { cwd: e.projectPath, encoding: "utf8" });
    }
    return { ok: true, msg: "worktree remove" };
  }
  const rm = spawnSync("rm", ["-rf", e.worktreeDir], { encoding: "utf8" });
  if (rm.status !== 0) {
    return { ok: false, msg: `rm -rf failed: ${rm.stderr.trim().slice(0, 200)}` };
  }
  spawnSync("git", ["worktree", "prune"], { cwd: e.projectPath, encoding: "utf8" });
  if (e.branch) {
    spawnSync("git", ["branch", "-D", e.branch], { cwd: e.projectPath, encoding: "utf8" });
  }
  return { ok: true, msg: "rm -rf + prune" };
}
async function worktreeCommand(args2) {
  const sub = args2[0] || "list";
  const rest = args2.slice(1);
  switch (sub) {
    case "list":
    case "ls":
      return cmdList2(rest);
    case "clean":
    case "rm":
      return cmdClean(rest);
    case "--help":
    case "-h":
    default:
      return printHelp3();
  }
}
async function cmdList2(args2) {
  const projectFilter = args2.find((a) => !a.startsWith("-"));
  const projects = await fetchProjects();
  const targets = projectFilter ? projects.filter((p) => p.name === projectFilter || p.path === projectFilter) : projects;
  if (projectFilter && targets.length === 0) {
    console.error(red3(`project not found: ${projectFilter}`));
    process.exit(1);
  }
  let total = 0, totalBytes = 0;
  for (const p of targets) {
    const entries = scanProject(p);
    if (!entries.length) continue;
    const subBytes = entries.reduce((a, e) => a + e.bytes, 0);
    console.log(`${cyan3(p.name)} ${dim3(p.path)} ${dim3(`(${entries.length}, ${fmtSize(subBytes)})`)}`);
    for (const e of entries) {
      const flag = e.registered ? grn2("\u25CF") : yel("\u25CB");
      const br = e.branch ? dim3(e.branch) : dim3("(unregistered)");
      console.log(`  ${flag} ${e.name.padEnd(30)} ${fmtSize(e.bytes).padStart(7)}  ${br}`);
    }
    total += entries.length;
    totalBytes += subBytes;
  }
  if (total === 0) console.log(dim3("(no Forge worktrees)"));
  else console.log(`
${total} worktree(s), ${fmtSize(totalBytes)}`);
}
async function cmdClean(args2) {
  const dryRun = args2.includes("--dry-run") || args2.includes("-n");
  const yes = args2.includes("--yes") || args2.includes("-y");
  const projectFilter = args2.find((a) => !a.startsWith("-"));
  const projects = await fetchProjects();
  const targets = projectFilter ? projects.filter((p) => p.name === projectFilter || p.path === projectFilter) : projects;
  if (projectFilter && targets.length === 0) {
    console.error(red3(`project not found: ${projectFilter}`));
    process.exit(1);
  }
  const all = [];
  for (const p of targets) all.push(...scanProject(p));
  if (all.length === 0) {
    console.log(dim3("(no Forge worktrees to clean)"));
    return;
  }
  const totalBytes = all.reduce((a, e) => a + e.bytes, 0);
  console.log(`Found ${all.length} worktree(s), ${fmtSize(totalBytes)} total:`);
  for (const e of all) console.log(`  ${dim3(e.project + ":")} ${e.name} ${dim3(`(${fmtSize(e.bytes)})`)}`);
  if (dryRun) {
    console.log(yel("\n--dry-run \u2014 nothing removed."));
    return;
  }
  if (!yes && isTty3) {
    process.stdout.write(`
Remove these ${all.length} worktree(s)? [y/N] `);
    const ans = await readOneLine();
    if (!/^y(es)?$/i.test(ans.trim())) {
      console.log("aborted");
      return;
    }
  }
  let ok = 0, fail = 0;
  for (const e of all) {
    const r = removeOne(e);
    if (r.ok) {
      console.log(`  ${grn2("\u2713")} ${e.project}/${e.name} ${dim3(r.msg)}`);
      ok++;
    } else {
      console.log(`  ${red3("\u2717")} ${e.project}/${e.name} ${red3(r.msg)}`);
      fail++;
    }
  }
  console.log(`
${grn2(`removed ${ok}`)}${fail ? `, ${red3(`${fail} failed`)}` : ""} (${fmtSize(totalBytes)} freed)`);
}
function readOneLine() {
  return new Promise((resolve) => {
    let buf = "";
    process.stdin.setEncoding("utf8");
    process.stdin.on("data", (chunk) => {
      buf += chunk;
      const i = buf.indexOf("\n");
      if (i >= 0) {
        process.stdin.pause();
        resolve(buf.slice(0, i));
      }
    });
    process.stdin.resume();
  });
}
function printHelp3() {
  console.log(`forge worktree \u2014 manage pipeline worktrees under <project>/.forge/worktrees/

Usage:
  forge worktree                   List all (alias of 'list')
  forge worktree list [project]    List worktrees, optionally filtered by project
  forge worktree clean [project]   Remove all worktrees (prompts unless --yes)
    --dry-run, -n                  Show what would be removed
    --yes,     -y                  Don't prompt

Worktrees are only created by Forge builtin pipelines (mantis-bug-fix-and-mr,
gitlab-issue-fix-and-review). Successful runs auto-clean; failed runs leave
the worktree for inspection. This command sweeps the failed leftovers.

Only paths under <project>/.forge/worktrees/ are ever touched.`);
}
var isTty3, dim3, red3, grn2, yel, cyan3;
var init_worktree = __esm({
  "cli/worktree.ts"() {
    "use strict";
    isTty3 = !!process.stdout.isTTY;
    dim3 = (s) => isTty3 ? `\x1B[2m${s}\x1B[0m` : s;
    red3 = (s) => isTty3 ? `\x1B[31m${s}\x1B[0m` : s;
    grn2 = (s) => isTty3 ? `\x1B[32m${s}\x1B[0m` : s;
    yel = (s) => isTty3 ? `\x1B[33m${s}\x1B[0m` : s;
    cyan3 = (s) => isTty3 ? `\x1B[36m${s}\x1B[0m` : s;
  }
});

// lib/dirs.ts
var dirs_exports = {};
__export(dirs_exports, {
  getClaudeDir: () => getClaudeDir,
  getConfigDir: () => getConfigDir,
  getDataDir: () => getDataDir,
  getEnterpriseKeysPath: () => getEnterpriseKeysPath,
  migrateDataDir: () => migrateDataDir
});
import { homedir as homedir2 } from "node:os";
import { join as join3 } from "node:path";
import { existsSync as existsSync3, mkdirSync as mkdirSync2, renameSync, copyFileSync } from "node:fs";
function getConfigDir() {
  return join3(homedir2(), ".forge");
}
function getDataDir() {
  return process.env.FORGE_DATA_DIR || join3(getConfigDir(), "data");
}
function migrateDataDir() {
  if (migrated) return;
  migrated = true;
  if (process.env.FORGE_DATA_DIR) return;
  const configDir = getConfigDir();
  const dataDir3 = join3(configDir, "data");
  const oldSettings = join3(configDir, "settings.yaml");
  const newSettings = join3(dataDir3, "settings.yaml");
  if (!existsSync3(oldSettings) || existsSync3(newSettings)) return;
  console.log("[forge] Migrating data from ~/.forge/ to ~/.forge/data/...");
  if (!existsSync3(dataDir3)) mkdirSync2(dataDir3, { recursive: true });
  for (const file2 of MIGRATE_FILES) {
    const src = join3(configDir, file2);
    const dest = join3(dataDir3, file2);
    if (existsSync3(src) && !existsSync3(dest)) {
      try {
        copyFileSync(src, dest);
        console.log(`  ${file2}`);
      } catch {
      }
    }
  }
  const oldDb = join3(configDir, "data.db");
  const newDb = join3(dataDir3, "workflow.db");
  if (existsSync3(oldDb) && !existsSync3(newDb)) {
    try {
      copyFileSync(oldDb, newDb);
      console.log("  data.db \u2192 workflow.db");
    } catch {
    }
  }
  for (const dir of MIGRATE_DIRS) {
    const src = join3(configDir, dir);
    const dest = join3(dataDir3, dir);
    if (existsSync3(src) && !existsSync3(dest)) {
      try {
        renameSync(src, dest);
        console.log(`  ${dir}/`);
      } catch {
      }
    }
  }
  console.log("[forge] Migration complete. Old files kept as backup.");
}
function getEnterpriseKeysPath() {
  return join3(getDataDir(), ".enterprise-keys.json");
}
function getClaudeDir() {
  return process.env.CLAUDE_HOME || join3(homedir2(), ".claude");
}
var MIGRATE_FILES, MIGRATE_DIRS, migrated;
var init_dirs = __esm({
  "lib/dirs.ts"() {
    "use strict";
    MIGRATE_FILES = [
      "settings.yaml",
      ".encrypt-key",
      ".env.local",
      "session-code.json",
      "terminal-state.json",
      "tunnel-state.json",
      "preview.json",
      "forge.pid",
      "forge.log"
    ];
    MIGRATE_DIRS = ["flows", "pipelines"];
    migrated = false;
  }
});

// lib/api-keys.ts
import { existsSync as existsSync4, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3, renameSync as renameSync2, rmSync as rmSync2, statSync as statSync2 } from "node:fs";
import { join as join4 } from "node:path";
import { randomBytes, createHash, timingSafeEqual } from "node:crypto";
function file() {
  return join4(getDataDir(), "api-keys.json");
}
function load() {
  try {
    const fp = file();
    if (!existsSync4(fp)) {
      _cache = null;
      return [];
    }
    const mtimeMs = statSync2(fp).mtimeMs;
    if (_cache && _cache.mtimeMs === mtimeMs) return _cache.keys;
    const parsed = JSON.parse(readFileSync2(fp, "utf-8"));
    const keys = Array.isArray(parsed) ? parsed : [];
    _cache = { mtimeMs, keys };
    return keys;
  } catch {
    return [];
  }
}
function persist(keys) {
  const dir = getDataDir();
  mkdirSync3(dir, { recursive: true });
  const tmp = join4(dir, `.api-keys.${randomBytes(6).toString("hex")}.tmp`);
  try {
    writeFileSync2(tmp, JSON.stringify(keys, null, 2), { mode: 384 });
    renameSync2(tmp, file());
    _cache = null;
  } catch (e) {
    try {
      rmSync2(tmp, { force: true });
    } catch {
    }
    throw e;
  }
}
function sha256(s) {
  return createHash("sha256").update(s).digest("hex");
}
function createApiKey(name) {
  const key = `${KEY_PREFIX}${randomBytes(24).toString("base64url")}`;
  const record = {
    id: randomBytes(6).toString("hex"),
    name: name.trim() || "agent key",
    prefix: `${key.slice(0, KEY_PREFIX.length + 6)}\u2026`,
    hash: sha256(key),
    createdAt: (/* @__PURE__ */ new Date()).toISOString(),
    lastUsedAt: null
  };
  const keys = load();
  keys.push(record);
  persist(keys);
  return { key, record: toPublic(record) };
}
function listApiKeys() {
  return load().map(toPublic);
}
function revokeApiKey(id) {
  const keys = load();
  const idx = keys.findIndex((k) => k.id === id);
  if (idx < 0) return false;
  keys.splice(idx, 1);
  persist(keys);
  return true;
}
var KEY_PREFIX, _cache, toPublic;
var init_api_keys = __esm({
  "lib/api-keys.ts"() {
    "use strict";
    init_dirs();
    KEY_PREFIX = "forge_sk_";
    _cache = null;
    toPublic = (r) => ({
      id: r.id,
      name: r.name,
      prefix: r.prefix,
      createdAt: r.createdAt,
      lastUsedAt: r.lastUsedAt
    });
  }
});

// cli/mcp-install.ts
var mcp_install_exports = {};
__export(mcp_install_exports, {
  mcpInstallCommand: () => mcpInstallCommand
});
import { spawnSync as spawnSync2 } from "node:child_process";
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync4 } from "node:fs";
import { homedir as homedir3 } from "node:os";
import { join as join5 } from "node:path";
function savedKey() {
  const env = (process.env.FORGE_API_KEY || process.env.FORGE_API_TOKEN || "").trim();
  if (env) return env;
  try {
    if (existsSync5(keyFile())) return readFileSync3(keyFile(), "utf-8").trim();
  } catch {
  }
  return "";
}
function ensureKey(remote) {
  const existing = savedKey();
  if (existing) return existing;
  if (remote) throw new Error("remote install needs a key \u2014 set FORGE_API_KEY (mint one in the remote's Settings \u2192 API Key)");
  const { key } = createApiKey("claude-code");
  mkdirSync4(join5(homedir3(), ".forge"), { recursive: true });
  writeFileSync3(keyFile(), key, { mode: 384 });
  console.error(`[forge mcp install] minted a local API key \u2192 ${keyFile()}`);
  return key;
}
async function mcpInstallCommand(args2) {
  const name = opt(args2, "--name") || "forge";
  const scope = opt(args2, "--scope");
  const remote = opt(args2, "--remote");
  const useHttp = args2.includes("--http");
  const printOnly = args2.includes("--print");
  const port = opt(args2, "--port");
  const base = (remote || process.env.MW_URL || `http://localhost:${port || "8403"}`).replace(/\/$/, "");
  if (!printOnly && !remote && !savedKey() && !process.env.FORGE_DATA_DIR && (process.env.MW_URL || port && port !== "8403")) {
    throw new Error("targeting a non-default instance \u2014 set FORGE_DATA_DIR to its data dir so the key is minted into the store that server reads");
  }
  const key = printOnly ? savedKey() : ensureKey(remote);
  const add = ["mcp", "add"];
  if (scope) add.push("--scope", scope);
  if (useHttp) {
    add.push("--transport", "http", name, `${base}/api/mcp`);
    if (key) add.push("--header", `Authorization: Bearer ${key}`);
    else if (printOnly) add.push("--header", "Authorization: Bearer forge_sk_YOUR_KEY");
  } else {
    if (remote || process.env.MW_URL) add.push("--env", `MW_URL=${base}`);
    add.push(name, "--", "forge", "mcp");
  }
  const pretty = `claude ${add.map((a) => a.includes(" ") ? JSON.stringify(a) : a).join(" ")}`;
  if (printOnly) {
    console.log(pretty);
    return;
  }
  const r = spawnSync2("claude", add, { stdio: "inherit" });
  if (r.error && r.error.code === "ENOENT") {
    console.error("[forge mcp install] the `claude` CLI is not on PATH. Run this yourself:\n");
    console.log(`  ${pretty}
`);
    return;
  }
  if (r.status && r.status !== 0) process.exit(r.status);
  console.error(`
[forge mcp install] added '${name}'. Run /mcp in a Claude Code session to confirm.`);
}
var opt, keyFile;
var init_mcp_install = __esm({
  "cli/mcp-install.ts"() {
    "use strict";
    init_api_keys();
    opt = (args2, name) => {
      const i = args2.indexOf(name);
      return i >= 0 ? args2[i + 1] : void 0;
    };
    keyFile = () => join5(homedir3(), ".forge", "mcp-key");
  }
});

// cli/mcp-proxy.ts
var mcp_proxy_exports = {};
__export(mcp_proxy_exports, {
  mcpProxyCommand: () => mcpProxyCommand
});
import { createInterface } from "node:readline";
import { readFileSync as readFileSync4, existsSync as existsSync6 } from "node:fs";
import { homedir as homedir4 } from "node:os";
import { join as join6 } from "node:path";
function resolveCredential() {
  const env = process.env.FORGE_API_KEY || process.env.FORGE_API_TOKEN;
  if (env) return env.trim();
  try {
    const f = join6(homedir4(), ".forge", "mcp-key");
    if (existsSync6(f)) return readFileSync4(f, "utf-8").trim();
  } catch {
  }
  return "";
}
async function mcpProxyCommand(_args, deps = {}) {
  const input = deps.input ?? process.stdin;
  const out = deps.writeOut ?? ((s) => {
    process.stdout.write(s);
  });
  const logErr = deps.logErr ?? ((s) => {
    process.stderr.write(s);
  });
  const doFetch = deps.fetchImpl ?? fetch;
  const base = (process.env.MW_URL || "http://localhost:8403").replace(/\/$/, "");
  const endpoint = `${base}/api/mcp`;
  const credential = resolveCredential();
  const applyAuth = (headers) => {
    if (!credential) return;
    if (credential.startsWith("forge_sk_")) headers["authorization"] = `Bearer ${credential}`;
    else headers["x-forge-token"] = credential;
  };
  const err = (m) => logErr(`[forge mcp] ${m}
`);
  err(`proxy \u2192 ${endpoint}`);
  let sessionId = "";
  let protocolVersion = "";
  let initFrame = "";
  const writeFrame = (text) => out(text.endsWith("\n") ? text : text + "\n");
  const emit = (obj) => out(JSON.stringify(obj) + "\n");
  const rpcError = (id, message, code = -32e3) => emit({ jsonrpc: "2.0", id, error: { code, message } });
  const post = (frame, useSession) => {
    const headers = {
      "content-type": "application/json",
      accept: "application/json, text/event-stream"
    };
    applyAuth(headers);
    if (useSession && sessionId) headers["mcp-session-id"] = sessionId;
    if (protocolVersion) headers["mcp-protocol-version"] = protocolVersion;
    return doFetch(endpoint, { method: "POST", headers, body: frame });
  };
  const adoptSession = (res) => {
    const sid = res.headers.get("mcp-session-id");
    if (sid) sessionId = sid;
  };
  const learnProtocol = (text) => {
    try {
      const v = JSON.parse(text)?.result?.protocolVersion;
      if (typeof v === "string") protocolVersion = v;
    } catch {
    }
  };
  const errorMessage = (status, text) => {
    try {
      const m = JSON.parse(text)?.error?.message;
      if (typeof m === "string") return m;
    } catch {
    }
    return `Forge server returned HTTP ${status}`;
  };
  const sessionDead = (status, text) => status === 404 || status === 400 && /not initialized|session not found/i.test(text);
  async function recover(frame, id, hasId) {
    err("session expired \u2014 re-initializing");
    sessionId = "";
    protocolVersion = "";
    let initRes;
    try {
      initRes = await post(initFrame, false);
    } catch (e) {
      if (hasId) rpcError(id, `forge mcp: re-init failed: ${e.message}`);
      return;
    }
    adoptSession(initRes);
    if (!initRes.ok) {
      if (hasId) rpcError(id, "forge mcp: re-initialization failed");
      return;
    }
    learnProtocol(await initRes.text());
    try {
      await post('{"jsonrpc":"2.0","method":"notifications/initialized"}', true);
    } catch {
    }
    let retry;
    try {
      retry = await post(frame, true);
    } catch (e) {
      if (hasId) rpcError(id, `forge mcp: ${e.message}`);
      return;
    }
    adoptSession(retry);
    if (retry.status === 202) return;
    const rtext = await retry.text();
    if (retry.ok) {
      if (rtext) writeFrame(rtext);
      return;
    }
    if (hasId) rpcError(id, errorMessage(retry.status, rtext));
  }
  const rl = createInterface({ input, crlfDelay: Infinity });
  for await (const line of rl) {
    const frame = line.trim();
    if (!frame) continue;
    let msg;
    try {
      msg = JSON.parse(frame);
    } catch {
      err("dropping non-JSON stdin line");
      continue;
    }
    const hasId = !!msg && "id" in msg && msg.id !== null && msg.id !== void 0;
    const id = hasId ? msg.id : void 0;
    if (msg?.method === "initialize") initFrame = frame;
    let res;
    try {
      res = await post(frame, true);
    } catch (e) {
      err(`request failed: ${e.message}`);
      if (hasId) rpcError(id, `forge mcp: ${e.message}`);
      continue;
    }
    adoptSession(res);
    if (res.status === 202) continue;
    const text = await res.text();
    if (res.ok) {
      learnProtocol(text);
      if (text) writeFrame(text);
      continue;
    }
    if (sessionDead(res.status, text) && initFrame && msg?.method !== "initialize") {
      await recover(frame, id, hasId);
      continue;
    }
    if (hasId) rpcError(id, errorMessage(res.status, text));
    else err(`server ${res.status} on notification (dropped)`);
  }
  if (sessionId) {
    try {
      const headers = { "mcp-session-id": sessionId };
      applyAuth(headers);
      if (protocolVersion) headers["mcp-protocol-version"] = protocolVersion;
      await doFetch(endpoint, { method: "DELETE", headers });
    } catch {
    }
  }
}
var init_mcp_proxy = __esm({
  "cli/mcp-proxy.ts"() {
    "use strict";
  }
});

// cli/key.ts
var key_exports = {};
__export(key_exports, {
  keyCommand: () => keyCommand
});
import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync5 } from "node:fs";
import { homedir as homedir5 } from "node:os";
import { join as join7 } from "node:path";
async function keyCommand(args2) {
  const sub = args2[0];
  const positional = args2.slice(1).filter((a) => !a.startsWith("--"));
  if (sub === "list") {
    const keys = listApiKeys();
    if (!keys.length) {
      console.log("No API keys. Create one: forge key create <name>");
      return;
    }
    for (const k of keys) {
      const used = k.lastUsedAt ? k.lastUsedAt.slice(0, 10) : "never";
      console.log(`  ${k.id}  ${String(k.prefix).padEnd(20)} ${k.name}  (created ${k.createdAt?.slice(0, 10)}, last used ${used})`);
    }
    return;
  }
  if (sub === "create") {
    const name = positional[0] || "agent key";
    const { key } = createApiKey(name);
    console.log(`
\u2713 API key created (${name}):

  ${key}
`);
    console.log("  Copy it now \u2014 it will not be shown again.");
    if (args2.includes("--save")) {
      const dir = join7(homedir5(), ".forge");
      mkdirSync5(dir, { recursive: true });
      const f = join7(dir, "mcp-key");
      writeFileSync4(f, key, { mode: 384 });
      console.log(`  Saved to ${f} \u2014 forge mcp will read it automatically.`);
    } else {
      console.log("  Use it:  export FORGE_API_KEY=<key>   (or rerun with --save)");
    }
    return;
  }
  if (sub === "revoke") {
    const id = positional[0];
    if (!id) {
      console.log("Usage: forge key revoke <id>");
      process.exit(1);
    }
    console.log(revokeApiKey(id) ? `\u2713 Revoked key ${id}` : `Key ${id} not found`);
    return;
  }
  console.log(`forge key \u2014 manage API keys (service/agent credentials, incl. MCP)

  forge key create [name] [--save]   Mint a key (--save writes it to ~/.forge/mcp-key)
  forge key list                     List keys
  forge key revoke <id>              Revoke a key

  No admin password \u2014 operates on the local key store directly.
  Set FORGE_DATA_DIR to target a non-default instance.`);
}
var init_key = __esm({
  "cli/key.ts"() {
    "use strict";
    init_api_keys();
  }
});

// cli/clean.ts
var clean_exports = {};
__export(clean_exports, {
  cleanCommand: () => cleanCommand
});
import { existsSync as existsSync7, readdirSync as readdirSync2, rmSync as rmSync3, statSync as statSync3 } from "node:fs";
import { join as join8 } from "node:path";
import { homedir as homedir6 } from "node:os";
function dataDir2() {
  return process.env.FORGE_DATA_DIR || join8(homedir6(), ".forge", "data");
}
function dirSizeBytes2(p) {
  let total = 0;
  try {
    for (const ent of readdirSync2(p, { withFileTypes: true })) {
      const child = join8(p, ent.name);
      try {
        if (ent.isDirectory()) total += dirSizeBytes2(child);
        else total += statSync3(child).size;
      } catch {
      }
    }
  } catch {
  }
  return total;
}
function humanBytes(n) {
  if (n < 1024) return `${n} B`;
  if (n < 1024 ** 2) return `${(n / 1024).toFixed(1)} KB`;
  if (n < 1024 ** 3) return `${(n / 1024 ** 2).toFixed(1)} MB`;
  return `${(n / 1024 ** 3).toFixed(1)} GB`;
}
function printHelp4() {
  console.log(`
forge clean \u2014 wipe Forge caches.

Subcommands:
  forge clean cache               List cached items + sizes (dry-run).
  forge clean cache --yes         Actually delete everything.
  forge clean cache <name> --yes  Delete one entry (e.g. fortinet-fortinac-dev).

Targets cleaned:
  \u2022 <dataDir>/cloned-projects/    Auto-cloned gitlab repos (gitlab fallback).
`);
}
async function cmdCache(args2) {
  const root = join8(dataDir2(), "cloned-projects");
  const yes = args2.includes("--yes") || args2.includes("-y");
  const target = args2.find((a) => !a.startsWith("-"));
  if (!existsSync7(root)) {
    console.log(`Nothing to clean. ${root} does not exist.`);
    return;
  }
  const entries = [];
  for (const ent of readdirSync2(root, { withFileTypes: true })) {
    if (!ent.isDirectory()) continue;
    const p = join8(root, ent.name);
    entries.push({ name: ent.name, path: p, size: dirSizeBytes2(p) });
  }
  entries.sort((a, b) => b.size - a.size);
  if (entries.length === 0) {
    console.log(`Nothing to clean. ${root} is empty.`);
    return;
  }
  const totalBytes = entries.reduce((s, e) => s + e.size, 0);
  if (target) {
    const hit = entries.find((e) => e.name === target);
    if (!hit) {
      console.error(`Not found: ${target}`);
      console.error(`Available: ${entries.map((e) => e.name).join(", ")}`);
      process.exit(1);
    }
    console.log(`${hit.name}  ${humanBytes(hit.size)}  ${hit.path}`);
    if (!yes) {
      console.log("\nDry-run. Add --yes to actually delete.");
      return;
    }
    try {
      rmSync3(hit.path, { recursive: true, force: true });
      console.log(`\u2713 Deleted ${hit.name}`);
    } catch (e) {
      console.error(`Failed: ${e.message}`);
      process.exit(1);
    }
    return;
  }
  console.log(`Cached repos under ${root}:
`);
  for (const e of entries) {
    console.log(`  ${e.name.padEnd(40)} ${humanBytes(e.size).padStart(10)}`);
  }
  console.log(`
Total: ${entries.length} entries, ${humanBytes(totalBytes)}`);
  if (!yes) {
    console.log("\nDry-run. Add --yes to delete everything.");
    return;
  }
  let deleted = 0;
  for (const e of entries) {
    try {
      rmSync3(e.path, { recursive: true, force: true });
      deleted++;
    } catch (err) {
      console.error(`Failed ${e.name}: ${err.message}`);
    }
  }
  console.log(`
\u2713 Deleted ${deleted} entries (${humanBytes(totalBytes)} freed).`);
}
async function cleanCommand(args2) {
  const sub = args2[0] || "";
  switch (sub) {
    case "cache":
      await cmdCache(args2.slice(1));
      break;
    case "--help":
    case "-h":
    case "":
    default:
      printHelp4();
      break;
  }
}
var init_clean = __esm({
  "cli/clean.ts"() {
    "use strict";
  }
});

// cli/mw.ts
var _cliPort = process.argv.find((a, i) => i > 0 && process.argv[i - 1] === "--port");
var BASE2 = process.env.MW_URL || `http://localhost:${_cliPort || "3000"}`;
var [, , cmd, ...args] = process.argv;
async function checkForUpdate() {
  try {
    const { readFileSync: readFileSync5 } = await import("node:fs");
    const { join: join9, dirname } = await import("node:path");
    const { fileURLToPath } = await import("node:url");
    const pkg = JSON.parse(readFileSync5(join9(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
    const current = pkg.version;
    const controller = new AbortController();
    const timeout = setTimeout(() => controller.abort(), 3e3);
    const res = await fetch("https://registry.npmjs.org/@aion0/forge/latest", {
      signal: controller.signal,
      headers: { "Accept": "application/json" }
    });
    clearTimeout(timeout);
    if (!res.ok) return;
    const data = await res.json();
    const latest = data.version;
    const [ca, cb, cc] = current.split(".").map(Number);
    const [la, lb, lc] = latest.split(".").map(Number);
    if (la > ca || la === ca && lb > cb || la === ca && lb === cb && lc > cc) {
      console.log(`
  Update available: v${current} \u2192 v${latest}`);
      console.log(`  Run: forge upgrade
`);
    }
  } catch {
  }
}
async function api3(path, opts) {
  const res = await fetch(`${BASE2}${path}`, opts);
  if (!res.ok) {
    const text = await res.text();
    console.error(`Error ${res.status}: ${text}`);
    process.exit(1);
  }
  return res.json();
}
async function main() {
  if (cmd === "--version" || cmd === "-v") {
    const { readFileSync: readFileSync5 } = await import("node:fs");
    const { join: join9, dirname } = await import("node:path");
    const { fileURLToPath } = await import("node:url");
    try {
      const pkg = JSON.parse(readFileSync5(join9(dirname(fileURLToPath(import.meta.url)), "..", "package.json"), "utf-8"));
      console.log(`@aion0/forge v${pkg.version}`);
    } catch {
      console.log("forge (version unknown)");
    }
    process.exit(0);
  }
  if (cmd === "--reset-password") {
    const { spawnSync: spawnSync3 } = await import("node:child_process");
    const { join: join9, dirname } = await import("node:path");
    const { fileURLToPath } = await import("node:url");
    const serverScript = join9(dirname(fileURLToPath(import.meta.url)), "..", "bin", "forge-server.mjs");
    const passthru = process.argv.slice(3);
    spawnSync3("node", [serverScript, "--reset-password", ...passthru], { stdio: "inherit" });
    process.exit(0);
  }
  if (cmd === "--add-enterprise-key") {
    const { spawnSync: spawnSync3 } = await import("node:child_process");
    const { join: join9, dirname } = await import("node:path");
    const { fileURLToPath } = await import("node:url");
    const serverScript = join9(dirname(fileURLToPath(import.meta.url)), "..", "bin", "forge-server.mjs");
    const r = spawnSync3("node", [serverScript, "--add-enterprise-key", ...args], { stdio: "inherit" });
    process.exit(r.status ?? 1);
  }
  switch (cmd) {
    case "chat":
    case "c": {
      const { chatCommand: chatCommand2 } = await Promise.resolve().then(() => (init_chat(), chat_exports));
      await chatCommand2(args);
      break;
    }
    case "jobs":
    case "j": {
      const { jobsCommand: jobsCommand2 } = await Promise.resolve().then(() => (init_jobs(), jobs_exports));
      await jobsCommand2(args);
      break;
    }
    case "worktree":
    case "wt": {
      const { worktreeCommand: worktreeCommand2 } = await Promise.resolve().then(() => (init_worktree(), worktree_exports));
      await worktreeCommand2(args);
      break;
    }
    case "mcp": {
      if (args[0] === "install") {
        const { mcpInstallCommand: mcpInstallCommand2 } = await Promise.resolve().then(() => (init_mcp_install(), mcp_install_exports));
        await mcpInstallCommand2(args.slice(1));
        break;
      }
      const { mcpProxyCommand: mcpProxyCommand2 } = await Promise.resolve().then(() => (init_mcp_proxy(), mcp_proxy_exports));
      await mcpProxyCommand2(args);
      break;
    }
    case "key": {
      const { keyCommand: keyCommand2 } = await Promise.resolve().then(() => (init_key(), key_exports));
      await keyCommand2(args);
      break;
    }
    case "clean": {
      const { cleanCommand: cleanCommand2 } = await Promise.resolve().then(() => (init_clean(), clean_exports));
      await cleanCommand2(args);
      break;
    }
    case "task":
    case "t": {
      const newSession = args.includes("--new");
      const filtered = args.filter((a) => a !== "--new");
      const project = filtered[0];
      const prompt = filtered.slice(1).join(" ");
      if (!project || !prompt) {
        console.log("Usage: mw task <project> <prompt> [--new]");
        console.log("  --new    Start a fresh session (ignore previous context)");
        console.log('Example: mw task my-app "Fix the login bug"');
        process.exit(1);
      }
      const task = await api3("/api/tasks", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ projectName: project, prompt, newSession })
      });
      const session = task.conversationId ? "(continuing session)" : "(new session)";
      console.log(`\u2713 Task ${task.id} created ${session}`);
      console.log(`  Project: ${task.projectName}`);
      console.log(`  ${prompt}`);
      console.log(`
  Watch: mw watch ${task.id}`);
      console.log(`  Status: mw status ${task.id}`);
      break;
    }
    case "run":
    case "r": {
      const flowName = args[0];
      if (!flowName) {
        console.log("Usage: mw run <flow-name>");
        console.log("List flows: mw flows");
        process.exit(1);
      }
      const result = await api3("/api/flows/run", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name: flowName })
      });
      console.log(`\u2713 Flow "${flowName}" started`);
      for (const t of result.tasks) {
        console.log(`  Task ${t.id}: ${t.projectName} \u2014 ${t.prompt.slice(0, 60)}`);
      }
      break;
    }
    case "tasks":
    case "ls": {
      const status = args[0] || "";
      const query = status ? `?status=${status}` : "";
      const tasks = await api3(`/api/tasks${query}`);
      if (tasks.length === 0) {
        console.log("No tasks.");
        break;
      }
      const icons = {
        queued: "\u23F3",
        running: "\u{1F504}",
        done: "\u2705",
        failed: "\u274C",
        cancelled: "\u26AA"
      };
      for (const t of tasks) {
        const icon = icons[t.status] || "?";
        const cost = t.costUSD != null ? ` $${t.costUSD.toFixed(3)}` : "";
        console.log(`${icon} ${t.id}  ${t.status.padEnd(9)} ${t.projectName.padEnd(20)} ${t.prompt.slice(0, 50)}${cost}`);
      }
      break;
    }
    case "s": {
      const id = args[0];
      if (!id) {
        console.log("Usage: forge status <id>");
        process.exit(1);
      }
      const task = await api3(`/api/tasks/${id}`);
      console.log(`Task: ${task.id}`);
      console.log(`Project: ${task.projectName} (${task.projectPath})`);
      console.log(`Status: ${task.status}`);
      console.log(`Prompt: ${task.prompt}`);
      if (task.startedAt) console.log(`Started: ${task.startedAt}`);
      if (task.completedAt) console.log(`Completed: ${task.completedAt}`);
      if (task.costUSD != null) console.log(`Cost: $${task.costUSD.toFixed(4)}`);
      if (task.error) console.log(`Error: ${task.error}`);
      if (task.resultSummary) {
        console.log(`
Result:
${task.resultSummary}`);
      }
      if (task.gitDiff) {
        console.log(`
Git Diff:
${task.gitDiff.slice(0, 2e3)}`);
      }
      break;
    }
    case "log":
    case "l": {
      const id = args[0];
      if (!id) {
        console.log("Usage: mw log <id>");
        process.exit(1);
      }
      const task = await api3(`/api/tasks/${id}`);
      if (task.log.length === 0) {
        console.log("No log entries.");
        break;
      }
      for (const entry of task.log) {
        const prefix = entry.subtype === "tool_use" ? `\u{1F527} [${entry.tool}]` : entry.subtype === "error" ? "\u2757" : entry.type === "result" ? "\u2705" : entry.subtype === "tool_result" ? "  \u21B3" : "  ";
        console.log(`${prefix} ${entry.content.slice(0, 300)}`);
      }
      break;
    }
    case "watch":
    case "w": {
      const id = args[0];
      if (!id) {
        console.log("Usage: mw watch <id>");
        process.exit(1);
      }
      console.log(`Watching task ${id}... (Ctrl+C to stop)
`);
      const res = await fetch(`${BASE2}/api/tasks/${id}/stream`);
      if (!res.ok || !res.body) {
        console.error("Failed to connect to stream");
        process.exit(1);
      }
      const reader = res.body.getReader();
      const decoder = new TextDecoder();
      let buffer = "";
      while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        buffer += decoder.decode(value, { stream: true });
        const lines = buffer.split("\n");
        buffer = lines.pop() || "";
        for (const line of lines) {
          if (!line.startsWith("data: ")) continue;
          try {
            const data = JSON.parse(line.slice(6));
            if (data.type === "log") {
              const e = data.entry;
              if (e.subtype === "tool_use") {
                console.log(`\u{1F527} [${e.tool}] ${e.content.slice(0, 200)}`);
              } else if (e.subtype === "text") {
                process.stdout.write(e.content);
              } else if (e.type === "result") {
                console.log(`
\u2705 ${e.content}`);
              } else if (e.subtype === "error") {
                console.log(`\u2757 ${e.content}`);
              }
            } else if (data.type === "status") {
              if (data.status === "done") {
                console.log("\n\u2705 Task completed");
              } else if (data.status === "failed") {
                console.log("\n\u274C Task failed");
              } else if (data.status === "running") {
                console.log("\u{1F680} Started...\n");
              }
            } else if (data.type === "complete") {
              if (data.task?.costUSD != null) {
                console.log(`Cost: $${data.task.costUSD.toFixed(4)}`);
              }
              process.exit(0);
            }
          } catch {
          }
        }
      }
      break;
    }
    case "cancel": {
      const id = args[0];
      if (!id) {
        console.log("Usage: mw cancel <id>");
        process.exit(1);
      }
      await api3(`/api/tasks/${id}`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "cancel" })
      });
      console.log(`\u2713 Task ${id} cancelled`);
      break;
    }
    case "retry": {
      const id = args[0];
      if (!id) {
        console.log("Usage: mw retry <id>");
        process.exit(1);
      }
      const task = await api3(`/api/tasks/${id}`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "retry" })
      });
      console.log(`\u2713 Retrying as task ${task.id}`);
      break;
    }
    case "flows":
    case "f": {
      const flows = await api3("/api/flows");
      if (flows.length === 0) {
        console.log("No flows defined.");
        console.log(`Create flows in ~/.forge/flows/*.yaml`);
        break;
      }
      for (const f of flows) {
        const schedule = f.schedule ? ` (${f.schedule})` : "";
        console.log(`  ${f.name}${schedule} \u2014 ${f.steps.length} steps`);
      }
      break;
    }
    case "session": {
      const subCmd = args[0];
      if (subCmd === "link") {
        const project2 = args[1];
        const sessionId = args[2];
        if (!project2 || !sessionId) {
          console.log("Usage: mw session link <project> <session-id>");
          console.log("\nFind your session ID:");
          console.log("  In Claude Code CLI, look for the session ID in the output");
          console.log("  Or check: ls ~/.claude/projects/");
          process.exit(1);
        }
        const result = await api3("/api/tasks/link", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ projectName: project2, conversationId: sessionId })
        });
        console.log(`\u2713 Linked session ${sessionId} to project ${result.projectName}`);
        console.log(`  Future "mw task ${result.projectName} ..." will continue this session`);
        break;
      }
      if (!subCmd) {
        const tasks2 = await api3("/api/tasks?status=done");
        const sessions = /* @__PURE__ */ new Map();
        for (const t of tasks2) {
          if (t.conversationId && !sessions.has(t.projectName)) {
            sessions.set(t.projectName, {
              id: t.conversationId,
              project: t.projectName,
              path: t.projectPath,
              lastUsed: t.completedAt
            });
          }
        }
        if (sessions.size === 0) {
          console.log("No active sessions. Submit a task first, or link a local session:");
          console.log("  mw session link <project> <session-id>");
          break;
        }
        console.log("Project sessions:\n");
        for (const [name, s] of sessions) {
          console.log(`  ${name.padEnd(25)} ${s.id}`);
          console.log(`  ${"".padEnd(25)} cd ${s.path} && claude --resume ${s.id}`);
          console.log();
        }
        break;
      }
      const project = subCmd;
      const tasks = await api3("/api/tasks?status=done");
      const match = tasks.find((t) => t.projectName === project && t.conversationId);
      if (!match) {
        console.log(`No session found for project: ${project}`);
        console.log(`
Link a local session: mw session link ${project} <session-id>`);
        break;
      }
      console.log(`Project: ${match.projectName}`);
      console.log(`Session: ${match.conversationId}`);
      console.log(`Path:    ${match.projectPath}`);
      console.log(`
Resume in CLI:`);
      console.log(`  cd ${match.projectPath} && claude --resume ${match.conversationId}`);
      break;
    }
    case "tunnel_code":
    case "tcode": {
      const { readFileSync: readFileSync5, existsSync: existsSync8 } = await import("node:fs");
      const { join: join9 } = await import("node:path");
      const { getDataDir: _gdd } = await Promise.resolve().then(() => (init_dirs(), dirs_exports));
      const dataDir3 = _gdd();
      const codeFile = join9(dataDir3, "session-code.json");
      try {
        if (existsSync8(codeFile)) {
          const data = JSON.parse(readFileSync5(codeFile, "utf-8"));
          if (data.code) {
            console.log(`Session code: ${data.code}`);
          } else {
            console.log("No session code. Start tunnel first.");
          }
        } else {
          console.log("No session code. Start tunnel first.");
        }
      } catch {
      }
      try {
        const tunnelState = JSON.parse(readFileSync5(join9(dataDir3, "tunnel-state.json"), "utf-8"));
        if (tunnelState.url) console.log(`Tunnel URL: ${tunnelState.url}`);
      } catch {
      }
      break;
    }
    case "projects":
    case "p": {
      const projects = await api3("/api/projects");
      for (const p of projects) {
        const lang = p.language ? `[${p.language}]` : "";
        console.log(`  ${p.name.padEnd(25)} ${lang.padEnd(6)} ${p.path}`);
      }
      console.log(`
${projects.length} projects`);
      break;
    }
    case "pipeline": {
      const sub = args[0];
      if (sub === "gc") {
        const dryRun = args.includes("--dry-run") || args.includes("-n");
        const res = await api3("/api/pipelines/gc", {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ dry_run: dryRun })
        });
        if (!res.ok) {
          console.error("error:", res.error);
          process.exit(1);
        }
        console.log(`Scanned: ${res.scanned} pipeline tmp dir(s)`);
        console.log(`${dryRun ? "Would remove" : "Removed"}: ${res.removed.length}`);
        for (const r of res.removed) console.log(`  - ${r.path}  (${r.reason})`);
        if (res.kept.length) {
          console.log(`Kept: ${res.kept.length}`);
          for (const k of res.kept.slice(0, 10)) console.log(`  - ${k.path}  (${k.reason})`);
          if (res.kept.length > 10) console.log(`  \u2026 ${res.kept.length - 10} more`);
        }
      } else {
        console.log("Usage: forge pipeline gc [--dry-run]");
        console.log("   Sweeps expired <project>/worktrees/pipeline-<id>/ scratch dirs");
        console.log("   per settings.pipelineTmpKeep{Failed,Cancelled}Days.");
      }
      break;
    }
    case "server": {
      const { execSync: execSync2 } = await import("node:child_process");
      const { join: join9, dirname } = await import("node:path");
      const { fileURLToPath } = await import("node:url");
      const serverScript = join9(dirname(fileURLToPath(import.meta.url)), "..", "bin", "forge-server.mjs");
      const sub = args[0] || "start";
      const serverArgs = args.slice(1);
      const flagMap = {
        "start": [],
        "stop": ["--stop"],
        "restart": ["--restart"],
        "rebuild": ["--rebuild"],
        "dev": ["--dev"]
      };
      const flags = flagMap[sub] || [];
      const allArgs = [...flags, ...serverArgs];
      try {
        execSync2(`node ${serverScript} ${allArgs.join(" ")}`, { stdio: "inherit" });
      } catch {
      }
      break;
    }
    case "status": {
      if (args[0]) {
        const task = await api3(`/api/tasks/${args[0]}`);
        console.log(`Task: ${task.id}`);
        console.log(`Project: ${task.projectName} (${task.projectPath})`);
        console.log(`Status: ${task.status}`);
        console.log(`Prompt: ${task.prompt}`);
        if (task.startedAt) console.log(`Started: ${task.startedAt}`);
        if (task.completedAt) console.log(`Completed: ${task.completedAt}`);
        if (task.costUSD != null) console.log(`Cost: $${task.costUSD.toFixed(4)}`);
        if (task.error) console.log(`Error: ${task.error}`);
        if (task.resultSummary) console.log(`
Result:
${task.resultSummary}`);
        if (task.gitDiff) console.log(`
Git Diff:
${task.gitDiff.slice(0, 2e3)}`);
        break;
      }
      const { execSync: execSync2 } = await import("node:child_process");
      const check = (pattern) => {
        try {
          const out = execSync2(`ps aux | grep '${pattern}' | grep -v grep | head -1`, { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim();
          return out ? out.split(/\s+/)[1] : null;
        } catch {
          return null;
        }
      };
      const nextPid = check("next-server");
      const termPid = check("terminal-standalone");
      const telePid = check("telegram-standalone");
      const tunnPid = check("cloudflared tunnel");
      console.log("");
      console.log(`  ${nextPid ? "\u25CF" : "\u25CB"} Next.js        ${nextPid ? `running (pid: ${nextPid})` : "stopped"}`);
      console.log(`  ${termPid ? "\u25CF" : "\u25CB"} Terminal       ${termPid ? `running (pid: ${termPid})` : "stopped"}`);
      console.log(`  ${telePid ? "\u25CF" : "\u25CB"} Telegram       ${telePid ? `running (pid: ${telePid})` : "stopped"}`);
      console.log(`  ${tunnPid ? "\u25CF" : "\u25CB"} Tunnel         ${tunnPid ? `running (pid: ${tunnPid})` : "stopped"}`);
      try {
        const { execSync: ex } = await import("node:child_process");
        const sessions = ex("tmux list-sessions -F '#{session_name} #{session_attached}' 2>/dev/null", { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }).trim().split("\n").filter((l) => l.startsWith("mw-"));
        console.log(`
  Sessions: ${sessions.length}`);
        for (const s of sessions) {
          const [name, att] = s.split(" ");
          console.log(`    ${att !== "0" ? "\u25CF" : "\u25CB"} ${name}`);
        }
      } catch {
        console.log("\n  Sessions: 0");
      }
      console.log("");
      break;
    }
    case "upgrade": {
      const { execSync: execSync2 } = await import("node:child_process");
      const { lstatSync } = await import("node:fs");
      const { join: join9, dirname } = await import("node:path");
      const { fileURLToPath } = await import("node:url");
      const cliDir = dirname(fileURLToPath(import.meta.url));
      let isLinked = false;
      try {
        isLinked = lstatSync(join9(cliDir, "..")).isSymbolicLink();
      } catch {
      }
      if (isLinked) {
        console.log("[forge] Installed via npm link (local source)");
        console.log("[forge] Pull latest and rebuild:");
        console.log("  cd " + join9(cliDir, ".."));
        console.log("  git pull && pnpm install && pnpm build");
      } else {
        console.log("[forge] Upgrading from npm...");
        try {
          const { homedir: homedir7 } = await import("node:os");
          execSync2("npm install -g @aion0/forge@latest --prefer-online", {
            stdio: "inherit",
            cwd: homedir7()
          });
          try {
            const { readFileSync: readFileSync5 } = await import("node:fs");
            const globalRoot = execSync2("npm root -g", { encoding: "utf-8", cwd: homedir7() }).trim();
            const pkg = JSON.parse(readFileSync5(join9(globalRoot, "@aion0", "forge", "package.json"), "utf-8"));
            console.log(`[forge] Upgraded to v${pkg.version}. Run: forge server restart`);
          } catch {
            console.log("[forge] Upgraded. Run: forge server restart");
          }
        } catch {
          console.log("[forge] Upgrade failed");
        }
      }
      break;
    }
    case "uninstall": {
      const { execSync: execSync2 } = await import("node:child_process");
      console.log("[forge] Stopping server...");
      try {
        execSync2("forge server stop", { stdio: "inherit" });
      } catch {
      }
      console.log("[forge] Uninstalling...");
      try {
        execSync2("npm uninstall -g @aion0/forge", { stdio: "inherit" });
        console.log("[forge] Uninstalled. Data remains in ~/.forge/");
      } catch {
        console.log("[forge] Uninstall failed");
      }
      break;
    }
    default:
      console.log(`forge \u2014 Forge CLI (@aion0/forge)

Usage:
  forge server start [options]   Start server (default: foreground)
  forge server stop              Stop server
  forge server restart           Restart server (safe for remote)
  forge server dev               Start in dev mode
  forge server rebuild           Force rebuild

  forge chat "<prompt>"          Chat with Forge (streams reply)
  forge chat sessions            List chat sessions
  forge jobs                     List scheduled connector-poll jobs
  forge jobs run <id>            Manually fire a job tick
  forge worktree                 List leftover pipeline worktrees
  forge worktree clean           Remove leftover pipeline worktrees
  forge task <project> <prompt>  Submit a task
  forge tasks [status]           List tasks
  forge watch <id>               Live stream output
  forge status [<id>]            Process status / task details
  forge log <id>                 Show execution log
  forge cancel <id>              Cancel a task
  forge retry <id>               Retry a failed task

  forge mcp install              Add the Forge management MCP to Claude Code
  forge key create [name]        Create an API key (for agents/MCP)
  forge key list                 List API keys
  forge key revoke <id>          Revoke an API key

  forge run <flow>               Run a workflow
  forge flows                    List workflows
  forge projects                 List projects
  forge session [project]        Show session info
  forge password                 Show login password

  forge upgrade                  Update to latest version
  forge uninstall                Remove forge

Options for 'forge server start':
  --port 4000                    Custom web port (default: 8403)
  --terminal-port 4001           Custom terminal port (default: 8404)
  --dir ~/.forge-staging         Custom data directory
  --background                   Run in background
  --reset-terminal               Kill terminal server on start

Shortcuts: c=chat, j=jobs, wt=worktree, t=task, ls=tasks, w=watch, s=status, l=log, f=flows, p=projects, pw=password`);
  }
}
var skipUpdateCheck = ["upgrade", "uninstall", "--version", "-v", "--reset-password", "--add-enterprise-key", "mcp", "key"];
main().then(() => {
  if (!skipUpdateCheck.includes(cmd)) return checkForUpdate();
}).catch((err) => {
  console.error(err.message);
  process.exit(1);
});
