zidane 8.2.2 → 8.2.4

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 (168) hide show
  1. package/dist/{acp-oYRjlh71.js → acp-BIInKhVI.js} +6 -6
  2. package/dist/{acp-oYRjlh71.js.map → acp-BIInKhVI.js.map} +1 -1
  3. package/dist/acp-cli.js +4 -4
  4. package/dist/acp.d.ts +2 -2
  5. package/dist/acp.js +1 -1
  6. package/dist/{agent-CBQglXtH.js → agent-CmhCOmyO.js} +10 -5
  7. package/dist/{agent-CBQglXtH.js.map → agent-CmhCOmyO.js.map} +1 -1
  8. package/dist/agent.d.ts +1 -1
  9. package/dist/agent.js +1 -1
  10. package/dist/chat/pure.d.ts +4 -4
  11. package/dist/chat/pure.js +7 -7
  12. package/dist/chat.d.ts +3 -3
  13. package/dist/chat.js +16 -16
  14. package/dist/{cli-shared-E5m_pI8A.js → cli-shared-GOehTeqk.js} +2 -2
  15. package/dist/{cli-shared-E5m_pI8A.js.map → cli-shared-GOehTeqk.js.map} +1 -1
  16. package/dist/{client-core-BdD20sOv.d.ts → client-core-Dz8hELs4.d.ts} +15 -5
  17. package/dist/client-core-Dz8hELs4.d.ts.map +1 -0
  18. package/dist/codemode-worker.js +1 -1
  19. package/dist/codemode.js +1 -1
  20. package/dist/{connection-loop-DTk82YVl.d.ts → connection-loop-dm_TN-QD.d.ts} +3 -3
  21. package/dist/{connection-loop-DTk82YVl.d.ts.map → connection-loop-dm_TN-QD.d.ts.map} +1 -1
  22. package/dist/contexts/daytona.d.ts +1 -1
  23. package/dist/contexts/e2b.d.ts +1 -1
  24. package/dist/{credentials-B9DfBJP9.js → credentials-DxOQmset.js} +2 -2
  25. package/dist/{credentials-B9DfBJP9.js.map → credentials-DxOQmset.js.map} +1 -1
  26. package/dist/daemon-cli.js +2 -2
  27. package/dist/{dispatcher-BICXFGiJ.js → dispatcher-Dclv6HXn.js} +22 -18
  28. package/dist/dispatcher-Dclv6HXn.js.map +1 -0
  29. package/dist/{engine-QCX3GV-i.js → engine-C_U6kDfA.js} +6 -6
  30. package/dist/{engine-QCX3GV-i.js.map → engine-C_U6kDfA.js.map} +1 -1
  31. package/dist/{eval-CfQlBR81.js → eval-CIHIF_Ns.js} +3 -3
  32. package/dist/{eval-CfQlBR81.js.map → eval-CIHIF_Ns.js.map} +1 -1
  33. package/dist/eval.d.ts +1 -1
  34. package/dist/eval.js +1 -1
  35. package/dist/{executor-B8GoXaaE.js → executor-BFaR2nfc.js} +3 -3
  36. package/dist/{executor-B8GoXaaE.js.map → executor-BFaR2nfc.js.map} +1 -1
  37. package/dist/{extensions-BeH3D-bq.js → extensions-DIbpUj5R.js} +3 -3
  38. package/dist/{extensions-BeH3D-bq.js.map → extensions-DIbpUj5R.js.map} +1 -1
  39. package/dist/extensions.d.ts +1 -1
  40. package/dist/extensions.js +1 -1
  41. package/dist/{fold-rules-DSypi36k.d.ts → fold-rules-CMpWNPqk.d.ts} +2 -2
  42. package/dist/{fold-rules-DSypi36k.d.ts.map → fold-rules-CMpWNPqk.d.ts.map} +1 -1
  43. package/dist/{headless-R4pbpTkm.js → headless-Bs9Lb4vs.js} +4 -4
  44. package/dist/{headless-R4pbpTkm.js.map → headless-Bs9Lb4vs.js.map} +1 -1
  45. package/dist/headless.d.ts +1 -1
  46. package/dist/headless.js +1 -1
  47. package/dist/history-worker.js +1 -1
  48. package/dist/{host-scoped-settings-CCnaBkCV.js → host-scoped-settings-DxxI_yRo.js} +2 -2
  49. package/dist/{host-scoped-settings-CCnaBkCV.js.map → host-scoped-settings-DxxI_yRo.js.map} +1 -1
  50. package/dist/{host-signals-DI9JQvR9.js → host-signals-CQ2jZR-2.js} +5 -5
  51. package/dist/{host-signals-DI9JQvR9.js.map → host-signals-CQ2jZR-2.js.map} +1 -1
  52. package/dist/{index-DyQyDLX1.d.ts → index-4mDRyTGJ.d.ts} +1722 -1691
  53. package/dist/index-4mDRyTGJ.d.ts.map +1 -0
  54. package/dist/{index-lcuvhQfF.d.ts → index-Dlmu8HnG.d.ts} +5 -5
  55. package/dist/{index-lcuvhQfF.d.ts.map → index-Dlmu8HnG.d.ts.map} +1 -1
  56. package/dist/{index-BkNeiJYI.d.ts → index-QsAN9yjZ.d.ts} +3 -3
  57. package/dist/{index-BkNeiJYI.d.ts.map → index-QsAN9yjZ.d.ts.map} +1 -1
  58. package/dist/index.d.ts +3 -3
  59. package/dist/index.js +8 -8
  60. package/dist/ipc/remote.d.ts +5 -5
  61. package/dist/ipc/remote.js +3 -3
  62. package/dist/ipc.d.ts +5 -5
  63. package/dist/ipc.js +9 -9
  64. package/dist/{links-CN5JFFde.js → links-D5itfH8P.js} +10 -10
  65. package/dist/{links-CN5JFFde.js.map → links-D5itfH8P.js.map} +1 -1
  66. package/dist/{mcp-allowlist-BQ5MvhHi.js → mcp-allowlist-BbnTBKNN.js} +15 -4
  67. package/dist/mcp-allowlist-BbnTBKNN.js.map +1 -0
  68. package/dist/mcp.d.ts +1 -1
  69. package/dist/{oauth-TVPPye84.js → oauth-Ck3wQX4Q.js} +133 -43
  70. package/dist/oauth-Ck3wQX4Q.js.map +1 -0
  71. package/dist/output/stream-json.d.ts +1 -1
  72. package/dist/output/terminal.d.ts +1 -1
  73. package/dist/plugins.d.ts +1 -1
  74. package/dist/{prelude-HzY_JEGq.js → prelude-qjPUkw4f.js} +26 -2
  75. package/dist/prelude-qjPUkw4f.js.map +1 -0
  76. package/dist/{presets-DXeHsd24.js → presets-C4hkykN-.js} +2 -2
  77. package/dist/{presets-DXeHsd24.js.map → presets-C4hkykN-.js.map} +1 -1
  78. package/dist/presets.d.ts +1 -1
  79. package/dist/presets.js +1 -1
  80. package/dist/{protocol-BIv1Yuvl.js → protocol-Do_i6Ras.js} +3 -1
  81. package/dist/protocol-Do_i6Ras.js.map +1 -0
  82. package/dist/providers/anthropic.d.ts +1 -1
  83. package/dist/providers/arcee.d.ts +1 -1
  84. package/dist/providers/baseten.d.ts +1 -1
  85. package/dist/providers/cerebras.d.ts +1 -1
  86. package/dist/providers/google.d.ts +1 -1
  87. package/dist/providers/local.d.ts +1 -1
  88. package/dist/providers/neuralwatt.d.ts +1 -1
  89. package/dist/providers/openai-compat.d.ts +1 -1
  90. package/dist/providers/openai.d.ts +1 -1
  91. package/dist/providers/openrouter.d.ts +1 -1
  92. package/dist/providers/xai.d.ts +1 -1
  93. package/dist/providers.d.ts +1 -1
  94. package/dist/{registry-PNi9rsGi.js → registry-DxPP_tAt.js} +3 -3
  95. package/dist/{registry-PNi9rsGi.js.map → registry-DxPP_tAt.js.map} +1 -1
  96. package/dist/{remote-hosts-b6Luwdhu.js → remote-hosts-BkOwRszu.js} +4 -4
  97. package/dist/{remote-hosts-b6Luwdhu.js.map → remote-hosts-BkOwRszu.js.map} +1 -1
  98. package/dist/restate.d.ts +1 -1
  99. package/dist/{secret-shapes-CEoGmfEE.js → secret-shapes-HvaYVzwc.js} +17 -4
  100. package/dist/secret-shapes-HvaYVzwc.js.map +1 -0
  101. package/dist/session/sqlite.d.ts +1 -1
  102. package/dist/{session-7TUHs42_.js → session-CCsKXID6.js} +6 -2
  103. package/dist/session-CCsKXID6.js.map +1 -0
  104. package/dist/{session-factory-CMwIXE-7.js → session-factory-Ct7J9CK1.js} +6 -6
  105. package/dist/{session-factory-CMwIXE-7.js.map → session-factory-Ct7J9CK1.js.map} +1 -1
  106. package/dist/session.d.ts +2 -2
  107. package/dist/session.js +1 -1
  108. package/dist/{settings-keys-DssjmqAV.js → settings-keys-QoGJ5n3e.js} +4 -4
  109. package/dist/{settings-keys-DssjmqAV.js.map → settings-keys-QoGJ5n3e.js.map} +1 -1
  110. package/dist/{shell-path-BUFwqU_A.js → shell-path-BXXtRmEA.js} +4 -4
  111. package/dist/{shell-path-BUFwqU_A.js.map → shell-path-BXXtRmEA.js.map} +1 -1
  112. package/dist/skills.d.ts +1 -1
  113. package/dist/{spaces-CyjMh5Zf.js → spaces-C_x2bvEl.js} +14 -4
  114. package/dist/spaces-C_x2bvEl.js.map +1 -0
  115. package/dist/{store-L01W0gGI.js → store-BpkWIaSH.js} +10 -30
  116. package/dist/store-BpkWIaSH.js.map +1 -0
  117. package/dist/{todo-edits-H77ROt6X.js → todo-edits-DKoxu7Ss.js} +3 -3
  118. package/dist/{todo-edits-H77ROt6X.js.map → todo-edits-DKoxu7Ss.js.map} +1 -1
  119. package/dist/tools/fetch-url.d.ts +1 -1
  120. package/dist/tools/web-search.d.ts +1 -1
  121. package/dist/{tools-pZVFIJ_K.js → tools-FHEIKGkh.js} +2 -2
  122. package/dist/{tools-pZVFIJ_K.js.map → tools-FHEIKGkh.js.map} +1 -1
  123. package/dist/tools.d.ts +1 -1
  124. package/dist/tools.js +2 -2
  125. package/dist/{transcript-anchors-s-yxh0jt.js → transcript-anchors-D8jWLpe5.js} +10 -10
  126. package/dist/{transcript-anchors-s-yxh0jt.js.map → transcript-anchors-D8jWLpe5.js.map} +1 -1
  127. package/dist/{transcript-cache-DXdgVuoy.js → transcript-cache-CdHKtx9y.js} +4 -4
  128. package/dist/{transcript-cache-DXdgVuoy.js.map → transcript-cache-CdHKtx9y.js.map} +1 -1
  129. package/dist/{transcript-reconcile-DeFLIQ14.js → transcript-reconcile-D_SWw6dX.js} +11 -3
  130. package/dist/transcript-reconcile-D_SWw6dX.js.map +1 -0
  131. package/dist/tui.d.ts +7 -7
  132. package/dist/tui.js +24 -23
  133. package/dist/tui.js.map +1 -1
  134. package/dist/{turn-operations-BbGJ-KSt.d.ts → turn-operations-Dbk3xPKu.d.ts} +19 -4
  135. package/dist/turn-operations-Dbk3xPKu.d.ts.map +1 -0
  136. package/dist/{types-C0PJmBaO.d.ts → types-CMXR7A0b.d.ts} +2 -2
  137. package/dist/{types-C0PJmBaO.d.ts.map → types-CMXR7A0b.d.ts.map} +1 -1
  138. package/dist/types-CzmghX7C.js.map +1 -1
  139. package/dist/types.d.ts +2 -2
  140. package/dist/{url-CLl0u4Z8.js → url-D4vG7lzO.js} +2 -2
  141. package/dist/{url-CLl0u4Z8.js.map → url-D4vG7lzO.js.map} +1 -1
  142. package/dist/{version-v77Y4A3C.js → version-DDRBX7K6.js} +2 -2
  143. package/dist/{version-v77Y4A3C.js.map → version-DDRBX7K6.js.map} +1 -1
  144. package/dist/{workspace-exec-stream-kqARO8iC.js → workspace-exec-stream-iHv9cooF.js} +5 -5
  145. package/dist/{workspace-exec-stream-kqARO8iC.js.map → workspace-exec-stream-iHv9cooF.js.map} +1 -1
  146. package/dist/{write-file-QYrcDdl3.js → write-file-ChtRPdEL.js} +3 -3
  147. package/dist/{write-file-QYrcDdl3.js.map → write-file-ChtRPdEL.js.map} +1 -1
  148. package/dist/{ws-BD9FN98u.js → ws-B2HB87nz.js} +6 -6
  149. package/dist/{ws-BD9FN98u.js.map → ws-B2HB87nz.js.map} +1 -1
  150. package/dist/{ws-client-D7eb2UbH.js → ws-client-DPC9ZJHr.js} +4 -4
  151. package/dist/{ws-client-D7eb2UbH.js.map → ws-client-DPC9ZJHr.js.map} +1 -1
  152. package/docs/CODEMODE.md +6 -0
  153. package/docs/IPC.md +2 -0
  154. package/docs/ONBOARDING.md +1 -1
  155. package/package.json +1 -1
  156. package/dist/client-core-BdD20sOv.d.ts.map +0 -1
  157. package/dist/dispatcher-BICXFGiJ.js.map +0 -1
  158. package/dist/index-DyQyDLX1.d.ts.map +0 -1
  159. package/dist/mcp-allowlist-BQ5MvhHi.js.map +0 -1
  160. package/dist/oauth-TVPPye84.js.map +0 -1
  161. package/dist/prelude-HzY_JEGq.js.map +0 -1
  162. package/dist/protocol-BIv1Yuvl.js.map +0 -1
  163. package/dist/secret-shapes-CEoGmfEE.js.map +0 -1
  164. package/dist/session-7TUHs42_.js.map +0 -1
  165. package/dist/spaces-CyjMh5Zf.js.map +0 -1
  166. package/dist/store-L01W0gGI.js.map +0 -1
  167. package/dist/transcript-reconcile-DeFLIQ14.js.map +0 -1
  168. package/dist/turn-operations-BbGJ-KSt.d.ts.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"host-scoped-settings-CCnaBkCV.js","names":[],"sources":["../src/chat/shell-parse.ts","../src/chat/safe-mode.ts","../src/chat/foreign-configs.ts","../src/chat/ruler-configs.ts","../src/chat/mcps-discovery.ts","../src/chat/system-md.ts","../src/chat/host-scoped-settings.ts"],"sourcesContent":["/**\n * Lightweight shell command parser for safelist enforcement.\n *\n * Extracts the head token (program name) of every command in a bash\n * command string — including chains (`&&`, `||`, `;`), pipes (`|`),\n * subshells (`(…)`), and command substitutions (`$(…)` and backticks).\n *\n * Handles quoting (`'…'`, `\"…\"`, `\\x`), variable assignments\n * (`NAME=val`), I/O redirection, here-documents (`<<`, `<<-`), `#`\n * comments, and the `!` negation prefix.\n *\n * Returns `null` when parsing fails so callers can fall back to\n * prompting (safe default).\n */\n\n/**\n * The head token (program name) of every command in `command`, in source\n * order, including those inside pipes, subshells, substitutions and expanding\n * here-document bodies. Returns `[]` for an empty or assignment-only command\n * and `null` when the parser gives up.\n *\n * @param command - The bash command string to parse.\n */\nexport function extractCommandHeads(command: string): string[] | null {\n try {\n const p = new ShellParser(command)\n p.list()\n return p.heads\n }\n catch {\n return null\n }\n}\n\n// Characters that unconditionally end an unquoted word.\nconst META = new Set(' \\t\\n\\r;|&()<>'.split(''))\n\ninterface PendingHeredoc {\n delim: string\n stripTabs: boolean\n /** Any quoting in the delimiter word disables expansion in the body. */\n quoted: boolean\n}\n\nclass ShellParser {\n readonly heads: string[] = []\n private i = 0\n /** Heredocs opened on the current line, bodies not yet consumed. */\n private pendingHeredocs: PendingHeredoc[] = []\n\n constructor(private readonly s: string) {}\n\n // -- public entry point ------------------------------------------------\n\n /** Command list — pipelines separated by `;`, `&&`, `||`, `\\n`, `&`. */\n list(closer?: string): void {\n for (;;) {\n this.ws()\n if (this.end(closer))\n return\n\n this.pipeline(closer)\n\n this.ws()\n if (this.end(closer))\n return\n const c = this.c()\n if (c === ';') { this.i++; continue }\n if (c === '\\n' || c === '\\r') { this.newline(); continue }\n if (this.is('&&') || this.is('||')) { this.i += 2; continue }\n if (c === '&') { this.i++; continue }\n break\n }\n }\n\n // -- grammar -----------------------------------------------------------\n\n /** Pipeline — simple commands separated by `|` (not `||`). */\n private pipeline(closer?: string): void {\n this.cmd(closer)\n for (;;) {\n this.ws()\n if (this.c() === '|' && !this.is('||')) { this.i++; this.cmd(closer) }\n else {\n break\n }\n }\n }\n\n /** Simple command — extract head, skip remaining arguments. */\n private cmd(closer?: string): void {\n this.ws()\n if (this.i >= this.s.length)\n return\n if (closer && this.c() === closer)\n return\n\n // Subshell.\n if (this.c() === '(') {\n this.i++\n this.list(')')\n return\n }\n\n // `!` negation prefix — skip when followed by whitespace.\n if (this.c() === '!' && this.i + 1 < this.s.length\n && (this.s[this.i + 1] === ' ' || this.s[this.i + 1] === '\\t')) {\n this.i++; this.ws()\n }\n\n // Read tokens, skipping leading variable assignments (NAME=val).\n while (this.i < this.s.length && !this.sep(closer)) {\n if (this.c() === '(') {\n this.i++\n this.list(')')\n break\n }\n const w = this.word()\n if (!w)\n break\n if (isAssignment(w)) { this.ws(); continue }\n this.heads.push(w)\n break\n }\n\n this.tail(closer)\n }\n\n // -- word-level --------------------------------------------------------\n\n /** Read one shell word, handling quoting and substitutions. */\n private word(): string {\n let out = ''\n while (this.i < this.s.length) {\n const c = this.c()\n if (META.has(c))\n break\n\n if (c === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (c === '\\'') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] !== '\\'') out += this.s[this.i++]\n if (this.i < this.s.length)\n this.i++\n continue\n }\n if (c === '\"') { out += this.dquote(); continue }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (c === '`') { this.btick(); continue }\n out += c; this.i++\n }\n return out\n }\n\n /** Double-quoted string — returns literal chars, recurses into substitutions. */\n private dquote(): string {\n this.i++\n let out = ''\n while (this.i < this.s.length && this.s[this.i] !== '\"') {\n if (this.s[this.i] === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (this.s[this.i] === '`') { this.btick(); continue }\n out += this.s[this.i++]\n }\n if (this.i < this.s.length)\n this.i++\n return out\n }\n\n /** Backtick substitution — creates a sub-parser for the inner content. */\n private btick(): void {\n this.i++\n const close = this.s.indexOf('`', this.i)\n const inner = close < 0 ? this.s.slice(this.i) : this.s.slice(this.i, close)\n const sub = new ShellParser(inner)\n sub.list()\n this.heads.push(...sub.heads)\n this.i = close < 0 ? this.s.length : close + 1\n }\n\n // -- tail / redirections -----------------------------------------------\n\n /** Skip remaining arguments and redirections until the next command boundary. */\n private tail(closer?: string): void {\n while (this.i < this.s.length) {\n this.ws()\n if (this.i >= this.s.length || this.sep(closer))\n break\n if (this.redir())\n continue\n if (this.c() === '(') {\n this.i++\n this.list(')')\n continue\n }\n const w = this.word()\n if (!w)\n break\n }\n }\n\n /** Try to consume an I/O redirection operator + target. Returns true if consumed. */\n private redir(): boolean {\n const c = this.c()\n let yes = c === '<' || c === '>'\n if (!yes && c >= '0' && c <= '9' && this.i + 1 < this.s.length) {\n const n = this.s[this.i + 1]\n yes = n === '<' || n === '>'\n }\n if (!yes)\n return false\n\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n if (this.c() === '>') {\n this.i++\n if (this.c() === '>')\n this.i++\n if (this.c() === '&') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n return true\n }\n }\n else if (this.c() === '<') {\n this.i++\n if (this.c() === '<') {\n this.i++\n // `<<<` is a here-string: the operand is an ordinary word, not a\n // delimiter, so fall through to the generic target read below.\n if (this.c() === '<') {\n this.i++\n }\n else {\n let stripTabs = false\n if (this.c() === '-') { this.i++; stripTabs = true }\n this.ws()\n // `word()` strips the quotes of `<<'EOF'` / `<<\"EOF\"`; the\n // terminator line is the bare delimiter either way.\n const start = this.i\n const delim = this.word()\n const quoted = /['\"\\\\]/.test(this.s.slice(start, this.i))\n if (delim)\n this.pendingHeredocs.push({ delim, stripTabs, quoted })\n return true\n }\n }\n }\n this.ws()\n this.word()\n return true\n }\n\n /** Consume one newline (`\\r\\n` counts once) plus any heredoc bodies it opens. */\n private newline(): void {\n this.i += this.is('\\r\\n') ? 2 : 1\n this.heredocs()\n }\n\n /**\n * Skip the bodies of heredocs opened on the line just ended, in the order\n * they were introduced. Without this the body is tokenized as shell code\n * and each line's first word lands in `heads` — so a command that embeds a\n * script (`python3 - <<'PY' … PY`) yields a different head set on every\n * call and can never be safelisted.\n *\n * An unquoted delimiter (`<<EOF`) still expands `$(…)` and backticks in\n * the body, so those substitutions are parsed for heads.\n */\n private heredocs(): void {\n if (this.pendingHeredocs.length === 0)\n return\n const pending = this.pendingHeredocs\n this.pendingHeredocs = []\n for (const { delim, stripTabs, quoted } of pending) {\n const body: string[] = []\n while (this.i < this.s.length) {\n const nl = this.s.indexOf('\\n', this.i)\n const raw = nl < 0 ? this.s.slice(this.i) : this.s.slice(this.i, nl)\n this.i = nl < 0 ? this.s.length : nl + 1\n const line = raw.endsWith('\\r') ? raw.slice(0, -1) : raw\n if ((stripTabs ? line.replace(/^\\t+/, '') : line) === delim)\n break\n body.push(line)\n }\n if (!quoted)\n this.bodySubstitutions(body.join('\\n'))\n }\n }\n\n /** Collect heads of `$(…)` and backtick substitutions in an expanding heredoc body. */\n private bodySubstitutions(body: string): void {\n let k = 0\n while (k < body.length) {\n const c = body[k]\n if (c === '\\\\') { k += 2; continue }\n if (c === '$' && body[k + 1] === '(') {\n const sub = new ShellParser(body.slice(k + 2))\n sub.list(')')\n this.heads.push(...sub.heads)\n k += 2 + sub.i\n continue\n }\n if (c === '`') {\n const sub = new ShellParser(body.slice(k))\n sub.btick()\n this.heads.push(...sub.heads)\n k += sub.i\n continue\n }\n k++\n }\n }\n\n // -- predicates --------------------------------------------------------\n\n /** True at a command boundary (`;`, `\\n`, `&`, `|`, `)`, or closer). */\n private sep(closer?: string): boolean {\n const c = this.c()\n if (c === ';' || c === '\\n' || c === '\\r' || c === '&' || c === ')')\n return true\n if (c === '|')\n return true\n if (closer && c === closer)\n return true\n return false\n }\n\n /**\n * True when past end or at closer (closer is consumed).\n *\n * Throws when a group closes with a heredoc still pending: the parser has\n * no notion of arithmetic, so `$((x<<1))` / `(( x << 1 ))` reads the shift\n * as a heredoc whose body would swallow (and hide) the following lines.\n */\n private end(closer?: string): boolean {\n if (this.i >= this.s.length)\n return true\n if (closer && this.c() === closer) {\n if (this.pendingHeredocs.length > 0)\n throw new Error('heredoc pending at group close')\n this.i++\n return true\n }\n return false\n }\n\n private c(): string { return this.s[this.i] ?? '' }\n private is(s: string): boolean { return this.s.startsWith(s, this.i) }\n /**\n * Skip blanks, then a `#` comment when it starts a word (bash's rule): a\n * heredoc operator inside a comment must never register, or the lines\n * after it would be skipped as its body and hidden from the safelist.\n * Stops before the newline so the caller still sees the boundary.\n */\n private ws(): void {\n while (this.i < this.s.length && (this.s[this.i] === ' ' || this.s[this.i] === '\\t')) this.i++\n if (this.c() === '#' && (this.i === 0 || META.has(this.s[this.i - 1]))) {\n const nl = this.s.indexOf('\\n', this.i)\n this.i = nl < 0 ? this.s.length : nl\n }\n }\n}\n\n/** `NAME=…` where NAME is a valid shell identifier. */\nfunction isAssignment(w: string): boolean {\n const eq = w.indexOf('=')\n if (eq <= 0)\n return false\n return /^[A-Z_]\\w*$/i.test(w.slice(0, eq))\n}\n","/**\n * Safe-mode storage + matching for the TUI.\n *\n * Lives at `<dataDir>/projects.json` (default `~/.zidane/projects.json`). Each\n * top-level key is an absolute project directory; the value carries that\n * project's persisted tool-call `safelist`.\n *\n * ```json\n * {\n * \"/Users/me/proj-a\": { \"safelist\": [\"read_file\", \"shell:git:*\"] }\n * }\n * ```\n *\n * Two granularities for safelist entries:\n * - **bare tool name** — `\"read_file\"` matches every `read_file` call.\n * - **tool + first-arg token + wildcard** — `\"shell:git:*\"` matches `shell`\n * calls whose primary string argument starts with the token `git`\n * (followed by whitespace or end-of-string). Modelled on Claude Code's\n * `Bash(git:*)` syntax.\n *\n * A short list of read-only tools is **implicitly safe** without being\n * persisted — see {@link IMPLICITLY_SAFE_TOOLS}.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { isLocalBrowserUrl, normalizeBrowserUrl } from './browser/url'\nimport { findGitRoot, projectRootOf } from './project-root'\nimport { extractCommandHeads } from './shell-parse'\n\n/**\n * The `projects.json` key for a working directory: its project root (the\n * main checkout for a linked worktree; the dir itself outside git) — the\n * same \"project\" the TUI scopes its safelist to, so a subdirectory session,\n * every worktree of the repo, and every host share one safelist.\n *\n * @param cwd - Working directory to resolve.\n */\nexport function safelistProjectKey(cwd: string): string {\n return projectRootOf(cwd)\n}\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\n\n/** Per-project settings stored in `projects.json`. */\nexport interface ProjectEntry {\n /** Tool entries the user approved for this project. */\n safelist?: string[]\n}\n\n/** Contents of `projects.json`: project entries keyed by project root path. */\nexport type ProjectsFile = Record<string, ProjectEntry>\n\n/**\n * Resolve `projects.json`'s on-disk path given the TUI data directory.\n *\n * @param dataDir - Directory that holds `projects.json`.\n */\nexport function projectsFilePath(dataDir: string): string {\n return resolve(dataDir, 'projects.json')\n}\n\n/**\n * Reads `projects.json`. Returns `{}` when the file is missing or unreadable.\n *\n * @param dataDir - Directory that holds the file.\n */\nexport function readProjects(dataDir: string): ProjectsFile {\n const path = projectsFilePath(dataDir)\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as ProjectsFile\n }\n catch {\n // Corrupt file → treat as empty so the user can re-bootstrap.\n }\n return {}\n}\n\n/**\n * Writes `projects.json` atomically, creating the directory when needed.\n *\n * @param dataDir - Directory that holds the file.\n * @param file - The full projects map to write.\n */\nexport function writeProjects(dataDir: string, file: ProjectsFile): void {\n writeFileAtomic(projectsFilePath(dataDir), JSON.stringify(file, null, 2), { ensureDir: true })\n}\n\n/**\n * Append one or more entries to the safelist for `projectDir`, dedup-aware.\n * Accepts a single string or a list (a compound shell command needs one\n * entry per command head — see {@link suggestSafelistEntries}). Writes once\n * and returns the updated entry list (post-write) so callers can render it\n * without re-reading. No write happens when every entry already exists.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param projectDir - Project key the entries are stored under.\n * @param entries - One entry or a list. Entries already present are skipped.\n */\nexport function addToSafelist(\n dataDir: string,\n projectDir: string,\n entries: string | readonly string[],\n): readonly string[] {\n const toAdd = typeof entries === 'string' ? [entries] : entries\n const file = readProjects(dataDir)\n const existing = file[projectDir]?.safelist ?? []\n const next = [...existing]\n for (const entry of toAdd) {\n if (!next.includes(entry))\n next.push(entry)\n }\n if (next.length === existing.length)\n return existing\n file[projectDir] = { ...file[projectDir], safelist: next }\n writeProjects(dataDir, file)\n return next\n}\n\n/**\n * Read the safelist for one project. Returns `[]` for unknown projects.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param projectDir - Project key to read.\n */\nexport function getSafelist(dataDir: string, projectDir: string): readonly string[] {\n return readProjects(dataDir)[projectDir]?.safelist ?? []\n}\n\n/**\n * The safelist in effect for a working directory — what every host's gate\n * must read. The project's entries ({@link safelistProjectKey}), plus\n * entries saved under older keys for the same place, still honored but never\n * written again:\n *\n * - the CHECKOUT root — the TUI keyed a linked worktree by its own dir\n * before worktrees resolved to their main checkout;\n * - the exact `cwd` — the daemon/GUI keyed by raw cwd before keys were\n * normalized to the project.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param cwd - Working directory. Entries under its project key, git root and own path are merged.\n */\nexport function getSafelistForCwd(dataDir: string, cwd: string): readonly string[] {\n const file = readProjects(dataDir)\n const keys = new Set([safelistProjectKey(cwd), findGitRoot(cwd) ?? cwd, cwd])\n const merged: string[] = []\n for (const key of keys) {\n for (const entry of file[key]?.safelist ?? []) {\n if (!merged.includes(entry))\n merged.push(entry)\n }\n }\n return merged\n}\n\n/**\n * {@link addToSafelist} under the project key for `cwd` — the write every host's gate uses.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param cwd - Working directory. Entries are stored under its project key.\n * @param entries - One entry or a list. Entries already present are skipped.\n */\nexport function addToSafelistForCwd(dataDir: string, cwd: string, entries: string | readonly string[]): readonly string[] {\n return addToSafelist(dataDir, safelistProjectKey(cwd), entries)\n}\n\n// ---------------------------------------------------------------------------\n// Matching\n// ---------------------------------------------------------------------------\n\n/**\n * Tools that always pass without prompting. Four categories, plus the\n * dev-browser, terminal-read and side-session tools noted inline in the list:\n *\n * - **Pure reads** (`read_file`, `list_files`, `glob`, `grep`) — no\n * side effects on disk or the model's own state.\n * - **Interaction tools** (`ask_user`, `present_plan`) — the call\n * itself surfaces a TUI picker the user has to answer, so prompting\n * for approval first would mean \"approve this prompt before you see\n * the prompt\": redundant + breaks the flow. The picker IS the gate.\n * - **Todos** (`todowrite`, `todoread`) — write to a slot on\n * `session.metadata.todos` and nothing else. No shell, no disk, no\n * network. The model uses these for checkpointing every few steps;\n * a per-call approval prompt would drown the conversation in\n * interruptions that buy no safety since the tool's surface is\n * read/write on a metadata bag we already trust the agent with.\n * - **Skill activation** (`skills_use`) — mutates a small per-agent\n * Map of active skills and returns the activated skill's bundled\n * instructions (or releases an entry on `mode: \"deactivate\"`).\n * Pure state on the agent — no shell, no disk writes, no network.\n * Gating it would also defeat the \"model recovers from\n * `AgentToolNotAllowedError` by calling\n * `skills_use({ mode: \"deactivate\", name })`\" path: a stuck model\n * would have to ask the user permission to *unstick* itself on\n * every retry. `skills_read` and `skills_run_script` are\n * intentionally NOT on this list — the former touches disk and\n * the latter executes arbitrary scripts, so both keep going\n * through the regular gate.\n *\n * Users who want to gate any of these must disable safe-mode entirely\n * (or fork this list in their own embedding).\n */\nexport const IMPLICITLY_SAFE_TOOLS: readonly string[] = [\n 'read_file',\n 'list_files',\n 'glob',\n 'grep',\n 'ask_user',\n 'present_plan',\n 'todowrite',\n 'todoread',\n 'skills_use',\n // A side session reading the one conversation it was opened on.\n 'read_parent_session',\n // The dev browser only loads http(s) pages in its own profile; the one\n // browser tool that runs arbitrary code in the page, `browser_evaluate`,\n // keeps going through the gate. `browser_open` / `browser_navigate` are\n // only implicit for loopback targets (see `isOnSafelist`), so the agent\n // can't point the browser at an arbitrary host by URL. This is not an\n // exfiltration barrier: clicks, form submits and redirects on a loaded\n // page can still reach any http(s) host without a prompt.\n 'browser_open',\n 'browser_navigate',\n 'browser_snapshot',\n 'browser_screenshot',\n 'browser_click',\n 'browser_type',\n 'browser_press',\n 'browser_scroll',\n 'browser_wait_for',\n 'browser_console',\n // Reading the session's terminals; the user can hide one from the agent.\n // `terminal_write` types into a shell, so it is gated like `shell`.\n 'terminal_list',\n 'terminal_read',\n]\n\n/** Common input keys carrying the \"primary argument\" we scope safelists on. */\nconst PRIMARY_ARG_KEYS = ['command', 'path', 'pattern', 'query'] as const\n\nfunction primaryArgValue(input: Record<string, unknown>): string {\n for (const key of PRIMARY_ARG_KEYS) {\n const v = input[key]\n if (typeof v === 'string' && v.length > 0)\n return v\n }\n return ''\n}\n\n/**\n * Extract the first whitespace-delimited token of the primary arg.\n * Leading whitespace is trimmed first so `\" git status\"` tokenizes to\n * `\"git\"`, not `\"\"` (an empty first element from `.split(/\\s+/)`).\n */\nfunction primaryArgToken(input: Record<string, unknown>): string {\n return primaryArgValue(input).trim().split(/\\s+/)[0] ?? ''\n}\n\n/**\n * Test whether a `{ tool, input }` pair is covered by one safelist entry.\n *\n * Supported entry shapes:\n * - `\"<tool>\"` — broad match on tool name.\n * - `\"<tool>:<token>:*\"` — match when the primary arg's first token\n * equals `<token>`.\n * - `\"browser_open:<host>\"` / `\"browser_navigate:<host>\"` — match a\n * load of that host (port included).\n *\n * This function matches a **single command** against a **single entry**.\n * For shell commands that chain multiple programs (`&&`, `||`, `;`,\n * `$(…)`, etc.), use {@link isOnSafelist} which parses the full command\n * and checks every head token independently.\n *\n * Entries that don't fit either shape are ignored (forward-compat for\n * future pattern syntax — readers shouldn't choke on entries written\n * by a newer version of the TUI).\n *\n * @param entry - One safelist entry: a tool name, or `tool:scope` for a scoped one.\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function matchesSafelistEntry(\n entry: string,\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (entry === tool)\n return true\n const sep = entry.indexOf(':')\n if (sep <= 0)\n return false\n if (entry.slice(0, sep) !== tool)\n return false\n const scope = entry.slice(sep + 1)\n if (scope.endsWith(':*'))\n return primaryArgToken(input) === scope.slice(0, -2)\n if (isBrowserLoadTool(tool))\n return browserScope(input) === scope\n return false\n}\n\n/**\n * True when a call matches ANY entry in the project's safelist (or is\n * implicitly safe).\n *\n * For `shell` commands, the full command string is parsed into individual\n * commands (handling `&&`, `||`, `;`, `|`, `$(…)`, backticks, subshells).\n * Every command head must be covered by at least one safelist entry for\n * the call to pass. If parsing fails, returns `false` (prompt the user).\n *\n * @param entries - The project's safelist.\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function isOnSafelist(\n entries: readonly string[],\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (IMPLICITLY_SAFE_TOOLS.includes(tool) && !isRemoteBrowserTarget(tool, input))\n return true\n\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n const heads = extractCommandHeads(cmd)\n if (heads === null)\n return false\n if (heads.length === 0)\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n return heads.every(head =>\n entries.some(e => matchesSafelistEntry(e, tool, { ...input, command: head })),\n )\n }\n\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n}\n\n/** A `browser_open` / `browser_navigate` call that would load a non-loopback address. */\nfunction isRemoteBrowserTarget(tool: string, input: Record<string, unknown>): boolean {\n if (!isBrowserLoadTool(tool))\n return false\n if (typeof input.url !== 'string' || !input.url.trim())\n return false\n const url = normalizeBrowserUrl(input.url)\n return !url || !isLocalBrowserUrl(url)\n}\n\nfunction isBrowserLoadTool(tool: string): boolean {\n return tool === 'browser_open' || tool === 'browser_navigate'\n}\n\n/** What a remembered browser load is scoped to: the URL's host, else the raw URL. */\nfunction browserScope(input: Record<string, unknown>): string {\n const raw = typeof input.url === 'string' ? input.url.trim() : ''\n const url = raw ? normalizeBrowserUrl(raw) : null\n let host = ''\n try {\n host = url ? new URL(url).host : ''\n }\n catch {}\n return host || raw\n}\n\n/**\n * Suggest the safelist entries to persist when the user picks \"accept and\n * remember\" (or \"accept for session\") for a `{ tool, input }`. Heuristic:\n *\n * - `shell` → one `shell:<head>:*` per DISTINCT command head, parsing the\n * command the SAME way {@link isOnSafelist} matches it (`&&`, `||`, `;`,\n * `|`, `$(…)`, subshells, backticks). So `cd dir && cat file` yields\n * `[\"shell:cd:*\", \"shell:cat:*\"]` — persisting the whole set is what\n * lets the next identical call skip the gate. A single first-token\n * entry would only cover `cd`, so `isOnSafelist`'s `every-head` check\n * would fail on `cat` and re-prompt forever.\n * - `shell` with an unparseable / empty command → first-token heuristic,\n * else the bare tool name.\n * - `browser_open` / `browser_navigate` to a non-loopback address →\n * `<tool>:<host>`: approving one site does not approve the web.\n * - anything else → bare tool name (broad).\n *\n * Always returns a non-empty list so the UI has a concrete set to render\n * and persist.\n *\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function suggestSafelistEntries(\n tool: string,\n input: Record<string, unknown>,\n): string[] {\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n const heads = extractCommandHeads(cmd)\n if (heads && heads.length > 0) {\n // Emit one entry per head, deriving the token EXACTLY as\n // `isOnSafelist` looks it up (`primaryArgToken({ command: head })`)\n // and skipping nothing — that symmetry is what guarantees the\n // round-trip: whatever we persist here re-approves the same command\n // next time, for any command the parser accepts (a degenerate head\n // that tokenizes to \"\" yields `shell::*`, which the matcher checks\n // for the same \"\" token, so it still round-trips).\n const out: string[] = []\n for (const head of heads) {\n const entry = `${tool}:${primaryArgToken({ command: head })}:*`\n if (!out.includes(entry))\n out.push(entry)\n }\n return out\n }\n // No heads (assignment-only like `FOO=bar`, or empty/whitespace cmd):\n // mirror `isOnSafelist`'s `heads.length === 0` branch, which matches\n // against the whole input. `extractCommandHeads` returning null is the\n // only case that can't round-trip — the matcher refuses a null parse\n // outright — so this stays a best-effort one-shot grant there.\n const token = primaryArgToken(input)\n if (token)\n return [`${tool}:${token}:*`]\n }\n if (isRemoteBrowserTarget(tool, input)) {\n const scope = browserScope(input)\n if (scope)\n return [`${tool}:${scope}`]\n }\n return [tool]\n}\n","/**\n * Read-only discovery of MCP servers + skills from OTHER coding agents'\n * config locations — Claude Code, Codex, and Pi — at both project and\n * user level. Gated behind the `importForeignConfigs` setting (default\n * off): the files belong to the other tools, so zidane only ever READS\n * them; installs, edits, and toggles keep writing to zidane's own\n * `.zidane` / `.agents` locations.\n *\n * Locations covered (missing files/dirs are skipped silently):\n *\n * MCP servers Skills\n * ── project ────────────────────────── ── project ───────────────\n * {root}/.mcp.json (Claude Code) {root}/.claude/skills\n * ~/.claude.json projects[{root}] (same) {root}/.codex/skills\n * {root}/.pi/mcps.json + mcp.json (Pi) {root}/.pi/skills\n * ── user ───────────────────────────── ── user ──────────────────\n * ~/.claude.json (Claude Code) ~/.claude/skills\n * ~/.codex/config.toml (Codex) ~/.codex/skills\n * ~/.pi/agent/mcps.json + mcp.json (Pi) ~/.pi/agent/skills\n *\n * `.agents/` is NOT here — it's the cross-agent convention zidane\n * already reads natively (see `project-user-paths.ts`), setting or no\n * setting.\n *\n * Precedence is decided by the callers (`mcps-discovery` /\n * `skills-discovery`): native locations always beat foreign ones, and\n * project beats user within each group.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport type { ProjectUserPath } from './project-user-paths'\nimport { realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve, sep } from 'node:path'\nimport { normalizeMcpServers } from '../mcp'\nimport { findGitRoot } from './project-root'\n\n/**\n * How a foreign MCP file is parsed:\n * - `json` — the shared `mcp(s).json` grammar (`parseMcpsFile` handles\n * wrappers, name-keyed maps, and flat arrays).\n * - `claude-user-state` — `~/.claude.json` is Claude Code's whole\n * user-state file; ONLY its `mcpServers` key is imported (treating\n * the rest as a name-keyed server map would produce garbage).\n * - `claude-project-state` — the same file's\n * `projects[\"<abs project root>\"].mcpServers` map: where `claude\n * mcp add` (default \"local\" scope) stores per-project servers.\n * - `codex-toml` — `~/.codex/config.toml` with `[mcp_servers.<name>]`\n * tables.\n */\nexport type ForeignMcpFormat = 'json' | 'claude-user-state' | 'claude-project-state' | 'codex-toml'\n\n/** A foreign MCP config file: its path and scope (from {@link ProjectUserPath}) plus how to parse it. */\nexport interface ForeignMcpFile extends ProjectUserPath {\n /** Parser the file needs. */\n format: ForeignMcpFormat\n}\n\ninterface ForeignPathOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n}\n\nfunction roots(opts: ForeignPathOptions): { projectRoot: string, home: string } {\n const cwd = opts.cwd ?? process.cwd()\n return {\n projectRoot: findGitRoot(cwd) ?? cwd,\n home: opts.home ?? homedir(),\n }\n}\n\n/**\n * MCP config files other agents maintain, project entries first.\n * Returned regardless of existence — callers `existsSync`-filter, same\n * contract as `defaultMcpsConfigPaths`.\n */\nexport function foreignMcpConfigFiles(opts: ForeignPathOptions = {}): ForeignMcpFile[] {\n const { projectRoot, home } = roots(opts)\n return [\n // Claude Code's project-scope convention: `.mcp.json` at the repo\n // root (mcpServers wrapper — the shared JSON grammar unwraps it).\n { path: resolve(projectRoot, '.mcp.json'), source: 'project', format: 'json' },\n // … and `claude mcp add`'s default \"local\" scope, which lives in the\n // USER state file keyed by absolute project path — project-sourced\n // because that's what it means, wherever it's stored.\n { path: resolve(home, '.claude.json'), source: 'project', format: 'claude-project-state' },\n { path: resolve(projectRoot, '.pi/mcps.json'), source: 'project', format: 'json' },\n { path: resolve(projectRoot, '.pi/mcp.json'), source: 'project', format: 'json' },\n { path: resolve(home, '.claude.json'), source: 'user', format: 'claude-user-state' },\n { path: resolve(home, '.codex/config.toml'), source: 'user', format: 'codex-toml' },\n { path: resolve(home, '.pi/agent/mcps.json'), source: 'user', format: 'json' },\n { path: resolve(home, '.pi/agent/mcp.json'), source: 'user', format: 'json' },\n ]\n}\n\n/**\n * Skill directories other agents maintain, project entries first. Feed\n * directly into `discoverSkills` — the SKILL.md format is shared across\n * the ecosystem, so foreign skills parse with the same lenient loader.\n */\nexport function foreignSkillScanPaths(opts: ForeignPathOptions = {}): ProjectUserPath[] {\n const { projectRoot, home } = roots(opts)\n return [\n { path: resolve(projectRoot, '.claude/skills'), source: 'project' },\n { path: resolve(projectRoot, '.codex/skills'), source: 'project' },\n { path: resolve(projectRoot, '.pi/skills'), source: 'project' },\n { path: resolve(home, '.claude/skills'), source: 'user' },\n { path: resolve(home, '.codex/skills'), source: 'user' },\n { path: resolve(home, '.pi/agent/skills'), source: 'user' },\n ]\n}\n\n/**\n * Parse the two foreign-only formats. The `json` format is NOT handled\n * here — callers route it through the shared `parseMcpsFile` (keeping\n * whitespace sanitizing + error hints in one place, and avoiding an\n * import cycle with `mcps-discovery`).\n */\nexport function parseForeignMcpConfig(\n text: string,\n format: Exclude<ForeignMcpFormat, 'json'>,\n opts: { projectRoot?: string } = {},\n): McpServerConfig[] {\n if (format === 'codex-toml')\n return parseCodexToml(text)\n return parseClaudeState(JSON.parse(text), format, opts.projectRoot)\n}\n\n/**\n * Extract MCP servers from a PRE-PARSED `~/.claude.json` state object.\n * Split from {@link parseForeignMcpConfig} so a discovery pass that reads\n * the file for BOTH scopes (user + project — it appears twice in the path\n * list) can read and `JSON.parse` the multi-MB state file once.\n *\n * - `claude-user-state` — ONLY the top-level `mcpServers` map; an\n * absent/malformed key means \"nothing to import\" rather than an error,\n * since the file legitimately exists without it.\n * - `claude-project-state` — `projects[<dir>].mcpServers`, where `claude\n * mcp add` (default \"local\" scope) stores per-project servers.\n */\nexport function parseClaudeState(\n state: unknown,\n format: 'claude-user-state' | 'claude-project-state',\n projectRoot?: string,\n): McpServerConfig[] {\n if (!state || typeof state !== 'object' || Array.isArray(state))\n return []\n if (format === 'claude-user-state')\n return normalizeForeignEntries((state as { mcpServers?: unknown }).mcpServers)\n return claudeProjectServers(state as { projects?: unknown }, projectRoot)\n}\n\n/**\n * Claude keys `projects` by the directory it was LAUNCHED from — for a\n * monorepo that's often a subdirectory of the git root we resolve to, and\n * paths surface in realpath variants (`/var` vs `/private/var` on macOS,\n * symlinked homes). Accept every key that realpath-normalizes to the root\n * or to a directory inside it; nearer-to-root keys win name collisions so\n * a root-registered server beats a subdirectory duplicate.\n */\nfunction claudeProjectServers(state: { projects?: unknown }, projectRoot: string | undefined): McpServerConfig[] {\n if (!projectRoot)\n return []\n const projects = state.projects\n if (!projects || typeof projects !== 'object' || Array.isArray(projects))\n return []\n const rootKey = normalizePathKey(projectRoot)\n const matches = Object.entries(projects as Record<string, unknown>)\n .map(([key, value]) => ({ key: normalizePathKey(key), value }))\n .filter(({ key }) => key === rootKey || key.startsWith(rootKey + sep))\n .sort((a, b) => a.key.length - b.key.length)\n const out: McpServerConfig[] = []\n const seen = new Set<string>()\n for (const { value } of matches) {\n const servers = value && typeof value === 'object' ? (value as { mcpServers?: unknown }).mcpServers : undefined\n for (const config of normalizeForeignEntries(servers)) {\n if (seen.has(config.name))\n continue\n seen.add(config.name)\n out.push(config)\n }\n }\n return out\n}\n\n/** Realpath-normalize for key comparison; deleted paths fall back to `resolve`. */\nfunction normalizePathKey(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return resolve(path)\n }\n}\n\n/**\n * Normalize a name-keyed server map ENTRY BY ENTRY, skipping the ones\n * `normalizeMcpServers` rejects (no inferrable transport, exotic shapes).\n * Whole-file normalization would let one bad entry drop every server in\n * a file the user doesn't own — and can't fix from zidane.\n */\nfunction normalizeForeignEntries(servers: unknown): McpServerConfig[] {\n if (!servers || typeof servers !== 'object' || Array.isArray(servers))\n return []\n const out: McpServerConfig[] = []\n for (const [name, entry] of Object.entries(servers as Record<string, unknown>)) {\n try {\n out.push(...normalizeMcpServers({ [name]: entry }))\n }\n catch {\n // Skip — the entry works (or fails) in its owning tool; not ours to error on.\n }\n }\n return out\n}\n\n/**\n * `~/.codex/config.toml` — `[mcp_servers.<name>]` tables. TOML parsing\n * rides Bun's built-in parser; on a non-Bun host the Codex import is\n * silently unavailable (returns `[]`) rather than pulling in a TOML\n * dependency for one optional file. Only the fields our config\n * understands are forwarded — Codex-specific extras\n * (`startup_timeout_ms`, …) are dropped before normalization.\n */\nfunction parseCodexToml(text: string): McpServerConfig[] {\n const toml = (globalThis as { Bun?: { TOML?: { parse: (t: string) => unknown } } }).Bun?.TOML\n if (!toml)\n return []\n const parsed = toml.parse(text) as { mcp_servers?: unknown }\n const tables = parsed && typeof parsed === 'object' ? parsed.mcp_servers : undefined\n if (!tables || typeof tables !== 'object' || Array.isArray(tables))\n return []\n const map: Record<string, unknown> = {}\n for (const [name, entry] of Object.entries(tables as Record<string, unknown>)) {\n if (!entry || typeof entry !== 'object')\n continue\n const raw = entry as Record<string, unknown>\n map[name] = {\n ...(raw.command !== undefined ? { command: raw.command } : {}),\n ...(raw.args !== undefined ? { args: raw.args } : {}),\n ...(raw.env !== undefined ? { env: raw.env } : {}),\n // Codex's experimental remote servers — normalize infers transport.\n ...(raw.url !== undefined ? { url: raw.url } : {}),\n }\n }\n return normalizeForeignEntries(map)\n}\n","/**\n * Read-only discovery of [Ruler](https://github.com/intellectronica/ruler)\n * `.ruler/` directories — MCP servers, skills, and instruction markdown.\n *\n * Ruler centralizes AI-agent configuration in one project directory and\n * GENERATES per-agent files from it (`ruler apply`). zidane reads the\n * SOURCES directly, so a ruler-managed repo works without running ruler:\n *\n * .ruler/ruler.toml `[mcp_servers.<name>]` tables — the same TOML\n * grammar as Codex's config, parsed by the shared\n * `codex-toml` handler in `foreign-configs.ts`.\n * .ruler/mcp.json legacy ruler MCP file — shared JSON grammar.\n * .ruler/skills/ `<name>/SKILL.md` — the ecosystem-shared format.\n * .ruler/*.md instruction files, concatenated by ruler into the\n * generated AGENTS.md; zidane loads them as per-file\n * sections instead. Recursive, excluding the\n * `skills/` and `agents/` subtrees (ruler does the\n * same). Ordering mirrors ruler: `AGENTS.md` first\n * (legacy `instructions.md` as fallback primary),\n * remaining files sorted by relative path.\n *\n * User scope is ruler's global config dir: `$XDG_CONFIG_HOME/ruler`\n * (default `~/.config/ruler`) — same three surfaces.\n *\n * CONTAINMENT CONTRACT: every piece of ruler knowledge lives in this\n * module. The discovery surfaces (`mcps-discovery`, `skills-discovery`,\n * `agents-md`) each make exactly one append/skip call into it, all gated\n * behind the `importRulerConfigs` setting — deleting this file and those\n * call sites removes the feature completely. zidane never WRITES to\n * `.ruler/`; installs and toggles keep landing in `.zidane` / `.agents`.\n */\n\nimport type { ForeignMcpFile } from './foreign-configs'\nimport type { ProjectUserPath } from './project-user-paths'\nimport { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { findGitRoot } from './project-root'\n\ninterface RulerPathOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** XDG config home override. Default: `$XDG_CONFIG_HOME`, else `<home>/.config`. */\n xdgConfigHome?: string\n}\n\n/** Subtrees of `.ruler/` that are NOT instruction markdown (mirrors ruler's own exclusions). */\nconst NON_INSTRUCTION_DIRS = new Set(['skills', 'agents'])\n\n/** Primary instruction file (ordered first), and ruler's legacy fallback primary. */\nconst PRIMARY_MD = 'AGENTS.md'\nconst LEGACY_PRIMARY_MD = 'instructions.md'\n\nfunction rulerRoots(opts: RulerPathOptions): { project: string, user: string } {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n // An explicitly injected `home` beats the process's $XDG_CONFIG_HOME:\n // callers that redirect the user scope (tests, hosts with virtual homes,\n // `discoverAgentsMd({ home })` which can't thread `xdgConfigHome`) must\n // not silently read the REAL user's global ruler dir through the env.\n const xdg = opts.xdgConfigHome\n ?? (opts.home !== undefined\n ? resolve(opts.home, '.config')\n : process.env.XDG_CONFIG_HOME ?? resolve(home, '.config'))\n return {\n project: resolve(findGitRoot(cwd) ?? cwd, '.ruler'),\n user: resolve(xdg, 'ruler'),\n }\n}\n\n/**\n * MCP config files ruler maintains, project first. Returned regardless of\n * existence (callers `existsSync`-filter — same contract as\n * `foreignMcpConfigFiles`). The `codex-toml` format tag names the shared\n * `[mcp_servers.<name>]` TOML *grammar*, not the tool: ruler.toml uses it\n * verbatim, so the existing parser applies unchanged.\n */\nexport function rulerMcpConfigFiles(opts: RulerPathOptions = {}): ForeignMcpFile[] {\n const { project, user } = rulerRoots(opts)\n return [\n { path: join(project, 'ruler.toml'), source: 'project', format: 'codex-toml' },\n { path: join(project, 'mcp.json'), source: 'project', format: 'json' },\n { path: join(user, 'ruler.toml'), source: 'user', format: 'codex-toml' },\n { path: join(user, 'mcp.json'), source: 'user', format: 'json' },\n ]\n}\n\n/**\n * Skill directories ruler maintains, project first. Feed directly into\n * `discoverSkills` — ruler's `skills/<name>/SKILL.md` layout is the\n * ecosystem-shared format the lenient loader already parses.\n */\nexport function rulerSkillScanPaths(opts: RulerPathOptions = {}): ProjectUserPath[] {\n const { project, user } = rulerRoots(opts)\n return [\n { path: join(project, 'skills'), source: 'project' },\n { path: join(user, 'skills'), source: 'user' },\n ]\n}\n\n/**\n * Instruction markdown files under both ruler roots, in ruler's own\n * concatenation order per root (primary first, rest sorted by relative\n * path), user scope before project scope so the narrower scope lands\n * closer to the conversation — matching `discoverAgentsMd`'s convention.\n *\n * Only existing files are returned (unlike the config-file lists above):\n * the walk itself has to touch the filesystem anyway, and callers feed\n * these straight into per-file readers.\n */\nexport function rulerInstructionFiles(opts: RulerPathOptions = {}): ProjectUserPath[] {\n const { project, user } = rulerRoots(opts)\n return [\n ...collectInstructionMd(user).map(path => ({ path, source: 'user' as const })),\n ...collectInstructionMd(project).map(path => ({ path, source: 'project' as const })),\n ]\n}\n\n/**\n * True when a generated file (e.g. the repo-root `AGENTS.md`) was written\n * by `ruler apply` — it aggregates the `.ruler/*.md` sources, so loading\n * both would double every instruction. Two signals, either suffices:\n * ruler's generation marker, and the per-section source comments older\n * versions emitted without the marker.\n */\nexport function isRulerGenerated(contents: string): boolean {\n return contents.includes('<!-- Generated by Ruler -->')\n || contents.includes('<!-- Source: .ruler/')\n}\n\n/**\n * Bounds for the instruction walk — same philosophy as the 64 KB per-file\n * cap in `agents-md.ts`: a `.ruler/docs -> $HOME` symlink or a huge\n * committed tree must not stall session activation (the walk runs on the\n * main path, per activation and per discovery refresh). Ruler setups in\n * the wild are a handful of files, two levels deep.\n */\nconst MAX_WALK_DEPTH = 6\nconst MAX_INSTRUCTION_FILES = 100\n\n/** Recursive `.md` walk under one ruler root, ruler-ordered. Missing/unreadable dirs → `[]`. */\nfunction collectInstructionMd(rulerDir: string): string[] {\n if (!existsSync(rulerDir))\n return []\n const collected: string[] = []\n // Symlinked dirs are followed (a linked instructions tree should load),\n // so guard against cycles: each REAL directory is walked at most once.\n const visited = new Set<string>([realpathOr(rulerDir)])\n const walk = (dir: string, top: boolean, depth: number): void => {\n if (depth > MAX_WALK_DEPTH || collected.length >= MAX_INSTRUCTION_FILES)\n return\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n }\n catch {\n return // permission glitch — tolerate, like every discovery surface\n }\n for (const entry of entries) {\n if (collected.length >= MAX_INSTRUCTION_FILES)\n return\n if (entry.name.startsWith('.'))\n continue\n const full = join(dir, entry.name)\n // `withFileTypes` reports symlinks as neither file nor dir — stat\n // through them so a symlinked instructions file still loads.\n const kind = entry.isSymbolicLink() ? statKind(full) : entry.isDirectory() ? 'dir' : entry.isFile() ? 'file' : 'other'\n if (kind === 'dir') {\n if (top && NON_INSTRUCTION_DIRS.has(entry.name))\n continue\n const real = realpathOr(full)\n if (visited.has(real))\n continue\n visited.add(real)\n walk(full, false, depth + 1)\n }\n else if (kind === 'file' && entry.name.endsWith('.md')) {\n collected.push(full)\n }\n }\n }\n walk(rulerDir, true, 0)\n // Ruler's ordering: top-level AGENTS.md is the primary (legacy\n // instructions.md when AGENTS.md is absent), everything else follows\n // in sorted order.\n const primary = join(rulerDir, PRIMARY_MD)\n const legacy = join(rulerDir, LEGACY_PRIMARY_MD)\n const first = collected.includes(primary) ? primary : collected.includes(legacy) ? legacy : undefined\n const rest = collected.filter(p => p !== first).sort()\n return first ? [first, ...rest] : rest\n}\n\nfunction realpathOr(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\nfunction statKind(path: string): 'dir' | 'file' | 'other' {\n try {\n const s = statSync(path)\n return s.isDirectory() ? 'dir' : s.isFile() ? 'file' : 'other'\n }\n catch {\n return 'other'\n }\n}\n","/**\n * MCP server discovery from project + user config files.\n *\n * Reads `mcps.json` (zidane's canonical name) AND `mcp.json` (Cursor /\n * common ecosystem alias) from both `.agents/` and `.{prefix}/` in\n * each of the project + user dirs — a list of `McpServerConfig`\n * shapes the SDK accepts. Files are normalized via the same path the\n * agent uses (`normalizeMcpServers`), and the user's enabled-toggle\n * list folds the result into a server array the agent picks up at\n * construction.\n *\n * Co-existing files merge by first-found-per-name: e.g.\n * `.agents/mcps.json` declares `linear`, `.agents/mcp.json` declares\n * `linear` + `notion` → discovery emits the `mcps.json` `linear` plus\n * `mcp.json`'s `notion`. Project always beats user across all files.\n *\n * Pattern mirrors `skills-discovery.ts` — the chat layer owns\n * discovery + UI, the agent receives the same `McpServerConfig[]` it\n * always did.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport type { ForeignMcpFormat } from './foreign-configs'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { errorMessage } from '../errors'\nimport { normalizeMcpServers } from '../mcp'\nimport { foreignMcpConfigFiles, parseClaudeState, parseForeignMcpConfig } from './foreign-configs'\nimport { findGitRoot } from './project-root'\nimport { projectUserPaths } from './project-user-paths'\nimport { rulerMcpConfigFiles } from './ruler-configs'\n\n/**\n * File names searched in each of the four project/user roots. Order\n * matters within a single directory — `mcps.json` (zidane's canonical\n * name) beats `mcp.json` (Cursor / common ecosystem alias) when both\n * exist side-by-side. Across files we merge per-name with first-found\n * winning, so co-existing `mcp.json` entries are folded in for names\n * not declared in the canonical file.\n */\nconst MCP_CONFIG_FILE_NAMES = ['mcps.json', 'mcp.json'] as const\n\n/**\n * One entry in the discovered list. `path` is the source file (one of\n * the `mcp(s).json` variants under `.agents/` or `.{prefix}/`) so a\n * Settings picker can show \"Defined in ~/.zidane/mcp.json\" tooltips\n * and the user can tell which file declared what.\n */\nexport interface DiscoveredMcp {\n /** Normalized server config. */\n config: McpServerConfig\n /** Whether the file came from the project or the user directory. */\n source: 'project' | 'user'\n /** Absolute path of the file that declared the server. */\n path: string\n}\n\n/**\n * Search order for MCP config files — see {@link projectUserPaths}.\n *\n * Both `mcps.json` (zidane's canonical name) and `mcp.json` (Cursor /\n * common ecosystem alias) are picked up from every search root. The\n * canonical name wins within the same directory; across files the\n * usual first-found-by-name dedup applies, so `mcp.json` effectively\n * merges its non-overlapping entries on top of `mcps.json`.\n *\n * Order per scope (project beats user):\n * 1. `{project}/.agents/mcps.json`\n * 2. `{project}/.agents/mcp.json`\n * 3. `{project}/.{prefix}/mcps.json`\n * 4. `{project}/.{prefix}/mcp.json`\n * 5. `~/.agents/mcps.json`\n * 6. `~/.agents/mcp.json`\n * 7. `~/.{prefix}/mcps.json`\n * 8. `~/.{prefix}/mcp.json`\n *\n * Non-existent files are skipped without error.\n *\n * @param opts - Overrides for the working directory, home directory and dot-directory prefix.\n */\nexport function defaultMcpsConfigPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): { path: string, source: 'project' | 'user' }[] {\n // Build the path list per-filename via the shared helper, then\n // interleave so `<dir>/mcps.json` lands immediately before\n // `<dir>/mcp.json` for each scope. Same ordering invariant that\n // `projectUserPaths` already guarantees within a filename — the\n // interleave preserves it across filenames.\n const perName = MCP_CONFIG_FILE_NAMES.map(name => projectUserPaths({ subPath: name, ...opts }))\n const rootCount = perName[0].length\n const out: { path: string, source: 'project' | 'user' }[] = []\n for (let i = 0; i < rootCount; i++) {\n for (const list of perName)\n out.push(list[i])\n }\n return out\n}\n\n/**\n * Parse one `mcps.json` file. Canonical zidane shape is a flat array; the\n * other shapes exist for copy-paste from neighboring tools:\n *\n * - `McpServerConfig[]` — flat array (recommended).\n * - `{ name: {...} }` — name-keyed map.\n * - `{ \"mcpServers\": { name: {...} } }` — Claude Desktop / Claude Code /\n * Cursor wrapper. Unwrapped before normalization (otherwise\n * `normalizeMcpServers` would treat the whole wrapper as a single server).\n *\n * Per-entry `enabled: false` (Cursor runtime state) drops the entry — same\n * intent as the source tool. Use the TUI toggle list for \"show but disabled\".\n *\n * Tolerant to common copy-paste artifacts: when strict `JSON.parse` fails,\n * retries after normalizing invisible whitespace that's valid Unicode but\n * not valid JSON whitespace (BOM, NBSP, zero-width spaces, line/paragraph\n * separators) — only OUTSIDE string literals, so user data isn't mutated.\n * If the sanitized version still fails, throws the original error with a\n * targeted hint when NBSPs are present.\n *\n * Validation flows through `normalizeMcpServers` so the result matches\n * what `createAgent` accepts. Throws on truly malformed JSON; the caller\n * decides whether to surface or swallow.\n *\n * @param text - Raw file contents, either a bare server map or array, or an object with an `mcpServers` key.\n */\nexport function parseMcpsFile(text: string): McpServerConfig[] {\n const raw = parseJsonTolerant(text)\n const target: unknown = raw && typeof raw === 'object' && !Array.isArray(raw) && 'mcpServers' in raw\n ? (raw as { mcpServers: unknown }).mcpServers\n : raw\n return normalizeMcpServers(dropDisabledEntries(target))\n}\n\n/**\n * Strict `JSON.parse` with the whitespace-tolerance retry described on\n * {@link parseMcpsFile}, returning the RAW parsed value. Exported for the\n * write side (`mcps-write.ts`): a file that discovery reads fine MUST also\n * be editable — upsert/remove failing with \"Unrecognized token\" on an NBSP\n * the read path silently tolerates would strand the user.\n */\nexport function parseJsonTolerant(text: string): unknown {\n try {\n return JSON.parse(text)\n }\n catch (firstErr) {\n const sanitized = sanitizeJsonWhitespace(text)\n if (!sanitized.changed)\n throw firstErr\n try {\n return JSON.parse(sanitized.text)\n }\n catch {\n throw enhanceJsonError(firstErr, text)\n }\n }\n}\n\n/**\n * Code points that are valid Unicode whitespace but NOT valid JSON whitespace.\n * Copy-paste from rich-text contexts (markdown editors, chat UIs, web pages)\n * frequently slips these in:\n *\n * - `\\uFEFF` BOM — sometimes prepended by Windows editors.\n * - `\\u00A0` NBSP — markdown/HTML smart-replace common culprit.\n * - `\\u200B`-`\\u200D` zero-width family — often invisible in editors.\n * - `\\u2028` / `\\u2029` line/paragraph separators — valid in JS strings\n * pre-ES2019 but never in strict JSON.\n * - `\\u2060` word joiner.\n *\n * Replace with ASCII space (zero-width chars are removed; the others become\n * a single space). Replacements happen ONLY outside string literals so a\n * user's intentional NBSP inside a `url` or display name is left alone.\n */\nconst PROBLEMATIC_WHITESPACE = new Set<number>([\n 0xFEFF,\n 0x00A0,\n 0x200B,\n 0x200C,\n 0x200D,\n 0x2028,\n 0x2029,\n 0x2060,\n])\n\nfunction sanitizeJsonWhitespace(text: string): { text: string, changed: boolean } {\n let changed = false\n let inString = false\n let escaped = false\n const out: string[] = []\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (inString) {\n out.push(ch)\n if (escaped)\n escaped = false\n else if (ch === '\\\\')\n escaped = true\n else if (ch === '\"')\n inString = false\n continue\n }\n if (ch === '\"') {\n inString = true\n out.push(ch)\n continue\n }\n const code = ch.charCodeAt(0)\n if (PROBLEMATIC_WHITESPACE.has(code)) {\n changed = true\n // Zero-width chars (200B-200D, 2060, FEFF) erased; visible whitespace\n // replacements become a single space so token boundaries are preserved.\n if (code === 0x00A0 || code === 0x2028 || code === 0x2029)\n out.push(' ')\n continue\n }\n out.push(ch)\n }\n return { text: out.join(''), changed }\n}\n\nfunction enhanceJsonError(err: unknown, text: string): Error {\n const orig = err instanceof Error ? err : new Error(String(err))\n const hints: string[] = []\n if (text.includes('\\u00A0'))\n hints.push('the file contains non-breaking spaces (U+00A0), likely from copy-paste. Replace them with regular spaces')\n if (text.charCodeAt(0) === 0xFEFF)\n hints.push('the file starts with a BOM (U+FEFF), which strict JSON.parse rejects')\n if (hints.length === 0)\n return orig\n return new Error(`${orig.message}. ${hints.join('; ')}`)\n}\n\n/**\n * Strip `enabled: false` entries before normalization — same intent as the\n * source tool that wrote them (Cursor flips this when the user toggles a\n * server off in its UI). One pass over array OR map shape, O(N).\n *\n * Done pre-normalize so we don't have to teach `normalizeMcpServers` about\n * the flag — the canonical `McpServerConfig` has no `enabled` field.\n */\nfunction dropDisabledEntries(target: unknown): unknown {\n if (Array.isArray(target))\n return target.filter(e => !isEntryDisabled(e))\n if (target && typeof target === 'object') {\n const filtered: Record<string, unknown> = {}\n for (const [name, entry] of Object.entries(target as Record<string, unknown>)) {\n if (!isEntryDisabled(entry))\n filtered[name] = entry\n }\n return filtered\n }\n return target\n}\n\nfunction isEntryDisabled(entry: unknown): boolean {\n return !!entry && typeof entry === 'object'\n && (entry as { enabled?: unknown }).enabled === false\n}\n\n/**\n * Per-file failure surfaced from {@link discoverProjectMcps}. The TUI shows\n * these in the MCP settings empty / preamble state so a malformed\n * `mcps.json` doesn't look identical to a missing one.\n */\nexport interface DiscoveryError {\n /** Absolute path of the file that failed. */\n path: string\n /** Whether the file came from the project or the user directory. */\n source: 'project' | 'user'\n /** Human-readable description of the failure. */\n message: string\n /**\n * A deliberate, documented behavior being reported (e.g. the ruler stdio\n * security carve-out) rather than a failure. Hosts should style these as\n * info, not error — crying wolf over a working default trains users to\n * ignore the channel that also carries real parse failures.\n */\n informational?: boolean\n}\n\n/**\n * Result of {@link discoverProjectMcps}. `servers` is the per-source-priority\n * merged list (project beats user, first-found wins on name collision).\n * `errors` is one entry per file that existed but failed to parse, plus\n * informational entries for servers a security gate skipped (ruler stdio\n * without the foreign-agent opt-in) — empty on the happy path.\n */\nexport interface DiscoveryResult {\n /** Servers found, deduplicated by name with project entries ahead of user entries. */\n servers: DiscoveredMcp[]\n /** Files that failed to parse and informational notices. */\n errors: DiscoveryError[]\n}\n\n/**\n * Walk the default config paths, parse every file that exists, and return\n * `{ servers, errors }` in source-priority order. Project entries appear\n * before user entries; first occurrence of a `name` wins (later\n * duplicates dropped).\n *\n * Parse failures are captured into `errors` rather than thrown — the picker\n * surfaces them while still showing whatever DID parse. A `ZIDANE_DEBUG`\n * log line is also emitted for ops visibility.\n *\n * @param opts - Roots to search and flags that enable foreign-agent and Ruler config files.\n */\nexport function discoverProjectMcps(opts: {\n cwd?: string\n home?: string\n prefix?: string\n /**\n * Also read MCP configs other agents maintain (Claude Code, Codex,\n * Pi) — see `foreign-configs.ts`. Mirrors the `importForeignConfigs`\n * setting; native locations always win name collisions, and project\n * beats user within each group.\n */\n foreignAgents?: boolean\n /**\n * Also read Ruler MCP configs (`.ruler/ruler.toml` + `mcp.json`,\n * project and XDG-global) — see `ruler-configs.ts`. Mirrors the\n * `importRulerConfigs` setting; same collision rules as above.\n */\n ruler?: boolean\n} = {}): DiscoveryResult {\n const paths = mcpsDiscoveryPaths(opts).filter(p => existsSync(p.path))\n // Claude's \"local\"-scope servers are keyed by absolute project path\n // inside the user state file — resolve once for the whole pass.\n const projectRoot = findGitRoot(opts.cwd ?? process.cwd()) ?? (opts.cwd ?? process.cwd())\n const seen = new Set<string>()\n // Pass-level record of names already reported as stdio-dropped — a server\n // declared in BOTH `.ruler/ruler.toml` and `.ruler/mcp.json` would\n // otherwise appear in two informational notices, inflating the count.\n const droppedStdioSeen = new Set<string>()\n const servers: DiscoveredMcp[] = []\n const errors: DiscoveryError[] = []\n // `~/.claude.json` appears TWICE in the path list (project + user scopes)\n // and runs to multiple MB in the wild (it embeds per-project history) —\n // read + `JSON.parse` it once per pass. Caching the failure too collapses\n // a torn read (a live Claude Code session rewriting the file) into ONE\n // transient error instead of two.\n const claudeStateCache = new Map<string, { state: unknown } | { failed: true }>()\n for (const { path, source, format, dropStdio } of paths) {\n const isClaudeState = format === 'claude-user-state' || format === 'claude-project-state'\n const cached = isClaudeState ? claudeStateCache.get(path) : undefined\n if (cached && 'failed' in cached)\n continue // parse failure already recorded for this path this pass\n let configs: McpServerConfig[]\n try {\n if (isClaudeState) {\n let state: unknown\n if (cached && 'state' in cached) {\n state = cached.state\n }\n else {\n try {\n state = JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch (err) {\n claudeStateCache.set(path, { failed: true })\n throw err\n }\n claudeStateCache.set(path, { state })\n }\n configs = parseClaudeState(state, format, projectRoot)\n }\n else {\n const text = readFileSync(path, 'utf8')\n configs = format === 'json' ? parseMcpsFile(text) : parseForeignMcpConfig(text, format, { projectRoot })\n }\n }\n catch (err) {\n const message = errorMessage(err)\n errors.push({ path, source, message })\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] failed to parse \"${path}\": ${message}\\n`)\n continue\n }\n const droppedStdio: string[] = []\n for (const config of configs) {\n if (seen.has(config.name))\n continue\n // Ruler stdio servers require the explicit stdio opt-in (see\n // `mcpsDiscoveryPaths`) — a `command` from a repo-committed file\n // would otherwise execute on the first bootstrap of a freshly\n // cloned (possibly hostile) repo, since `importRulerConfigs`\n // defaults ON and discovered servers are enabled by default.\n // (Checked AFTER the dedup guard so a server that already imported\n // from a native location isn't misreported as dropped.)\n if (dropStdio && config.transport === 'stdio') {\n if (!droppedStdioSeen.has(config.name)) {\n droppedStdioSeen.add(config.name)\n droppedStdio.push(config.name)\n }\n continue\n }\n seen.add(config.name)\n servers.push({ config, source, path })\n }\n // Surface the security carve-out instead of vanishing silently — the\n // picker renders these alongside parse errors, so the user learns WHY\n // a `.ruler` stdio server is missing and which switch enables it.\n if (droppedStdio.length > 0) {\n errors.push({\n path,\n source,\n message: `${droppedStdio.length} stdio server${droppedStdio.length === 1 ? '' : 's'} (${droppedStdio.join(', ')}) skipped. Running stdio commands from Ruler files needs “Import configurations from other agents” enabled.`,\n informational: true, // working security default, not a failure\n })\n }\n }\n return { servers, errors }\n}\n\n/**\n * Full path list a discovery pass reads, formats attached — native\n * `mcp(s).json` locations first (they win name collisions), then the\n * foreign-agent files when enabled.\n *\n * Ruler files carry `dropStdio` unless the foreign-agent opt-in is ALSO\n * set: `importRulerConfigs` defaults ON (it's an intentional committed\n * config source), but stdio servers execute local commands — the exact\n * risk `importForeignConfigs` exists to gate (`types.ts`: \"importing them\n * is an explicit opt-in\"). Remote (url) ruler servers import by default;\n * `command`-based ones wait for the same opt-in as every other foreign\n * stdio source.\n */\nfunction mcpsDiscoveryPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n foreignAgents?: boolean\n ruler?: boolean\n} = {}): { path: string, source: 'project' | 'user', format: ForeignMcpFormat, dropStdio?: boolean }[] {\n const native = defaultMcpsConfigPaths(opts).map(p => ({ ...p, format: 'json' as const }))\n return [\n ...native,\n ...(opts.foreignAgents ? foreignMcpConfigFiles(opts) : []),\n ...(opts.ruler ? rulerMcpConfigFiles(opts).map(p => ({ ...p, dropStdio: opts.foreignAgents !== true })) : []),\n ]\n}\n\n/**\n * Map a user-toggled enable list onto the `mcpServers` array the agent\n * receives. Conventions match `buildSkillsConfig`:\n *\n * - `enabled === undefined` → every discovered server enabled (default).\n * - `enabled === []` → no servers (the agent runs MCP-less).\n * - `enabled === [names]` → allowlist; servers not in the list are\n * dropped.\n *\n * `disabledTools` is the per-server tool deny-list from\n * `Settings.disabledMcpTools`. For each server that survives the\n * server-level enable filter, the per-tool names are merged into\n * `McpServerConfig.disabledTools` via **union** with anything the JSON\n * file already pinned — a tool listed in either source is dropped.\n * Union (not override) keeps JSON-side policy stable: a server file\n * that pins `disabledTools: ['dangerous_tool']` can't be re-enabled\n * via the UI by accident.\n *\n * `enabledTools` (JSON-side allowlist) takes precedence over the\n * UI-side deny-list when both are set on the same server — the\n * allowlist's \"only these\" semantics would be meaningless if a UI\n * deny-list could subtract from it again. We don't emit `disabledTools`\n * when `enabledTools` is present.\n *\n * Returns a fresh array — callers can mutate without affecting the\n * underlying discovery list. Per-server config objects are also fresh\n * copies (shallow), so the merged `disabledTools` doesn't leak back\n * onto the discovery catalog.\n *\n * @param opts - Discovered servers, the enabled-name allowlist and per-server UI tool denylists to fold in.\n */\nexport function buildMcpServers(opts: {\n discovered: readonly DiscoveredMcp[]\n enabled?: readonly string[]\n disabledTools?: Record<string, readonly string[]>\n}): McpServerConfig[] {\n const survivors = filterByEnabled(opts.discovered, opts.enabled)\n const deny = opts.disabledTools\n if (!deny || Object.keys(deny).length === 0)\n return survivors.map(d => ({ ...d.config }))\n\n return survivors.map((d) => {\n const next: McpServerConfig = { ...d.config }\n const uiDeny = deny[d.config.name]\n if (!uiDeny || uiDeny.length === 0)\n return next\n // `enabledTools` is JSON-side authority — see JSDoc.\n if (next.enabledTools && next.enabledTools.length > 0)\n return next\n const merged = new Set<string>(next.disabledTools ?? [])\n for (const name of uiDeny)\n merged.add(name)\n next.disabledTools = [...merged].sort()\n return next\n })\n}\n\n/**\n * Dedup a merged MCP-server list by `name`. First entry wins.\n *\n * Applied at agent build across every source that can contribute a server —\n * `mcps.json`, Agent Plugins, launch options, extensions — so the earliest\n * source in that order takes precedence.\n *\n * Drops are REPORTED rather than silent, via `onDropped`. A plugin shipping a\n * `github` server loses to a `github` in `mcps.json`, and nothing downstream\n * says so: the plugin's settings row still counts the server it shipped,\n * because the row renders what the PACKAGE declares and only this merge knows\n * what survived. That mirrors how the skills side reports the same class of\n * event — `resolveSkills` emits `name-collision-shadowed` on the winner rather\n * than annotating the loser.\n *\n * The reporter is injected rather than calling a logger directly so this stays\n * a pure function of its input, testable without a logging shim. It lives here\n * beside {@link buildMcpServers} rather than in the TUI because merge\n * precedence is a config concern, not a rendering one.\n *\n * @param servers - Servers to filter. The first entry per `name` wins.\n * @param onDropped - Called once with the dropped duplicate names when any were removed.\n */\nexport function dedupMcpServersByName<T extends { name: string }>(\n servers: readonly T[],\n onDropped?: (names: readonly string[]) => void,\n): T[] {\n const seen = new Set<string>()\n const out: T[] = []\n const dropped: string[] = []\n for (const server of servers) {\n if (seen.has(server.name)) {\n dropped.push(server.name)\n continue\n }\n seen.add(server.name)\n out.push(server)\n }\n if (dropped.length > 0)\n onDropped?.(dropped)\n return out\n}\n\n/**\n * Anchor host-spawned stdio servers without an explicit `cwd` at the\n * session's directory. Otherwise they inherit the host process's cwd —\n * the session dir in a TUI that chdir'd, but an arbitrary dir in the daemon\n * or GUI, so relative args / project-scoped servers would see the wrong\n * tree. Context-scoped servers run inside the execution context and are\n * left alone.\n */\nexport function withSessionStdioCwd(servers: readonly McpServerConfig[], cwd: string): McpServerConfig[] {\n return servers.map(server =>\n server.transport === 'stdio' && server.scope !== 'context' && !server.cwd\n ? { ...server, cwd }\n : server,\n )\n}\n\nfunction filterByEnabled(\n discovered: readonly DiscoveredMcp[],\n enabled: readonly string[] | undefined,\n): readonly DiscoveredMcp[] {\n if (enabled === undefined)\n return discovered\n if (enabled.length === 0)\n return []\n const allow = new Set(enabled)\n return discovered.filter(d => allow.has(d.config.name))\n}\n","/**\n * System-prompt override discovery — load a `SYSTEM.md` that REPLACES the\n * built-in doctrine.\n *\n * The sibling of `agents-md.ts`, with deliberately inverted semantics:\n *\n * | | `AGENTS.md` | `SYSTEM.md` |\n * |------------|------------------------------|---------------------------------|\n * | effect | appends to the doctrine | replaces the doctrine |\n * | multiples | every match loads, in order | exactly one wins |\n * | precedence | broad → narrow, all present | project beats user, rest shadowed |\n *\n * Concatenating overrides would be incoherent — two files each claiming to BE\n * the system prompt can't both be it — so a single winner is picked and the\n * losers are reported in {@link SystemMdResult.shadowed} for the UI to show.\n * A surprising precedence should be visible, not mysterious.\n *\n * Search order (winner first):\n *\n * 1. `<project root>/SYSTEM.md` — project scope, most specific, wins\n * 2. `~/.{prefix}/SYSTEM.md` — user scope (`~/.zidane/SYSTEM.md`)\n *\n * What the override does NOT replace: the `AGENTS.md` block (a separate,\n * additive layer) and the `<env>` section below the prompt-cache boundary.\n * Stripping `<env>` would take the agent's cwd + project root with it and\n * silently break path resolution in the file tools — an override is for\n * rewriting the agent's instructions, not amputating its sense of place.\n */\n\nimport type { AgentsMdScope } from './agents-md'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { findGitRoot } from './project-root'\n\n/** A discovered `SYSTEM.md`. */\nexport interface SystemMdFile {\n /** Absolute path of the file. */\n path: string\n /** Whether the file is the project's or the user's. */\n source: 'project' | 'user'\n /** File text. */\n contents: string\n}\n\n/** Result of {@link discoverSystemMd}. */\nexport interface SystemMdResult {\n /**\n * The winning override, or `null` when no `SYSTEM.md` exists (the built-in\n * doctrine then applies unchanged).\n */\n file: SystemMdFile | null\n /**\n * Files that exist but lost to `file` on precedence. Non-empty only when the\n * user has both a project and a user `SYSTEM.md`. Surfaced so the UI can mark\n * them as inactive instead of leaving the user wondering why edits to one of\n * them changed nothing.\n */\n shadowed: readonly SystemMdFile[]\n}\n\n/** Same per-file cap as `agents-md.ts` — a runaway file shouldn't blow the prompt. */\nconst MAX_BYTES_PER_FILE = 64 * 1024\n\n/** Options for {@link discoverSystemMd}. */\nexport interface DiscoverSystemMdOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n /**\n * Which scopes may contribute an override — the same\n * {@link AgentsMdScope} the host applies to `AGENTS.md`, so one setting\n * governs both surfaces:\n * - `'none'` — no override, ever. Short-circuits before any FS probe.\n * - `'user'` — only `~/.{prefix}/SYSTEM.md`. The repo's file is IGNORED,\n * which is the whole point of the setting: it is how a user\n * says \"do not trust this checkout\".\n * - `'project'` — only `<root>/SYSTEM.md`.\n * - `'both'` — union, project winning (default).\n */\n scope?: AgentsMdScope\n}\n\n/**\n * Absolute path of the USER-scope `SYSTEM.md` — the file the TUI creates when\n * the user asks for an override and none exists yet.\n *\n * Exported so the host doesn't hardcode the layout and can't drift from what\n * discovery actually reads.\n *\n * @param opts - Overrides for the home directory and dot-directory prefix.\n */\nexport function userSystemMdPath(opts: { home?: string, prefix?: string } = {}): string {\n const home = opts.home ?? homedir()\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n return join(home, `.${prefix}`, 'SYSTEM.md')\n}\n\n/**\n * Whether a USER-scope `SYSTEM.md` would be loaded under `scope`.\n *\n * Mirrors the candidate filter in {@link discoverSystemMd} so a host can ask\n * \"would writing `~/.{prefix}/SYSTEM.md` accomplish anything?\" without\n * re-deriving the rule and drifting from it. `'project'` excludes the user\n * candidate and `'none'` skips discovery altogether, so under either setting\n * creating that file is a silent no-op.\n *\n * @param scope - The active `userInstructionsScope`. Treated as `both` when undefined.\n */\nexport function userScopeOverridesLoad(scope: AgentsMdScope | undefined): boolean {\n const effective: AgentsMdScope = scope ?? 'both'\n return effective !== 'project' && effective !== 'none'\n}\n\n/**\n * Whether a PROJECT-scope `SYSTEM.md` would be loaded under `scope`.\n *\n * The mirror of {@link userScopeOverridesLoad}, against the same candidate\n * filter. Used by {@link renderSystemMdSeed} to decide whether telling the user\n * \"a repo file outranks this one\" is true: under `'user'` the project candidate\n * is never even probed, so claiming a checkout can outrank them contradicts the\n * setting they chose precisely to distrust checkouts.\n */\nexport function projectScopeOverridesLoad(scope: AgentsMdScope | undefined): boolean {\n const effective: AgentsMdScope = scope ?? 'both'\n return effective !== 'user' && effective !== 'none'\n}\n\n/**\n * Find the active `SYSTEM.md` override, if any.\n *\n * Project paths anchor at the git root when one exists above `cwd`, else at\n * `cwd` — the same anchor as skills / mcps / AGENTS.md discovery, so all four\n * surfaces agree on what \"this project\" means.\n *\n * Tolerant: an unreadable or empty file is treated as absent (logged under\n * `ZIDANE_DEBUG`) and the next candidate gets its turn. Never throws — this\n * runs on the session-activation path.\n *\n * Candidates are filtered by {@link DiscoverSystemMdOptions.scope}, mirroring\n * how `discoverAgentsMd` builds its own list.\n *\n * @param opts - Roots to search and which scopes to consult.\n */\nexport function discoverSystemMd(opts: DiscoverSystemMdOptions = {}): SystemMdResult {\n const scope: AgentsMdScope = opts.scope ?? 'both'\n // Short-circuit before touching the filesystem, same as `discoverAgentsMd`.\n if (scope === 'none')\n return { file: null, shadowed: [] }\n\n const cwd = opts.cwd ?? process.cwd()\n const projectRoot = findGitRoot(cwd) ?? cwd\n\n const candidates: { path: string, source: 'project' | 'user' }[] = []\n if (scope !== 'user')\n candidates.push({ path: join(projectRoot, 'SYSTEM.md'), source: 'project' })\n if (scope !== 'project')\n candidates.push({ path: userSystemMdPath(opts), source: 'user' })\n\n const found: SystemMdFile[] = []\n const seen = new Set<string>()\n for (const c of candidates) {\n // Dedup by absolute path. The two candidates can only ever be the same\n // file when `prefix` is empty or dot-only: `userSystemMdPath` then\n // collapses to `<home>/SYSTEM.md`, which equals the project candidate if\n // `projectRoot` is also `<home>`. With any real prefix (`.zidane`) they are\n // `<root>/SYSTEM.md` and `<home>/.zidane/SYSTEM.md` — distinct whether or\n // not `$HOME` happens to be a git repo. Without the guard that one case\n // would report the file as shadowing itself.\n if (seen.has(c.path))\n continue\n seen.add(c.path)\n const file = readIfPresent(c.path, c.source)\n if (file)\n found.push(file)\n }\n\n const [winner, ...shadowed] = found\n return { file: winner ?? null, shadowed }\n}\n\nfunction readIfPresent(path: string, source: 'project' | 'user'): SystemMdFile | null {\n if (!existsSync(path))\n return null\n let raw: string\n try {\n raw = readFileSync(path, 'utf8')\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] system-md: failed to read \"${path}\": ${errorMessage(err)}\\n`)\n return null\n }\n const contents = raw.length > MAX_BYTES_PER_FILE\n ? `${raw.slice(0, MAX_BYTES_PER_FILE)}\\n\\n[... truncated; ${raw.length - MAX_BYTES_PER_FILE} more bytes in source file ...]`\n : raw\n // An empty override is almost certainly an accident (a file created but not\n // yet written). Treating it as absent keeps the built-in doctrine in place\n // rather than handing the model a blank prompt.\n if (contents.trim().length === 0)\n return null\n return { path, source, contents }\n}\n\n/**\n * Seed contents for a freshly created `SYSTEM.md` override, built from the\n * built-in doctrine.\n *\n * Seeding with the real doctrine (rather than an empty file) means creating the\n * override is a behavioral no-op until the user edits it. An empty seed would\n * silently delete every line of doctrine the moment the file appeared, making\n * the agent measurably worse with no visible cause.\n *\n * `currentSystem` should be DOCTRINE ONLY — callers pass\n * `splitSystemPrompt(doctrineOnlySystem()).static`, which excludes both the\n * generated `<env>` block (rebuilt per turn) and the `AGENTS.md` block. Baking\n * the latter in would duplicate it, since it is re-appended below the override\n * on every compose.\n *\n * `scope` is the active `userInstructionsScope`; it decides whether the seed's\n * precedence note is true — see {@link projectScopeOverridesLoad}.\n *\n * @param currentSystem - The built-in doctrine to seed the file with.\n * @param scope - The active `userInstructionsScope`. Decides whether the precedence note mentions a project file.\n */\nexport function renderSystemMdSeed(currentSystem: string, scope?: AgentsMdScope): string {\n return [\n '<!--',\n ' SYSTEM.md replaces zidane\\'s built-in system prompt.',\n '',\n ' It was seeded with the built-in doctrine, so right now it changes nothing.',\n ' Edit freely.',\n '',\n ' Still applied on top of whatever you write here:',\n ' - your AGENTS.md / CLAUDE.md instructions (appended below this text)',\n ' - the <env> block (cwd, project root, date, platform)',\n '',\n // Only true when the project candidate is actually probed. Under\n // `userInstructionsScope: 'user'` it is not, and creation is still allowed\n // there — so printing this unconditionally told the one user who opted out\n // of project files that a checkout outranks them.\n ...(projectScopeOverridesLoad(scope)\n ? [' A project-level SYSTEM.md at the repo root takes precedence over this one.']\n : [' userInstructionsScope is \"user\", so a repo-level SYSTEM.md is ignored and', ' this file always wins.']),\n ' Delete this file to restore the built-in prompt.',\n '-->',\n '',\n currentSystem.trim(),\n '',\n ].join('\\n')\n}\n","/**\n * Which settings a host owns when a window edits it (the active session\n * runs on a server), and which stay the window's. Shared by the TUI's and\n * the GUI's Settings surfaces.\n *\n * Window-only: how the window looks and behaves (the UI category, theme,\n * fps, icons, keyboard and editor habits) and which hosts it uses\n * (`WINDOW_LOCAL_SETTINGS`). Everything else (safe mode, sandbox, limits,\n * compaction, the enabled skills / MCP servers / extensions…) shapes the\n * host's agents, so it is read with `settings.get` and written with\n * `settings.set` on that host, never into this computer's `state.json`.\n */\nimport type { ApprovalMode, Settings } from './types'\nimport { SETTINGS_CHOICES, SETTINGS_INPUTS, SETTINGS_TOGGLES } from './settings-catalog'\nimport { approvalModeFromSafeMode } from './settings-defaults'\n\n/**\n * Settings that belong to the window even while a host owns the settings\n * file (daemon or remote mode): which host the next launch uses.\n */\nexport const WINDOW_LOCAL_SETTINGS = ['useDaemon', 'remoteHosts'] as const satisfies readonly (keyof Settings)[]\n\n/** Agent-category keys that are still about the window, not the agents. */\nconst WINDOW_ONLY_KEYS: readonly (keyof Settings)[] = [\n 'theme',\n 'targetFps',\n 'mouseInteractions',\n 'uiMode',\n 'ctrlCBehavior',\n 'terminalTitle',\n 'sessionIcon',\n 'attentionIcon',\n 'activeIcon',\n 'thinkingBinding',\n 'toolCallDisplay',\n 'editDiffDisplay',\n 'transcriptDensity',\n 'resumeLastSession',\n 'externalEditor',\n 'inlineAutocomplete',\n 'checkForUpdates',\n 'subscriptionUsage',\n 'openIn',\n // How this computer groups its sessions (the `spaces` toggle is `ui`, so window-only already).\n 'spaceList',\n // What this computer's new sessions start on: the model names its own accounts, the agent its extensions.\n 'defaultAgent',\n 'defaultModel',\n 'defaultEffort',\n 'defaultModelOptions',\n]\n\nconst WINDOW_ONLY: ReadonlySet<string> = new Set<string>([\n ...WINDOW_LOCAL_SETTINGS,\n ...WINDOW_ONLY_KEYS,\n ...[...SETTINGS_TOGGLES, ...SETTINGS_CHOICES, ...SETTINGS_INPUTS].filter(row => row.category === 'ui').map(row => row.key),\n])\n\n/**\n * Tells whether a settings key stays with the window instead of the host.\n *\n * @param key - Settings key to check.\n * @returns `true` for window-only keys (UI settings and {@link WINDOW_LOCAL_SETTINGS}).\n */\nexport function isWindowOnlySetting(key: keyof Settings | string): boolean {\n return WINDOW_ONLY.has(key)\n}\n\n/** The allowlists a host leaving them unset means \"everything enabled\" for. */\nexport const HOST_ENABLED_LISTS = ['enabledSkills', 'enabledMcps', 'enabledExtensions', 'enabledPlugins', 'disabledMcpTools'] as const satisfies readonly (keyof Settings)[]\n\n/**\n * Settings as sent to a host or to a window. Peers older than `approvalMode`\n * know tool approvals only as the legacy `safeMode` boolean, so it rides\n * along; a current peer drops it (`settingsFromWire`).\n *\n * @param values - Settings, whole or partial; `safeMode` is added only when they carry `approvalMode`.\n */\nexport function settingsToWire<T extends object>(values: T): T & { safeMode?: boolean } {\n return 'approvalMode' in values ? { ...values, safeMode: values.approvalMode !== 'off' } : { ...values }\n}\n\n/**\n * Settings from a peer as this side reads them, without `safeMode`. Beside\n * a matching `approvalMode` it is only the copy `settingsToWire` adds;\n * alone, or contradicting an `approvalMode` (one a newer client stored on\n * an older server, which kept enforcing `safeMode`), it is the peer's\n * actual state and maps onto the approval mode.\n *\n * @param values - The received values.\n * @param current - The approval mode a lone `safeMode: true` keeps (unless `'off'`).\n */\nexport function settingsFromWire<T extends object>(values: T & { safeMode?: unknown }, current: ApprovalMode): Omit<T, 'safeMode'> {\n const { safeMode, ...rest } = values\n const stated = 'approvalMode' in rest ? rest.approvalMode : undefined\n if (typeof safeMode !== 'boolean' || (stated !== undefined && (stated !== 'off') === safeMode))\n return rest\n return { ...rest, approvalMode: approvalModeFromSafeMode(safeMode, stated === 'off' ? 'off' : current) }\n}\n\n/**\n * `local` with the host's values for every key the host owns.\n *\n * @param local - The window's own settings.\n * @param host - Settings read from the host (`settings.get`, `settings.set`). Window-only keys in it are ignored.\n */\nexport function overlayHostSettings<S extends Settings>(local: S, host: Partial<Settings> & { safeMode?: boolean }): S {\n const merged = { ...local } as Record<string, unknown>\n // A server older than `approvalMode` reports only `safeMode`, which asks when on.\n for (const [key, value] of Object.entries(settingsFromWire(host, 'ask'))) {\n if (!isWindowOnlySetting(key))\n merged[key] = value\n }\n // Allowlists the host leaves unset mean \"everything enabled\": not this window's lists.\n for (const key of HOST_ENABLED_LISTS) {\n if (!(key in host))\n delete merged[key]\n }\n return merged as S\n}\n\n/**\n * Split a settings patch into the window's part and the host's part.\n *\n * @param patch - Settings patch to partition by owner.\n */\nexport function splitHostSettingsPatch<S extends Partial<Settings>>(patch: S): { window: Partial<S>, host: Partial<S> } {\n const window: Record<string, unknown> = {}\n const host: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch))\n (isWindowOnlySetting(key) ? window : host)[key] = value\n return { window: window as Partial<S>, host: host as Partial<S> }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"host-scoped-settings-DxxI_yRo.js","names":[],"sources":["../src/chat/shell-parse.ts","../src/chat/safe-mode.ts","../src/chat/foreign-configs.ts","../src/chat/ruler-configs.ts","../src/chat/mcps-discovery.ts","../src/chat/system-md.ts","../src/chat/host-scoped-settings.ts"],"sourcesContent":["/**\n * Lightweight shell command parser for safelist enforcement.\n *\n * Extracts the head token (program name) of every command in a bash\n * command string — including chains (`&&`, `||`, `;`), pipes (`|`),\n * subshells (`(…)`), and command substitutions (`$(…)` and backticks).\n *\n * Handles quoting (`'…'`, `\"…\"`, `\\x`), variable assignments\n * (`NAME=val`), I/O redirection, here-documents (`<<`, `<<-`), `#`\n * comments, and the `!` negation prefix.\n *\n * Returns `null` when parsing fails so callers can fall back to\n * prompting (safe default).\n */\n\n/**\n * The head token (program name) of every command in `command`, in source\n * order, including those inside pipes, subshells, substitutions and expanding\n * here-document bodies. Returns `[]` for an empty or assignment-only command\n * and `null` when the parser gives up.\n *\n * @param command - The bash command string to parse.\n */\nexport function extractCommandHeads(command: string): string[] | null {\n try {\n const p = new ShellParser(command)\n p.list()\n return p.heads\n }\n catch {\n return null\n }\n}\n\n// Characters that unconditionally end an unquoted word.\nconst META = new Set(' \\t\\n\\r;|&()<>'.split(''))\n\ninterface PendingHeredoc {\n delim: string\n stripTabs: boolean\n /** Any quoting in the delimiter word disables expansion in the body. */\n quoted: boolean\n}\n\nclass ShellParser {\n readonly heads: string[] = []\n private i = 0\n /** Heredocs opened on the current line, bodies not yet consumed. */\n private pendingHeredocs: PendingHeredoc[] = []\n\n constructor(private readonly s: string) {}\n\n // -- public entry point ------------------------------------------------\n\n /** Command list — pipelines separated by `;`, `&&`, `||`, `\\n`, `&`. */\n list(closer?: string): void {\n for (;;) {\n this.ws()\n if (this.end(closer))\n return\n\n this.pipeline(closer)\n\n this.ws()\n if (this.end(closer))\n return\n const c = this.c()\n if (c === ';') { this.i++; continue }\n if (c === '\\n' || c === '\\r') { this.newline(); continue }\n if (this.is('&&') || this.is('||')) { this.i += 2; continue }\n if (c === '&') { this.i++; continue }\n break\n }\n }\n\n // -- grammar -----------------------------------------------------------\n\n /** Pipeline — simple commands separated by `|` (not `||`). */\n private pipeline(closer?: string): void {\n this.cmd(closer)\n for (;;) {\n this.ws()\n if (this.c() === '|' && !this.is('||')) { this.i++; this.cmd(closer) }\n else {\n break\n }\n }\n }\n\n /** Simple command — extract head, skip remaining arguments. */\n private cmd(closer?: string): void {\n this.ws()\n if (this.i >= this.s.length)\n return\n if (closer && this.c() === closer)\n return\n\n // Subshell.\n if (this.c() === '(') {\n this.i++\n this.list(')')\n return\n }\n\n // `!` negation prefix — skip when followed by whitespace.\n if (this.c() === '!' && this.i + 1 < this.s.length\n && (this.s[this.i + 1] === ' ' || this.s[this.i + 1] === '\\t')) {\n this.i++; this.ws()\n }\n\n // Read tokens, skipping leading variable assignments (NAME=val).\n while (this.i < this.s.length && !this.sep(closer)) {\n if (this.c() === '(') {\n this.i++\n this.list(')')\n break\n }\n const w = this.word()\n if (!w)\n break\n if (isAssignment(w)) { this.ws(); continue }\n this.heads.push(w)\n break\n }\n\n this.tail(closer)\n }\n\n // -- word-level --------------------------------------------------------\n\n /** Read one shell word, handling quoting and substitutions. */\n private word(): string {\n let out = ''\n while (this.i < this.s.length) {\n const c = this.c()\n if (META.has(c))\n break\n\n if (c === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (c === '\\'') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] !== '\\'') out += this.s[this.i++]\n if (this.i < this.s.length)\n this.i++\n continue\n }\n if (c === '\"') { out += this.dquote(); continue }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (c === '`') { this.btick(); continue }\n out += c; this.i++\n }\n return out\n }\n\n /** Double-quoted string — returns literal chars, recurses into substitutions. */\n private dquote(): string {\n this.i++\n let out = ''\n while (this.i < this.s.length && this.s[this.i] !== '\"') {\n if (this.s[this.i] === '\\\\' && this.i + 1 < this.s.length) {\n out += this.s[this.i + 1]\n this.i += 2\n continue\n }\n if (this.is('$(')) {\n this.i += 2\n this.list(')')\n continue\n }\n if (this.s[this.i] === '`') { this.btick(); continue }\n out += this.s[this.i++]\n }\n if (this.i < this.s.length)\n this.i++\n return out\n }\n\n /** Backtick substitution — creates a sub-parser for the inner content. */\n private btick(): void {\n this.i++\n const close = this.s.indexOf('`', this.i)\n const inner = close < 0 ? this.s.slice(this.i) : this.s.slice(this.i, close)\n const sub = new ShellParser(inner)\n sub.list()\n this.heads.push(...sub.heads)\n this.i = close < 0 ? this.s.length : close + 1\n }\n\n // -- tail / redirections -----------------------------------------------\n\n /** Skip remaining arguments and redirections until the next command boundary. */\n private tail(closer?: string): void {\n while (this.i < this.s.length) {\n this.ws()\n if (this.i >= this.s.length || this.sep(closer))\n break\n if (this.redir())\n continue\n if (this.c() === '(') {\n this.i++\n this.list(')')\n continue\n }\n const w = this.word()\n if (!w)\n break\n }\n }\n\n /** Try to consume an I/O redirection operator + target. Returns true if consumed. */\n private redir(): boolean {\n const c = this.c()\n let yes = c === '<' || c === '>'\n if (!yes && c >= '0' && c <= '9' && this.i + 1 < this.s.length) {\n const n = this.s[this.i + 1]\n yes = n === '<' || n === '>'\n }\n if (!yes)\n return false\n\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n if (this.c() === '>') {\n this.i++\n if (this.c() === '>')\n this.i++\n if (this.c() === '&') {\n this.i++\n while (this.i < this.s.length && this.s[this.i] >= '0' && this.s[this.i] <= '9') this.i++\n return true\n }\n }\n else if (this.c() === '<') {\n this.i++\n if (this.c() === '<') {\n this.i++\n // `<<<` is a here-string: the operand is an ordinary word, not a\n // delimiter, so fall through to the generic target read below.\n if (this.c() === '<') {\n this.i++\n }\n else {\n let stripTabs = false\n if (this.c() === '-') { this.i++; stripTabs = true }\n this.ws()\n // `word()` strips the quotes of `<<'EOF'` / `<<\"EOF\"`; the\n // terminator line is the bare delimiter either way.\n const start = this.i\n const delim = this.word()\n const quoted = /['\"\\\\]/.test(this.s.slice(start, this.i))\n if (delim)\n this.pendingHeredocs.push({ delim, stripTabs, quoted })\n return true\n }\n }\n }\n this.ws()\n this.word()\n return true\n }\n\n /** Consume one newline (`\\r\\n` counts once) plus any heredoc bodies it opens. */\n private newline(): void {\n this.i += this.is('\\r\\n') ? 2 : 1\n this.heredocs()\n }\n\n /**\n * Skip the bodies of heredocs opened on the line just ended, in the order\n * they were introduced. Without this the body is tokenized as shell code\n * and each line's first word lands in `heads` — so a command that embeds a\n * script (`python3 - <<'PY' … PY`) yields a different head set on every\n * call and can never be safelisted.\n *\n * An unquoted delimiter (`<<EOF`) still expands `$(…)` and backticks in\n * the body, so those substitutions are parsed for heads.\n */\n private heredocs(): void {\n if (this.pendingHeredocs.length === 0)\n return\n const pending = this.pendingHeredocs\n this.pendingHeredocs = []\n for (const { delim, stripTabs, quoted } of pending) {\n const body: string[] = []\n while (this.i < this.s.length) {\n const nl = this.s.indexOf('\\n', this.i)\n const raw = nl < 0 ? this.s.slice(this.i) : this.s.slice(this.i, nl)\n this.i = nl < 0 ? this.s.length : nl + 1\n const line = raw.endsWith('\\r') ? raw.slice(0, -1) : raw\n if ((stripTabs ? line.replace(/^\\t+/, '') : line) === delim)\n break\n body.push(line)\n }\n if (!quoted)\n this.bodySubstitutions(body.join('\\n'))\n }\n }\n\n /** Collect heads of `$(…)` and backtick substitutions in an expanding heredoc body. */\n private bodySubstitutions(body: string): void {\n let k = 0\n while (k < body.length) {\n const c = body[k]\n if (c === '\\\\') { k += 2; continue }\n if (c === '$' && body[k + 1] === '(') {\n const sub = new ShellParser(body.slice(k + 2))\n sub.list(')')\n this.heads.push(...sub.heads)\n k += 2 + sub.i\n continue\n }\n if (c === '`') {\n const sub = new ShellParser(body.slice(k))\n sub.btick()\n this.heads.push(...sub.heads)\n k += sub.i\n continue\n }\n k++\n }\n }\n\n // -- predicates --------------------------------------------------------\n\n /** True at a command boundary (`;`, `\\n`, `&`, `|`, `)`, or closer). */\n private sep(closer?: string): boolean {\n const c = this.c()\n if (c === ';' || c === '\\n' || c === '\\r' || c === '&' || c === ')')\n return true\n if (c === '|')\n return true\n if (closer && c === closer)\n return true\n return false\n }\n\n /**\n * True when past end or at closer (closer is consumed).\n *\n * Throws when a group closes with a heredoc still pending: the parser has\n * no notion of arithmetic, so `$((x<<1))` / `(( x << 1 ))` reads the shift\n * as a heredoc whose body would swallow (and hide) the following lines.\n */\n private end(closer?: string): boolean {\n if (this.i >= this.s.length)\n return true\n if (closer && this.c() === closer) {\n if (this.pendingHeredocs.length > 0)\n throw new Error('heredoc pending at group close')\n this.i++\n return true\n }\n return false\n }\n\n private c(): string { return this.s[this.i] ?? '' }\n private is(s: string): boolean { return this.s.startsWith(s, this.i) }\n /**\n * Skip blanks, then a `#` comment when it starts a word (bash's rule): a\n * heredoc operator inside a comment must never register, or the lines\n * after it would be skipped as its body and hidden from the safelist.\n * Stops before the newline so the caller still sees the boundary.\n */\n private ws(): void {\n while (this.i < this.s.length && (this.s[this.i] === ' ' || this.s[this.i] === '\\t')) this.i++\n if (this.c() === '#' && (this.i === 0 || META.has(this.s[this.i - 1]))) {\n const nl = this.s.indexOf('\\n', this.i)\n this.i = nl < 0 ? this.s.length : nl\n }\n }\n}\n\n/** `NAME=…` where NAME is a valid shell identifier. */\nfunction isAssignment(w: string): boolean {\n const eq = w.indexOf('=')\n if (eq <= 0)\n return false\n return /^[A-Z_]\\w*$/i.test(w.slice(0, eq))\n}\n","/**\n * Safe-mode storage + matching for the TUI.\n *\n * Lives at `<dataDir>/projects.json` (default `~/.zidane/projects.json`). Each\n * top-level key is an absolute project directory; the value carries that\n * project's persisted tool-call `safelist`.\n *\n * ```json\n * {\n * \"/Users/me/proj-a\": { \"safelist\": [\"read_file\", \"shell:git:*\"] }\n * }\n * ```\n *\n * Two granularities for safelist entries:\n * - **bare tool name** — `\"read_file\"` matches every `read_file` call.\n * - **tool + first-arg token + wildcard** — `\"shell:git:*\"` matches `shell`\n * calls whose primary string argument starts with the token `git`\n * (followed by whitespace or end-of-string). Modelled on Claude Code's\n * `Bash(git:*)` syntax.\n *\n * A short list of read-only tools is **implicitly safe** without being\n * persisted — see {@link IMPLICITLY_SAFE_TOOLS}.\n */\n\nimport { existsSync, readFileSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { writeFileAtomic } from '../atomic-write'\nimport { isLocalBrowserUrl, normalizeBrowserUrl } from './browser/url'\nimport { findGitRoot, projectRootOf } from './project-root'\nimport { extractCommandHeads } from './shell-parse'\n\n/**\n * The `projects.json` key for a working directory: its project root (the\n * main checkout for a linked worktree; the dir itself outside git) — the\n * same \"project\" the TUI scopes its safelist to, so a subdirectory session,\n * every worktree of the repo, and every host share one safelist.\n *\n * @param cwd - Working directory to resolve.\n */\nexport function safelistProjectKey(cwd: string): string {\n return projectRootOf(cwd)\n}\n\n// ---------------------------------------------------------------------------\n// Persistence\n// ---------------------------------------------------------------------------\n\n/** Per-project settings stored in `projects.json`. */\nexport interface ProjectEntry {\n /** Tool entries the user approved for this project. */\n safelist?: string[]\n}\n\n/** Contents of `projects.json`: project entries keyed by project root path. */\nexport type ProjectsFile = Record<string, ProjectEntry>\n\n/**\n * Resolve `projects.json`'s on-disk path given the TUI data directory.\n *\n * @param dataDir - Directory that holds `projects.json`.\n */\nexport function projectsFilePath(dataDir: string): string {\n return resolve(dataDir, 'projects.json')\n}\n\n/**\n * Reads `projects.json`. Returns `{}` when the file is missing or unreadable.\n *\n * @param dataDir - Directory that holds the file.\n */\nexport function readProjects(dataDir: string): ProjectsFile {\n const path = projectsFilePath(dataDir)\n if (!existsSync(path))\n return {}\n try {\n const parsed = JSON.parse(readFileSync(path, 'utf-8'))\n if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))\n return parsed as ProjectsFile\n }\n catch {\n // Corrupt file → treat as empty so the user can re-bootstrap.\n }\n return {}\n}\n\n/**\n * Writes `projects.json` atomically, creating the directory when needed.\n *\n * @param dataDir - Directory that holds the file.\n * @param file - The full projects map to write.\n */\nexport function writeProjects(dataDir: string, file: ProjectsFile): void {\n writeFileAtomic(projectsFilePath(dataDir), JSON.stringify(file, null, 2), { ensureDir: true })\n}\n\n/**\n * Append one or more entries to the safelist for `projectDir`, dedup-aware.\n * Accepts a single string or a list (a compound shell command needs one\n * entry per command head — see {@link suggestSafelistEntries}). Writes once\n * and returns the updated entry list (post-write) so callers can render it\n * without re-reading. No write happens when every entry already exists.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param projectDir - Project key the entries are stored under.\n * @param entries - One entry or a list. Entries already present are skipped.\n */\nexport function addToSafelist(\n dataDir: string,\n projectDir: string,\n entries: string | readonly string[],\n): readonly string[] {\n const toAdd = typeof entries === 'string' ? [entries] : entries\n const file = readProjects(dataDir)\n const existing = file[projectDir]?.safelist ?? []\n const next = [...existing]\n for (const entry of toAdd) {\n if (!next.includes(entry))\n next.push(entry)\n }\n if (next.length === existing.length)\n return existing\n file[projectDir] = { ...file[projectDir], safelist: next }\n writeProjects(dataDir, file)\n return next\n}\n\n/**\n * Read the safelist for one project. Returns `[]` for unknown projects.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param projectDir - Project key to read.\n */\nexport function getSafelist(dataDir: string, projectDir: string): readonly string[] {\n return readProjects(dataDir)[projectDir]?.safelist ?? []\n}\n\n/**\n * The safelist in effect for a working directory — what every host's gate\n * must read. The project's entries ({@link safelistProjectKey}), plus\n * entries saved under older keys for the same place, still honored but never\n * written again:\n *\n * - the CHECKOUT root — the TUI keyed a linked worktree by its own dir\n * before worktrees resolved to their main checkout;\n * - the exact `cwd` — the daemon/GUI keyed by raw cwd before keys were\n * normalized to the project.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param cwd - Working directory. Entries under its project key, git root and own path are merged.\n */\nexport function getSafelistForCwd(dataDir: string, cwd: string): readonly string[] {\n const file = readProjects(dataDir)\n const keys = new Set([safelistProjectKey(cwd), findGitRoot(cwd) ?? cwd, cwd])\n const merged: string[] = []\n for (const key of keys) {\n for (const entry of file[key]?.safelist ?? []) {\n if (!merged.includes(entry))\n merged.push(entry)\n }\n }\n return merged\n}\n\n/**\n * {@link addToSafelist} under the project key for `cwd` — the write every host's gate uses.\n *\n * @param dataDir - Directory that holds `projects.json`.\n * @param cwd - Working directory. Entries are stored under its project key.\n * @param entries - One entry or a list. Entries already present are skipped.\n */\nexport function addToSafelistForCwd(dataDir: string, cwd: string, entries: string | readonly string[]): readonly string[] {\n return addToSafelist(dataDir, safelistProjectKey(cwd), entries)\n}\n\n// ---------------------------------------------------------------------------\n// Matching\n// ---------------------------------------------------------------------------\n\n/**\n * Tools that always pass without prompting. Four categories, plus the\n * dev-browser, terminal-read and side-session tools noted inline in the list:\n *\n * - **Pure reads** (`read_file`, `list_files`, `glob`, `grep`) — no\n * side effects on disk or the model's own state.\n * - **Interaction tools** (`ask_user`, `present_plan`) — the call\n * itself surfaces a TUI picker the user has to answer, so prompting\n * for approval first would mean \"approve this prompt before you see\n * the prompt\": redundant + breaks the flow. The picker IS the gate.\n * - **Todos** (`todowrite`, `todoread`) — write to a slot on\n * `session.metadata.todos` and nothing else. No shell, no disk, no\n * network. The model uses these for checkpointing every few steps;\n * a per-call approval prompt would drown the conversation in\n * interruptions that buy no safety since the tool's surface is\n * read/write on a metadata bag we already trust the agent with.\n * - **Skill activation** (`skills_use`) — mutates a small per-agent\n * Map of active skills and returns the activated skill's bundled\n * instructions (or releases an entry on `mode: \"deactivate\"`).\n * Pure state on the agent — no shell, no disk writes, no network.\n * Gating it would also defeat the \"model recovers from\n * `AgentToolNotAllowedError` by calling\n * `skills_use({ mode: \"deactivate\", name })`\" path: a stuck model\n * would have to ask the user permission to *unstick* itself on\n * every retry. `skills_read` and `skills_run_script` are\n * intentionally NOT on this list — the former touches disk and\n * the latter executes arbitrary scripts, so both keep going\n * through the regular gate.\n *\n * Users who want to gate any of these must disable safe-mode entirely\n * (or fork this list in their own embedding).\n */\nexport const IMPLICITLY_SAFE_TOOLS: readonly string[] = [\n 'read_file',\n 'list_files',\n 'glob',\n 'grep',\n 'ask_user',\n 'present_plan',\n 'todowrite',\n 'todoread',\n 'skills_use',\n // A side session reading the one conversation it was opened on.\n 'read_parent_session',\n // The dev browser only loads http(s) pages in its own profile; the one\n // browser tool that runs arbitrary code in the page, `browser_evaluate`,\n // keeps going through the gate. `browser_open` / `browser_navigate` are\n // only implicit for loopback targets (see `isOnSafelist`), so the agent\n // can't point the browser at an arbitrary host by URL. This is not an\n // exfiltration barrier: clicks, form submits and redirects on a loaded\n // page can still reach any http(s) host without a prompt.\n 'browser_open',\n 'browser_navigate',\n 'browser_snapshot',\n 'browser_screenshot',\n 'browser_click',\n 'browser_type',\n 'browser_press',\n 'browser_scroll',\n 'browser_wait_for',\n 'browser_console',\n // Reading the session's terminals; the user can hide one from the agent.\n // `terminal_write` types into a shell, so it is gated like `shell`.\n 'terminal_list',\n 'terminal_read',\n]\n\n/** Common input keys carrying the \"primary argument\" we scope safelists on. */\nconst PRIMARY_ARG_KEYS = ['command', 'path', 'pattern', 'query'] as const\n\nfunction primaryArgValue(input: Record<string, unknown>): string {\n for (const key of PRIMARY_ARG_KEYS) {\n const v = input[key]\n if (typeof v === 'string' && v.length > 0)\n return v\n }\n return ''\n}\n\n/**\n * Extract the first whitespace-delimited token of the primary arg.\n * Leading whitespace is trimmed first so `\" git status\"` tokenizes to\n * `\"git\"`, not `\"\"` (an empty first element from `.split(/\\s+/)`).\n */\nfunction primaryArgToken(input: Record<string, unknown>): string {\n return primaryArgValue(input).trim().split(/\\s+/)[0] ?? ''\n}\n\n/**\n * Test whether a `{ tool, input }` pair is covered by one safelist entry.\n *\n * Supported entry shapes:\n * - `\"<tool>\"` — broad match on tool name.\n * - `\"<tool>:<token>:*\"` — match when the primary arg's first token\n * equals `<token>`.\n * - `\"browser_open:<host>\"` / `\"browser_navigate:<host>\"` — match a\n * load of that host (port included).\n *\n * This function matches a **single command** against a **single entry**.\n * For shell commands that chain multiple programs (`&&`, `||`, `;`,\n * `$(…)`, etc.), use {@link isOnSafelist} which parses the full command\n * and checks every head token independently.\n *\n * Entries that don't fit either shape are ignored (forward-compat for\n * future pattern syntax — readers shouldn't choke on entries written\n * by a newer version of the TUI).\n *\n * @param entry - One safelist entry: a tool name, or `tool:scope` for a scoped one.\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function matchesSafelistEntry(\n entry: string,\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (entry === tool)\n return true\n const sep = entry.indexOf(':')\n if (sep <= 0)\n return false\n if (entry.slice(0, sep) !== tool)\n return false\n const scope = entry.slice(sep + 1)\n if (scope.endsWith(':*'))\n return primaryArgToken(input) === scope.slice(0, -2)\n if (isBrowserLoadTool(tool))\n return browserScope(input) === scope\n return false\n}\n\n/**\n * True when a call matches ANY entry in the project's safelist (or is\n * implicitly safe).\n *\n * For `shell` commands, the full command string is parsed into individual\n * commands (handling `&&`, `||`, `;`, `|`, `$(…)`, backticks, subshells).\n * Every command head must be covered by at least one safelist entry for\n * the call to pass. If parsing fails, returns `false` (prompt the user).\n *\n * @param entries - The project's safelist.\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function isOnSafelist(\n entries: readonly string[],\n tool: string,\n input: Record<string, unknown>,\n): boolean {\n if (IMPLICITLY_SAFE_TOOLS.includes(tool) && !isRemoteBrowserTarget(tool, input))\n return true\n\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n const heads = extractCommandHeads(cmd)\n if (heads === null)\n return false\n if (heads.length === 0)\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n return heads.every(head =>\n entries.some(e => matchesSafelistEntry(e, tool, { ...input, command: head })),\n )\n }\n\n return entries.some(e => matchesSafelistEntry(e, tool, input))\n}\n\n/** A `browser_open` / `browser_navigate` call that would load a non-loopback address. */\nfunction isRemoteBrowserTarget(tool: string, input: Record<string, unknown>): boolean {\n if (!isBrowserLoadTool(tool))\n return false\n if (typeof input.url !== 'string' || !input.url.trim())\n return false\n const url = normalizeBrowserUrl(input.url)\n return !url || !isLocalBrowserUrl(url)\n}\n\nfunction isBrowserLoadTool(tool: string): boolean {\n return tool === 'browser_open' || tool === 'browser_navigate'\n}\n\n/** What a remembered browser load is scoped to: the URL's host, else the raw URL. */\nfunction browserScope(input: Record<string, unknown>): string {\n const raw = typeof input.url === 'string' ? input.url.trim() : ''\n const url = raw ? normalizeBrowserUrl(raw) : null\n let host = ''\n try {\n host = url ? new URL(url).host : ''\n }\n catch {}\n return host || raw\n}\n\n/**\n * Suggest the safelist entries to persist when the user picks \"accept and\n * remember\" (or \"accept for session\") for a `{ tool, input }`. Heuristic:\n *\n * - `shell` → one `shell:<head>:*` per DISTINCT command head, parsing the\n * command the SAME way {@link isOnSafelist} matches it (`&&`, `||`, `;`,\n * `|`, `$(…)`, subshells, backticks). So `cd dir && cat file` yields\n * `[\"shell:cd:*\", \"shell:cat:*\"]` — persisting the whole set is what\n * lets the next identical call skip the gate. A single first-token\n * entry would only cover `cd`, so `isOnSafelist`'s `every-head` check\n * would fail on `cat` and re-prompt forever.\n * - `shell` with an unparseable / empty command → first-token heuristic,\n * else the bare tool name.\n * - `browser_open` / `browser_navigate` to a non-loopback address →\n * `<tool>:<host>`: approving one site does not approve the web.\n * - anything else → bare tool name (broad).\n *\n * Always returns a non-empty list so the UI has a concrete set to render\n * and persist.\n *\n * @param tool - Tool name.\n * @param input - The tool call's input.\n */\nexport function suggestSafelistEntries(\n tool: string,\n input: Record<string, unknown>,\n): string[] {\n if (tool === 'shell') {\n const cmd = typeof input.command === 'string' ? input.command : ''\n const heads = extractCommandHeads(cmd)\n if (heads && heads.length > 0) {\n // Emit one entry per head, deriving the token EXACTLY as\n // `isOnSafelist` looks it up (`primaryArgToken({ command: head })`)\n // and skipping nothing — that symmetry is what guarantees the\n // round-trip: whatever we persist here re-approves the same command\n // next time, for any command the parser accepts (a degenerate head\n // that tokenizes to \"\" yields `shell::*`, which the matcher checks\n // for the same \"\" token, so it still round-trips).\n const out: string[] = []\n for (const head of heads) {\n const entry = `${tool}:${primaryArgToken({ command: head })}:*`\n if (!out.includes(entry))\n out.push(entry)\n }\n return out\n }\n // No heads (assignment-only like `FOO=bar`, or empty/whitespace cmd):\n // mirror `isOnSafelist`'s `heads.length === 0` branch, which matches\n // against the whole input. `extractCommandHeads` returning null is the\n // only case that can't round-trip — the matcher refuses a null parse\n // outright — so this stays a best-effort one-shot grant there.\n const token = primaryArgToken(input)\n if (token)\n return [`${tool}:${token}:*`]\n }\n if (isRemoteBrowserTarget(tool, input)) {\n const scope = browserScope(input)\n if (scope)\n return [`${tool}:${scope}`]\n }\n return [tool]\n}\n","/**\n * Read-only discovery of MCP servers + skills from OTHER coding agents'\n * config locations — Claude Code, Codex, and Pi — at both project and\n * user level. Gated behind the `importForeignConfigs` setting (default\n * off): the files belong to the other tools, so zidane only ever READS\n * them; installs, edits, and toggles keep writing to zidane's own\n * `.zidane` / `.agents` locations.\n *\n * Locations covered (missing files/dirs are skipped silently):\n *\n * MCP servers Skills\n * ── project ────────────────────────── ── project ───────────────\n * {root}/.mcp.json (Claude Code) {root}/.claude/skills\n * ~/.claude.json projects[{root}] (same) {root}/.codex/skills\n * {root}/.pi/mcps.json + mcp.json (Pi) {root}/.pi/skills\n * ── user ───────────────────────────── ── user ──────────────────\n * ~/.claude.json (Claude Code) ~/.claude/skills\n * ~/.codex/config.toml (Codex) ~/.codex/skills\n * ~/.pi/agent/mcps.json + mcp.json (Pi) ~/.pi/agent/skills\n *\n * `.agents/` is NOT here — it's the cross-agent convention zidane\n * already reads natively (see `project-user-paths.ts`), setting or no\n * setting.\n *\n * Precedence is decided by the callers (`mcps-discovery` /\n * `skills-discovery`): native locations always beat foreign ones, and\n * project beats user within each group.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport type { ProjectUserPath } from './project-user-paths'\nimport { realpathSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { resolve, sep } from 'node:path'\nimport { normalizeMcpServers } from '../mcp'\nimport { findGitRoot } from './project-root'\n\n/**\n * How a foreign MCP file is parsed:\n * - `json` — the shared `mcp(s).json` grammar (`parseMcpsFile` handles\n * wrappers, name-keyed maps, and flat arrays).\n * - `claude-user-state` — `~/.claude.json` is Claude Code's whole\n * user-state file; ONLY its `mcpServers` key is imported (treating\n * the rest as a name-keyed server map would produce garbage).\n * - `claude-project-state` — the same file's\n * `projects[\"<abs project root>\"].mcpServers` map: where `claude\n * mcp add` (default \"local\" scope) stores per-project servers.\n * - `codex-toml` — `~/.codex/config.toml` with `[mcp_servers.<name>]`\n * tables.\n */\nexport type ForeignMcpFormat = 'json' | 'claude-user-state' | 'claude-project-state' | 'codex-toml'\n\n/** A foreign MCP config file: its path and scope (from {@link ProjectUserPath}) plus how to parse it. */\nexport interface ForeignMcpFile extends ProjectUserPath {\n /** Parser the file needs. */\n format: ForeignMcpFormat\n}\n\ninterface ForeignPathOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n}\n\nfunction roots(opts: ForeignPathOptions): { projectRoot: string, home: string } {\n const cwd = opts.cwd ?? process.cwd()\n return {\n projectRoot: findGitRoot(cwd) ?? cwd,\n home: opts.home ?? homedir(),\n }\n}\n\n/**\n * MCP config files other agents maintain, project entries first.\n * Returned regardless of existence — callers `existsSync`-filter, same\n * contract as `defaultMcpsConfigPaths`.\n */\nexport function foreignMcpConfigFiles(opts: ForeignPathOptions = {}): ForeignMcpFile[] {\n const { projectRoot, home } = roots(opts)\n return [\n // Claude Code's project-scope convention: `.mcp.json` at the repo\n // root (mcpServers wrapper — the shared JSON grammar unwraps it).\n { path: resolve(projectRoot, '.mcp.json'), source: 'project', format: 'json' },\n // … and `claude mcp add`'s default \"local\" scope, which lives in the\n // USER state file keyed by absolute project path — project-sourced\n // because that's what it means, wherever it's stored.\n { path: resolve(home, '.claude.json'), source: 'project', format: 'claude-project-state' },\n { path: resolve(projectRoot, '.pi/mcps.json'), source: 'project', format: 'json' },\n { path: resolve(projectRoot, '.pi/mcp.json'), source: 'project', format: 'json' },\n { path: resolve(home, '.claude.json'), source: 'user', format: 'claude-user-state' },\n { path: resolve(home, '.codex/config.toml'), source: 'user', format: 'codex-toml' },\n { path: resolve(home, '.pi/agent/mcps.json'), source: 'user', format: 'json' },\n { path: resolve(home, '.pi/agent/mcp.json'), source: 'user', format: 'json' },\n ]\n}\n\n/**\n * Skill directories other agents maintain, project entries first. Feed\n * directly into `discoverSkills` — the SKILL.md format is shared across\n * the ecosystem, so foreign skills parse with the same lenient loader.\n */\nexport function foreignSkillScanPaths(opts: ForeignPathOptions = {}): ProjectUserPath[] {\n const { projectRoot, home } = roots(opts)\n return [\n { path: resolve(projectRoot, '.claude/skills'), source: 'project' },\n { path: resolve(projectRoot, '.codex/skills'), source: 'project' },\n { path: resolve(projectRoot, '.pi/skills'), source: 'project' },\n { path: resolve(home, '.claude/skills'), source: 'user' },\n { path: resolve(home, '.codex/skills'), source: 'user' },\n { path: resolve(home, '.pi/agent/skills'), source: 'user' },\n ]\n}\n\n/**\n * Parse the two foreign-only formats. The `json` format is NOT handled\n * here — callers route it through the shared `parseMcpsFile` (keeping\n * whitespace sanitizing + error hints in one place, and avoiding an\n * import cycle with `mcps-discovery`).\n */\nexport function parseForeignMcpConfig(\n text: string,\n format: Exclude<ForeignMcpFormat, 'json'>,\n opts: { projectRoot?: string } = {},\n): McpServerConfig[] {\n if (format === 'codex-toml')\n return parseCodexToml(text)\n return parseClaudeState(JSON.parse(text), format, opts.projectRoot)\n}\n\n/**\n * Extract MCP servers from a PRE-PARSED `~/.claude.json` state object.\n * Split from {@link parseForeignMcpConfig} so a discovery pass that reads\n * the file for BOTH scopes (user + project — it appears twice in the path\n * list) can read and `JSON.parse` the multi-MB state file once.\n *\n * - `claude-user-state` — ONLY the top-level `mcpServers` map; an\n * absent/malformed key means \"nothing to import\" rather than an error,\n * since the file legitimately exists without it.\n * - `claude-project-state` — `projects[<dir>].mcpServers`, where `claude\n * mcp add` (default \"local\" scope) stores per-project servers.\n */\nexport function parseClaudeState(\n state: unknown,\n format: 'claude-user-state' | 'claude-project-state',\n projectRoot?: string,\n): McpServerConfig[] {\n if (!state || typeof state !== 'object' || Array.isArray(state))\n return []\n if (format === 'claude-user-state')\n return normalizeForeignEntries((state as { mcpServers?: unknown }).mcpServers)\n return claudeProjectServers(state as { projects?: unknown }, projectRoot)\n}\n\n/**\n * Claude keys `projects` by the directory it was LAUNCHED from — for a\n * monorepo that's often a subdirectory of the git root we resolve to, and\n * paths surface in realpath variants (`/var` vs `/private/var` on macOS,\n * symlinked homes). Accept every key that realpath-normalizes to the root\n * or to a directory inside it; nearer-to-root keys win name collisions so\n * a root-registered server beats a subdirectory duplicate.\n */\nfunction claudeProjectServers(state: { projects?: unknown }, projectRoot: string | undefined): McpServerConfig[] {\n if (!projectRoot)\n return []\n const projects = state.projects\n if (!projects || typeof projects !== 'object' || Array.isArray(projects))\n return []\n const rootKey = normalizePathKey(projectRoot)\n const matches = Object.entries(projects as Record<string, unknown>)\n .map(([key, value]) => ({ key: normalizePathKey(key), value }))\n .filter(({ key }) => key === rootKey || key.startsWith(rootKey + sep))\n .sort((a, b) => a.key.length - b.key.length)\n const out: McpServerConfig[] = []\n const seen = new Set<string>()\n for (const { value } of matches) {\n const servers = value && typeof value === 'object' ? (value as { mcpServers?: unknown }).mcpServers : undefined\n for (const config of normalizeForeignEntries(servers)) {\n if (seen.has(config.name))\n continue\n seen.add(config.name)\n out.push(config)\n }\n }\n return out\n}\n\n/** Realpath-normalize for key comparison; deleted paths fall back to `resolve`. */\nfunction normalizePathKey(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return resolve(path)\n }\n}\n\n/**\n * Normalize a name-keyed server map ENTRY BY ENTRY, skipping the ones\n * `normalizeMcpServers` rejects (no inferrable transport, exotic shapes).\n * Whole-file normalization would let one bad entry drop every server in\n * a file the user doesn't own — and can't fix from zidane.\n */\nfunction normalizeForeignEntries(servers: unknown): McpServerConfig[] {\n if (!servers || typeof servers !== 'object' || Array.isArray(servers))\n return []\n const out: McpServerConfig[] = []\n for (const [name, entry] of Object.entries(servers as Record<string, unknown>)) {\n try {\n out.push(...normalizeMcpServers({ [name]: entry }))\n }\n catch {\n // Skip — the entry works (or fails) in its owning tool; not ours to error on.\n }\n }\n return out\n}\n\n/**\n * `~/.codex/config.toml` — `[mcp_servers.<name>]` tables. TOML parsing\n * rides Bun's built-in parser; on a non-Bun host the Codex import is\n * silently unavailable (returns `[]`) rather than pulling in a TOML\n * dependency for one optional file. Only the fields our config\n * understands are forwarded — Codex-specific extras\n * (`startup_timeout_ms`, …) are dropped before normalization.\n */\nfunction parseCodexToml(text: string): McpServerConfig[] {\n const toml = (globalThis as { Bun?: { TOML?: { parse: (t: string) => unknown } } }).Bun?.TOML\n if (!toml)\n return []\n const parsed = toml.parse(text) as { mcp_servers?: unknown }\n const tables = parsed && typeof parsed === 'object' ? parsed.mcp_servers : undefined\n if (!tables || typeof tables !== 'object' || Array.isArray(tables))\n return []\n const map: Record<string, unknown> = {}\n for (const [name, entry] of Object.entries(tables as Record<string, unknown>)) {\n if (!entry || typeof entry !== 'object')\n continue\n const raw = entry as Record<string, unknown>\n map[name] = {\n ...(raw.command !== undefined ? { command: raw.command } : {}),\n ...(raw.args !== undefined ? { args: raw.args } : {}),\n ...(raw.env !== undefined ? { env: raw.env } : {}),\n // Codex's experimental remote servers — normalize infers transport.\n ...(raw.url !== undefined ? { url: raw.url } : {}),\n }\n }\n return normalizeForeignEntries(map)\n}\n","/**\n * Read-only discovery of [Ruler](https://github.com/intellectronica/ruler)\n * `.ruler/` directories — MCP servers, skills, and instruction markdown.\n *\n * Ruler centralizes AI-agent configuration in one project directory and\n * GENERATES per-agent files from it (`ruler apply`). zidane reads the\n * SOURCES directly, so a ruler-managed repo works without running ruler:\n *\n * .ruler/ruler.toml `[mcp_servers.<name>]` tables — the same TOML\n * grammar as Codex's config, parsed by the shared\n * `codex-toml` handler in `foreign-configs.ts`.\n * .ruler/mcp.json legacy ruler MCP file — shared JSON grammar.\n * .ruler/skills/ `<name>/SKILL.md` — the ecosystem-shared format.\n * .ruler/*.md instruction files, concatenated by ruler into the\n * generated AGENTS.md; zidane loads them as per-file\n * sections instead. Recursive, excluding the\n * `skills/` and `agents/` subtrees (ruler does the\n * same). Ordering mirrors ruler: `AGENTS.md` first\n * (legacy `instructions.md` as fallback primary),\n * remaining files sorted by relative path.\n *\n * User scope is ruler's global config dir: `$XDG_CONFIG_HOME/ruler`\n * (default `~/.config/ruler`) — same three surfaces.\n *\n * CONTAINMENT CONTRACT: every piece of ruler knowledge lives in this\n * module. The discovery surfaces (`mcps-discovery`, `skills-discovery`,\n * `agents-md`) each make exactly one append/skip call into it, all gated\n * behind the `importRulerConfigs` setting — deleting this file and those\n * call sites removes the feature completely. zidane never WRITES to\n * `.ruler/`; installs and toggles keep landing in `.zidane` / `.agents`.\n */\n\nimport type { ForeignMcpFile } from './foreign-configs'\nimport type { ProjectUserPath } from './project-user-paths'\nimport { existsSync, readdirSync, realpathSync, statSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join, resolve } from 'node:path'\nimport { findGitRoot } from './project-root'\n\ninterface RulerPathOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** XDG config home override. Default: `$XDG_CONFIG_HOME`, else `<home>/.config`. */\n xdgConfigHome?: string\n}\n\n/** Subtrees of `.ruler/` that are NOT instruction markdown (mirrors ruler's own exclusions). */\nconst NON_INSTRUCTION_DIRS = new Set(['skills', 'agents'])\n\n/** Primary instruction file (ordered first), and ruler's legacy fallback primary. */\nconst PRIMARY_MD = 'AGENTS.md'\nconst LEGACY_PRIMARY_MD = 'instructions.md'\n\nfunction rulerRoots(opts: RulerPathOptions): { project: string, user: string } {\n const cwd = opts.cwd ?? process.cwd()\n const home = opts.home ?? homedir()\n // An explicitly injected `home` beats the process's $XDG_CONFIG_HOME:\n // callers that redirect the user scope (tests, hosts with virtual homes,\n // `discoverAgentsMd({ home })` which can't thread `xdgConfigHome`) must\n // not silently read the REAL user's global ruler dir through the env.\n const xdg = opts.xdgConfigHome\n ?? (opts.home !== undefined\n ? resolve(opts.home, '.config')\n : process.env.XDG_CONFIG_HOME ?? resolve(home, '.config'))\n return {\n project: resolve(findGitRoot(cwd) ?? cwd, '.ruler'),\n user: resolve(xdg, 'ruler'),\n }\n}\n\n/**\n * MCP config files ruler maintains, project first. Returned regardless of\n * existence (callers `existsSync`-filter — same contract as\n * `foreignMcpConfigFiles`). The `codex-toml` format tag names the shared\n * `[mcp_servers.<name>]` TOML *grammar*, not the tool: ruler.toml uses it\n * verbatim, so the existing parser applies unchanged.\n */\nexport function rulerMcpConfigFiles(opts: RulerPathOptions = {}): ForeignMcpFile[] {\n const { project, user } = rulerRoots(opts)\n return [\n { path: join(project, 'ruler.toml'), source: 'project', format: 'codex-toml' },\n { path: join(project, 'mcp.json'), source: 'project', format: 'json' },\n { path: join(user, 'ruler.toml'), source: 'user', format: 'codex-toml' },\n { path: join(user, 'mcp.json'), source: 'user', format: 'json' },\n ]\n}\n\n/**\n * Skill directories ruler maintains, project first. Feed directly into\n * `discoverSkills` — ruler's `skills/<name>/SKILL.md` layout is the\n * ecosystem-shared format the lenient loader already parses.\n */\nexport function rulerSkillScanPaths(opts: RulerPathOptions = {}): ProjectUserPath[] {\n const { project, user } = rulerRoots(opts)\n return [\n { path: join(project, 'skills'), source: 'project' },\n { path: join(user, 'skills'), source: 'user' },\n ]\n}\n\n/**\n * Instruction markdown files under both ruler roots, in ruler's own\n * concatenation order per root (primary first, rest sorted by relative\n * path), user scope before project scope so the narrower scope lands\n * closer to the conversation — matching `discoverAgentsMd`'s convention.\n *\n * Only existing files are returned (unlike the config-file lists above):\n * the walk itself has to touch the filesystem anyway, and callers feed\n * these straight into per-file readers.\n */\nexport function rulerInstructionFiles(opts: RulerPathOptions = {}): ProjectUserPath[] {\n const { project, user } = rulerRoots(opts)\n return [\n ...collectInstructionMd(user).map(path => ({ path, source: 'user' as const })),\n ...collectInstructionMd(project).map(path => ({ path, source: 'project' as const })),\n ]\n}\n\n/**\n * True when a generated file (e.g. the repo-root `AGENTS.md`) was written\n * by `ruler apply` — it aggregates the `.ruler/*.md` sources, so loading\n * both would double every instruction. Two signals, either suffices:\n * ruler's generation marker, and the per-section source comments older\n * versions emitted without the marker.\n */\nexport function isRulerGenerated(contents: string): boolean {\n return contents.includes('<!-- Generated by Ruler -->')\n || contents.includes('<!-- Source: .ruler/')\n}\n\n/**\n * Bounds for the instruction walk — same philosophy as the 64 KB per-file\n * cap in `agents-md.ts`: a `.ruler/docs -> $HOME` symlink or a huge\n * committed tree must not stall session activation (the walk runs on the\n * main path, per activation and per discovery refresh). Ruler setups in\n * the wild are a handful of files, two levels deep.\n */\nconst MAX_WALK_DEPTH = 6\nconst MAX_INSTRUCTION_FILES = 100\n\n/** Recursive `.md` walk under one ruler root, ruler-ordered. Missing/unreadable dirs → `[]`. */\nfunction collectInstructionMd(rulerDir: string): string[] {\n if (!existsSync(rulerDir))\n return []\n const collected: string[] = []\n // Symlinked dirs are followed (a linked instructions tree should load),\n // so guard against cycles: each REAL directory is walked at most once.\n const visited = new Set<string>([realpathOr(rulerDir)])\n const walk = (dir: string, top: boolean, depth: number): void => {\n if (depth > MAX_WALK_DEPTH || collected.length >= MAX_INSTRUCTION_FILES)\n return\n let entries\n try {\n entries = readdirSync(dir, { withFileTypes: true })\n }\n catch {\n return // permission glitch — tolerate, like every discovery surface\n }\n for (const entry of entries) {\n if (collected.length >= MAX_INSTRUCTION_FILES)\n return\n if (entry.name.startsWith('.'))\n continue\n const full = join(dir, entry.name)\n // `withFileTypes` reports symlinks as neither file nor dir — stat\n // through them so a symlinked instructions file still loads.\n const kind = entry.isSymbolicLink() ? statKind(full) : entry.isDirectory() ? 'dir' : entry.isFile() ? 'file' : 'other'\n if (kind === 'dir') {\n if (top && NON_INSTRUCTION_DIRS.has(entry.name))\n continue\n const real = realpathOr(full)\n if (visited.has(real))\n continue\n visited.add(real)\n walk(full, false, depth + 1)\n }\n else if (kind === 'file' && entry.name.endsWith('.md')) {\n collected.push(full)\n }\n }\n }\n walk(rulerDir, true, 0)\n // Ruler's ordering: top-level AGENTS.md is the primary (legacy\n // instructions.md when AGENTS.md is absent), everything else follows\n // in sorted order.\n const primary = join(rulerDir, PRIMARY_MD)\n const legacy = join(rulerDir, LEGACY_PRIMARY_MD)\n const first = collected.includes(primary) ? primary : collected.includes(legacy) ? legacy : undefined\n const rest = collected.filter(p => p !== first).sort()\n return first ? [first, ...rest] : rest\n}\n\nfunction realpathOr(path: string): string {\n try {\n return realpathSync(path)\n }\n catch {\n return path\n }\n}\n\nfunction statKind(path: string): 'dir' | 'file' | 'other' {\n try {\n const s = statSync(path)\n return s.isDirectory() ? 'dir' : s.isFile() ? 'file' : 'other'\n }\n catch {\n return 'other'\n }\n}\n","/**\n * MCP server discovery from project + user config files.\n *\n * Reads `mcps.json` (zidane's canonical name) AND `mcp.json` (Cursor /\n * common ecosystem alias) from both `.agents/` and `.{prefix}/` in\n * each of the project + user dirs — a list of `McpServerConfig`\n * shapes the SDK accepts. Files are normalized via the same path the\n * agent uses (`normalizeMcpServers`), and the user's enabled-toggle\n * list folds the result into a server array the agent picks up at\n * construction.\n *\n * Co-existing files merge by first-found-per-name: e.g.\n * `.agents/mcps.json` declares `linear`, `.agents/mcp.json` declares\n * `linear` + `notion` → discovery emits the `mcps.json` `linear` plus\n * `mcp.json`'s `notion`. Project always beats user across all files.\n *\n * Pattern mirrors `skills-discovery.ts` — the chat layer owns\n * discovery + UI, the agent receives the same `McpServerConfig[]` it\n * always did.\n */\n\nimport type { McpServerConfig } from '../mcp'\nimport type { ForeignMcpFormat } from './foreign-configs'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { errorMessage } from '../errors'\nimport { normalizeMcpServers } from '../mcp'\nimport { foreignMcpConfigFiles, parseClaudeState, parseForeignMcpConfig } from './foreign-configs'\nimport { findGitRoot } from './project-root'\nimport { projectUserPaths } from './project-user-paths'\nimport { rulerMcpConfigFiles } from './ruler-configs'\n\n/**\n * File names searched in each of the four project/user roots. Order\n * matters within a single directory — `mcps.json` (zidane's canonical\n * name) beats `mcp.json` (Cursor / common ecosystem alias) when both\n * exist side-by-side. Across files we merge per-name with first-found\n * winning, so co-existing `mcp.json` entries are folded in for names\n * not declared in the canonical file.\n */\nconst MCP_CONFIG_FILE_NAMES = ['mcps.json', 'mcp.json'] as const\n\n/**\n * One entry in the discovered list. `path` is the source file (one of\n * the `mcp(s).json` variants under `.agents/` or `.{prefix}/`) so a\n * Settings picker can show \"Defined in ~/.zidane/mcp.json\" tooltips\n * and the user can tell which file declared what.\n */\nexport interface DiscoveredMcp {\n /** Normalized server config. */\n config: McpServerConfig\n /** Whether the file came from the project or the user directory. */\n source: 'project' | 'user'\n /** Absolute path of the file that declared the server. */\n path: string\n}\n\n/**\n * Search order for MCP config files — see {@link projectUserPaths}.\n *\n * Both `mcps.json` (zidane's canonical name) and `mcp.json` (Cursor /\n * common ecosystem alias) are picked up from every search root. The\n * canonical name wins within the same directory; across files the\n * usual first-found-by-name dedup applies, so `mcp.json` effectively\n * merges its non-overlapping entries on top of `mcps.json`.\n *\n * Order per scope (project beats user):\n * 1. `{project}/.agents/mcps.json`\n * 2. `{project}/.agents/mcp.json`\n * 3. `{project}/.{prefix}/mcps.json`\n * 4. `{project}/.{prefix}/mcp.json`\n * 5. `~/.agents/mcps.json`\n * 6. `~/.agents/mcp.json`\n * 7. `~/.{prefix}/mcps.json`\n * 8. `~/.{prefix}/mcp.json`\n *\n * Non-existent files are skipped without error.\n *\n * @param opts - Overrides for the working directory, home directory and dot-directory prefix.\n */\nexport function defaultMcpsConfigPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n} = {}): { path: string, source: 'project' | 'user' }[] {\n // Build the path list per-filename via the shared helper, then\n // interleave so `<dir>/mcps.json` lands immediately before\n // `<dir>/mcp.json` for each scope. Same ordering invariant that\n // `projectUserPaths` already guarantees within a filename — the\n // interleave preserves it across filenames.\n const perName = MCP_CONFIG_FILE_NAMES.map(name => projectUserPaths({ subPath: name, ...opts }))\n const rootCount = perName[0].length\n const out: { path: string, source: 'project' | 'user' }[] = []\n for (let i = 0; i < rootCount; i++) {\n for (const list of perName)\n out.push(list[i])\n }\n return out\n}\n\n/**\n * Parse one `mcps.json` file. Canonical zidane shape is a flat array; the\n * other shapes exist for copy-paste from neighboring tools:\n *\n * - `McpServerConfig[]` — flat array (recommended).\n * - `{ name: {...} }` — name-keyed map.\n * - `{ \"mcpServers\": { name: {...} } }` — Claude Desktop / Claude Code /\n * Cursor wrapper. Unwrapped before normalization (otherwise\n * `normalizeMcpServers` would treat the whole wrapper as a single server).\n *\n * Per-entry `enabled: false` (Cursor runtime state) drops the entry — same\n * intent as the source tool. Use the TUI toggle list for \"show but disabled\".\n *\n * Tolerant to common copy-paste artifacts: when strict `JSON.parse` fails,\n * retries after normalizing invisible whitespace that's valid Unicode but\n * not valid JSON whitespace (BOM, NBSP, zero-width spaces, line/paragraph\n * separators) — only OUTSIDE string literals, so user data isn't mutated.\n * If the sanitized version still fails, throws the original error with a\n * targeted hint when NBSPs are present.\n *\n * Validation flows through `normalizeMcpServers` so the result matches\n * what `createAgent` accepts. Throws on truly malformed JSON; the caller\n * decides whether to surface or swallow.\n *\n * @param text - Raw file contents, either a bare server map or array, or an object with an `mcpServers` key.\n */\nexport function parseMcpsFile(text: string): McpServerConfig[] {\n const raw = parseJsonTolerant(text)\n const target: unknown = raw && typeof raw === 'object' && !Array.isArray(raw) && 'mcpServers' in raw\n ? (raw as { mcpServers: unknown }).mcpServers\n : raw\n return normalizeMcpServers(dropDisabledEntries(target))\n}\n\n/**\n * Strict `JSON.parse` with the whitespace-tolerance retry described on\n * {@link parseMcpsFile}, returning the RAW parsed value. Exported for the\n * write side (`mcps-write.ts`): a file that discovery reads fine MUST also\n * be editable — upsert/remove failing with \"Unrecognized token\" on an NBSP\n * the read path silently tolerates would strand the user.\n */\nexport function parseJsonTolerant(text: string): unknown {\n try {\n return JSON.parse(text)\n }\n catch (firstErr) {\n const sanitized = sanitizeJsonWhitespace(text)\n if (!sanitized.changed)\n throw firstErr\n try {\n return JSON.parse(sanitized.text)\n }\n catch {\n throw enhanceJsonError(firstErr, text)\n }\n }\n}\n\n/**\n * Code points that are valid Unicode whitespace but NOT valid JSON whitespace.\n * Copy-paste from rich-text contexts (markdown editors, chat UIs, web pages)\n * frequently slips these in:\n *\n * - `\\uFEFF` BOM — sometimes prepended by Windows editors.\n * - `\\u00A0` NBSP — markdown/HTML smart-replace common culprit.\n * - `\\u200B`-`\\u200D` zero-width family — often invisible in editors.\n * - `\\u2028` / `\\u2029` line/paragraph separators — valid in JS strings\n * pre-ES2019 but never in strict JSON.\n * - `\\u2060` word joiner.\n *\n * Replace with ASCII space (zero-width chars are removed; the others become\n * a single space). Replacements happen ONLY outside string literals so a\n * user's intentional NBSP inside a `url` or display name is left alone.\n */\nconst PROBLEMATIC_WHITESPACE = new Set<number>([\n 0xFEFF,\n 0x00A0,\n 0x200B,\n 0x200C,\n 0x200D,\n 0x2028,\n 0x2029,\n 0x2060,\n])\n\nfunction sanitizeJsonWhitespace(text: string): { text: string, changed: boolean } {\n let changed = false\n let inString = false\n let escaped = false\n const out: string[] = []\n for (let i = 0; i < text.length; i++) {\n const ch = text[i]\n if (inString) {\n out.push(ch)\n if (escaped)\n escaped = false\n else if (ch === '\\\\')\n escaped = true\n else if (ch === '\"')\n inString = false\n continue\n }\n if (ch === '\"') {\n inString = true\n out.push(ch)\n continue\n }\n const code = ch.charCodeAt(0)\n if (PROBLEMATIC_WHITESPACE.has(code)) {\n changed = true\n // Zero-width chars (200B-200D, 2060, FEFF) erased; visible whitespace\n // replacements become a single space so token boundaries are preserved.\n if (code === 0x00A0 || code === 0x2028 || code === 0x2029)\n out.push(' ')\n continue\n }\n out.push(ch)\n }\n return { text: out.join(''), changed }\n}\n\nfunction enhanceJsonError(err: unknown, text: string): Error {\n const orig = err instanceof Error ? err : new Error(String(err))\n const hints: string[] = []\n if (text.includes('\\u00A0'))\n hints.push('the file contains non-breaking spaces (U+00A0), likely from copy-paste. Replace them with regular spaces')\n if (text.charCodeAt(0) === 0xFEFF)\n hints.push('the file starts with a BOM (U+FEFF), which strict JSON.parse rejects')\n if (hints.length === 0)\n return orig\n return new Error(`${orig.message}. ${hints.join('; ')}`)\n}\n\n/**\n * Strip `enabled: false` entries before normalization — same intent as the\n * source tool that wrote them (Cursor flips this when the user toggles a\n * server off in its UI). One pass over array OR map shape, O(N).\n *\n * Done pre-normalize so we don't have to teach `normalizeMcpServers` about\n * the flag — the canonical `McpServerConfig` has no `enabled` field.\n */\nfunction dropDisabledEntries(target: unknown): unknown {\n if (Array.isArray(target))\n return target.filter(e => !isEntryDisabled(e))\n if (target && typeof target === 'object') {\n const filtered: Record<string, unknown> = {}\n for (const [name, entry] of Object.entries(target as Record<string, unknown>)) {\n if (!isEntryDisabled(entry))\n filtered[name] = entry\n }\n return filtered\n }\n return target\n}\n\nfunction isEntryDisabled(entry: unknown): boolean {\n return !!entry && typeof entry === 'object'\n && (entry as { enabled?: unknown }).enabled === false\n}\n\n/**\n * Per-file failure surfaced from {@link discoverProjectMcps}. The TUI shows\n * these in the MCP settings empty / preamble state so a malformed\n * `mcps.json` doesn't look identical to a missing one.\n */\nexport interface DiscoveryError {\n /** Absolute path of the file that failed. */\n path: string\n /** Whether the file came from the project or the user directory. */\n source: 'project' | 'user'\n /** Human-readable description of the failure. */\n message: string\n /**\n * A deliberate, documented behavior being reported (e.g. the ruler stdio\n * security carve-out) rather than a failure. Hosts should style these as\n * info, not error — crying wolf over a working default trains users to\n * ignore the channel that also carries real parse failures.\n */\n informational?: boolean\n}\n\n/**\n * Result of {@link discoverProjectMcps}. `servers` is the per-source-priority\n * merged list (project beats user, first-found wins on name collision).\n * `errors` is one entry per file that existed but failed to parse, plus\n * informational entries for servers a security gate skipped (ruler stdio\n * without the foreign-agent opt-in) — empty on the happy path.\n */\nexport interface DiscoveryResult {\n /** Servers found, deduplicated by name with project entries ahead of user entries. */\n servers: DiscoveredMcp[]\n /** Files that failed to parse and informational notices. */\n errors: DiscoveryError[]\n}\n\n/**\n * Walk the default config paths, parse every file that exists, and return\n * `{ servers, errors }` in source-priority order. Project entries appear\n * before user entries; first occurrence of a `name` wins (later\n * duplicates dropped).\n *\n * Parse failures are captured into `errors` rather than thrown — the picker\n * surfaces them while still showing whatever DID parse. A `ZIDANE_DEBUG`\n * log line is also emitted for ops visibility.\n *\n * @param opts - Roots to search and flags that enable foreign-agent and Ruler config files.\n */\nexport function discoverProjectMcps(opts: {\n cwd?: string\n home?: string\n prefix?: string\n /**\n * Also read MCP configs other agents maintain (Claude Code, Codex,\n * Pi) — see `foreign-configs.ts`. Mirrors the `importForeignConfigs`\n * setting; native locations always win name collisions, and project\n * beats user within each group.\n */\n foreignAgents?: boolean\n /**\n * Also read Ruler MCP configs (`.ruler/ruler.toml` + `mcp.json`,\n * project and XDG-global) — see `ruler-configs.ts`. Mirrors the\n * `importRulerConfigs` setting; same collision rules as above.\n */\n ruler?: boolean\n} = {}): DiscoveryResult {\n const paths = mcpsDiscoveryPaths(opts).filter(p => existsSync(p.path))\n // Claude's \"local\"-scope servers are keyed by absolute project path\n // inside the user state file — resolve once for the whole pass.\n const projectRoot = findGitRoot(opts.cwd ?? process.cwd()) ?? (opts.cwd ?? process.cwd())\n const seen = new Set<string>()\n // Pass-level record of names already reported as stdio-dropped — a server\n // declared in BOTH `.ruler/ruler.toml` and `.ruler/mcp.json` would\n // otherwise appear in two informational notices, inflating the count.\n const droppedStdioSeen = new Set<string>()\n const servers: DiscoveredMcp[] = []\n const errors: DiscoveryError[] = []\n // `~/.claude.json` appears TWICE in the path list (project + user scopes)\n // and runs to multiple MB in the wild (it embeds per-project history) —\n // read + `JSON.parse` it once per pass. Caching the failure too collapses\n // a torn read (a live Claude Code session rewriting the file) into ONE\n // transient error instead of two.\n const claudeStateCache = new Map<string, { state: unknown } | { failed: true }>()\n for (const { path, source, format, dropStdio } of paths) {\n const isClaudeState = format === 'claude-user-state' || format === 'claude-project-state'\n const cached = isClaudeState ? claudeStateCache.get(path) : undefined\n if (cached && 'failed' in cached)\n continue // parse failure already recorded for this path this pass\n let configs: McpServerConfig[]\n try {\n if (isClaudeState) {\n let state: unknown\n if (cached && 'state' in cached) {\n state = cached.state\n }\n else {\n try {\n state = JSON.parse(readFileSync(path, 'utf8')) as unknown\n }\n catch (err) {\n claudeStateCache.set(path, { failed: true })\n throw err\n }\n claudeStateCache.set(path, { state })\n }\n configs = parseClaudeState(state, format, projectRoot)\n }\n else {\n const text = readFileSync(path, 'utf8')\n configs = format === 'json' ? parseMcpsFile(text) : parseForeignMcpConfig(text, format, { projectRoot })\n }\n }\n catch (err) {\n const message = errorMessage(err)\n errors.push({ path, source, message })\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] failed to parse \"${path}\": ${message}\\n`)\n continue\n }\n const droppedStdio: string[] = []\n for (const config of configs) {\n if (seen.has(config.name))\n continue\n // Ruler stdio servers require the explicit stdio opt-in (see\n // `mcpsDiscoveryPaths`) — a `command` from a repo-committed file\n // would otherwise execute on the first bootstrap of a freshly\n // cloned (possibly hostile) repo, since `importRulerConfigs`\n // defaults ON and discovered servers are enabled by default.\n // (Checked AFTER the dedup guard so a server that already imported\n // from a native location isn't misreported as dropped.)\n if (dropStdio && config.transport === 'stdio') {\n if (!droppedStdioSeen.has(config.name)) {\n droppedStdioSeen.add(config.name)\n droppedStdio.push(config.name)\n }\n continue\n }\n seen.add(config.name)\n servers.push({ config, source, path })\n }\n // Surface the security carve-out instead of vanishing silently — the\n // picker renders these alongside parse errors, so the user learns WHY\n // a `.ruler` stdio server is missing and which switch enables it.\n if (droppedStdio.length > 0) {\n errors.push({\n path,\n source,\n message: `${droppedStdio.length} stdio server${droppedStdio.length === 1 ? '' : 's'} (${droppedStdio.join(', ')}) skipped. Running stdio commands from Ruler files needs “Import configurations from other agents” enabled.`,\n informational: true, // working security default, not a failure\n })\n }\n }\n return { servers, errors }\n}\n\n/**\n * Full path list a discovery pass reads, formats attached — native\n * `mcp(s).json` locations first (they win name collisions), then the\n * foreign-agent files when enabled.\n *\n * Ruler files carry `dropStdio` unless the foreign-agent opt-in is ALSO\n * set: `importRulerConfigs` defaults ON (it's an intentional committed\n * config source), but stdio servers execute local commands — the exact\n * risk `importForeignConfigs` exists to gate (`types.ts`: \"importing them\n * is an explicit opt-in\"). Remote (url) ruler servers import by default;\n * `command`-based ones wait for the same opt-in as every other foreign\n * stdio source.\n */\nfunction mcpsDiscoveryPaths(opts: {\n cwd?: string\n home?: string\n prefix?: string\n foreignAgents?: boolean\n ruler?: boolean\n} = {}): { path: string, source: 'project' | 'user', format: ForeignMcpFormat, dropStdio?: boolean }[] {\n const native = defaultMcpsConfigPaths(opts).map(p => ({ ...p, format: 'json' as const }))\n return [\n ...native,\n ...(opts.foreignAgents ? foreignMcpConfigFiles(opts) : []),\n ...(opts.ruler ? rulerMcpConfigFiles(opts).map(p => ({ ...p, dropStdio: opts.foreignAgents !== true })) : []),\n ]\n}\n\n/**\n * Map a user-toggled enable list onto the `mcpServers` array the agent\n * receives. Conventions match `buildSkillsConfig`:\n *\n * - `enabled === undefined` → every discovered server enabled (default).\n * - `enabled === []` → no servers (the agent runs MCP-less).\n * - `enabled === [names]` → allowlist; servers not in the list are\n * dropped.\n *\n * `disabledTools` is the per-server tool deny-list from\n * `Settings.disabledMcpTools`. For each server that survives the\n * server-level enable filter, the per-tool names are merged into\n * `McpServerConfig.disabledTools` via **union** with anything the JSON\n * file already pinned — a tool listed in either source is dropped.\n * Union (not override) keeps JSON-side policy stable: a server file\n * that pins `disabledTools: ['dangerous_tool']` can't be re-enabled\n * via the UI by accident.\n *\n * `enabledTools` (JSON-side allowlist) takes precedence over the\n * UI-side deny-list when both are set on the same server — the\n * allowlist's \"only these\" semantics would be meaningless if a UI\n * deny-list could subtract from it again. We don't emit `disabledTools`\n * when `enabledTools` is present.\n *\n * Returns a fresh array — callers can mutate without affecting the\n * underlying discovery list. Per-server config objects are also fresh\n * copies (shallow), so the merged `disabledTools` doesn't leak back\n * onto the discovery catalog.\n *\n * @param opts - Discovered servers, the enabled-name allowlist and per-server UI tool denylists to fold in.\n */\nexport function buildMcpServers(opts: {\n discovered: readonly DiscoveredMcp[]\n enabled?: readonly string[]\n disabledTools?: Record<string, readonly string[]>\n}): McpServerConfig[] {\n const survivors = filterByEnabled(opts.discovered, opts.enabled)\n const deny = opts.disabledTools\n if (!deny || Object.keys(deny).length === 0)\n return survivors.map(d => ({ ...d.config }))\n\n return survivors.map((d) => {\n const next: McpServerConfig = { ...d.config }\n const uiDeny = deny[d.config.name]\n if (!uiDeny || uiDeny.length === 0)\n return next\n // `enabledTools` is JSON-side authority — see JSDoc.\n if (next.enabledTools && next.enabledTools.length > 0)\n return next\n const merged = new Set<string>(next.disabledTools ?? [])\n for (const name of uiDeny)\n merged.add(name)\n next.disabledTools = [...merged].sort()\n return next\n })\n}\n\n/**\n * Dedup a merged MCP-server list by `name`. First entry wins.\n *\n * Applied at agent build across every source that can contribute a server —\n * `mcps.json`, Agent Plugins, launch options, extensions — so the earliest\n * source in that order takes precedence.\n *\n * Drops are REPORTED rather than silent, via `onDropped`. A plugin shipping a\n * `github` server loses to a `github` in `mcps.json`, and nothing downstream\n * says so: the plugin's settings row still counts the server it shipped,\n * because the row renders what the PACKAGE declares and only this merge knows\n * what survived. That mirrors how the skills side reports the same class of\n * event — `resolveSkills` emits `name-collision-shadowed` on the winner rather\n * than annotating the loser.\n *\n * The reporter is injected rather than calling a logger directly so this stays\n * a pure function of its input, testable without a logging shim. It lives here\n * beside {@link buildMcpServers} rather than in the TUI because merge\n * precedence is a config concern, not a rendering one.\n *\n * @param servers - Servers to filter. The first entry per `name` wins.\n * @param onDropped - Called once with the dropped duplicate names when any were removed.\n */\nexport function dedupMcpServersByName<T extends { name: string }>(\n servers: readonly T[],\n onDropped?: (names: readonly string[]) => void,\n): T[] {\n const seen = new Set<string>()\n const out: T[] = []\n const dropped: string[] = []\n for (const server of servers) {\n if (seen.has(server.name)) {\n dropped.push(server.name)\n continue\n }\n seen.add(server.name)\n out.push(server)\n }\n if (dropped.length > 0)\n onDropped?.(dropped)\n return out\n}\n\n/**\n * Anchor host-spawned stdio servers without an explicit `cwd` at the\n * session's directory. Otherwise they inherit the host process's cwd —\n * the session dir in a TUI that chdir'd, but an arbitrary dir in the daemon\n * or GUI, so relative args / project-scoped servers would see the wrong\n * tree. Context-scoped servers run inside the execution context and are\n * left alone.\n */\nexport function withSessionStdioCwd(servers: readonly McpServerConfig[], cwd: string): McpServerConfig[] {\n return servers.map(server =>\n server.transport === 'stdio' && server.scope !== 'context' && !server.cwd\n ? { ...server, cwd }\n : server,\n )\n}\n\nfunction filterByEnabled(\n discovered: readonly DiscoveredMcp[],\n enabled: readonly string[] | undefined,\n): readonly DiscoveredMcp[] {\n if (enabled === undefined)\n return discovered\n if (enabled.length === 0)\n return []\n const allow = new Set(enabled)\n return discovered.filter(d => allow.has(d.config.name))\n}\n","/**\n * System-prompt override discovery — load a `SYSTEM.md` that REPLACES the\n * built-in doctrine.\n *\n * The sibling of `agents-md.ts`, with deliberately inverted semantics:\n *\n * | | `AGENTS.md` | `SYSTEM.md` |\n * |------------|------------------------------|---------------------------------|\n * | effect | appends to the doctrine | replaces the doctrine |\n * | multiples | every match loads, in order | exactly one wins |\n * | precedence | broad → narrow, all present | project beats user, rest shadowed |\n *\n * Concatenating overrides would be incoherent — two files each claiming to BE\n * the system prompt can't both be it — so a single winner is picked and the\n * losers are reported in {@link SystemMdResult.shadowed} for the UI to show.\n * A surprising precedence should be visible, not mysterious.\n *\n * Search order (winner first):\n *\n * 1. `<project root>/SYSTEM.md` — project scope, most specific, wins\n * 2. `~/.{prefix}/SYSTEM.md` — user scope (`~/.zidane/SYSTEM.md`)\n *\n * What the override does NOT replace: the `AGENTS.md` block (a separate,\n * additive layer) and the `<env>` section below the prompt-cache boundary.\n * Stripping `<env>` would take the agent's cwd + project root with it and\n * silently break path resolution in the file tools — an override is for\n * rewriting the agent's instructions, not amputating its sense of place.\n */\n\nimport type { AgentsMdScope } from './agents-md'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport { errorMessage } from '../errors'\nimport { findGitRoot } from './project-root'\n\n/** A discovered `SYSTEM.md`. */\nexport interface SystemMdFile {\n /** Absolute path of the file. */\n path: string\n /** Whether the file is the project's or the user's. */\n source: 'project' | 'user'\n /** File text. */\n contents: string\n}\n\n/** Result of {@link discoverSystemMd}. */\nexport interface SystemMdResult {\n /**\n * The winning override, or `null` when no `SYSTEM.md` exists (the built-in\n * doctrine then applies unchanged).\n */\n file: SystemMdFile | null\n /**\n * Files that exist but lost to `file` on precedence. Non-empty only when the\n * user has both a project and a user `SYSTEM.md`. Surfaced so the UI can mark\n * them as inactive instead of leaving the user wondering why edits to one of\n * them changed nothing.\n */\n shadowed: readonly SystemMdFile[]\n}\n\n/** Same per-file cap as `agents-md.ts` — a runaway file shouldn't blow the prompt. */\nconst MAX_BYTES_PER_FILE = 64 * 1024\n\n/** Options for {@link discoverSystemMd}. */\nexport interface DiscoverSystemMdOptions {\n /** Discovery cwd. Default: `process.cwd()`. */\n cwd?: string\n /** User home directory. Default: `os.homedir()`. */\n home?: string\n /** Storage prefix. Leading dot tolerated. Default: `'.zidane'`. */\n prefix?: string\n /**\n * Which scopes may contribute an override — the same\n * {@link AgentsMdScope} the host applies to `AGENTS.md`, so one setting\n * governs both surfaces:\n * - `'none'` — no override, ever. Short-circuits before any FS probe.\n * - `'user'` — only `~/.{prefix}/SYSTEM.md`. The repo's file is IGNORED,\n * which is the whole point of the setting: it is how a user\n * says \"do not trust this checkout\".\n * - `'project'` — only `<root>/SYSTEM.md`.\n * - `'both'` — union, project winning (default).\n */\n scope?: AgentsMdScope\n}\n\n/**\n * Absolute path of the USER-scope `SYSTEM.md` — the file the TUI creates when\n * the user asks for an override and none exists yet.\n *\n * Exported so the host doesn't hardcode the layout and can't drift from what\n * discovery actually reads.\n *\n * @param opts - Overrides for the home directory and dot-directory prefix.\n */\nexport function userSystemMdPath(opts: { home?: string, prefix?: string } = {}): string {\n const home = opts.home ?? homedir()\n const prefix = (opts.prefix ?? '.zidane').replace(/^\\./, '')\n return join(home, `.${prefix}`, 'SYSTEM.md')\n}\n\n/**\n * Whether a USER-scope `SYSTEM.md` would be loaded under `scope`.\n *\n * Mirrors the candidate filter in {@link discoverSystemMd} so a host can ask\n * \"would writing `~/.{prefix}/SYSTEM.md` accomplish anything?\" without\n * re-deriving the rule and drifting from it. `'project'` excludes the user\n * candidate and `'none'` skips discovery altogether, so under either setting\n * creating that file is a silent no-op.\n *\n * @param scope - The active `userInstructionsScope`. Treated as `both` when undefined.\n */\nexport function userScopeOverridesLoad(scope: AgentsMdScope | undefined): boolean {\n const effective: AgentsMdScope = scope ?? 'both'\n return effective !== 'project' && effective !== 'none'\n}\n\n/**\n * Whether a PROJECT-scope `SYSTEM.md` would be loaded under `scope`.\n *\n * The mirror of {@link userScopeOverridesLoad}, against the same candidate\n * filter. Used by {@link renderSystemMdSeed} to decide whether telling the user\n * \"a repo file outranks this one\" is true: under `'user'` the project candidate\n * is never even probed, so claiming a checkout can outrank them contradicts the\n * setting they chose precisely to distrust checkouts.\n */\nexport function projectScopeOverridesLoad(scope: AgentsMdScope | undefined): boolean {\n const effective: AgentsMdScope = scope ?? 'both'\n return effective !== 'user' && effective !== 'none'\n}\n\n/**\n * Find the active `SYSTEM.md` override, if any.\n *\n * Project paths anchor at the git root when one exists above `cwd`, else at\n * `cwd` — the same anchor as skills / mcps / AGENTS.md discovery, so all four\n * surfaces agree on what \"this project\" means.\n *\n * Tolerant: an unreadable or empty file is treated as absent (logged under\n * `ZIDANE_DEBUG`) and the next candidate gets its turn. Never throws — this\n * runs on the session-activation path.\n *\n * Candidates are filtered by {@link DiscoverSystemMdOptions.scope}, mirroring\n * how `discoverAgentsMd` builds its own list.\n *\n * @param opts - Roots to search and which scopes to consult.\n */\nexport function discoverSystemMd(opts: DiscoverSystemMdOptions = {}): SystemMdResult {\n const scope: AgentsMdScope = opts.scope ?? 'both'\n // Short-circuit before touching the filesystem, same as `discoverAgentsMd`.\n if (scope === 'none')\n return { file: null, shadowed: [] }\n\n const cwd = opts.cwd ?? process.cwd()\n const projectRoot = findGitRoot(cwd) ?? cwd\n\n const candidates: { path: string, source: 'project' | 'user' }[] = []\n if (scope !== 'user')\n candidates.push({ path: join(projectRoot, 'SYSTEM.md'), source: 'project' })\n if (scope !== 'project')\n candidates.push({ path: userSystemMdPath(opts), source: 'user' })\n\n const found: SystemMdFile[] = []\n const seen = new Set<string>()\n for (const c of candidates) {\n // Dedup by absolute path. The two candidates can only ever be the same\n // file when `prefix` is empty or dot-only: `userSystemMdPath` then\n // collapses to `<home>/SYSTEM.md`, which equals the project candidate if\n // `projectRoot` is also `<home>`. With any real prefix (`.zidane`) they are\n // `<root>/SYSTEM.md` and `<home>/.zidane/SYSTEM.md` — distinct whether or\n // not `$HOME` happens to be a git repo. Without the guard that one case\n // would report the file as shadowing itself.\n if (seen.has(c.path))\n continue\n seen.add(c.path)\n const file = readIfPresent(c.path, c.source)\n if (file)\n found.push(file)\n }\n\n const [winner, ...shadowed] = found\n return { file: winner ?? null, shadowed }\n}\n\nfunction readIfPresent(path: string, source: 'project' | 'user'): SystemMdFile | null {\n if (!existsSync(path))\n return null\n let raw: string\n try {\n raw = readFileSync(path, 'utf8')\n }\n catch (err) {\n if (process.env.ZIDANE_DEBUG)\n process.stderr.write(`[zidane/chat] system-md: failed to read \"${path}\": ${errorMessage(err)}\\n`)\n return null\n }\n const contents = raw.length > MAX_BYTES_PER_FILE\n ? `${raw.slice(0, MAX_BYTES_PER_FILE)}\\n\\n[... truncated; ${raw.length - MAX_BYTES_PER_FILE} more bytes in source file ...]`\n : raw\n // An empty override is almost certainly an accident (a file created but not\n // yet written). Treating it as absent keeps the built-in doctrine in place\n // rather than handing the model a blank prompt.\n if (contents.trim().length === 0)\n return null\n return { path, source, contents }\n}\n\n/**\n * Seed contents for a freshly created `SYSTEM.md` override, built from the\n * built-in doctrine.\n *\n * Seeding with the real doctrine (rather than an empty file) means creating the\n * override is a behavioral no-op until the user edits it. An empty seed would\n * silently delete every line of doctrine the moment the file appeared, making\n * the agent measurably worse with no visible cause.\n *\n * `currentSystem` should be DOCTRINE ONLY — callers pass\n * `splitSystemPrompt(doctrineOnlySystem()).static`, which excludes both the\n * generated `<env>` block (rebuilt per turn) and the `AGENTS.md` block. Baking\n * the latter in would duplicate it, since it is re-appended below the override\n * on every compose.\n *\n * `scope` is the active `userInstructionsScope`; it decides whether the seed's\n * precedence note is true — see {@link projectScopeOverridesLoad}.\n *\n * @param currentSystem - The built-in doctrine to seed the file with.\n * @param scope - The active `userInstructionsScope`. Decides whether the precedence note mentions a project file.\n */\nexport function renderSystemMdSeed(currentSystem: string, scope?: AgentsMdScope): string {\n return [\n '<!--',\n ' SYSTEM.md replaces zidane\\'s built-in system prompt.',\n '',\n ' It was seeded with the built-in doctrine, so right now it changes nothing.',\n ' Edit freely.',\n '',\n ' Still applied on top of whatever you write here:',\n ' - your AGENTS.md / CLAUDE.md instructions (appended below this text)',\n ' - the <env> block (cwd, project root, date, platform)',\n '',\n // Only true when the project candidate is actually probed. Under\n // `userInstructionsScope: 'user'` it is not, and creation is still allowed\n // there — so printing this unconditionally told the one user who opted out\n // of project files that a checkout outranks them.\n ...(projectScopeOverridesLoad(scope)\n ? [' A project-level SYSTEM.md at the repo root takes precedence over this one.']\n : [' userInstructionsScope is \"user\", so a repo-level SYSTEM.md is ignored and', ' this file always wins.']),\n ' Delete this file to restore the built-in prompt.',\n '-->',\n '',\n currentSystem.trim(),\n '',\n ].join('\\n')\n}\n","/**\n * Which settings a host owns when a window edits it (the active session\n * runs on a server), and which stay the window's. Shared by the TUI's and\n * the GUI's Settings surfaces.\n *\n * Window-only: how the window looks and behaves (the UI category, theme,\n * fps, icons, keyboard and editor habits) and which hosts it uses\n * (`WINDOW_LOCAL_SETTINGS`). Everything else (safe mode, sandbox, limits,\n * compaction, the enabled skills / MCP servers / extensions…) shapes the\n * host's agents, so it is read with `settings.get` and written with\n * `settings.set` on that host, never into this computer's `state.json`.\n */\nimport type { ApprovalMode, Settings } from './types'\nimport { SETTINGS_CHOICES, SETTINGS_INPUTS, SETTINGS_TOGGLES } from './settings-catalog'\nimport { approvalModeFromSafeMode } from './settings-defaults'\n\n/**\n * Settings that belong to the window even while a host owns the settings\n * file (daemon or remote mode): which host the next launch uses.\n */\nexport const WINDOW_LOCAL_SETTINGS = ['useDaemon', 'remoteHosts'] as const satisfies readonly (keyof Settings)[]\n\n/** Agent-category keys that are still about the window, not the agents. */\nconst WINDOW_ONLY_KEYS: readonly (keyof Settings)[] = [\n 'theme',\n 'targetFps',\n 'mouseInteractions',\n 'uiMode',\n 'ctrlCBehavior',\n 'terminalTitle',\n 'sessionIcon',\n 'attentionIcon',\n 'activeIcon',\n 'thinkingBinding',\n 'toolCallDisplay',\n 'editDiffDisplay',\n 'transcriptDensity',\n 'resumeLastSession',\n 'externalEditor',\n 'inlineAutocomplete',\n 'checkForUpdates',\n 'subscriptionUsage',\n 'openIn',\n // How this computer groups its sessions (the `spaces` toggle is `ui`, so window-only already).\n 'spaceList',\n // What this computer's new sessions start on: the model names its own accounts, the agent its extensions.\n 'defaultAgent',\n 'defaultModel',\n 'defaultEffort',\n 'defaultModelOptions',\n]\n\nconst WINDOW_ONLY: ReadonlySet<string> = new Set<string>([\n ...WINDOW_LOCAL_SETTINGS,\n ...WINDOW_ONLY_KEYS,\n ...[...SETTINGS_TOGGLES, ...SETTINGS_CHOICES, ...SETTINGS_INPUTS].filter(row => row.category === 'ui').map(row => row.key),\n])\n\n/**\n * Tells whether a settings key stays with the window instead of the host.\n *\n * @param key - Settings key to check.\n * @returns `true` for window-only keys (UI settings and {@link WINDOW_LOCAL_SETTINGS}).\n */\nexport function isWindowOnlySetting(key: keyof Settings | string): boolean {\n return WINDOW_ONLY.has(key)\n}\n\n/** The allowlists a host leaving them unset means \"everything enabled\" for. */\nexport const HOST_ENABLED_LISTS = ['enabledSkills', 'enabledMcps', 'enabledExtensions', 'enabledPlugins', 'disabledMcpTools'] as const satisfies readonly (keyof Settings)[]\n\n/**\n * Settings as sent to a host or to a window. Peers older than `approvalMode`\n * know tool approvals only as the legacy `safeMode` boolean, so it rides\n * along; a current peer drops it (`settingsFromWire`).\n *\n * @param values - Settings, whole or partial; `safeMode` is added only when they carry `approvalMode`.\n */\nexport function settingsToWire<T extends object>(values: T): T & { safeMode?: boolean } {\n return 'approvalMode' in values ? { ...values, safeMode: values.approvalMode !== 'off' } : { ...values }\n}\n\n/**\n * Settings from a peer as this side reads them, without `safeMode`. Beside\n * a matching `approvalMode` it is only the copy `settingsToWire` adds;\n * alone, or contradicting an `approvalMode` (one a newer client stored on\n * an older server, which kept enforcing `safeMode`), it is the peer's\n * actual state and maps onto the approval mode.\n *\n * @param values - The received values.\n * @param current - The approval mode a lone `safeMode: true` keeps (unless `'off'`).\n */\nexport function settingsFromWire<T extends object>(values: T & { safeMode?: unknown }, current: ApprovalMode): Omit<T, 'safeMode'> {\n const { safeMode, ...rest } = values\n const stated = 'approvalMode' in rest ? rest.approvalMode : undefined\n if (typeof safeMode !== 'boolean' || (stated !== undefined && (stated !== 'off') === safeMode))\n return rest\n return { ...rest, approvalMode: approvalModeFromSafeMode(safeMode, stated === 'off' ? 'off' : current) }\n}\n\n/**\n * `local` with the host's values for every key the host owns.\n *\n * @param local - The window's own settings.\n * @param host - Settings read from the host (`settings.get`, `settings.set`). Window-only keys in it are ignored.\n */\nexport function overlayHostSettings<S extends Settings>(local: S, host: Partial<Settings> & { safeMode?: boolean }): S {\n const merged = { ...local } as Record<string, unknown>\n // A server older than `approvalMode` reports only `safeMode`, which asks when on.\n for (const [key, value] of Object.entries(settingsFromWire(host, 'ask'))) {\n if (!isWindowOnlySetting(key))\n merged[key] = value\n }\n // Allowlists the host leaves unset mean \"everything enabled\": not this window's lists.\n for (const key of HOST_ENABLED_LISTS) {\n if (!(key in host))\n delete merged[key]\n }\n return merged as S\n}\n\n/**\n * Split a settings patch into the window's part and the host's part.\n *\n * @param patch - Settings patch to partition by owner.\n */\nexport function splitHostSettingsPatch<S extends Partial<Settings>>(patch: S): { window: Partial<S>, host: Partial<S> } {\n const window: Record<string, unknown> = {}\n const host: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(patch))\n (isWindowOnlySetting(key) ? window : host)[key] = value\n return { window: window as Partial<S>, host: host as Partial<S> }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { t as JsonRpcConnectionClosedError } from "./json-rpc-G0rBfEar.js";
2
- import { l as defaultSocketPath, p as selfDaemonSpawnCommand, u as ensureDaemonRunning } from "./settings-keys-DssjmqAV.js";
3
- import { h as connectOrSpawnDaemon, m as connectIpcClient } from "./dispatcher-BICXFGiJ.js";
2
+ import { l as defaultSocketPath, p as selfDaemonSpawnCommand, u as ensureDaemonRunning } from "./settings-keys-QoGJ5n3e.js";
3
+ import { h as connectOrSpawnDaemon, m as connectIpcClient } from "./dispatcher-Dclv6HXn.js";
4
4
  //#region src/tui/runtime/transport.ts
5
5
  /** The shared daemon over its socket; spawns one when none is listening. */
6
6
  function socketTransport(options = {}) {
@@ -41,7 +41,7 @@ function wsTransport(options) {
41
41
  return {
42
42
  async connect(connectOptions = {}) {
43
43
  connectOptions.signal?.throwIfAborted();
44
- const { connectWsClient } = await import("./ws-client-D7eb2UbH.js").then((n) => n.i);
44
+ const { connectWsClient } = await import("./ws-client-DPC9ZJHr.js").then((n) => n.i);
45
45
  connectOptions.signal?.throwIfAborted();
46
46
  return connectWsClient({
47
47
  url: options.url,
@@ -53,7 +53,7 @@ function wsTransport(options) {
53
53
  });
54
54
  },
55
55
  async classify(err) {
56
- const { classifyWsFailure } = await import("./ws-BD9FN98u.js").then((n) => n.i);
56
+ const { classifyWsFailure } = await import("./ws-B2HB87nz.js").then((n) => n.i);
57
57
  return classifyWsFailure(err);
58
58
  }
59
59
  };
@@ -121,4 +121,4 @@ function hostSignalHandlers(sinks) {
121
121
  //#endregion
122
122
  export { wsTransport as i, lazyInProcessTransport as n, socketTransport as r, hostSignalHandlers as t };
123
123
 
124
- //# sourceMappingURL=host-signals-DI9JQvR9.js.map
124
+ //# sourceMappingURL=host-signals-CQ2jZR-2.js.map