github-router 0.3.131 → 0.3.135

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/main.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
- import { $ as relayAnthropicStream, At as withInstallLock, B as vscodeRipgrepPath, Bt as resolveModel, C as stopGateEnabledForRepo, Ct as DEFAULT_CODEX_MODEL_FALLBACKS, D as liveExec, Dt as generateRandomPort, E as resolveSealedGate, Et as UPSTREAM_INACTIVITY_TIMEOUT_MS, Ft as cacheModels, G as ADVISOR_TOOL_INSTRUCTIONS, Gt as forwardError, H as assetFor, Ht as getModels, I as availableToolCommands, It as cacheVSCodeVersion, J as isAdvisorRequested, Jt as copilotHeaders, K as buildAdvisorStream, Kt as GITHUB_API_BASE_URL, L as buildToolbeltAwareness, Lt as filterBetaHeader, Mt as setupGitHubToken, Nt as tryRefreshAndRetry, Ot as pickClaudeDefault, Pt as cacheCopilotVersion, Q as readIteratorWithTimeout, R as toolbeltEnabled, Rt as isNullish, S as repoRoot, St as DEFAULT_CODEX_MODEL, T as trustRepo, Tt as UPSTREAM_FETCH_TIMEOUT_MS, U as searchWeb, Ut as fetchWithTransientRetry, V as TOOLBELT_TOOLS, Vt as sleep, W as ADVISOR_INTERNAL_TOOL_NAME, Wt as HTTPError, X as isControllerClosedError, Xt as state, Y as buildOpenAIErrorEvent, Yt as githubHeaders, Z as logStreamError, _ as fileFindingsStore, _t as extractTarGzMember, a as buildPeerAwarenessSnippet, at as workerToolsEnabled, b as isSubagentContext, bt as toolbeltPathOverride, c as buildStopHookCommand, ct as getTokenCount, d as fileBlockBudget, dt as MAX_RESPONSE_BODY_BYTES, et as handleMcpDelete, f as injectStopHookIntoSettingsFile, ft as readResponseBodyCapped, g as fileBaselineStore, gt as provisionAndIndexColbert, h as stopReviewEnabled, ht as hasSupportedBrowserInstalled, i as buildAgentPrompt, it as standInToolEnabled, jt as setupCopilotToken, kt as getPackageVersion, l as captureLaunchBaseline, lt as createResponses, m as stopGateId, mt as provisionBrowserAssets, n as MCP_GROUPS, nt as browserToolsEnabled, o as personasFor, ot as countTokens, p as launchBaselineKey, pt as parseJsonOrDiagnose, q as injectAdvisorTool, qt as copilotBaseUrl, r as assertMcpToolSurfaceConsistent, rt as fleetToolsEnabled, s as buildSessionBindHookCommand, st as createMessages, t as GROUP_META, tt as handleMcpPost, u as decideStopHook, ut as createChatCompletions, v as fileLastPromptStore, vt as extractZipMember, w as stopReviewStateDir, wt as DEFAULT_PORT, x as repoFingerprint, xt as DEFAULT_CLAUDE_MODEL_FALLBACKS, y as fileReviewDebounce, yt as collapsePathKeys, z as toolbeltSkipSet, zt as resolveCodexModel } from "./peer-mcp-personas-CH2gmdPN.js";
3
- import { a as removeOwnClaudeConfigMirror, i as isUnderClaudeConfigMirror, l as writeRuntimeFileSecure, n as ensureClaudeConfigMirror, r as ensurePaths, t as PATHS } from "./paths-CNgpeaWd.js";
4
- import { c as killManagedTree, d as runCommandCapture, f as runCommandVoid, l as parseBoolEnv, s as killChildProcessTree, u as resolveExecutable } from "./lifecycle-DTJ2Ugqf.js";
5
- import { a as sweepRegistry } from "./lifecycle-Cyxwmj1c.js";
2
+ import { $ as readIteratorWithTimeout, $t as state, At as generateRandomPort, B as toolbeltSkipSet, Bt as filterBetaHeader, C as repoRoot, Ct as toolbeltPathOverride, D as resolveSealedGate, Dt as DEFAULT_PORT, E as trustRepo, Et as DEFAULT_CODEX_MODEL_FALLBACKS, Ft as setupGitHubToken, G as ADVISOR_INTERNAL_TOOL_NAME, Gt as getModels, H as TOOLBELT_TOOLS, Ht as resolveCodexModel, It as tryRefreshAndRetry, J as injectAdvisorTool, Jt as forwardError, K as ADVISOR_TOOL_INSTRUCTIONS, Kt as fetchWithTransientRetry, L as availableToolCommands, Lt as cacheCopilotVersion, Mt as getPackageVersion, Nt as withInstallLock, O as liveExec, Ot as UPSTREAM_FETCH_TIMEOUT_MS, Pt as setupCopilotToken, Q as logStreamError, Qt as githubHeaders, R as buildToolbeltAwareness, Rt as cacheModels, S as repoFingerprint, St as collapsePathKeys, T as stopReviewStateDir, Tt as DEFAULT_CODEX_MODEL, U as assetFor, Ut as resolveModel, V as vscodeRipgrepPath, Vt as isNullish, W as searchWeb, Wt as sleep, X as buildOpenAIErrorEvent, Xt as copilotBaseUrl, Y as isAdvisorRequested, Yt as GITHUB_API_BASE_URL, Z as isControllerClosedError, Zt as copilotHeaders, _ as fileBaselineStore, _t as provisionAndIndexColbert, a as buildPeerAwarenessSnippet, at as standInToolEnabled, b as fileReviewDebounce, bt as shouldUseInsecureTls, c as buildSessionBindHookCommand, ct as createMessages, d as decideStopHook, dt as createChatCompletions, et as relayAnthropicStream, f as fileBlockBudget, ft as MAX_RESPONSE_BODY_BYTES, g as stopReviewEnabled, gt as hasSupportedBrowserInstalled, h as stopGateId, ht as provisionBrowserAssets, i as buildAgentPrompt, it as fleetToolsEnabled, jt as pickClaudeDefault, kt as UPSTREAM_INACTIVITY_TIMEOUT_MS, l as buildStopHookCommand, lt as getTokenCount, m as launchBaselineKey, mt as parseJsonOrDiagnose, n as MCP_GROUPS, nt as handleMcpPost, o as personasFor, ot as workerToolsEnabled, p as injectStopHookIntoSettingsFile, pt as readResponseBodyCapped, q as buildAdvisorStream, qt as HTTPError, r as assertMcpToolSurfaceConsistent, rt as browserToolsEnabled, s as buildArtifactOpenHookCommand, st as countTokens, t as GROUP_META, tt as handleMcpDelete, u as captureLaunchBaseline, ut as createResponses, v as fileFindingsStore, vt as extractTarGzMember, w as stopGateEnabledForRepo, wt as DEFAULT_CLAUDE_MODEL_FALLBACKS, x as isSubagentContext, xt as ArtifactClient, y as fileLastPromptStore, yt as extractZipMember, z as toolbeltEnabled, zt as cacheVSCodeVersion } from "./peer-mcp-personas-ClyKATAD.js";
3
+ import { a as removeOwnClaudeConfigMirror, i as isUnderClaudeConfigMirror, l as writeArtifactCredsToMirror, n as ensureClaudeConfigMirror, r as ensurePaths, t as PATHS, u as writeRuntimeFileSecure } from "./paths-Cn5OzmYL.js";
4
+ import { c as killManagedTree, d as runCommandCapture, f as runCommandVoid, l as parseBoolEnv, s as killChildProcessTree, u as resolveExecutable } from "./lifecycle-Cqe8OQVX.js";
5
+ import { a as sweepRegistry } from "./lifecycle-CeVDX6av.js";
6
6
  import { defineCommand, runMain } from "citty";
