@macintacos/caret 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +59 -52
  3. package/commands/discovery.md +4 -4
  4. package/dist/cli.js +312 -111
  5. package/opencode/commands/discovery.md +4 -4
  6. package/package.json +3 -2
  7. package/ui/dist/assets/{angular-html-BOjikgNB.js → angular-html-166Zp_qr.js} +1 -1
  8. package/ui/dist/assets/{angular-ts-BjxNvVmD.js → angular-ts-DJf9y5RJ.js} +1 -1
  9. package/ui/dist/assets/{apl-BkiW4daM.js → apl-BRtDRKgK.js} +1 -1
  10. package/ui/dist/assets/{astro-3adpTlIH.js → astro-B9_p1ZlX.js} +1 -1
  11. package/ui/dist/assets/{blade-DPTGWOFi.js → blade-DUQgH5UI.js} +1 -1
  12. package/ui/dist/assets/{c-Gr6AqmnN.js → c-DkmnqVR5.js} +1 -1
  13. package/ui/dist/assets/{cobol-C8hrPk4X.js → cobol-D_DeO3_Y.js} +1 -1
  14. package/ui/dist/assets/{coffee-COAUS6hR.js → coffee-BlbmSk0s.js} +1 -1
  15. package/ui/dist/assets/{cpp-DRNHed7s.js → cpp-C-2HRkcc.js} +1 -1
  16. package/ui/dist/assets/{crystal-xGCPBQ2Z.js → crystal-CpLflAjJ.js} +1 -1
  17. package/ui/dist/assets/{css-g3rqYwXR.js → css-BVuUbqzr.js} +1 -1
  18. package/ui/dist/assets/{dist-DAyUKqoN.js → dist-B9dAYtn2.js} +1 -1
  19. package/ui/dist/assets/{dist-B8chzuGI.js → dist-BtZ4f0sy.js} +1 -1
  20. package/ui/dist/assets/{dist-DPkspc9Q.js → dist-BxKUx0iz.js} +1 -1
  21. package/ui/dist/assets/{dist-D8G49gMQ.js → dist-CmhY3KNJ.js} +1 -1
  22. package/ui/dist/assets/{dist-B3rxpa4n.js → dist-D-vK_12F.js} +1 -1
  23. package/ui/dist/assets/{dist-D55VuegY.js → dist-D1hccSXB.js} +1 -1
  24. package/ui/dist/assets/{dist-Baejo1Q0.js → dist-DWjpoB5Q.js} +1 -1
  25. package/ui/dist/assets/{dist-ALQWrgOU.js → dist-Dd0x4Lgi.js} +1 -1
  26. package/ui/dist/assets/{dist--Ppdbw_u.js → dist-Deoj7mVd.js} +1 -1
  27. package/ui/dist/assets/{dist-B57N0_ad.js → dist-DtJjv6C-.js} +1 -1
  28. package/ui/dist/assets/{dist-B0fJR4WS.js → dist-DyJyAkU7.js} +1 -1
  29. package/ui/dist/assets/{dist-BeBmdH90.js → dist-DzbZtok6.js} +1 -1
  30. package/ui/dist/assets/{dist-BtEZoKjT.js → dist-Jn2c79bF.js} +1 -1
  31. package/ui/dist/assets/{dist-CqeZRBk3.js → dist-WBctQMJH.js} +1 -1
  32. package/ui/dist/assets/{dist-DGuk1LV9.js → dist-nPPt0-3f.js} +1 -1
  33. package/ui/dist/assets/{dist-CbLm1gmp.js → dist-qtw8Ijdq.js} +1 -1
  34. package/ui/dist/assets/{dist-ekL3xCM_.js → dist-vj9fsPbi.js} +1 -1
  35. package/ui/dist/assets/{edge-1tf_B5nG.js → edge-Bvaq3vgu.js} +1 -1
  36. package/ui/dist/assets/{elixir-Ca3rBU7D.js → elixir-AtYS8b3T.js} +1 -1
  37. package/ui/dist/assets/{elm-CbkPZkSz.js → elm-DiPfkLEI.js} +1 -1
  38. package/ui/dist/assets/{erb-DV7pKwEX.js → erb-DFPsOHl7.js} +1 -1
  39. package/ui/dist/assets/{git-rebase-DItBLEqi.js → git-rebase-DOQhjkkB.js} +1 -1
  40. package/ui/dist/assets/{glimmer-js-CAykgXKb.js → glimmer-js-DnsoKoXj.js} +1 -1
  41. package/ui/dist/assets/{glimmer-ts-BPT-EGHX.js → glimmer-ts-efCSUcGf.js} +1 -1
  42. package/ui/dist/assets/{glsl-CMfWGTLW.js → glsl-8mp3VUjq.js} +1 -1
  43. package/ui/dist/assets/{graphql-CR_NOWo2.js → graphql-NvuaWD7i.js} +1 -1
  44. package/ui/dist/assets/{hack-BWP6dwsC.js → hack-BJ5H84Wq.js} +1 -1
  45. package/ui/dist/assets/{haml-CDa-RN50.js → haml-Dzosfq8Y.js} +1 -1
  46. package/ui/dist/assets/{handlebars-sJHLZ5A7.js → handlebars-tKuTe0db.js} +1 -1
  47. package/ui/dist/assets/{html-63fn47vQ.js → html-JqbgrVrs.js} +1 -1
  48. package/ui/dist/assets/{html-derivative-BXnhYyFs.js → html-derivative-0a3abtuv.js} +1 -1
  49. package/ui/dist/assets/{http-IOW0RfUa.js → http-DMJqqRWL.js} +1 -1
  50. package/ui/dist/assets/{hurl-CTSYkbKC.js → hurl-N2MzWbSO.js} +1 -1
  51. package/ui/dist/assets/index-BMSMV2hN.css +1 -0
  52. package/ui/dist/assets/index-C_IKB4Ku.js +2501 -0
  53. package/ui/dist/assets/{java-DOX-o9K8.js → java-B3EVVFFi.js} +1 -1
  54. package/ui/dist/assets/{javascript-NWcTx9fC.js → javascript-Y0fZ_N_R.js} +1 -1
  55. package/ui/dist/assets/{jinja-TT5Clkg-.js → jinja-BD-QmqJ0.js} +1 -1
  56. package/ui/dist/assets/{jison-DoZAP4a3.js → jison-BK-X5L2R.js} +1 -1
  57. package/ui/dist/assets/{json-Dd1DbJZw.js → json-DlHOOSIb.js} +1 -1
  58. package/ui/dist/assets/{jsx-D95PvYev.js → jsx-XohAHRMn.js} +1 -1
  59. package/ui/dist/assets/{julia-DzNcHVqx.js → julia-BBLrPT8A.js} +1 -1
  60. package/ui/dist/assets/{just-DapN_z3p.js → just-DEry_8sa.js} +1 -1
  61. package/ui/dist/assets/{latex-Bf3O2fQb.js → latex-BuCB4qwR.js} +1 -1
  62. package/ui/dist/assets/{liquid-NXRAWoEl.js → liquid-ZtUmbkDf.js} +1 -1
  63. package/ui/dist/assets/{lua-DgnpxH9p.js → lua-BmOQjHgA.js} +1 -1
  64. package/ui/dist/assets/{marko-wkWPjoQ9.js → marko-BXMGyPGo.js} +1 -1
  65. package/ui/dist/assets/{mdc-DtOun-X2.js → mdc-Byn32K04.js} +1 -1
  66. package/ui/dist/assets/{nginx-XCdDX9Gp.js → nginx-BrQiZd4C.js} +1 -1
  67. package/ui/dist/assets/{nim-CnDu2vnO.js → nim-Ceo1IkbE.js} +1 -1
  68. package/ui/dist/assets/{perl-mCSP7zn3.js → perl-Bum057xI.js} +1 -1
  69. package/ui/dist/assets/{php-CnDYV7m9.js → php-DcJ8ynWT.js} +1 -1
  70. package/ui/dist/assets/{pug-DUv1_GFU.js → pug-CA9Zezth.js} +1 -1
  71. package/ui/dist/assets/{qml-Cw49qsJy.js → qml-BHS1pXn9.js} +1 -1
  72. package/ui/dist/assets/{r-DUSb08Z8.js → r-B4aRkCWL.js} +1 -1
  73. package/ui/dist/assets/{razor-CKco3Dbd.js → razor-BXH-K44T.js} +1 -1
  74. package/ui/dist/assets/{regexp-DbjqCAsi.js → regexp-DUs7aoKy.js} +1 -1
  75. package/ui/dist/assets/{rst-iLrpHokU.js → rst-DCrs7vP-.js} +1 -1
  76. package/ui/dist/assets/{ruby-BBYneGfk.js → ruby-0-lN-lO6.js} +1 -1
  77. package/ui/dist/assets/{sas-DoS0TCZm.js → sas-B-GmRmDQ.js} +1 -1
  78. package/ui/dist/assets/{scss-DwjazCIb.js → scss-oB6C5dt1.js} +1 -1
  79. package/ui/dist/assets/{shellscript-bR-cjL3L.js → shellscript-BDueQ3Pr.js} +1 -1
  80. package/ui/dist/assets/{shellsession-D54KuySr.js → shellsession-snwU-yjZ.js} +1 -1
  81. package/ui/dist/assets/{soy-QeEusSoH.js → soy-1UzSzCS_.js} +1 -1
  82. package/ui/dist/assets/{sql-BjJgWH0F.js → sql-_8rHrLe0.js} +1 -1
  83. package/ui/dist/assets/{stata---E5T02y.js → stata-BFBQv4-R.js} +1 -1
  84. package/ui/dist/assets/{surrealql-B3nSLmva.js → surrealql-C-hPyRoi.js} +1 -1
  85. package/ui/dist/assets/{svelte-BrJnUGsO.js → svelte-COzcYWbs.js} +1 -1
  86. package/ui/dist/assets/{templ-Cp6BywaR.js → templ-CIF5RvVU.js} +1 -1
  87. package/ui/dist/assets/{tex-yFYe_F-5.js → tex-DiIjyX_E.js} +1 -1
  88. package/ui/dist/assets/{ts-tags-Dn1XZNS9.js → ts-tags-Bb-cll5a.js} +1 -1
  89. package/ui/dist/assets/{tsx-BnhO4Hkv.js → tsx-B_TJQczU.js} +1 -1
  90. package/ui/dist/assets/{twig-B-zB3u-Z.js → twig-B2bev-dI.js} +1 -1
  91. package/ui/dist/assets/{typescript-D49Rl_Ln.js → typescript-Ii543U_f.js} +1 -1
  92. package/ui/dist/assets/{vue-DmTXiGeI.js → vue-CGYOL_ei.js} +1 -1
  93. package/ui/dist/assets/{vue-html-CeXpHptR.js → vue-html-DJiTs0pH.js} +1 -1
  94. package/ui/dist/assets/{vue-vine-C2uWvGRx.js → vue-vine-BWgBzdhj.js} +1 -1
  95. package/ui/dist/assets/{xml-SZiG-U1T.js → xml-wwBhpto-.js} +1 -1
  96. package/ui/dist/assets/{xsl-DksAOCj6.js → xsl-ia_QpSvH.js} +1 -1
  97. package/ui/dist/assets/{yaml-DX7HDqJF.js → yaml-DPR0vpXc.js} +1 -1
  98. package/ui/dist/index.html +2 -2
  99. package/ui/dist/assets/index-B-GFE-1x.css +0 -1
  100. package/ui/dist/assets/index-CtmSI3Ho.js +0 -1203
