chainlesschain 0.162.35 → 0.162.37

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 (193) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CJn02U42.js → AIOps-_oxz4VHy.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-ewURAAoy.js → ActionButton-uaeqFuDj.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BiSadESb.js → Analytics-BPVV0OUf.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BR0WOEug.js → AppLayout-ppCYKm3I.js} +4 -4
  6. package/src/assets/web-panel/assets/{Audit-CrqcYx0e.js → Audit-DFAY6umk.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-DtbSBn4e.js → Backup-pAPBFDyP.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-BjSc9j0o.js → BaseInput-BbBl0uT2.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-ixzrlCJE.js → Chat-Ct22JUnT.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-B78nEq05.js → ChatBubbleRenderer-DPlsLl22.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-UGYeSsgr.js → Checkbox-DEkCollc.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B97OOAg4.js → Codegen-Tor-de39.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-D9aGkaZ6.js → Col-ojNrLQU7.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-Dc2v2RGS.js → Community-CLOGhqMF.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-B_FYlUQR.js → Compact-CYKNlSZ4.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-C4FiTHyC.js → Compliance-C5E6ABuA.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CQ8j3LIg.js → Cowork-CHeEsZ3W.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-Dzjs9Z9Z.js → Cron-B4e1n2e7.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-BXI24uzI.js → Crosschain-DbNV8P9R.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-C-I4_d07.js → DID-C5_Tk3nC.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-BzzGh5mo.js → Dashboard-BhdV_c4N.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-Bh8H70De.js → Dropdown-CEi5AMtM.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-DI_qybJP.js → EmailListRenderer-DOhPiYng.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DkKTsfc4.js → FamilyGuardDashboard-fu4NRP3X.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DS7CmvVG.js → Federation-B7BtIWKL.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CI97WsB5.js → FormItemContext-BmPWZVLP.js} +1 -1
  27. package/src/assets/web-panel/assets/GenericCardRenderer-hsOPNJq8.js +1 -0
  28. package/src/assets/web-panel/assets/{Git-CEh0gR2W.js → Git-Bi_EFBUH.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-kIr3tls2.js → Governance-emf2ubDK.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-CC1GzyC1.js → Inference-B7KjKzkI.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-BNgTiWOB.js → KnowledgeGraph-uAaBK0F3.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-B2P10gB1.js → Logs-utK7hNpj.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-HPfBvbFZ.js → Marketplace-CzQe6n3z.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-ByYotSKb.js → McpTools-CuAaJr51.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BGIAzFVS.js → Memory-CRuZZJ75.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-CroNYTAH.js → MobileBridge-Cp06wunh.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-DJEdUwhr.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-BqhyIwo9.js → Mtc-8YY4dR7g.js} +2 -2
  39. package/src/assets/web-panel/assets/{MtcAudit-BpEKOvx9.js → MtcAudit-BmPJYHar.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DST1d_Qo.js → Multisig-d-ydyVdq.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-DlMsZcK_.js → NLProgramming-DA_ikw_n.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-C734UJvD.js → Notes-DIyF-fRe.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-C0-pPxvk.js → NotificationSettings-CzPZXEtK.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-BiLtg-LY.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-C5iHC_yW.js → Organization-DdDZ_Ap6.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CovuHHVR.js → Overflow-BnMBkttv.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-Dx9QL-Gy.js → P2P-Es1050f-.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-IP1dEt6-.js → PdhVaultBrowser-CKkRmyn9.js} +4 -4
  49. package/src/assets/web-panel/assets/{Permissions-BrR1XZG5.js → Permissions-zU9n9cAD.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BgqxVE5m.js → PersonalDataHub-BZi5Xwas.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-DzMk5HAz.js → Pipeline-CRfeGiFc.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-CDoLa6tk.js → Privacy-CQA_IgLA.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-Dy5gc6ve.js → ProjectInit-C9hmEvoT.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DXy-k4hG.js → ProjectSettings-yXA72ws4.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-BpWS-qam.js +1 -0
  56. package/src/assets/web-panel/assets/Providers-Cxe55dRD.js +1 -0
  57. package/src/assets/web-panel/assets/{QuickAsk-B8KEHCnd.js → QuickAsk-Do0aUTQr.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-DNVHGYYZ.js → Recommend--ysZHjyA.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CaDhWP03.js → Reputation-BOBU8JrH.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-CrGLI02x.js → Row-C6X7bRKE.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-BX7P8I6i.js → RssFeed-D8AwqlkQ.js} +3 -3
  62. package/src/assets/web-panel/assets/Search-Bi3rCZD4.js +1 -0
  63. package/src/assets/web-panel/assets/{Security-B6J7IFc1.js → Security-DxUDVrtY.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-vvdcO3mM.js → Services-BXXN7yC1.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-BoAoPTzZ.js → Skeleton-B3BR34tZ.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CyIQV5b3.js → Skills-BjYu8OQ1.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-BAQVgdZV.js → Sla-DDkCtD8w.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-Bxcn1Jkj.js → SpeechSettings-CGhYzP7V.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-Dpaj3hDM.js → SyncSettings-CYNKVAHA.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-Bwqo89En.js → Tasks-DavmlJpd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Bowcqifn.js → Templates-CQuYFf2C.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-DOkf85uG.js → Tenant-DdzZh8vE.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-D75WeG9d.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-B9A3zDXA.js → TimelineRenderer-DKOARnc_.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-jtVVqKFr.js → Tokens-D7QRNG8y.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-26Iw-iIl.js → Trigger-BCsqLZl4.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-DqY5ORrH.js → Trust-BarGUa6p.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-BFsbr3y7.js → UkeySign-pHrg5a8E.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BtDbj3oa.js → VideoEditing-Dug3m1py.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-BAwmwHbk.js → Wallet-BfK3Z_Ez.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DINJTsfq.js → WebAuthn-CYRdl9td.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-BEorm8SK.js → WorkflowEditor-DTW5AcqM.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CE39-Dxg.js → chat-CCXz4j38.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-C_cLZ93a.js → colors-BJBOhAqa.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BSioWA2c.js → compact-item-E9M6BQcM.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CGTk4mhN.js → createContext-Cg9CAws4.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BrsbTJUv.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Dl1fRwS_.js → hasIn-DhVtqv5L.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-pngH1and.js → index--7o5YdL6.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-BmbVyhk1.js → index-4N5lNXGP.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-BnEPB1Mz.js → index-6-04M2Nx.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-Cxw3p73X.js → index-B111fZ21.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-CST381Qf.js → index-B4NBF4Sa.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-ChwpS1f0.js → index-B8bjEHrQ.js} +1 -1
  95. package/src/assets/web-panel/assets/{index--SWvw6yW.js → index-BAB0nGP7.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CAwVwBOL.js → index-BFZPRd0T.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-hv4jUdG3.js → index-B_SMPD4L.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Qj2x55mz.js → index-BxSzyly9.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BWpfxzVm.js → index-ByazO4Q9.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Di6nvW1N.js → index-C-2dUIli.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BhqOTuMW.js → index-CFarAlXj.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CA6K7lZB.js → index-CFp-wdrQ.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DTKEXyaW.js → index-CJ8nNT8h.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-iiZfONfx.js → index-CSiyjCYi.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DTpCUi0m.js → index-CUp_c8Le.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bvi14vJ7.js → index-CVR_s-pT.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-DKEipmR8.js → index-Ca8BYV1g.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DJyeeygd.js → index-CeRlLp3F.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-C9tq8Da8.js → index-ChsSljaN.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B2QiUEgK.js → index-CkTeBHI9.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-OCxo0X6J.js → index-Cm1m7BJh.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-DrWERr8C.js → index-ComyTKz-.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-B016Fsqr.js → index-CznfPnOx.js} +3 -3
  114. package/src/assets/web-panel/assets/{index-CisXVbSt.js → index-D5yC2Ps8.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-C-VVk1Jg.js → index-D7DXdf7x.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DDQx2YFc.js → index-DDcJO27F.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-Ds2RzRG0.js → index-DSQazU6J.js} +1 -1
  118. package/src/assets/web-panel/assets/index-DSTQDO-Y.js +1 -0
  119. package/src/assets/web-panel/assets/{index-C4JXchTG.js → index-DaFe1aqY.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BAhinBPR.js → index-DdhnGez0.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-9_mmaR42.js → index-Di5LBXcE.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-D9D4q-qI.js → index-Dwvewrul.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CbXnyoSO.js → index-MdXEhfdJ.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-II3JhQu2.js → index-_PNqQ5mE.js} +1 -1
  125. package/src/assets/web-panel/assets/index-c2U6LV3Q.js +1 -0
  126. package/src/assets/web-panel/assets/{index-C2ly7sCw.js → index-kz1oXl1a.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-Ceo9P9tQ.js → index-wkt-o5q5.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-GOhLA2-f.js → initDefaultProps-iyBaePF-.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-jqxFzHTx.js → motion-RWtj4rgu.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-CSLsp6TA.js → move-CqPRVzpH.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-Cnlrb25c.js → omit-DsvJze25.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CLqlxWWD.js → pickAttrs-B4tfZBhc.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-BAWIWtul.js → placementArrow-KvHUwXMA.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-CSR1DayS.js → responsiveObserve-DGdJ-b7W.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-CNhoPJOp.js → slide-Cd6ebRmw.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-BZiYHRHW.js → statusUtils-Bg9GcIAn.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BMoY-Fm5.js → styleChecker-MQjKsG84.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-DhtNdlaS.js → useFlexGapSupport-C241WujP.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DNPtDOZ4.js → useFs-CMpy7RS4.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-DTdjNvAI.js → usePersonalDataHub-BLHtapKb.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-C9zW9IJ2.js → vnode-DmcTV67c.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-D-6RYJJr.js → zoom-DHL8_0Y8.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +161 -6
  145. package/src/commands/agents.js +8 -2
  146. package/src/commands/cli-anything.js +14 -6
  147. package/src/commands/command.js +7 -2
  148. package/src/commands/hook.js +136 -28
  149. package/src/commands/ide.js +168 -0
  150. package/src/commands/loop.js +450 -0
  151. package/src/commands/mcp.js +92 -0
  152. package/src/commands/output-style.js +127 -0
  153. package/src/commands/permissions.js +211 -0
  154. package/src/commands/statusline.js +93 -0
  155. package/src/index.js +10 -2
  156. package/src/lib/agent-core.js +7 -0
  157. package/src/lib/agents.js +5 -0
  158. package/src/lib/hook-manager.js +1 -0
  159. package/src/lib/hook-runner.cjs +183 -0
  160. package/src/lib/ide-bridge.js +310 -0
  161. package/src/lib/image-input.js +156 -0
  162. package/src/lib/loop.js +198 -0
  163. package/src/lib/mcp-oauth.js +415 -0
  164. package/src/lib/output-styles.js +179 -0
  165. package/src/lib/permission-rules.cjs +325 -0
  166. package/src/lib/provider-options.js +11 -7
  167. package/src/lib/settings-hook-events.cjs +102 -0
  168. package/src/lib/settings-hooks.cjs +163 -0
  169. package/src/lib/settings-loader.cjs +244 -0
  170. package/src/lib/slash-commands.js +4 -0
  171. package/src/lib/status-line.cjs +204 -0
  172. package/src/lib/sub-agent-profiles.js +3 -0
  173. package/src/lib/web-search.js +487 -0
  174. package/src/repl/agent-repl.js +450 -35
  175. package/src/repl/slash-macro.js +45 -0
  176. package/src/runtime/agent-core.js +799 -21
  177. package/src/runtime/coding-agent-contract-shared.cjs +94 -4
  178. package/src/runtime/coding-agent-policy.cjs +24 -0
  179. package/src/runtime/headless-runner.js +162 -6
  180. package/src/runtime/headless-stream.js +133 -7
  181. package/src/runtime/mcp-config.js +161 -15
  182. package/src/runtime/policies/agent-policy.js +4 -0
  183. package/src/runtime/system-prompt.js +6 -1
  184. package/src/assets/web-panel/assets/GenericCardRenderer-Da27EdR4.js +0 -1
  185. package/src/assets/web-panel/assets/MobileProjects-CH-qnGEV.js +0 -1
  186. package/src/assets/web-panel/assets/OrderTableRenderer-C7zT9eFc.js +0 -1
  187. package/src/assets/web-panel/assets/Projects-DvsaEbZR.js +0 -1
  188. package/src/assets/web-panel/assets/Providers-Demck9PO.js +0 -1
  189. package/src/assets/web-panel/assets/Search-laS6rz8M.js +0 -1
  190. package/src/assets/web-panel/assets/Terminal-v4MM9dCj.js +0 -3
  191. package/src/assets/web-panel/assets/devWarning-PObcVnJR.js +0 -1
  192. package/src/assets/web-panel/assets/index-BNwIzLyX.js +0 -1
  193. package/src/assets/web-panel/assets/index-Dh6FxR9B.js +0 -1
@@ -0,0 +1,244 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * settings-loader — discover + merge `.claude/settings.json` files into a
5
+ * single permission ruleset, Claude-Code style.
6
+ *
7
+ * Precedence (lowest → highest):
8
+ * 1. ~/.claude/settings.json (user, all projects)
9
+ * 2. <project>/.claude/settings.json (project, checked in)
10
+ * 3. <project>/.claude/settings.local.json (personal override, gitignored)
11
+ * 4. --settings <file> (explicit, CC `--settings` parity)
12
+ * 5. CC_PERMISSIONS_ALLOW / _ASK / _DENY env (comma-separated, kill-switch)
13
+ *
14
+ * Permission arrays are **unioned** across sources — a higher layer can add
15
+ * rules but never *remove* a lower layer's `deny` (deny can only accrete). The
16
+ * engine's deny > ask > allow precedence then resolves any overlap, so an
17
+ * inherited deny always beats a locally added allow.
18
+ *
19
+ * Robustness: a missing file is silently skipped; a malformed file warns to
20
+ * stderr and is skipped (fail-open to the existing risk-tier logic — a broken
21
+ * settings file must never wedge the agent). All reads are explicit UTF-8.
22
+ *
23
+ * `_deps` injection (fs / homedir) follows the CLI testing convention since
24
+ * `vi.mock` cannot intercept CJS `require`.
25
+ */
26
+
27
+ const fsDefault = require("node:fs");
28
+ const path = require("node:path");
29
+ const os = require("node:os");
30
+
31
+ const _deps = { fs: fsDefault, homedir: () => os.homedir() };
32
+
33
+ const KINDS = Object.freeze(["allow", "ask", "deny"]);
34
+
35
+ /** Read + JSON.parse one settings file. Returns null on missing/bad. */
36
+ function readSettingsFile(file, { onWarn } = {}) {
37
+ let text;
38
+ try {
39
+ if (!_deps.fs.existsSync(file)) return null;
40
+ text = _deps.fs.readFileSync(file, "utf-8");
41
+ } catch {
42
+ return null; // unreadable → treat as absent
43
+ }
44
+ try {
45
+ const parsed = JSON.parse(text);
46
+ return parsed && typeof parsed === "object" ? parsed : null;
47
+ } catch (err) {
48
+ if (typeof onWarn === "function") {
49
+ onWarn(`settings: ignoring malformed ${file} (${err.message})`);
50
+ } else {
51
+ process.stderr.write(
52
+ `settings: ignoring malformed ${file} (${err.message})\n`,
53
+ );
54
+ }
55
+ return null;
56
+ }
57
+ }
58
+
59
+ /** Push every string of `arr` into `target` + `sources`, de-duped. */
60
+ function accrete(target, sources, arr, file, kind) {
61
+ if (!Array.isArray(arr)) return;
62
+ for (const entry of arr) {
63
+ const rule = String(entry || "").trim();
64
+ if (!rule) continue;
65
+ if (!target.includes(rule)) target.push(rule);
66
+ // First source to introduce a rule wins as its provenance label.
67
+ const key = `${kind}:${rule}`;
68
+ if (!sources[key]) sources[key] = file;
69
+ }
70
+ }
71
+
72
+ /** The ordered list of candidate settings files for a cwd. */
73
+ function settingsPaths(cwd, explicitFile) {
74
+ const home = _deps.homedir();
75
+ const list = [
76
+ path.join(home, ".claude", "settings.json"),
77
+ path.join(cwd, ".claude", "settings.json"),
78
+ path.join(cwd, ".claude", "settings.local.json"),
79
+ ];
80
+ if (explicitFile) list.push(path.resolve(cwd, explicitFile));
81
+ return list;
82
+ }
83
+
84
+ /** Parse a comma/space separated env override into a string[]. */
85
+ function parseEnvList(value) {
86
+ if (!value) return [];
87
+ return String(value)
88
+ .split(/[,\n]+/)
89
+ .map((s) => s.trim())
90
+ .filter(Boolean);
91
+ }
92
+
93
+ /**
94
+ * Load + merge the effective permission ruleset for a project.
95
+ *
96
+ * @param {object} [opts]
97
+ * @param {string} [opts.cwd=process.cwd()]
98
+ * @param {string} [opts.settingsFile] value of --settings (CC parity)
99
+ * @param {object} [opts.env=process.env]
100
+ * @param {(msg:string)=>void} [opts.onWarn]
101
+ * @returns {{
102
+ * rules: { allow:string[], ask:string[], deny:string[] },
103
+ * sources: Record<string,string>, // "kind:rule" → originating file
104
+ * files: string[] // settings files that contributed
105
+ * }}
106
+ */
107
+ function loadSettings(opts = {}) {
108
+ const cwd = opts.cwd || process.cwd();
109
+ const env = opts.env || process.env;
110
+ const onWarn = opts.onWarn;
111
+
112
+ const rules = { allow: [], ask: [], deny: [] };
113
+ const sources = {};
114
+ const files = [];
115
+
116
+ for (const file of settingsPaths(cwd, opts.settingsFile)) {
117
+ const data = readSettingsFile(file, { onWarn });
118
+ if (!data) continue;
119
+ const perms =
120
+ data.permissions && typeof data.permissions === "object"
121
+ ? data.permissions
122
+ : {};
123
+ let contributed = false;
124
+ for (const kind of KINDS) {
125
+ const before = rules[kind].length;
126
+ accrete(rules[kind], sources, perms[kind], file, kind);
127
+ if (rules[kind].length !== before) contributed = true;
128
+ }
129
+ if (contributed) files.push(file);
130
+ }
131
+
132
+ // env kill-switch layer (highest precedence source label)
133
+ const envMap = {
134
+ allow: parseEnvList(env.CC_PERMISSIONS_ALLOW),
135
+ ask: parseEnvList(env.CC_PERMISSIONS_ASK),
136
+ deny: parseEnvList(env.CC_PERMISSIONS_DENY),
137
+ };
138
+ let envContributed = false;
139
+ for (const kind of KINDS) {
140
+ const before = rules[kind].length;
141
+ accrete(rules[kind], sources, envMap[kind], "<env>", kind);
142
+ if (rules[kind].length !== before) envContributed = true;
143
+ }
144
+ if (envContributed) files.push("<env>");
145
+
146
+ return { rules, sources, files };
147
+ }
148
+
149
+ /** Look up the source file a matched `{ kind, rule }` came from. */
150
+ function ruleSource(sources, kind, rule) {
151
+ return (sources && sources[`${kind}:${rule}`]) || null;
152
+ }
153
+
154
+ /** Resolve a write target file for a scope (project | local | user). */
155
+ function scopeFile(cwd, scope) {
156
+ if (scope === "user") {
157
+ return path.join(_deps.homedir(), ".claude", "settings.json");
158
+ }
159
+ if (scope === "local") {
160
+ return path.join(cwd, ".claude", "settings.local.json");
161
+ }
162
+ return path.join(cwd, ".claude", "settings.json"); // project (default)
163
+ }
164
+
165
+ /**
166
+ * Append a permission rule to a settings file (idempotent). Used by
167
+ * `cc permissions add` and the REPL "always allow" flow.
168
+ *
169
+ * @returns {{ file:string, added:boolean }} added=false → already present.
170
+ * @throws if the target file exists but is malformed JSON (refuse to clobber).
171
+ */
172
+ function addRule({ cwd = process.cwd(), kind, rule, scope = "project" } = {}) {
173
+ if (!KINDS.includes(kind)) {
174
+ throw new Error(`kind must be allow | ask | deny (got "${kind}")`);
175
+ }
176
+ const file = scopeFile(cwd, scope);
177
+ let data = {};
178
+ if (_deps.fs.existsSync(file)) {
179
+ const text = _deps.fs.readFileSync(file, "utf-8");
180
+ try {
181
+ data = JSON.parse(text) || {};
182
+ } catch (err) {
183
+ throw new Error(`refusing to overwrite malformed ${file} (${err.message})`);
184
+ }
185
+ }
186
+ if (!data.permissions || typeof data.permissions !== "object") {
187
+ data.permissions = {};
188
+ }
189
+ if (!Array.isArray(data.permissions[kind])) data.permissions[kind] = [];
190
+ if (data.permissions[kind].includes(rule)) return { file, added: false };
191
+ data.permissions[kind].push(rule);
192
+ _deps.fs.mkdirSync(path.dirname(file), { recursive: true });
193
+ _deps.fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf-8");
194
+ return { file, added: true };
195
+ }
196
+
197
+ /**
198
+ * Native-config overrides from the same settings files loadSettings reads
199
+ * (user → project → local → explicit --settings, last-write-wins). Mirrors
200
+ * Claude-Code settings.json `model` + `env`. Permissions stay with
201
+ * loadSettings; this is the config-override half — a one-shot way to set the
202
+ * model / env vars for a run without editing .chainlesschain/config.json.
203
+ *
204
+ * @param {object} [opts] { cwd, settingsFile, onWarn }
205
+ * @returns {{ model: string|null, env: Record<string,string>, files: string[] }}
206
+ */
207
+ function loadSettingsConfig(opts = {}) {
208
+ const cwd = opts.cwd || process.cwd();
209
+ let model = null;
210
+ const env = {};
211
+ const files = [];
212
+ for (const file of settingsPaths(cwd, opts.settingsFile)) {
213
+ const data = readSettingsFile(file, { onWarn: opts.onWarn });
214
+ if (!data) continue;
215
+ let contributed = false;
216
+ if (typeof data.model === "string" && data.model.trim()) {
217
+ model = data.model.trim();
218
+ contributed = true;
219
+ }
220
+ if (data.env && typeof data.env === "object" && !Array.isArray(data.env)) {
221
+ for (const [k, v] of Object.entries(data.env)) {
222
+ if (typeof v === "string") {
223
+ env[k] = v;
224
+ contributed = true;
225
+ }
226
+ }
227
+ }
228
+ if (contributed) files.push(file);
229
+ }
230
+ return { model, env, files };
231
+ }
232
+
233
+ module.exports = {
234
+ loadSettings,
235
+ loadSettingsConfig,
236
+ readSettingsFile,
237
+ settingsPaths,
238
+ parseEnvList,
239
+ ruleSource,
240
+ addRule,
241
+ scopeFile,
242
+ KINDS,
243
+ _deps,
244
+ };
@@ -63,7 +63,11 @@ export const BANG_TIMEOUT_MS = 10_000;
63
63
  export function commandDirs(cwd = process.cwd(), opts = {}) {
64
64
  const path = opts.deps?.path || _deps.path;
65
65
  const home = opts.home || homedir();
66
+ // Project-native `.chainlesschain/commands/` takes precedence, then the
67
+ // Claude-Code-portable `.claude/commands/` (so existing definitions work
68
+ // unchanged), then personal. Both project dirs share the "project" scope.
66
69
  return [
70
+ { dir: path.join(cwd, ".chainlesschain", "commands"), scope: "project" },
67
71
  { dir: path.join(cwd, ".claude", "commands"), scope: "project" },
68
72
  { dir: path.join(home, ".claude", "commands"), scope: "personal" },
69
73
  ];
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * status-line — Claude-Code `statusLine` parity. A user command rendered into
5
+ * a status line shown above the REPL prompt each turn (model / branch / cost /
6
+ * whatever the script prints).
7
+ *
8
+ * Config lives in the `.claude/settings.json` hierarchy under `statusLine`:
9
+ * { "statusLine": { "type": "command", "command": "~/.claude/status.sh" } }
10
+ * (a bare string is also accepted as the command). The command receives a JSON
11
+ * context on stdin — `{ session_id, model:{id}, workspace:{current_dir,
12
+ * project_dir}, cwd }` — and its first stdout line becomes the status line.
13
+ *
14
+ * Best-effort throughout: a missing config, a broken command, or a timeout just
15
+ * yields no status line — it never blocks or breaks the REPL. `_deps` injection
16
+ * (spawnSync / settings reader) for tests.
17
+ */
18
+
19
+ const cpDefault = require("node:child_process");
20
+ const path = require("node:path");
21
+ const { settingsPaths, readSettingsFile } = require("./settings-loader.cjs");
22
+
23
+ const _deps = {
24
+ spawnSync: cpDefault.spawnSync,
25
+ // injectable so tests don't touch real settings files
26
+ readSettings: (cwd, settingsFile) => {
27
+ const out = [];
28
+ for (const file of settingsPaths(cwd, settingsFile)) {
29
+ const data = readSettingsFile(file);
30
+ if (data) out.push(data);
31
+ }
32
+ return out;
33
+ },
34
+ };
35
+
36
+ /**
37
+ * Load the effective `statusLine` config (last layer wins). Returns
38
+ * `{ command, type, padding }` or null.
39
+ */
40
+ function loadStatusLineConfig({ cwd = process.cwd(), settingsFile } = {}) {
41
+ let cfg = null;
42
+ for (const data of _deps.readSettings(cwd, settingsFile)) {
43
+ if (!data || data.statusLine == null) continue;
44
+ const sl = data.statusLine;
45
+ if (typeof sl === "string" && sl.trim()) {
46
+ cfg = { type: "command", command: sl.trim(), padding: 0 };
47
+ } else if (sl && typeof sl === "object" && typeof sl.command === "string") {
48
+ cfg = {
49
+ type: sl.type || "command",
50
+ command: sl.command,
51
+ padding: Number(sl.padding) || 0,
52
+ };
53
+ } else if (sl === false || sl === null) {
54
+ cfg = null; // an explicit disable in a higher layer wins
55
+ }
56
+ }
57
+ return cfg && cfg.command ? cfg : null;
58
+ }
59
+
60
+ /**
61
+ * Build the JSON context handed to the status-line command on stdin (and used
62
+ * by the built-in renderer). Carries context-window usage so a custom command —
63
+ * or the built-in line — can show how full the window is. Additive: the prior
64
+ * fields are unchanged; `context` + `turn` are new.
65
+ */
66
+ function buildContext({
67
+ sessionId,
68
+ model,
69
+ provider,
70
+ cwd,
71
+ projectDir,
72
+ usedTokens = 0,
73
+ contextWindow = 0,
74
+ turn = 0,
75
+ } = {}) {
76
+ const used = Math.max(0, Number(usedTokens) || 0);
77
+ const window = Math.max(0, Number(contextWindow) || 0);
78
+ const pct = window > 0 ? Math.min(100, Math.round((used / window) * 100)) : 0;
79
+ return {
80
+ hook_event_name: "Status",
81
+ session_id: sessionId || null,
82
+ model: { id: model || null, display_name: model || null },
83
+ provider: provider || null,
84
+ workspace: {
85
+ current_dir: cwd || process.cwd(),
86
+ project_dir: projectDir || cwd || process.cwd(),
87
+ },
88
+ cwd: cwd || process.cwd(),
89
+ context: { used_tokens: used, window, pct },
90
+ turn: Math.max(0, Number(turn) || 0),
91
+ };
92
+ }
93
+
94
+ /** Compact a token count: 950 → "950", 12345 → "12.3k", 1500000 → "1.5M". */
95
+ function formatTokens(n) {
96
+ const v = Number(n) || 0;
97
+ if (v < 1000) return String(Math.max(0, Math.round(v)));
98
+ if (v < 1_000_000) {
99
+ const k = v / 1000;
100
+ return (k >= 100 ? Math.round(k) : Number(k.toFixed(1))) + "k";
101
+ }
102
+ return Number((v / 1_000_000).toFixed(1)) + "M";
103
+ }
104
+
105
+ /** Home-relative compact path: the home dir collapses to "~". */
106
+ function shortenPath(p) {
107
+ const cwd = String(p || "");
108
+ let home = "";
109
+ try {
110
+ home = require("node:os").homedir() || "";
111
+ } catch {
112
+ home = "";
113
+ }
114
+ if (
115
+ home &&
116
+ (cwd === home || cwd.startsWith(home + "/") || cwd.startsWith(home + "\\"))
117
+ ) {
118
+ return "~" + cwd.slice(home.length);
119
+ }
120
+ return cwd;
121
+ }
122
+
123
+ /**
124
+ * Built-in context-usage line shown when no custom `statusLine` command is
125
+ * configured: "model · ⛁ used/window (pct%) · cwd · turn N". No color — the
126
+ * caller dims it. Returns "" when there's genuinely nothing to show.
127
+ */
128
+ function renderDefaultStatusLine(context = {}) {
129
+ const c = context.context || {};
130
+ const parts = [];
131
+ if (context.model && context.model.id) parts.push(context.model.id);
132
+ if (c.window > 0) {
133
+ parts.push(
134
+ `⛁ ${formatTokens(c.used_tokens)}/${formatTokens(c.window)} (${c.pct}%)`,
135
+ );
136
+ } else if (c.used_tokens > 0) {
137
+ parts.push(`⛁ ${formatTokens(c.used_tokens)}`);
138
+ }
139
+ if (context.cwd) parts.push(shortenPath(context.cwd));
140
+ if (context.turn) parts.push(`turn ${context.turn}`);
141
+ return parts.join(" · ");
142
+ }
143
+
144
+ /**
145
+ * Whether the user explicitly disabled the status line via `statusLine: false`
146
+ * in the effective (last-layer-wins) settings. Lets the REPL suppress even the
147
+ * built-in line, while a mere absence of config still shows the built-in.
148
+ */
149
+ function isStatusLineDisabled({ cwd = process.cwd(), settingsFile } = {}) {
150
+ let disabled = false;
151
+ for (const data of _deps.readSettings(cwd, settingsFile)) {
152
+ if (!data || !("statusLine" in data)) continue;
153
+ disabled = data.statusLine === false;
154
+ }
155
+ return disabled;
156
+ }
157
+
158
+ /**
159
+ * Render the status line by running the command with the JSON context on stdin.
160
+ * Returns the first stdout line (trimmed, ANSI preserved) or null.
161
+ */
162
+ function renderStatusLine(config, context = {}, { cwd, timeout = 5000 } = {}) {
163
+ if (!config || !config.command) return null;
164
+ let res;
165
+ try {
166
+ res = _deps.spawnSync(config.command, {
167
+ input: JSON.stringify(context),
168
+ cwd: cwd || process.cwd(),
169
+ encoding: "utf-8",
170
+ timeout,
171
+ shell: true,
172
+ maxBuffer: 1024 * 1024,
173
+ });
174
+ } catch {
175
+ return null; // spawn failure → no status line
176
+ }
177
+ if (res.error || res.status !== 0) return null;
178
+ const out = String(res.stdout || "");
179
+ const firstLine = out.split(/\r?\n/)[0];
180
+ const line = (firstLine || "").trim();
181
+ if (!line) return null;
182
+ const pad = config.padding > 0 ? " ".repeat(config.padding) : "";
183
+ return pad + line;
184
+ }
185
+
186
+ /** Convenience: load + render in one call (used by the REPL each turn). */
187
+ function getStatusLine({ cwd, settingsFile, sessionId, model, provider, projectDir, timeout } = {}) {
188
+ const config = loadStatusLineConfig({ cwd, settingsFile });
189
+ if (!config) return null;
190
+ const context = buildContext({ sessionId, model, provider, cwd, projectDir });
191
+ return renderStatusLine(config, context, { cwd, timeout });
192
+ }
193
+
194
+ module.exports = {
195
+ loadStatusLineConfig,
196
+ buildContext,
197
+ renderStatusLine,
198
+ getStatusLine,
199
+ formatTokens,
200
+ shortenPath,
201
+ renderDefaultStatusLine,
202
+ isStatusLineDisabled,
203
+ _deps,
204
+ };
@@ -21,6 +21,7 @@ const READONLY_TOOLS = Object.freeze([
21
21
  "search_files",
22
22
  "search_sessions",
23
23
  "web_fetch",
24
+ "web_search",
24
25
  "list_skills",
25
26
  ]);
26
27
 
@@ -38,6 +39,7 @@ const FULL_TOOLS = Object.freeze([
38
39
  "run_skill",
39
40
  "list_skills",
40
41
  "web_fetch",
42
+ "web_search",
41
43
  "todo_write",
42
44
  "ask_user_question",
43
45
  ]);
@@ -50,6 +52,7 @@ const DESIGN_TOOLS = Object.freeze([
50
52
  "list_dir",
51
53
  "search_files",
52
54
  "web_fetch",
55
+ "web_search",
53
56
  "run_skill",
54
57
  "list_skills",
55
58
  "todo_write",