7
7
  import consola from "consola";
8
8
  import { createHash, randomBytes, randomUUID } from "node:crypto";
9
9
  import fs, { chmod, copyFile, link, mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
10
- import os, { tmpdir } from "node:os";
10
+ import os, { homedir, tmpdir } from "node:os";
11
11
  import nodePath from "node:path";
12
12
  import process$1 from "node:process";
13
13
  import { execFileSync, spawn } from "node:child_process";
@@ -19,6 +19,7 @@ import { getProxyForUrl } from "proxy-from-env";
19
19
  import { Hono } from "hono";
20
20
  import { cors } from "hono/cors";
21
21
  import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from "node:timers";
22
+ import { Marked } from "marked";
22
23
  import clipboard from "clipboardy";
23
24
 
24
25
  //#region src/auth.ts
@@ -579,7 +580,8 @@ const STRIPPED_PARENT_ENV_KEYS = [
579
580
  "OPENAI_BASE_URL",
580
581
  "CODEX_HOME",
581
582
  "AIORDIE_CLAUDE_BIND",
582
- "AIORDIE_TOKEN"
583
+ "AIORDIE_TOKEN",
584
+ "AIORDIE_INSECURE_TLS"
583
585
  ];
584
586
  /**
585
587
  * Strip auth-related keys from a parent-process env object. The result
@@ -2034,7 +2036,7 @@ async function discoverGateCommands(cwd, opts) {
2034
2036
  if (files.length === 0) return null;
2035
2037
  let result;
2036
2038
  try {
2037
- const { runWorkerAgent } = await import("./engine-DCsTvsSw.js");
2039
+ const { runWorkerAgent } = await import("./engine-CEaYFr0J.js");
2038
2040
  result = await runWorkerAgent({
2039
2041
  mode: "explore",
2040
2042
  workspace: root,
@@ -2590,6 +2592,41 @@ Return a compact brief, not the whole research dump:
2590
2592
  `
2591
2593
  };
2592
2594
 
2595
+ //#endregion
2596
+ //#region src/lib/injected-skills/artifact-review-skill.ts
2597
+ const ARTIFACT_REVIEW_SKILL = {
2598
+ name: "gh-artifact-review",
2599
+ md: `---
2600
+ name: gh-artifact-review
2601
+ description: Review plans and artifacts in the ai-or-die panel. Default to authoring a self-contained HTML artifact (rich, annotatable) and opening THAT for the human, then poll for feedback, revise, and end the loop. Use when running inside an ai-or-die tab and you have a plan, comparison, diagram, table, diff, or report the user should see before proceeding.
2602
+ user-invocable: true
2603
+ ---
2604
+
2605
+ # gh-artifact-review: human review in the ai-or-die panel
2606
+
2607
+ Use this when you finish a plan or produce something a user should review and you are inside an ai-or-die tab (the \`mcp__peers__artifact_*\` tools drive a live panel). The human can click any block or select text to attach a comment that comes back to you.
2608
+
2609
+ ## Default: present HTML, not raw markdown
2610
+
2611
+ HTML is the canonical review artifact — it renders richly and is annotatable element-by-element. When the content is anything visual or structured (a comparison, table, diagram, diff, dashboard, or a plan you want to look polished), **author a self-contained \`.html\` file** (inline CSS, no external deps, readable typography) and open THAT.
2612
+
2613
+ - Plan-mode plans: the panel auto-opens them already rendered to HTML — you do not need to convert them by hand.
2614
+ - Do not paste raw markdown into an \`.html\`; write real HTML (headings, lists, tables, \`<pre>\` for code). Opening a raw \`.md\` still renders, but a purpose-built HTML artifact reads better and annotates cleanly.
2615
+
2616
+ ## Loop
2617
+
2618
+ 1. Open: \`mcp__peers__artifact_open\` with the absolute path of the \`.html\` (or the file). Relay the returned \`viewUrl\` and tell the user to review in the panel — they can click a block or select text to comment.
2619
+ 2. Poll: \`mcp__peers__artifact_poll\`. If status is waiting, poll again. Each returned prompt may carry a \`selector\`, quoted \`text\`, and \`sourceLine\` pinpointing what the comment is about — act on that exact spot.
2620
+ 3. Apply: make the requested edits, then \`mcp__peers__artifact_reply\` with a concise summary of what changed.
2621
+ 4. Repeat 2-3 until the user is satisfied; then \`mcp__peers__artifact_end\`.
2622
+
2623
+ ## Honest limits
2624
+
2625
+ - If a tool errors (e.g. \`NOT_IN_AIORDIE_TAB\`, \`UNREACHABLE\`), report the code/message verbatim; do not claim the panel opened.
2626
+ - The panel is a review surface, not an approver: outward/irreversible actions still need explicit user confirmation.
2627
+ `
2628
+ };
2629
+
2593
2630
  //#endregion
2594
2631
  //#region src/lib/claude-md-injection.ts
2595
2632
  /**
@@ -2609,6 +2646,9 @@ const STYLE_MARKER_OPEN = "<!-- gh-router style directive — auto-injected, reg
2609
2646
  const STYLE_MARKER_CLOSE = "<!-- /gh-router style directive -->";
2610
2647
  const TOOLBELT_MARKER_OPEN = "<!-- gh-router toolbelt awareness — auto-injected, regenerated per launch -->";
2611
2648
  const TOOLBELT_MARKER_CLOSE = "<!-- /gh-router toolbelt awareness -->";
2649
+ const ARTIFACT_MARKER_OPEN = "<!-- gh-router artifact-panel directive — auto-injected when in an ai-or-die tab -->";
2650
+ const ARTIFACT_MARKER_CLOSE = "<!-- /gh-router artifact-panel directive -->";
2651
+ const ARTIFACT_PANEL_DIRECTIVE = "## Review plans in the artifact panel\n\nYou are running inside an ai-or-die tab, so the `mcp__peers__artifact_open/poll/reply/end` tools drive a live human-review panel. When you finish a plan or produce something the user should review, open it in the panel: `mcp__peers__artifact_open`, tell the user to review (they can click a block or select text to comment), then `mcp__peers__artifact_poll` for feedback, revise, and `mcp__peers__artifact_end` when done. Prefer HTML: for anything rich or structured, author a self-contained `.html` artifact and open THAT (the panel renders and annotates HTML natively). Plan-mode plans are auto-rendered to HTML for you; raw markdown is only a fallback. Default to this for plans before asking the user to proceed.";
2612
2652
  /**
2613
2653
  * Writing / communication style directive injected at the TOP of the
2614
2654
  * mirrored CLAUDE.md so every spawned agent (main, Agent-tool subagent,
@@ -2971,6 +3011,21 @@ async function appendToolbeltAwarenessToMirroredClaudeMd(snippet) {
2971
3011
  label: "toolbelt-awareness"
2972
3012
  });
2973
3013
  }
3014
+ /**
3015
+ * Prepend the artifact-panel review directive to the TOP of the mirrored
3016
+ * CLAUDE.md so plans/artifacts are reviewed in the ai-or-die panel by default.
3017
+ * Gated by the caller on AIORDIE_SESSION_ID (only useful inside a tab). Separate
3018
+ * marker fence; best-effort like the style/peer blocks.
3019
+ */
3020
+ async function prependArtifactPanelDirectiveToMirroredClaudeMd(directive = ARTIFACT_PANEL_DIRECTIVE) {
3021
+ await injectMarkerBlock({
3022
+ snippet: directive,
3023
+ markerOpen: ARTIFACT_MARKER_OPEN,
3024
+ markerClose: ARTIFACT_MARKER_CLOSE,
3025
+ position: "top",
3026
+ label: "artifact-panel-directive"
3027
+ });
3028
+ }
2974
3029
 
2975
3030
  //#endregion
2976
3031
  //#region src/lib/injected-skills/write.ts
@@ -3513,7 +3568,7 @@ function initProxyFromEnv() {
3513
3568
  //#endregion
3514
3569
  //#region package.json
3515
3570
  var name = "github-router";
3516
- var version$1 = "0.3.131";
3571
+ var version$1 = "0.3.135";
3517
3572
 
3518
3573
  //#endregion
3519
3574
  //#region src/lib/approval.ts
@@ -5716,6 +5771,20 @@ const claude = defineCommand({
5716
5771
  } catch (err) {
5717
5772
  consola.warn(`Could not register the ai-or-die session-bind hook: ${String(err)}`);
5718
5773
  }
5774
+ if ((process$1.env.AIORDIE_SESSION_ID ?? "").trim().length > 0) {
5775
+ await writeInjectedSkill(ARTIFACT_REVIEW_SKILL.name, ARTIFACT_REVIEW_SKILL.md).catch(() => ({ written: false }));
5776
+ try {
5777
+ await prependArtifactPanelDirectiveToMirroredClaudeMd();
5778
+ } catch (err) {
5779
+ consola.warn(`Artifact-panel directive prepend failed: ${String(err)}`);
5780
+ }
5781
+ try {
5782
+ await writeArtifactCredsToMirror(shouldUseInsecureTls(process$1.env.AIORDIE_BASE_URL ?? ""));
5783
+ await injectStopHookIntoSettingsFile(nodePath.join(PATHS.CLAUDE_CONFIG_DIR, "settings.json"), buildArtifactOpenHookCommand(process$1.execPath, process$1.argv[1]), "PostToolUse", void 0, "ExitPlanMode");
5784
+ } catch (err) {
5785
+ consola.warn(`Could not register the artifact auto-open hook: ${String(err)}`);
5786
+ }
5787
+ }
5719
5788
  if (args["trust-gate"] === true) try {
5720
5789
  const root = await trustRepo(sessionCwd);
5721
5790
  process$1.stderr.write(`Structural gate trusted for this repo (${root}); it will run on launch here from now on.\n`);
@@ -6115,7 +6184,7 @@ async function postJson(url, payload, opts) {
6115
6184
  * no such handle. Hooks always receive piped/redirected stdin, so this never
6116
6185
  * blocks (guarded against an interactive TTY, and any error -> "").
6117
6186
  */
6118
- function readStdin$2() {
6187
+ function readStdin$3() {
6119
6188
  try {
6120
6189
  if (process.stdin.isTTY) return "";
6121
6190
  return readFileSync(0, "utf8");
@@ -6146,7 +6215,7 @@ const internalPromptSubmit = defineCommand({
6146
6215
  },
6147
6216
  async run() {
6148
6217
  try {
6149
- const stdin = readStdin$2();
6218
+ const stdin = readStdin$3();
6150
6219
  const steerEnabled = parseBoolEnv(process.env.GH_ROUTER_DISABLE_PROMPT_STEER) !== true;
6151
6220
  const runtime = hookMcpRuntimeFromEnv();
6152
6221
  let decision;
@@ -6206,7 +6275,7 @@ const internalPromptSubmit = defineCommand({
6206
6275
  * teardown and trips a `uv_async_send` assertion; a synchronous read has no such
6207
6276
  * handle. Hooks always receive piped stdin (guarded against a TTY; any error -> "").
6208
6277
  */
6209
- function readStdin$1() {
6278
+ function readStdin$2() {
6210
6279
  try {
6211
6280
  if (process.stdin.isTTY) return "";
6212
6281
  return readFileSync(0, "utf8");
@@ -6296,13 +6365,279 @@ const internalSessionBind = defineCommand({
6296
6365
  try {
6297
6366
  const out = typeof args.out === "string" ? args.out.trim() : "";
6298
6367
  if (!out) return;
6299
- const record = decodeSessionBind(readStdin$1());
6368
+ const record = decodeSessionBind(readStdin$2());
6300
6369
  if (record) writeSidecar(out, record);
6301
6370
  } catch {}
6302
6371
  process.exitCode = 0;
6303
6372
  }
6304
6373
  });
6305
6374
 
6375
+ //#endregion
6376
+ //#region src/lib/artifact/plan-html.ts
6377
+ function escapeHtml(s) {
6378
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
6379
+ }
6380
+ /** Decode the HTML character references a browser would resolve in an attribute
6381
+ * value, so an entity-obfuscated scheme (`javascript&colon;`, `j&#97;vascript:`)
6382
+ * cannot slip past the scheme check below. Numeric refs cover any letter; the
6383
+ * small named map covers the scheme-relevant punctuation. */
6384
+ const NAMED_REFS = {
6385
+ colon: ":",
6386
+ tab: " ",
6387
+ newline: "\n",
6388
+ sol: "/",
6389
+ semi: ";",
6390
+ amp: "&",
6391
+ lpar: "(",
6392
+ rpar: ")"
6393
+ };
6394
+ function decodeEntities(s) {
6395
+ return s.replace(/&(#[xX][0-9a-fA-F]+|#[0-9]+|[a-zA-Z]+);?/g, (m, body) => {
6396
+ if (body[0] === "#") {
6397
+ const code = body[1] === "x" || body[1] === "X" ? Number.parseInt(body.slice(2), 16) : Number.parseInt(body.slice(1), 10);
6398
+ return Number.isFinite(code) && code > 0 && code <= 1114111 ? String.fromCodePoint(code) : m;
6399
+ }
6400
+ return NAMED_REFS[body.toLowerCase()] ?? m;
6401
+ });
6402
+ }
6403
+ /** Block script-bearing URL schemes (defense-in-depth on top of HTML escaping). */
6404
+ function isDangerousUrl(url, isImage) {
6405
+ const v = Array.from(decodeEntities(url)).filter((c) => c.charCodeAt(0) > 32).join("").toLowerCase();
6406
+ if (v.startsWith("javascript:") || v.startsWith("vbscript:")) return true;
6407
+ if (v.startsWith("data:")) return !(isImage && v.startsWith("data:image/") && !v.startsWith("data:image/svg"));
6408
+ return false;
6409
+ }
6410
+ /** A marked instance that escapes raw HTML (raw markup never becomes live). */
6411
+ function makeMarked() {
6412
+ const m = new Marked({
6413
+ gfm: true,
6414
+ breaks: false
6415
+ });
6416
+ m.use({ renderer: { html(token) {
6417
+ return escapeHtml(typeof token === "string" ? token : token.text ?? "");
6418
+ } } });
6419
+ return m;
6420
+ }
6421
+ /**
6422
+ * Recursively neutralise script-bearing link/image hrefs in the lexed token tree.
6423
+ * Done as an explicit walk (not marked's `walkTokens` hook) because we render via
6424
+ * the low-level `lexer`+`parser` split, which does not run registered walkTokens.
6425
+ */
6426
+ function neutralizeUrls(tokens) {
6427
+ for (const t of tokens) {
6428
+ if ((t.type === "link" || t.type === "image") && typeof t.href === "string") {
6429
+ if (isDangerousUrl(t.href, t.type === "image")) t.href = "#";
6430
+ }
6431
+ if (t.tokens) neutralizeUrls(t.tokens);
6432
+ if (t.items) neutralizeUrls(t.items);
6433
+ if (t.header) {
6434
+ for (const cell of t.header) if (cell.tokens) neutralizeUrls(cell.tokens);
6435
+ }
6436
+ if (t.rows) {
6437
+ for (const row of t.rows) for (const cell of row) if (cell.tokens) neutralizeUrls(cell.tokens);
6438
+ }
6439
+ }
6440
+ }
6441
+ /** Add `data-source-line="N"` to the first opening tag of a rendered block. */
6442
+ function tagSourceLine(html, line) {
6443
+ return html.replace(/^(\s*)<([a-zA-Z][\w-]*)/, `$1<$2 data-source-line="${line}"`);
6444
+ }
6445
+ /**
6446
+ * Render markdown to inner HTML, tagging each top-level block with its 1-based
6447
+ * source line so an annotation in the panel can map back to a plan line.
6448
+ */
6449
+ function renderMarkdownBody(source) {
6450
+ const m = makeMarked();
6451
+ let tokens;
6452
+ try {
6453
+ tokens = m.lexer(source);
6454
+ } catch {
6455
+ return `<pre>${escapeHtml(source)}</pre>`;
6456
+ }
6457
+ neutralizeUrls(tokens);
6458
+ let line = 1;
6459
+ let out = "";
6460
+ for (const tok of tokens) {
6461
+ let html = "";
6462
+ try {
6463
+ html = m.parser([tok]);
6464
+ } catch {
6465
+ html = "";
6466
+ }
6467
+ if (html.trim()) out += tagSourceLine(html, line);
6468
+ line += tok.raw?.match(/\n/g)?.length ?? 0;
6469
+ }
6470
+ return out || `<pre>${escapeHtml(source)}</pre>`;
6471
+ }
6472
+ /**
6473
+ * Wrap rendered markdown in a complete, self-contained HTML document with
6474
+ * readable typography (works even before ai-or-die injects its annotation SDK).
6475
+ */
6476
+ function renderPlanHtml(source, title = "Plan") {
6477
+ const body = renderMarkdownBody(source);
6478
+ return `<!doctype html>
6479
+ <html lang="en">
6480
+ <head>
6481
+ <meta charset="utf-8">
6482
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6483
+ <title>${escapeHtml(title)}</title>
6484
+ <style>
6485
+ :root {
6486
+ --bg: #0f1115; --fg: #f7f3ea; --muted: #aeb6c6; --border: #2a2f3a;
6487
+ --accent: #f4c95d; --code-bg: #171a21;
6488
+ --serif: 'Iowan Old Style', Georgia, 'Times New Roman', serif;
6489
+ --sans: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
6490
+ --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
6491
+ }
6492
+ * { box-sizing: border-box; }
6493
+ html, body { margin: 0; background: var(--bg); color: var(--fg); }
6494
+ body { font-family: var(--serif); font-size: 16px; line-height: 1.6;
6495
+ padding: 32px clamp(16px, 5vw, 56px); }
6496
+ main { max-width: 78ch; margin: 0 auto; }
6497
+ h1, h2, h3, h4 { font-family: var(--sans); line-height: 1.25; margin: 1.6em 0 0.5em; }
6498
+ h1 { font-size: 1.9em; } h2 { font-size: 1.45em; padding-bottom: 0.2em;
6499
+ border-bottom: 1px solid var(--border); } h3 { font-size: 1.2em; }
6500
+ p, li { color: var(--fg); }
6501
+ a { color: var(--accent); }
6502
+ code { font-family: var(--mono); font-size: 0.9em; background: var(--code-bg);
6503
+ padding: 0.12em 0.35em; border-radius: 5px; }
6504
+ pre { background: var(--code-bg); border: 1px solid var(--border); border-radius: 8px;
6505
+ padding: 12px 14px; overflow: auto; }
6506
+ pre code { background: none; padding: 0; }
6507
+ blockquote { margin: 1em 0; padding: 0.2em 1em; border-left: 3px solid var(--accent);
6508
+ color: var(--muted); }
6509
+ table { border-collapse: collapse; width: 100%; margin: 1em 0; font-family: var(--sans);
6510
+ font-size: 0.95em; }
6511
+ th, td { border: 1px solid var(--border); padding: 6px 10px; text-align: left; }
6512
+ th { background: var(--code-bg); }
6513
+ hr { border: 0; border-top: 1px solid var(--border); margin: 1.5em 0; }
6514
+ ul, ol { padding-left: 1.4em; }
6515
+ </style>
6516
+ </head>
6517
+ <body>
6518
+ <main>
6519
+ ${body}
6520
+ </main>
6521
+ </body>
6522
+ </html>
6523
+ `;
6524
+ }
6525
+
6526
+ //#endregion
6527
+ //#region src/internal-artifact-open.ts
6528
+ function readStdin$1() {
6529
+ try {
6530
+ if (process.stdin.isTTY) return "";
6531
+ return readFileSync(0, "utf8");
6532
+ } catch {
6533
+ return "";
6534
+ }
6535
+ }
6536
+ function credsPath() {
6537
+ const dir = process.env.CLAUDE_CONFIG_DIR || nodePath.join(homedir(), ".claude");
6538
+ return nodePath.join(dir, ".aiordie-artifact.json");
6539
+ }
6540
+ function isSubagent(payload) {
6541
+ try {
6542
+ return isSubagentContext(payload);
6543
+ } catch {
6544
+ return false;
6545
+ }
6546
+ }
6547
+ /** Pull the per-session plan path + markdown content out of the hook payload. */
6548
+ function parseExitPlanPayload(raw) {
6549
+ try {
6550
+ const ti = JSON.parse(raw).tool_input ?? {};
6551
+ return {
6552
+ planFilePath: typeof ti.planFilePath === "string" && ti.planFilePath.trim() ? ti.planFilePath : void 0,
6553
+ planMarkdown: typeof ti.plan === "string" && ti.plan.trim() ? ti.plan : void 0
6554
+ };
6555
+ } catch {
6556
+ return {};
6557
+ }
6558
+ }
6559
+ function readCreds() {
6560
+ try {
6561
+ const parsed = JSON.parse(readFileSync(credsPath(), "utf8"));
6562
+ if (!parsed.baseUrl || !parsed.token || !parsed.sessionId) return void 0;
6563
+ return {
6564
+ baseUrl: parsed.baseUrl,
6565
+ token: parsed.token,
6566
+ sessionId: parsed.sessionId,
6567
+ insecureTLS: parsed.insecureTLS === true
6568
+ };
6569
+ } catch {
6570
+ return;
6571
+ }
6572
+ }
6573
+ /**
6574
+ * Resolve the plan markdown + a title, preferring the file at `planFilePath`
6575
+ * (the source of truth, may be newer than the payload's snapshot), falling back
6576
+ * to the inline `tool_input.plan` content.
6577
+ */
6578
+ function resolvePlanMarkdown(p) {
6579
+ if (p.planFilePath) try {
6580
+ const md = readFileSync(p.planFilePath, "utf8");
6581
+ if (md.trim()) return {
6582
+ markdown: md,
6583
+ title: nodePath.basename(p.planFilePath, nodePath.extname(p.planFilePath)),
6584
+ nearPath: p.planFilePath
6585
+ };
6586
+ } catch {}
6587
+ if (p.planMarkdown) return {
6588
+ markdown: p.planMarkdown,
6589
+ title: "Plan"
6590
+ };
6591
+ }
6592
+ /**
6593
+ * Write the rendered HTML next to the plan (an allowed root) when we have a real
6594
+ * path, else into the plans dir under CLAUDE_CONFIG_DIR. Returns the realpath of
6595
+ * the written `.html`, or undefined on failure.
6596
+ */
6597
+ function writePlanHtml(html, nearPath) {
6598
+ let target;
6599
+ if (nearPath) target = `${nearPath.replace(/\.(md|markdown)$/i, "")}.aiordie.html`;
6600
+ else {
6601
+ const dir = nodePath.join(process.env.CLAUDE_CONFIG_DIR || nodePath.join(homedir(), ".claude"), "plans");
6602
+ target = nodePath.join(dir, `aiordie-plan-${process.pid}.html`);
6603
+ }
6604
+ try {
6605
+ writeFileSync(target, html, "utf8");
6606
+ try {
6607
+ return realpathSync.native(target);
6608
+ } catch {
6609
+ return target;
6610
+ }
6611
+ } catch {
6612
+ return;
6613
+ }
6614
+ }
6615
+ const internalArtifactOpen = defineCommand({
6616
+ meta: {
6617
+ name: "internal-artifact-open",
6618
+ description: "Open the finalized plan in the ai-or-die panel"
6619
+ },
6620
+ async run() {
6621
+ try {
6622
+ const raw = readStdin$1();
6623
+ let payload;
6624
+ try {
6625
+ payload = JSON.parse(raw);
6626
+ } catch {
6627
+ payload = void 0;
6628
+ }
6629
+ if (isSubagent(payload)) return;
6630
+ const creds = readCreds();
6631
+ if (!creds) return;
6632
+ const plan = resolvePlanMarkdown(parseExitPlanPayload(raw));
6633
+ if (!plan) return;
6634
+ const htmlPath = writePlanHtml(renderPlanHtml(plan.markdown, plan.title), plan.nearPath);
6635
+ if (!htmlPath) return;
6636
+ await new ArtifactClient(creds).open(htmlPath);
6637
+ } catch {}
6638
+ }
6639
+ });
6640
+
6306
6641
  //#endregion
6307
6642
  //#region src/internal-stop-hook.ts
6308
6643
  /**
@@ -6861,7 +7196,7 @@ process.on("uncaughtException", (error) => {
6861
7196
  const version = getPackageVersion();
6862
7197
  const argv = process.argv.slice(2);
6863
7198
  const isVersionFlag = argv.includes("--version");
6864
- const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-session-bind";
7199
+ const isInternalHook = argv[0] === "internal-stop-hook" || argv[0] === "internal-prompt-submit" || argv[0] === "internal-stop-review" || argv[0] === "internal-session-bind" || argv[0] === "internal-artifact-open";
6865
7200
  if (!isVersionFlag && !isInternalHook) consola.info(`github-router v${version}`);
6866
7201
  await runMain(defineCommand({
6867
7202
  meta: {
@@ -6880,7 +7215,8 @@ await runMain(defineCommand({
6880
7215
  "internal-stop-hook": internalStopHook,
6881
7216
  "internal-prompt-submit": internalPromptSubmit,
6882
7217
  "internal-stop-review": internalStopReview,
6883
- "internal-session-bind": internalSessionBind
7218
+ "internal-session-bind": internalSessionBind,
7219
+ "internal-artifact-open": internalArtifactOpen
6884
7220
  }
6885
7221
  }));
6886
7222