package/dist/cli.js CHANGED
@@ -5803,8 +5803,8 @@ __export(exports_dist, {
5803
5803
  });
5804
5804
  import { styleText as styleText2, stripVTControlCharacters } from "util";
5805
5805
  import process$1 from "process";
5806
- import { existsSync as existsSync10, lstatSync, readdirSync as readdirSync6 } from "fs";
5807
- import { dirname as dirname8, join as join13 } from "path";
5806
+ import { existsSync as existsSync10, lstatSync, readdirSync as readdirSync5 } from "fs";
5807
+ import { dirname as dirname8, join as join14 } from "path";
5808
5808
  function isUnicodeSupported() {
5809
5809
  if (process$1.platform !== "win32") {
5810
5810
  return process$1.env.TERM !== "linux";
@@ -6517,8 +6517,8 @@ ${n3}
6517
6517
  let i;
6518
6518
  existsSync10(t2) ? lstatSync(t2).isDirectory() && (!e.directory || t2.endsWith("/")) ? i = t2 : i = dirname8(t2) : i = dirname8(t2);
6519
6519
  const c2 = t2.length > 1 && t2.endsWith("/") ? t2.slice(0, -1) : t2;
6520
- return readdirSync6(i).map((r2) => {
6521
- const n3 = join13(i, r2), m3 = lstatSync(n3);
6520
+ return readdirSync5(i).map((r2) => {
6521
+ const n3 = join14(i, r2), m3 = lstatSync(n3);
6522
6522
  return {
6523
6523
  name: r2,
6524
6524
  path: n3,
@@ -24178,6 +24178,63 @@ config(en_default());
24178
24178
  var DEFAULT_PORT = 42718;
24179
24179
  var VANITY_HOST = "caret.localhost";
24180
24180
  var HOOK_TIMEOUT_S = 3900;
24181
+ var EXCERPT_RADIUS = 30;
24182
+ var KNOWN_FILE_EXTENSIONS = new Set([
24183
+ "ts",
24184
+ "tsx",
24185
+ "mts",
24186
+ "cts",
24187
+ "js",
24188
+ "jsx",
24189
+ "mjs",
24190
+ "cjs",
24191
+ "svelte",
24192
+ "vue",
24193
+ "json",
24194
+ "jsonc",
24195
+ "css",
24196
+ "scss",
24197
+ "less",
24198
+ "html",
24199
+ "htm",
24200
+ "xml",
24201
+ "svg",
24202
+ "md",
24203
+ "mdx",
24204
+ "py",
24205
+ "rb",
24206
+ "rs",
24207
+ "go",
24208
+ "java",
24209
+ "kt",
24210
+ "c",
24211
+ "h",
24212
+ "cc",
24213
+ "cpp",
24214
+ "hpp",
24215
+ "sh",
24216
+ "bash",
24217
+ "zsh",
24218
+ "toml",
24219
+ "yaml",
24220
+ "yml",
24221
+ "ini",
24222
+ "sql",
24223
+ "graphql",
24224
+ "gql",
24225
+ "php",
24226
+ "swift",
24227
+ "dart",
24228
+ "txt",
24229
+ "lock",
24230
+ "cfg",
24231
+ "conf"
24232
+ ]);
24233
+ function hasKnownFileExtension(path) {
24234
+ const base = path.split("/").pop() ?? "";
24235
+ const dot = base.lastIndexOf(".");
24236
+ return dot > 0 && KNOWN_FILE_EXTENSIONS.has(base.slice(dot + 1).toLowerCase());
24237
+ }
24181
24238
  var BUN_SOCKET_IDLE_CAP_S = 255;
24182
24239
  var IDLE_TIMEOUT_HEADROOM_S = 5;
24183
24240
  var MAX_HEARTBEAT_MS = (BUN_SOCKET_IDLE_CAP_S - IDLE_TIMEOUT_HEADROOM_S) * 1000;
@@ -24294,7 +24351,7 @@ async function loadUiAssets() {
24294
24351
  // package.json
24295
24352
  var package_default = {
24296
24353
  name: "@macintacos/caret",
24297
- version: "0.9.0",
24354
+ version: "0.10.0",
24298
24355
  license: "MIT",
24299
24356
  type: "module",
24300
24357
  description: "A Claude Code plugin that replaces the terminal plan-approval prompt with a local web UI for inline review, annotation, and approval.",
@@ -24323,7 +24380,7 @@ var package_default = {
24323
24380
  ".claude-plugin/plugin.json"
24324
24381
  ],
24325
24382
  scripts: {
24326
- test: "bun test --conditions browser",
24383
+ test: "bun test --conditions browser --timeout 30000",
24327
24384
  lint: "mise run lint",
24328
24385
  format: "mise run format",
24329
24386
  build: "mise run build"
@@ -24359,6 +24416,7 @@ var package_default = {
24359
24416
  "@lezer/highlight": "^1.2.3",
24360
24417
  "@opencode-ai/plugin": "^1.16.2",
24361
24418
  "@pierre/diffs": "1.2.7",
24419
+ "@pierre/trees": "1.0.0-beta.6",
24362
24420
  "bits-ui": "^2.18.1",
24363
24421
  clsx: "^2.1.1",
24364
24422
  commander: "^15.0.0",
@@ -25160,9 +25218,10 @@ var PlanInputSchema = exports_external.object({
25160
25218
  cwd: exports_external.string().optional(),
25161
25219
  title: exports_external.string().optional(),
25162
25220
  plan: exports_external.string().optional(),
25163
- planFilePath: exports_external.string().optional()
25221
+ planFilePath: exports_external.string().optional(),
25222
+ cmux: exports_external.object({ workspaceId: exports_external.string().min(1), surfaceId: exports_external.string().min(1) }).optional().catch(undefined)
25164
25223
  }).catch({});
25165
- var MAX_FILE_REFS = 200;
25224
+ var MAX_FILE_REFS = 1000;
25166
25225
  var FileRefsBodySchema = exports_external.object({ paths: exports_external.array(exports_external.string()).catch([]) }).catch({ paths: [] });
25167
25226
  var BehaviorSchema = exports_external.enum(["allow", "deny"]);
25168
25227
  var ResolveBodySchema = exports_external.object({
@@ -25226,12 +25285,41 @@ async function parseBody(req, schema) {
25226
25285
  return schema.parse(await req.json().catch(() => ({})));
25227
25286
  }
25228
25287
 
25288
+ // src/lib/cmux.ts
25289
+ function readCmuxPane(env = process.env) {
25290
+ const workspaceId = env.CMUX_WORKSPACE_ID;
25291
+ const surfaceId = env.CMUX_SURFACE_ID;
25292
+ if (!workspaceId || !surfaceId)
25293
+ return;
25294
+ return { workspaceId, surfaceId };
25295
+ }
25296
+ function markPaneRead(pane, deps = {}) {
25297
+ const spawn = deps.spawn ?? Bun.spawn;
25298
+ const log = deps.log ?? noopLogger;
25299
+ try {
25300
+ spawn([
25301
+ "cmux",
25302
+ "mark-notification-read",
25303
+ "--workspace",
25304
+ pane.workspaceId,
25305
+ "--surface",
25306
+ pane.surfaceId
25307
+ ], { stdio: ["ignore", "ignore", "ignore"] }).unref();
25308
+ } catch (err) {
25309
+ log.warn("spawn", "unread mark clear failed", { reason: String(err) });
25310
+ }
25311
+ }
25312
+
25313
+ // src/plan/directory.ts
25314
+ import { readdir as readdir2 } from "fs/promises";
25315
+ import { join as join8, relative as relative3, sep as sep2 } from "path";
25316
+
25229
25317
  // src/plan/excerpt.ts
25230
- import { readdirSync as readdirSync3, readFileSync as readFileSync7, realpathSync, statSync as statSync2 } from "fs";
25318
+ import { readFileSync as readFileSync7 } from "fs";
25319
+ import { readdir, realpath, stat } from "fs/promises";
25231
25320
  import { extname, isAbsolute, join as join7, relative as relative2, resolve, sep } from "path";
25232
- var EXCERPT_RADIUS = 30;
25233
25321
  var EXCERPT_HEAD_LINES = 60;
25234
- var MAX_EXCERPT_BYTES = 2 * 1024 * 1024;
25322
+ var MAX_EXCERPT_BYTES = 10 * 1024 * 1024;
25235
25323
  var MAX_SCAN_ENTRIES = 5000;
25236
25324
  var SKIP_DIRS = new Set(["node_modules", "dist", "build", "coverage", "out"]);
25237
25325
  var EXT_LANGUAGE = {
@@ -25285,36 +25373,32 @@ function languageForPath(path) {
25285
25373
  return EXT_LANGUAGE[extname(path).toLowerCase()] ?? "text";
25286
25374
  }
25287
25375
  function safeRealpath(path) {
25288
- try {
25289
- return realpathSync(path);
25290
- } catch {
25291
- return null;
25292
- }
25376
+ return realpath(path).catch(() => null);
25293
25377
  }
25294
- function containedFile(cwdReal, abs) {
25295
- const real = safeRealpath(abs);
25378
+ function safeStat(path) {
25379
+ return stat(path).catch(() => null);
25380
+ }
25381
+ async function contained(cwdReal, abs) {
25382
+ const real = await safeRealpath(abs);
25296
25383
  if (real === null)
25297
25384
  return null;
25298
25385
  if (real !== cwdReal && !real.startsWith(cwdReal + sep))
25299
25386
  return null;
25300
- try {
25301
- if (!statSync2(real).isFile())
25302
- return null;
25303
- } catch {
25387
+ const stats = await safeStat(real);
25388
+ if (stats === null)
25304
25389
  return null;
25305
- }
25306
- return real;
25390
+ if (stats.isFile())
25391
+ return { path: real, kind: "file" };
25392
+ return stats.isDirectory() ? { path: real, kind: "directory" } : null;
25307
25393
  }
25308
- function basenameSearch(cwdReal, name) {
25309
- if (name === "")
25310
- return null;
25394
+ async function basenameSearch(cwdReal, name) {
25311
25395
  let scanned = 0;
25312
25396
  const queue = [cwdReal];
25313
25397
  while (queue.length > 0) {
25314
25398
  const currentDir = queue.shift();
25315
25399
  let entries;
25316
25400
  try {
25317
- entries = readdirSync3(currentDir, { withFileTypes: true });
25401
+ entries = await readdir(currentDir, { withFileTypes: true });
25318
25402
  } catch {
25319
25403
  continue;
25320
25404
  }
@@ -25331,55 +25415,66 @@ function basenameSearch(cwdReal, name) {
25331
25415
  }
25332
25416
  return null;
25333
25417
  }
25334
- function resolveFileInCwd(cwd, candidate) {
25418
+ async function resolveInCwd(cwd, candidate) {
25335
25419
  if (cwd === "" || !isAbsolute(cwd))
25336
25420
  return null;
25337
- const cwdReal = safeRealpath(cwd);
25421
+ const cwdReal = await safeRealpath(cwd);
25338
25422
  if (cwdReal === null)
25339
25423
  return null;
25340
25424
  const cleaned = candidate.trim();
25341
25425
  if (cleaned === "")
25342
25426
  return null;
25343
- const direct = containedFile(cwdReal, resolve(cwdReal, cleaned));
25427
+ const direct = await contained(cwdReal, resolve(cwdReal, cleaned));
25344
25428
  if (direct !== null)
25345
25429
  return direct;
25346
- const name = cleaned.split("/").pop() ?? "";
25347
- return basenameSearch(cwdReal, name);
25430
+ if (cleaned.includes("/") || !hasKnownFileExtension(cleaned))
25431
+ return null;
25432
+ const found = await basenameSearch(cwdReal, cleaned);
25433
+ return found === null ? null : { path: found, kind: "file" };
25348
25434
  }
25349
- function isFileTooLargeToPreview(cwd, candidate) {
25350
- const abs = resolveFileInCwd(cwd, candidate);
25351
- if (abs === null)
25435
+ async function isFileTooLargeToPreview(cwd, candidate) {
25436
+ const hit = await resolveInCwd(cwd, candidate);
25437
+ if (hit === null || hit.kind !== "file")
25352
25438
  return false;
25353
- try {
25354
- return statSync2(abs).size > MAX_EXCERPT_BYTES;
25355
- } catch {
25356
- return false;
25357
- }
25439
+ const stats = await safeStat(hit.path);
25440
+ return stats !== null && stats.size > MAX_EXCERPT_BYTES;
25358
25441
  }
25359
- function readFileExcerpt(cwd, candidate, line, range) {
25360
- const abs = resolveFileInCwd(cwd, candidate);
25361
- if (abs === null)
25362
- return null;
25363
- let size;
25364
- try {
25365
- size = statSync2(abs).size;
25366
- } catch {
25367
- return null;
25368
- }
25369
- if (size > MAX_EXCERPT_BYTES)
25370
- return null;
25442
+ var memo = null;
25443
+ function statIdentity(stats) {
25444
+ return `${stats.size}:${stats.mtimeMs}:${stats.ino}`;
25445
+ }
25446
+ function splitLines(content) {
25447
+ const lines = content.split(`
25448
+ `);
25449
+ if (lines.length > 1 && lines[lines.length - 1] === "")
25450
+ lines.pop();
25451
+ return lines;
25452
+ }
25453
+ function readLines(abs, stats) {
25454
+ const identity = statIdentity(stats);
25455
+ if (memo !== null && memo.path === abs && memo.identity === identity)
25456
+ return memo.lines;
25371
25457
  let content;
25372
25458
  try {
25373
25459
  content = readFileSync7(abs, "utf-8");
25374
25460
  } catch {
25375
25461
  return null;
25376
25462
  }
25377
- if (content.includes("\x00"))
25463
+ const lines = content.includes("\x00") ? null : splitLines(content);
25464
+ memo = { path: abs, identity, lines };
25465
+ return lines;
25466
+ }
25467
+ async function readFileExcerpt(cwd, candidate, line, range) {
25468
+ const hit = await resolveInCwd(cwd, candidate);
25469
+ if (hit === null || hit.kind !== "file")
25470
+ return null;
25471
+ const abs = hit.path;
25472
+ const stats = await safeStat(abs);
25473
+ if (stats === null || stats.size > MAX_EXCERPT_BYTES)
25474
+ return null;
25475
+ const allLines = readLines(abs, stats);
25476
+ if (allLines === null)
25378
25477
  return null;
25379
- const allLines = content.split(`
25380
- `);
25381
- if (allLines.length > 1 && allLines[allLines.length - 1] === "")
25382
- allLines.pop();
25383
25478
  const totalLines = allLines.length;
25384
25479
  let startLine;
25385
25480
  let endLine;
@@ -25394,7 +25489,7 @@ function readFileExcerpt(cwd, candidate, line, range) {
25394
25489
  startLine = 1;
25395
25490
  endLine = Math.min(totalLines, EXCERPT_HEAD_LINES);
25396
25491
  }
25397
- const cwdReal = safeRealpath(cwd) ?? cwd;
25492
+ const cwdReal = await safeRealpath(cwd) ?? cwd;
25398
25493
  return {
25399
25494
  path: relative2(cwdReal, abs),
25400
25495
  language: languageForPath(abs),
@@ -25405,6 +25500,62 @@ function readFileExcerpt(cwd, candidate, line, range) {
25405
25500
  };
25406
25501
  }
25407
25502
 
25503
+ // src/plan/directory.ts
25504
+ var MAX_DIR_ENTRIES = 500;
25505
+ var MAX_DIR_DEPTH = 10;
25506
+ function byKindThenName(a, b) {
25507
+ if (a.kind !== b.kind)
25508
+ return a.kind === "directory" ? -1 : 1;
25509
+ return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
25510
+ }
25511
+ function entryFor(name, kind) {
25512
+ if (kind === "file")
25513
+ return { name, kind };
25514
+ return SKIP_DIRS.has(name) || name.startsWith(".") ? { name, kind, skipped: true } : { name, kind };
25515
+ }
25516
+ async function listDirectory(cwd, root, path) {
25517
+ const rootHit = await resolveInCwd(cwd, root);
25518
+ if (rootHit === null || rootHit.kind !== "directory")
25519
+ return null;
25520
+ const target = path === "" ? rootHit : await resolveInCwd(cwd, path);
25521
+ if (target === null || target.kind !== "directory")
25522
+ return null;
25523
+ const rel = relative3(rootHit.path, target.path);
25524
+ if (rel === ".." || rel.startsWith(`..${sep2}`))
25525
+ return null;
25526
+ if (rel !== "" && rel.split(sep2).length > MAX_DIR_DEPTH)
25527
+ return null;
25528
+ let dirents;
25529
+ try {
25530
+ dirents = await readdir2(target.path, { withFileTypes: true });
25531
+ } catch {
25532
+ return null;
25533
+ }
25534
+ const entries = [];
25535
+ const links = [];
25536
+ for (const d of dirents) {
25537
+ if (d.isFile())
25538
+ entries.push(entryFor(d.name, "file"));
25539
+ else if (d.isDirectory())
25540
+ entries.push(entryFor(d.name, "directory"));
25541
+ else if (d.isSymbolicLink())
25542
+ links.push(d);
25543
+ }
25544
+ const targets = await Promise.all(links.map((d) => resolveInCwd(cwd, join8(target.path, d.name))));
25545
+ targets.forEach((hit, i) => {
25546
+ const link = links[i];
25547
+ if (hit !== null && link !== undefined)
25548
+ entries.push(entryFor(link.name, hit.kind));
25549
+ });
25550
+ entries.sort(byKindThenName);
25551
+ const cwdReal = await safeRealpath(cwd) ?? cwd;
25552
+ return {
25553
+ path: relative3(cwdReal, target.path),
25554
+ entries: entries.slice(0, MAX_DIR_ENTRIES),
25555
+ total: entries.length
25556
+ };
25557
+ }
25558
+
25408
25559
  // src/review/decisions.ts
25409
25560
  function createDecisions(log = noopLogger) {
25410
25561
  const pending = new Map;
@@ -25451,12 +25602,12 @@ function createDecisions(log = noopLogger) {
25451
25602
  import { randomBytes } from "crypto";
25452
25603
 
25453
25604
  // src/plan/canonical-file.ts
25454
- import { appendFileSync, existsSync as existsSync4, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
25605
+ import { appendFileSync, existsSync as existsSync4, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
25455
25606
  function guardedPlanFileWrite(planFilePath, log, failMsg, write) {
25456
25607
  try {
25457
25608
  if (!planFilePath.endsWith(".md"))
25458
25609
  return;
25459
- if (!existsSync4(planFilePath) || !statSync3(planFilePath).isFile())
25610
+ if (!existsSync4(planFilePath) || !statSync2(planFilePath).isFile())
25460
25611
  return;
25461
25612
  write(planFilePath);
25462
25613
  } catch (err) {
@@ -25481,7 +25632,7 @@ import { createHash as createHash2 } from "crypto";
25481
25632
  import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync2, renameSync, writeFileSync as writeFileSync3 } from "fs";
25482
25633
  import { mkdtemp, rm } from "fs/promises";
25483
25634
  import { tmpdir } from "os";
25484
- import { dirname as dirname5, join as join8 } from "path";
25635
+ import { dirname as dirname5, join as join9 } from "path";
25485
25636
  var RUMDL_VERSION = "0.2.47";
25486
25637
  var ASSETS = {
25487
25638
  "darwin-arm64": {
@@ -25524,14 +25675,14 @@ async function downloadRumdl(binPath, asset, fetchImpl = fetch) {
25524
25675
  if (actual !== asset.sha256) {
25525
25676
  throw new Error(`rumdl: checksum mismatch for ${asset.url} (expected ${asset.sha256}, got ${actual})`);
25526
25677
  }
25527
- const work = await mkdtemp(join8(tmpdir(), "caret-rumdl-"));
25678
+ const work = await mkdtemp(join9(tmpdir(), "caret-rumdl-"));
25528
25679
  try {
25529
- const archive = join8(work, "rumdl.tar.gz");
25680
+ const archive = join9(work, "rumdl.tar.gz");
25530
25681
  await Bun.write(archive, bytes);
25531
25682
  const tar = Bun.spawn(["tar", "-xzf", archive, "-C", work], { stderr: "pipe" });
25532
25683
  if (await tar.exited !== 0)
25533
25684
  throw new Error(`rumdl: failed to extract ${asset.url}`);
25534
- const extracted = join8(work, "rumdl");
25685
+ const extracted = join9(work, "rumdl");
25535
25686
  if (!existsSync5(extracted))
25536
25687
  throw new Error(`rumdl: binary not found in ${asset.url}`);
25537
25688
  mkdirSync2(dirname5(binPath), { recursive: true });
@@ -25564,8 +25715,9 @@ async function ensureRumdl(install = installPinnedRumdl) {
25564
25715
  const config2 = rumdlConfig();
25565
25716
  writeFileSync3(config2, RUMDL_CONFIG);
25566
25717
  const override = process.env.CARET_RUMDL_BIN?.trim();
25567
- if (override)
25718
+ if (override && await installedVersion(override) === RUMDL_VERSION) {
25568
25719
  return { bin: override, config: config2, installed: false };
25720
+ }
25569
25721
  const bin = rumdlBin();
25570
25722
  if (await installedVersion(bin) === RUMDL_VERSION)
25571
25723
  return { bin, config: config2, installed: false };
@@ -25648,6 +25800,7 @@ async function routeIncomingPlan(input, store, log = noopLogger) {
25648
25800
  await store.update(latest.id, (r) => {
25649
25801
  r.versions.push({ version: version2, plan, annotations: [], createdAt: now });
25650
25802
  r.status = "pending";
25803
+ r.cmux = input.cmux ?? r.cmux;
25651
25804
  r.decision = undefined;
25652
25805
  });
25653
25806
  log.info("review", `review appended: ${shortId(latest.id)} v${version2}`, {
@@ -25674,6 +25827,7 @@ async function routeIncomingPlan(input, store, log = noopLogger) {
25674
25827
  title: input.title?.trim() || deriveTitle(plan),
25675
25828
  status: "pending",
25676
25829
  planEpoch,
25830
+ cmux: input.cmux,
25677
25831
  versions: [{ version: 1, plan, annotations: [], createdAt: now }],
25678
25832
  createdAt: now,
25679
25833
  updatedAt: now
@@ -25760,10 +25914,11 @@ function resolveOptions(opts) {
25760
25914
  approveVariants: opts.approveVariants,
25761
25915
  source: opts.source,
25762
25916
  diagnostics: opts.diagnostics,
25917
+ markPaneRead: opts.markPaneRead ?? ((pane) => markPaneRead(pane, { log: opts.log })),
25763
25918
  log: opts.log ?? noopLogger
25764
25919
  };
25765
25920
  }
25766
- var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire|\/file-refs|\/file)?$/;
25921
+ var ID_ROUTE_RE = /^\/api\/reviews\/([^/]+)(\/decision|\/resolve|\/draft|\/expire|\/seen|\/file-refs|\/file|\/dir)?$/;
25767
25922
  function matchIdRoute(path) {
25768
25923
  const m = path.match(ID_ROUTE_RE);
25769
25924
  if (!m)
@@ -25929,18 +26084,27 @@ function createServer(opts) {
25929
26084
  return notFound();
25930
26085
  const { paths } = await parseBody(req, FileRefsBodySchema);
25931
26086
  const seen = new Set;
25932
- const resolved = [];
25933
- for (const p of paths.slice(0, MAX_FILE_REFS)) {
26087
+ const unique = [];
26088
+ for (const p of paths) {
25934
26089
  if (seen.has(p))
25935
26090
  continue;
25936
26091
  seen.add(p);
25937
- if (resolveFileInCwd(r.cwd, p) !== null)
25938
- resolved.push(p);
26092
+ if (unique.push(p) === MAX_FILE_REFS)
26093
+ break;
25939
26094
  }
25940
- log.debug("request", `file-refs resolved: ${resolved.length}/${seen.size}`, { reviewId: id });
26095
+ const entries = await Promise.all(unique.map(async (p) => {
26096
+ const hit = await resolveInCwd(r.cwd, p);
26097
+ return hit === null ? null : [p, hit.kind];
26098
+ }));
26099
+ const resolved = Object.fromEntries(entries.filter((e) => e !== null));
26100
+ log.debug("request", `file-refs resolved: ${Object.keys(resolved).length}/${unique.length}`, {
26101
+ reviewId: id,
26102
+ requested: paths.length,
26103
+ directories: Object.values(resolved).filter((k) => k === "directory").length
26104
+ });
25941
26105
  return Response.json({ resolved });
25942
26106
  }
25943
- function handleFileExcerpt(req, id) {
26107
+ async function handleFileExcerpt(req, id) {
25944
26108
  const r = store.get(id);
25945
26109
  if (!r)
25946
26110
  return notFound();
@@ -25954,10 +26118,25 @@ function createServer(opts) {
25954
26118
  const start = num("start");
25955
26119
  const end = num("end");
25956
26120
  const range = start !== undefined && end !== undefined ? { start, end } : undefined;
25957
- const excerpt = readFileExcerpt(r.cwd, path, line, range);
26121
+ const excerpt = await readFileExcerpt(r.cwd, path, line, range);
25958
26122
  if (excerpt)
25959
26123
  return Response.json(excerpt);
25960
- return isFileTooLargeToPreview(r.cwd, path) ? tooLarge() : notFound();
26124
+ return await isFileTooLargeToPreview(r.cwd, path) ? tooLarge() : notFound();
26125
+ }
26126
+ async function handleDirListing(req, id) {
26127
+ const r = store.get(id);
26128
+ if (!r)
26129
+ return notFound();
26130
+ const params = new URL(req.url).searchParams;
26131
+ const listing = await listDirectory(r.cwd, params.get("root") ?? "", params.get("path") ?? "");
26132
+ if (listing === null)
26133
+ return notFound();
26134
+ log.debug("request", "dir listed", {
26135
+ reviewId: id,
26136
+ total: listing.total,
26137
+ returned: listing.entries.length
26138
+ });
26139
+ return Response.json(listing);
25961
26140
  }
25962
26141
  async function handleDecision(id) {
25963
26142
  const inMem = store.get(id);
@@ -26015,6 +26194,10 @@ function createServer(opts) {
26015
26194
  await store.update(id, (r) => {
26016
26195
  r.decision = decision;
26017
26196
  r.status = decision.behavior === "allow" ? "approved" : "rejected";
26197
+ const general = r.generalCommentDraft?.trim();
26198
+ if (general && decision.behavior === "deny" && decision.feedback?.includes(general)) {
26199
+ currentVersion(r).generalComment = general;
26200
+ }
26018
26201
  r.generalCommentDraft = "";
26019
26202
  currentVersion(r).composerScratches = [];
26020
26203
  });
@@ -26027,6 +26210,8 @@ function createServer(opts) {
26027
26210
  });
26028
26211
  }
26029
26212
  }
26213
+ if (existing.cmux)
26214
+ cfg.markPaneRead(existing.cmux);
26030
26215
  setTimeout(() => resolveDecision(id, decision), 0);
26031
26216
  log.info("resolve", `review ${shortId(id)} resolved: ${decision.behavior}`, {
26032
26217
  reviewId: id,
@@ -26047,6 +26232,14 @@ function createServer(opts) {
26047
26232
  });
26048
26233
  return Response.json({ ok: true });
26049
26234
  }
26235
+ function handleSeen(id) {
26236
+ const existing = store.get(id);
26237
+ if (!existing)
26238
+ return notFound();
26239
+ if (existing.cmux)
26240
+ cfg.markPaneRead(existing.cmux);
26241
+ return new Response(null, { status: 204 });
26242
+ }
26050
26243
  async function dispatch(req, method, path) {
26051
26244
  if (method === "GET" && path === "/api/health")
26052
26245
  return handleHealth();
@@ -26073,6 +26266,8 @@ function createServer(opts) {
26073
26266
  return handleGetReview(id);
26074
26267
  if (method === "GET" && sub === "/file")
26075
26268
  return handleFileExcerpt(req, id);
26269
+ if (method === "GET" && sub === "/dir")
26270
+ return handleDirListing(req, id);
26076
26271
  if (method === "POST" && sub === "/file-refs")
26077
26272
  return handleFileRefs(req, id);
26078
26273
  if (method === "GET" && sub === "/decision")
@@ -26083,6 +26278,8 @@ function createServer(opts) {
26083
26278
  return handleResolve(req, id);
26084
26279
  if (method === "POST" && sub === "/expire")
26085
26280
  return handleExpire(id);
26281
+ if (method === "POST" && sub === "/seen")
26282
+ return handleSeen(id);
26086
26283
  }
26087
26284
  if (method === "GET") {
26088
26285
  const asset = handleAsset(path);
@@ -26164,8 +26361,8 @@ function createServer(opts) {
26164
26361
  }
26165
26362
 
26166
26363
  // src/review/store.ts
26167
- import { readdir, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
26168
- import { join as join9 } from "path";
26364
+ import { readdir as readdir3, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
26365
+ import { join as join10 } from "path";
26169
26366
  function createStore(dir, log = noopLogger) {
26170
26367
  const reviews = new Map;
26171
26368
  const epochs = new Map;
@@ -26174,7 +26371,7 @@ function createStore(dir, log = noopLogger) {
26174
26371
  const prev = writeChains.get(review.id) ?? Promise.resolve();
26175
26372
  const next = prev.catch(() => {}).then(async () => {
26176
26373
  ensureStateDir(dir);
26177
- await writeFile2(join9(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
26374
+ await writeFile2(join10(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
26178
26375
  mode: 384
26179
26376
  });
26180
26377
  log.debug("store", `review persisted: ${shortId(review.id)}`, { reviewId: review.id });
@@ -26224,7 +26421,7 @@ function createStore(dir, log = noopLogger) {
26224
26421
  return review;
26225
26422
  },
26226
26423
  async persisted(id) {
26227
- return await readJsonFile(join9(dir, `${id}.json`)) ?? undefined;
26424
+ return await readJsonFile(join10(dir, `${id}.json`)) ?? undefined;
26228
26425
  },
26229
26426
  size() {
26230
26427
  return reviews.size;
@@ -26241,7 +26438,7 @@ function createStore(dir, log = noopLogger) {
26241
26438
  async rehydrate() {
26242
26439
  let files;
26243
26440
  try {
26244
- files = await readdir(dir);
26441
+ files = await readdir3(dir);
26245
26442
  } catch {
26246
26443
  log.debug("store", "no reviews dir; nothing to rehydrate");
26247
26444
  return;
@@ -26251,7 +26448,7 @@ function createStore(dir, log = noopLogger) {
26251
26448
  if (!file2.endsWith(".json"))
26252
26449
  continue;
26253
26450
  try {
26254
- const review = JSON.parse(await readFile3(join9(dir, file2), "utf-8"));
26451
+ const review = JSON.parse(await readFile3(join10(dir, file2), "utf-8"));
26255
26452
  if (isUnresolved(review.status)) {
26256
26453
  reviews.set(review.id, review);
26257
26454
  loaded++;
@@ -26350,8 +26547,8 @@ import { existsSync as existsSync7 } from "fs";
26350
26547
  import { release } from "os";
26351
26548
 
26352
26549
  // src/discovery.ts
26353
- import { readdirSync as readdirSync4, statSync as statSync4 } from "fs";
26354
- import { basename as basename2, join as join10 } from "path";
26550
+ import { readdirSync as readdirSync3, statSync as statSync3 } from "fs";
26551
+ import { basename as basename2, join as join11 } from "path";
26355
26552
  var CARET_BIN = "caret-native";
26356
26553
  async function safe(build) {
26357
26554
  try {
@@ -26551,7 +26748,7 @@ function listReviewFiles() {
26551
26748
  const dir = reviewsDir();
26552
26749
  let files;
26553
26750
  try {
26554
- files = readdirSync4(dir);
26751
+ files = readdirSync3(dir);
26555
26752
  } catch {
26556
26753
  return [];
26557
26754
  }
@@ -26561,7 +26758,7 @@ function listReviewFiles() {
26561
26758
  continue;
26562
26759
  if (out.length >= 5000)
26563
26760
  break;
26564
- const raw = readJsonFileSync(join10(dir, file2));
26761
+ const raw = readJsonFileSync(join11(dir, file2));
26565
26762
  if (raw && typeof raw.id === "string" && typeof raw.status === "string") {
26566
26763
  out.push({ id: raw.id, status: raw.status });
26567
26764
  }
@@ -26572,7 +26769,7 @@ var TAIL_BYTES = 256 * 1024;
26572
26769
  async function logStats(path) {
26573
26770
  let size;
26574
26771
  try {
26575
- size = statSync4(path).size;
26772
+ size = statSync3(path).size;
26576
26773
  } catch {
26577
26774
  return { path, exists: false, size: 0, errors: 0, warns: 0 };
26578
26775
  }
@@ -26665,33 +26862,33 @@ import {
26665
26862
  existsSync as existsSync9,
26666
26863
  mkdirSync as mkdirSync3,
26667
26864
  rmSync as rmSync2,
26668
- statSync as statSync5,
26865
+ statSync as statSync4,
26669
26866
  symlinkSync,
26670
26867
  writeFileSync as writeFileSync5
26671
26868
  } from "fs";
26672
- import { join as join12, resolve as resolve2 } from "path";
26869
+ import { join as join13, resolve as resolve2 } from "path";
26673
26870
 
26674
26871
  // src/adapters/opencode/packaging.ts
26675
- import { existsSync as existsSync8, readdirSync as readdirSync5, readFileSync as readFileSync8 } from "fs";
26676
- import { dirname as dirname7, join as join11 } from "path";
26872
+ import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync8 } from "fs";
26873
+ import { dirname as dirname7, join as join12 } from "path";
26677
26874
  function resolveCaretRoot() {
26678
26875
  const candidates = [];
26679
26876
  const script = process.argv[1];
26680
26877
  if (script)
26681
- candidates.push(join11(dirname7(script), ".."));
26682
- candidates.push(join11(dirname7(process.execPath), ".."));
26878
+ candidates.push(join12(dirname7(script), ".."));
26879
+ candidates.push(join12(dirname7(process.execPath), ".."));
26683
26880
  for (const root of candidates) {
26684
- if (existsSync8(join11(root, "opencode", "caret.plugin.ts")))
26881
+ if (existsSync8(join12(root, "opencode", "caret.plugin.ts")))
26685
26882
  return root;
26686
26883
  }
26687
26884
  throw new Error(`caret OpenCode packaging not found (no opencode/caret.plugin.ts under: ${candidates.join(", ") || "(none)"}). The OpenCode plugin source must ship with caret.`);
26688
26885
  }
26689
26886
  function loadOpencodePackaging(root = resolveCaretRoot()) {
26690
- const binPath = join11(root, "bin", "caret");
26691
- const commandsDir = join11(root, "opencode", "commands");
26887
+ const binPath = join12(root, "bin", "caret");
26888
+ const commandsDir = join12(root, "opencode", "commands");
26692
26889
  let commands = [];
26693
26890
  try {
26694
- commands = readdirSync5(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync8(join11(commandsDir, f), "utf-8") }));
26891
+ commands = readdirSync4(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync8(join12(commandsDir, f), "utf-8") }));
26695
26892
  } catch {
26696
26893
  commands = [];
26697
26894
  }
@@ -26700,17 +26897,17 @@ function loadOpencodePackaging(root = resolveCaretRoot()) {
26700
26897
 
26701
26898
  // src/commands/install/local.ts
26702
26899
  function devMarketplaceDir() {
26703
- return join12(stateDir(), "dev-marketplace");
26900
+ return join13(stateDir(), "dev-marketplace");
26704
26901
  }
26705
26902
  function resolveLocalCheckout(opts = {}, deps = {}) {
26706
26903
  const repoDir = resolve2(caretRoot(deps));
26707
- if (!existsSync9(join12(repoDir, ".claude-plugin", "marketplace.json"))) {
26904
+ if (!existsSync9(join13(repoDir, ".claude-plugin", "marketplace.json"))) {
26708
26905
  throw new Error(`--from-local must run from a caret checkout (no .claude-plugin/marketplace.json at ${repoDir}). A published caret installs with a plain \`caret install\`.`);
26709
26906
  }
26710
26907
  if (opts.requireArtifacts !== false) {
26711
26908
  const missing = [
26712
- isExecutable(join12(repoDir, "bin", "caret-native")) ? null : "bin/caret-native",
26713
- isDirectory(join12(repoDir, "bin", "ui")) ? null : "bin/ui"
26909
+ isExecutable(join13(repoDir, "bin", "caret-native")) ? null : "bin/caret-native",
26910
+ isDirectory(join13(repoDir, "bin", "ui")) ? null : "bin/ui"
26714
26911
  ].filter((m) => m !== null);
26715
26912
  if (missing.length > 0) {
26716
26913
  throw new Error(`--from-local reuses the build artifacts bin/caret-native + bin/ui, and ${repoDir} is missing ${missing.join(" + ")} \u2014 run \`mise run build\` first.`);
@@ -26729,9 +26926,9 @@ function caretRoot(deps) {
26729
26926
  }
26730
26927
  function writeDevMarketplace(repoDir, outDir) {
26731
26928
  rmSync2(outDir, { recursive: true, force: true });
26732
- mkdirSync3(join12(outDir, ".claude-plugin"), { recursive: true });
26733
- symlinkSync(resolve2(repoDir), join12(outDir, "caret"));
26734
- writeFileSync5(join12(outDir, ".claude-plugin", "marketplace.json"), `${JSON.stringify({
26929
+ mkdirSync3(join13(outDir, ".claude-plugin"), { recursive: true });
26930
+ symlinkSync(resolve2(repoDir), join13(outDir, "caret"));
26931
+ writeFileSync5(join13(outDir, ".claude-plugin", "marketplace.json"), `${JSON.stringify({
26735
26932
  name: "caret",
26736
26933
  description: "Local caret dev build (generated by caret install --from-local).",
26737
26934
  owner: { name: "macintacos" },
@@ -26742,20 +26939,20 @@ function writeDevMarketplace(repoDir, outDir) {
26742
26939
  async function prewarmLocalBuild(repoDir, deps = {}) {
26743
26940
  const spawn = deps.spawn ?? runCaptured;
26744
26941
  try {
26745
- await spawn([join12(repoDir, "bin", "caret-native"), "prewarm"]);
26942
+ await spawn([join13(repoDir, "bin", "caret-native"), "prewarm"]);
26746
26943
  } catch {}
26747
26944
  }
26748
26945
  function isExecutable(path) {
26749
26946
  try {
26750
26947
  accessSync(path, constants.X_OK);
26751
- return statSync5(path).isFile();
26948
+ return statSync4(path).isFile();
26752
26949
  } catch {
26753
26950
  return false;
26754
26951
  }
26755
26952
  }
26756
26953
  function isDirectory(path) {
26757
26954
  try {
26758
- return statSync5(path).isDirectory();
26955
+ return statSync4(path).isDirectory();
26759
26956
  } catch {
26760
26957
  return false;
26761
26958
  }
@@ -27017,7 +27214,7 @@ async function runInstallClaudeTarget(opts, deps = {}) {
27017
27214
 
27018
27215
  // src/commands/install/opencode.ts
27019
27216
  import { existsSync as existsSync13, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
27020
- import { basename as basename3, join as join14 } from "path";
27217
+ import { basename as basename3, join as join15 } from "path";
27021
27218
 
27022
27219
  // src/adapters/opencode/deploy.ts
27023
27220
  import { existsSync as existsSync11, mkdirSync as mkdirSync4, rmSync as rmSync3, writeFileSync as writeFileSync6 } from "fs";
@@ -27120,7 +27317,7 @@ async function promptUpgrade(verdict, clack) {
27120
27317
 
27121
27318
  // src/commands/install/opencode.ts
27122
27319
  function isCaretCheckout(dir) {
27123
- return existsSync13(join14(dir, "opencode", "caret.plugin.ts"));
27320
+ return existsSync13(join15(dir, "opencode", "caret.plugin.ts"));
27124
27321
  }
27125
27322
  function caretEntries(text2, isCheckout) {
27126
27323
  return pluginEntries(text2).filter((entry) => {
@@ -27146,7 +27343,7 @@ async function runInstallOpencodeTarget(opts, deps = {}) {
27146
27343
  const ui = deps.ui ?? silentUI;
27147
27344
  const isCheckout = deps.isCheckout ?? isCaretCheckout;
27148
27345
  const configFile2 = resolveConfigFile(dir);
27149
- const commandPaths = pkg.commands.map((c3) => join14(commandDir(dir), namespacedCommandFilename(c3.name)));
27346
+ const commandPaths = pkg.commands.map((c3) => join15(commandDir(dir), namespacedCommandFilename(c3.name)));
27150
27347
  const legacy = existingLegacyInstallFiles(dir);
27151
27348
  const specifier = opts.local ? localPluginSpecifier(opts.local.repoDir) : CARET_PACKAGE;
27152
27349
  if (opts.dryRun) {
@@ -27169,7 +27366,7 @@ async function runInstallOpencodeTarget(opts, deps = {}) {
27169
27366
  await upgradeStep(configFile2, opts, deps, ui);
27170
27367
  await sweepLegacy(legacy, dir, ui);
27171
27368
  const files = pkg.commands.map((c3) => ({
27172
- path: join14(commandDir(dir), namespacedCommandFilename(c3.name)),
27369
+ path: join15(commandDir(dir), namespacedCommandFilename(c3.name)),
27173
27370
  contents: renderPlugin(c3.contents, { version: VERSION, binPath: pkg.binPath })
27174
27371
  }));
27175
27372
  await ui.step("Deploying the /caret:* command files", async () => deployFiles(files, { dryRun: false }), (deployed) => `Deployed ${deployed.paths.length} command file(s) to ${dir}`);
@@ -28839,7 +29036,10 @@ async function runReview(stdin2, deps) {
28839
29036
  step = "ensureDaemon";
28840
29037
  baseUrl = await deps.ensureDaemon();
28841
29038
  step = "postReview";
28842
- const { id, hasLiveClient } = await deps.postReview(baseUrl, input);
29039
+ const { id, hasLiveClient } = await deps.postReview(baseUrl, {
29040
+ ...input,
29041
+ cmux: deps.readPane?.()
29042
+ });
28843
29043
  ctx.reviewId = id;
28844
29044
  deps.onPosted?.(baseUrl, id);
28845
29045
  logDebug("review", `review created: ${shortId(id)}`, { ...ctx });
@@ -28906,6 +29106,7 @@ function prodReviewDeps(s, adapter) {
28906
29106
  postReview,
28907
29107
  longPoll,
28908
29108
  openBrowser,
29109
+ readPane: readCmuxPane,
28909
29110
  timeoutMs: reviewTimeoutMs(s),
28910
29111
  expire: expireReview
28911
29112
  };