chainlesschain 0.162.34 → 0.162.36

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 (185) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BYfi9NYS.js → AIOps-vAVAFNJ4.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BiS_tAN7.js → ActionButton-BnRHFCKM.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-jiWl_p-B.js → Analytics-BOjwqWqG.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-m4sIzDot.js → AppLayout-Dc0D1Txn.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CPla3Erm.js → Audit-dd_2efaZ.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BGeQzTaB.js → Backup-HF1jgm8G.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-DTf7Z1iU.js → BaseInput-CCtzmoKe.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DPTlQlD-.js → Chat-BNfH1c3p.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-BgRXce4e.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-DY-XuQMu.js → Checkbox-BOr-NscK.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B6oxPiZI.js → Codegen-DE058N7-.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-Dqxb4wSE.js → Col-SOREo1XE.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DCIX514p.js → Community-sOvNZo9f.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-BGtCzDoJ.js → Compact-DnBe558D.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-zcOYd55o.js → Compliance-o-r6CUbg.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-DVTtdIdM.js → Cowork-D6_k9mHP.js} +4 -4
  18. package/src/assets/web-panel/assets/{Cron-CPUaR69k.js → Cron-CEV3Xkrm.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-DnjUS6QH.js → Crosschain-eJ1lQWKU.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-Dnz8VDmx.js → DID-B-WqM9Hp.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-CtWf27j7.js → Dashboard-ZnKPcsHN.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-B4GC1ZV4.js → Dropdown-B8uLWDIP.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-wjij3kzr.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-rS-2W4u5.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-90p5Tnoz.js → Federation-C_07GXoq.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Cnrw7gzq.js → FormItemContext-D3kbYrMU.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-C85NsWa3.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-BFAVM9F8.js → Git-BlwWlMMB.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-DBoRonpq.js → Governance-DxN3wQZ_.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DHRyD66j.js → Inference-ls7pSw_D.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-CTvUKecD.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CB0dv_Ts.js → Logs-CvEVY5TK.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-CN7Hm5Uw.js → Marketplace-C3qvQJT7.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-q5H25_8L.js → McpTools-DiwKpnKx.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BCV3pZ1d.js → Memory-CIBPi_da.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-C04Mngt4.js → MobileBridge-D-v0Se8y.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-cP1apTQD.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-ByAMz2DN.js → Mtc-BMFWrI65.js} +4 -4
  39. package/src/assets/web-panel/assets/{MtcAudit-B7V7byJq.js → MtcAudit-2s8LaHtR.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-DtKmcVQV.js → Multisig-dL_nvj7d.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CaMbT5SC.js → NLProgramming-BbrJp06R.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-DRjbSTCU.js → Notes-jR9irwy3.js} +4 -4
  43. package/src/assets/web-panel/assets/{NotificationSettings-B9YbJID5.js → NotificationSettings-Dk-STCIX.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-BcI_-vGS.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-oTask4BE.js → Organization-BCK5jylo.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-Bab06ey7.js → Overflow-BRAY7Smt.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P--wlBeU0N.js → P2P-BltVRGjb.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-D4t77Pwc.js → PdhVaultBrowser-CV8UbXHe.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-B3sf6CJ3.js → Permissions-_tNl47Qh.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-BXOojk63.js → PersonalDataHub-Cgc4HjpX.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-DReqtBFN.js → Pipeline-Bn_QU4mu.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-cT1GwKLx.js → Privacy-jzJowp5P.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BhTAzVhH.js → ProjectInit-B_1pJ8qd.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-CK-D8Fyj.js → ProjectSettings-CPVZpXzs.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-CbHiwen6.js → Projects-CQsHOWnT.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-B-ftiXa8.js → Providers-CzzMiLC0.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-CT5XPwTF.js → QuickAsk-MxBKIn9o.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CohhlBZ_.js → Recommend-D8lN6Lis.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CrgbixFz.js → Reputation-CfYK-IrV.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-ClExmBn3.js → Row-Bg7NZDP9.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-VV0qizCJ.js → RssFeed-BOVNJhj0.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-CqJapSiL.js → Search-B38qzmhY.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DY66Zie6.js → Security-CjqleZpe.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-RQwxat7-.js → Services-Bu9JSJap.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-0v37UTU_.js → Skeleton-B2RvRkaX.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-B4Vm4DxN.js → Skills-_h42mxMN.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CggphTlo.js → Sla-BssLs56D.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BAOU08C7.js → SpeechSettings-DCxFYHsd.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-DmtC4J1w.js → SyncSettings-D2xQuNLE.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DhpOGOlo.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-C1QK0YoU.js → Templates-CYG-R-aS.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-CieOfmqp.js → Tenant-BQRYLsvP.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-DWdhrxRq.js → Terminal-imKU7N5j.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjFVUUDU.js → TimelineRenderer-BIZzBftk.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-Bwbk3id9.js → Tokens-uMLH5p_a.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-uJle_yj4.js → Trigger-BzS6XPqx.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-BcOuxAA5.js → Trust-R4zhHufZ.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DUu7Ufg6.js → UkeySign-DATQCoGe.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-Ck8JtQ2n.js → VideoEditing-ClUmKOtS.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-B3jw43on.js → Wallet-DzJTbQzD.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-Baf9K0y7.js → WebAuthn-CrXrLmzQ.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-CTEDl_83.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-CKV51quV.js → chat-a6wpYmVL.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-BO_RP_yz.js → colors-CXJADb1t.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BZsxw_ZG.js → compact-item-CL2pohS_.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-CAbvtzVL.js → createContext-xFi_1G5_.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-BtmELbtB.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-QmHT8zDz.js → hasIn-Bchh1rAi.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-fnDgExTu.js → index-B3Tpv7-d.js} +1 -1
  90. package/src/assets/web-panel/assets/index-B4l4vLTB.js +1 -0
  91. package/src/assets/web-panel/assets/{index-BEJa1FiF.js → index-B4zNisy9.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-jd2r-T4p.js → index-B6NehWty.js} +1 -1
  93. package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
  94. package/src/assets/web-panel/assets/{index-BPZHeug4.js → index-B7knYOpm.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-GPY0LjCu.js → index-B7wT5VRi.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DKnngF_f.js → index-BF4xx1_b.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-BRNYA0BV.js → index-BH9t10pe.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-DKquNxL2.js → index-BPH5ESqs.js} +3 -3
  99. package/src/assets/web-panel/assets/{index-CEh2Ry_A.js → index-BmsIKzyu.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Dob6B6qS.js → index-BoaRB-4a.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Ha2_56mf.js → index-BrbJBnT-.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Dln_vjSY.js → index-C2eMYASq.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-CqiKnXtL.js → index-C4yBRKT4.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-B3fwyCjJ.js → index-CGq4HQno.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-B2aiE8jk.js → index-CMybtJY6.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-B5zhcul9.js → index-CR3kFPuC.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-8BMLlHCv.js → index-CTRd7vkq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-C6i3reUS.js → index-CdU8BwRW.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BNvTNZ1V.js → index-Cua_P8St.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DjrDGJP2.js → index-CuehgDOp.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BCsZiq4i.js → index-D-TT9Swq.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-qPafbZmr.js → index-DEYcLAl7.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DeC7lehI.js → index-DQ_hw_5P.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BnPBG3Tr.js → index-DTEu7TSF.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D8CHQnPl.js → index-DVo1GJoj.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-9IqJODII.js → index-DjdOL159.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DBCYOypV.js → index-DsbMVBj1.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BL7gQAuB.js → index-DxahxRP7.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DC1CFfQU.js → index-EPERz4Pu.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CVoYeZ5Q.js → index-IkvkNxbc.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CsBx0u5G.js → index-KCib1PTw.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-5hlO2-JQ.js → index-M8SZI11a.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-CSaI8R_7.js → index-TxbHusq2.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-C6AA-xB2.js → index-dsLc7t6W.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-DRK0oAV5.js → index-jMcv1u5o.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B9Z83FTS.js → index-majCS3s2.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C3K1eHDd.js → index-u8K1y_lh.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-Bc2GWeWe.js → initDefaultProps-DYn3Gc09.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BI-Rxw6o.js → motion-ZS3eolb9.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DRPdwDQB.js → move-CEw4uqr3.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-B4XTl3jW.js → omit-DlHFZnPp.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-Do5d86Wr.js → pickAttrs-eZQvV5fA.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B8VGZ0ZF.js → placementArrow-B31jQwa-.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-Cf0kI_vN.js → responsiveObserve-DAsNmVto.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Cb0psjSL.js → slide-gPQPrYZC.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Bjuo5Oal.js → statusUtils-DwWKX5co.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BLMhoHJ5.js → styleChecker-B3VOtXuH.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-BdCwAfNU.js → useFlexGapSupport-6ADctM2r.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-9Jhaz5gG.js → useFs-6Zx1SSKs.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-xYFyXKwD.js → usePersonalDataHub-BzReowln.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-CVhepE6Z.js → vnode-C8IpEQbD.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-IbbtJ4Zr.js → zoom-ruc9vHr0.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 +199 -0
  146. package/src/commands/command.js +7 -2
  147. package/src/commands/hook.js +136 -28
  148. package/src/commands/ide.js +168 -0
  149. package/src/commands/mcp.js +92 -0
  150. package/src/commands/output-style.js +127 -0
  151. package/src/commands/permissions.js +211 -0
  152. package/src/commands/statusline.js +93 -0
  153. package/src/index.js +8 -0
  154. package/src/lib/agent-core.js +7 -0
  155. package/src/lib/agents.js +147 -0
  156. package/src/lib/hook-manager.js +1 -0
  157. package/src/lib/hook-runner.cjs +183 -0
  158. package/src/lib/ide-bridge.js +310 -0
  159. package/src/lib/image-input.js +156 -0
  160. package/src/lib/mcp-oauth.js +415 -0
  161. package/src/lib/output-styles.js +179 -0
  162. package/src/lib/permission-rules.cjs +325 -0
  163. package/src/lib/provider-options.js +11 -7
  164. package/src/lib/settings-hook-events.cjs +102 -0
  165. package/src/lib/settings-hooks.cjs +163 -0
  166. package/src/lib/settings-loader.cjs +244 -0
  167. package/src/lib/slash-commands.js +21 -13
  168. package/src/lib/status-line.cjs +204 -0
  169. package/src/lib/sub-agent-profiles.js +3 -0
  170. package/src/lib/web-search.js +487 -0
  171. package/src/repl/agent-repl.js +445 -35
  172. package/src/repl/slash-macro.js +45 -0
  173. package/src/runtime/agent-core.js +799 -21
  174. package/src/runtime/coding-agent-contract-shared.cjs +94 -4
  175. package/src/runtime/coding-agent-policy.cjs +24 -0
  176. package/src/runtime/headless-runner.js +162 -6
  177. package/src/runtime/headless-stream.js +133 -7
  178. package/src/runtime/mcp-config.js +161 -15
  179. package/src/runtime/policies/agent-policy.js +1 -0
  180. package/src/runtime/system-prompt.js +6 -1
  181. package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +0 -1
  182. package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +0 -1
  183. package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +0 -1
  184. package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +0 -1
  185. package/src/assets/web-panel/assets/index-CZZnSJEX.js +0 -1
@@ -11,6 +11,8 @@ import fs from "node:fs";
11
11
  import { createAgentRuntimeFactory } from "../runtime/runtime-factory.js";
12
12
  import { resolvePromptText } from "../runtime/system-prompt.js";
13
13
  import { makeFallbackChatFn } from "../runtime/fallback-model.js";
14
+ import { resolveImages, resolveVisionLlm } from "../lib/image-input.js";
15
+ import { loadConfig } from "../lib/config-manager.js";
14
16
 
15
17
  /**
16
18
  * Resolve + validate `--add-dir` values into absolute, existing, de-duped
@@ -37,6 +39,23 @@ export function resolveAddDirs(rawDirs) {
37
39
  return out;
38
40
  }
39
41
 
42
+ /**
43
+ * Parse `--thinking-budget <n>` into a positive integer (Anthropic legacy-model
44
+ * thinking `budget_tokens`), or undefined when unset/invalid. Pure; exported for
45
+ * tests. The companion `thinking` toggle comes from --think/--ultrathink; a
46
+ * budget without that toggle is a no-op (chatWithTools only reads it when
47
+ * thinking is on, and only for legacy models — adaptive models use effort).
48
+ *
49
+ * @param {string|number} [raw]
50
+ * @returns {number|undefined}
51
+ */
52
+ export function resolveThinkingBudget(raw) {
53
+ if (raw === undefined || raw === null || raw === "") return undefined;
54
+ const n = Number(raw);
55
+ if (!Number.isFinite(n) || n <= 0) return undefined;
56
+ return Math.floor(n);
57
+ }
58
+
40
59
  /**
41
60
  * Read all of stdin as a UTF-8 string. Resolves "" immediately when stdin is a
42
61
  * TTY (nothing piped) so we never block an interactive invocation.
@@ -75,6 +94,27 @@ export function registerAgentCommand(program) {
75
94
  )
76
95
  .option("--base-url <url>", "API base URL")
77
96
  .option("--api-key <key>", "API key")
97
+ .option(
98
+ "--think [level]",
99
+ "Enable Anthropic extended thinking (level: think | hard | ultra; Anthropic models only)",
100
+ )
101
+ .option(
102
+ "--ultrathink",
103
+ "Maximum Anthropic extended thinking (= --think ultra)",
104
+ )
105
+ .option(
106
+ "--thinking-budget <n>",
107
+ "Thinking token budget for legacy Claude models (Sonnet 4.5 / Opus 4.0-4.5 / older); clamped below max_tokens. Adaptive-thinking models ignore it (they use --think's effort). Requires --think/--ultrathink.",
108
+ )
109
+ .option(
110
+ "--image <path>",
111
+ "Attach an image (png/jpg/jpeg/gif/webp) to the prompt for a vision-capable model (headless; repeatable)",
112
+ (val, prev) => (prev || []).concat([val]),
113
+ )
114
+ .option(
115
+ "--vision-model <id>",
116
+ "Model to use when an image is attached (default: config llm.visionModel or doubao-seed-1-6-vision-250815)",
117
+ )
78
118
  .option("--session <id>", "Resume a previous agent session")
79
119
  .option(
80
120
  "-c, --continue",
@@ -141,6 +181,10 @@ export function registerAgentCommand(program) {
141
181
  "--append-system-prompt <text>",
142
182
  "Append extra guidance to the system prompt (literal text or @file)",
143
183
  )
184
+ .option(
185
+ "--output-style <name>",
186
+ "Apply a named output-style persona (.claude/output-styles/<name>.md or a built-in: explanatory | learning)",
187
+ )
144
188
  .option(
145
189
  "--input-format <fmt>",
146
190
  "Headless input: text | stream-json (NDJSON user events on stdin, multi-turn)",
@@ -166,10 +210,23 @@ export function registerAgentCommand(program) {
166
210
  "--mcp-config <file>",
167
211
  "Load ad-hoc MCP servers from a JSON file for this run (headless); their tools become callable (mcp__<server>__<tool>)",
168
212
  )
213
+ .option(
214
+ "--no-mcp",
215
+ "Don't auto-connect MCP servers registered with `cc mcp add --auto-connect` (--mcp-config still loads)",
216
+ )
217
+ .option(
218
+ "--ide",
219
+ "Force-enable IDE bridge auto-connect: discover a running editor's MCP server via ~/.chainlesschain/ide/*.json (default: auto inside an IDE integrated terminal)",
220
+ )
221
+ .option("--no-ide", "Disable IDE bridge auto-connect")
169
222
  .option(
170
223
  "--permission-prompt-tool <tool>",
171
224
  "Defer tool approvals to an MCP tool (mcp__<server>__<tool>; requires --mcp-config) instead of headless fail-closed",
172
225
  )
226
+ .option(
227
+ "--settings <file>",
228
+ "Merge an extra .claude/settings.json-shaped file for this run: permission rules (allow/ask/deny) + native config overrides (model, env)",
229
+ )
173
230
  .action(async (task, options) => {
174
231
  // `--continue` / `--resume` resolve a session id so the user need not
175
232
  // copy it. Explicit `--session <id>` always wins. `--resume <id>` targets
@@ -235,9 +292,76 @@ export function registerAgentCommand(program) {
235
292
  process.exit(1);
236
293
  }
237
294
 
295
+ // The explicit `--model` the user typed, captured BEFORE the --settings
296
+ // block below may default options.model — so vision input can tell an
297
+ // explicit model from a settings default.
298
+ const explicitCliModel = options.model;
299
+
300
+ // --settings native config overrides: a .claude/settings.json-shaped file
301
+ // (and the discovered .claude settings) may set `model` + `env` for this
302
+ // run, without editing .chainlesschain/config.json. `--model` still wins
303
+ // over a settings model. Applied once here so every branch (headless +
304
+ // interactive, which all read options.model) picks it up; env vars are
305
+ // set on the process so the agent loop + child tools inherit them.
306
+ try {
307
+ const { loadSettingsConfig } =
308
+ await import("../lib/settings-loader.cjs");
309
+ const sc = loadSettingsConfig({
310
+ cwd: process.cwd(),
311
+ settingsFile: options.settings || null,
312
+ });
313
+ for (const [k, v] of Object.entries(sc.env || {})) {
314
+ process.env[k] = v;
315
+ }
316
+ if (!options.model && sc.model) options.model = sc.model;
317
+ } catch {
318
+ // settings overrides are best-effort — never block the run
319
+ }
320
+
238
321
  // Extra workspace roots (--add-dir) — shared by headless + interactive.
239
322
  const additionalDirectories = resolveAddDirs(options.addDir);
240
323
 
324
+ // --image <path> (repeatable): read into {mediaType, base64} for the
325
+ // headless prompt's vision input. A bad extension fails loudly here
326
+ // rather than sending a broken request.
327
+ let images = [];
328
+ if (Array.isArray(options.image) && options.image.length) {
329
+ try {
330
+ images = resolveImages(options.image);
331
+ } catch (err) {
332
+ process.stderr.write(`Error: ${err.message}\n`);
333
+ process.exit(1);
334
+ }
335
+ }
336
+
337
+ // When an image is attached, default the run to the configured vision LLM
338
+ // (config.llm provider/baseUrl/apiKey + llm.visionModel | --vision-model |
339
+ // doubao default) so `cc agent --image foo.png` works without extra flags.
340
+ // Explicit --provider/--model/etc. always win; no image → unchanged.
341
+ const visionLlm = resolveVisionLlm({
342
+ hasImage: images.length > 0,
343
+ flags: {
344
+ provider: options.provider,
345
+ model: explicitCliModel,
346
+ baseUrl: options.baseUrl,
347
+ apiKey: options.apiKey,
348
+ visionModel: options.visionModel,
349
+ },
350
+ llm: loadConfig().llm || {},
351
+ });
352
+
353
+ // --think / --ultrathink → options.thinking for the agent loop (Anthropic
354
+ // extended thinking; ignored by other providers). --think with no value →
355
+ // true; --think <level> → that level; --ultrathink wins as "ultra".
356
+ const thinking = options.ultrathink
357
+ ? "ultra"
358
+ : options.think === true
359
+ ? true
360
+ : options.think || undefined;
361
+ // --thinking-budget <n>: legacy-model thinking budget (no-op without
362
+ // --think/--ultrathink and on adaptive models). undefined → engine default.
363
+ const thinkingBudget = resolveThinkingBudget(options.thinkingBudget);
364
+
241
365
  // --fallback-model: a chatFn that retries once on the backup model when
242
366
  // the primary errors out (overload / rate-limit / network). Passed into
243
367
  // the headless runners via options.chatFn (the agent loop's seam), so no
@@ -265,6 +389,8 @@ export function registerAgentCommand(program) {
265
389
  try {
266
390
  outcome = await runAgentHeadlessStream({
267
391
  model: options.model,
392
+ thinking,
393
+ thinkingBudget,
268
394
  provider: options.provider,
269
395
  baseUrl: options.baseUrl,
270
396
  apiKey: options.apiKey,
@@ -284,7 +410,12 @@ export function registerAgentCommand(program) {
284
410
  includePartialMessages: options.includePartialMessages === true,
285
411
  goal: options.goal,
286
412
  mcpConfig: options.mcpConfig || null,
413
+ useRegisteredMcp: options.mcp !== false,
414
+ ide: options.ide,
415
+ cwd,
287
416
  permissionPromptTool: options.permissionPromptTool || null,
417
+ settingsFile: options.settings || null,
418
+ outputStyle: options.outputStyle || null,
288
419
  chatFn: fallbackChatFn,
289
420
  });
290
421
  } catch (err) {
@@ -347,10 +478,13 @@ export function registerAgentCommand(program) {
347
478
  try {
348
479
  outcome = await runAgentHeadless({
349
480
  prompt,
350
- model: options.model,
351
- provider: options.provider,
352
- baseUrl: options.baseUrl,
353
- apiKey: options.apiKey,
481
+ images,
482
+ model: visionLlm.model || options.model,
483
+ thinking,
484
+ thinkingBudget,
485
+ provider: visionLlm.provider || options.provider,
486
+ baseUrl: visionLlm.baseUrl || options.baseUrl,
487
+ apiKey: visionLlm.apiKey || options.apiKey,
354
488
  sessionId: options.session,
355
489
  // A resolved --session/--continue/--resume id means "replay this
356
490
  // conversation and persist the new turns"; the runner loads prior
@@ -380,8 +514,16 @@ export function registerAgentCommand(program) {
380
514
  goalAssess: options.goalAssess === true,
381
515
  // --mcp-config: connect ad-hoc MCP servers + expose their tools
382
516
  mcpConfig: options.mcpConfig || null,
517
+ // --no-mcp: skip registered (cc mcp add) auto-connect servers
518
+ useRegisteredMcp: options.mcp !== false,
519
+ // --ide / --no-ide: auto-connect a running editor's MCP bridge
520
+ ide: options.ide,
521
+ cwd: process.cwd(),
383
522
  // --permission-prompt-tool: defer approvals to an MCP tool
384
523
  permissionPromptTool: options.permissionPromptTool || null,
524
+ // --settings: extra .claude/settings.json permission rules
525
+ settingsFile: options.settings || null,
526
+ outputStyle: options.outputStyle || null,
385
527
  // --fallback-model: retry once on a backup model on transient errors
386
528
  chatFn: fallbackChatFn,
387
529
  });
@@ -393,8 +535,18 @@ export function registerAgentCommand(program) {
393
535
  return;
394
536
  }
395
537
 
538
+ // Reached only for an interactive session, where --image has no turn to
539
+ // attach to — warn instead of silently dropping the attachment.
540
+ if (images.length) {
541
+ process.stderr.write(
542
+ "--image is only used in headless mode (-p / a task / piped stdin); ignoring for the interactive session.\n",
543
+ );
544
+ }
545
+
396
546
  const runtime = createAgentRuntimeFactory().createAgentRuntime({
397
547
  model: options.model,
548
+ thinking,
549
+ thinkingBudget,
398
550
  provider: options.provider,
399
551
  baseUrl: options.baseUrl,
400
552
  apiKey: options.apiKey,
@@ -418,9 +570,12 @@ export function registerAgentCommand(program) {
418
570
  }),
419
571
  // --fallback-model also applies interactively (wrapper built in the REPL)
420
572
  fallbackModel: options.fallbackModel || null,
421
- // --mcp-config: connect ad-hoc MCP servers for the interactive session
422
- // too (the REPL loads them via the same mcp-config engine as headless).
573
+ // --mcp-config + registered (cc mcp add) servers also apply to the
574
+ // interactive session (the REPL resolves both via the mcp-config engine).
423
575
  mcpConfig: options.mcpConfig || null,
576
+ useRegisteredMcp: options.mcp !== false,
577
+ // --ide / --no-ide: IDE bridge auto-connect for the interactive session
578
+ ide: options.ide,
424
579
  });
425
580
  await runtime.startAgentSession();
426
581
  });
@@ -0,0 +1,199 @@
1
+ /**
2
+ * cc agents — user-defined subagents (Claude-Code parity, `.claude/agents/*.md`).
3
+ *
4
+ * cc agents list [--json] list discovered agents
5
+ * cc agents show <name> show metadata + system prompt
6
+ * cc agents run <name> <task...> [opts] run the task as this agent (headless)
7
+ * cc agents new <name> [--description <d>] scaffold an agent file
8
+ *
9
+ * Each `.claude/agents/<name>.md` (project, recursive) or `~/.claude/agents/`
10
+ * (personal) defines a subagent: the body is its system prompt; frontmatter
11
+ * declares `description`, `tools` (allow-list), `model`. `run` maps the agent
12
+ * onto a one-shot headless run (system prompt + tool scope + model), so a
13
+ * portable Claude-Code agent definition is runnable as-is. Distinct from `cc
14
+ * command` (prompt macros) and `cc skill` (AI-invoked capabilities).
15
+ */
16
+
17
+ import chalk from "chalk";
18
+ import { logger } from "../lib/logger.js";
19
+
20
+ export function registerAgentsCommand(program) {
21
+ const cmd = program
22
+ .command("agents")
23
+ .description("User-defined subagents (.claude/agents/*.md)");
24
+
25
+ // ── list ──────────────────────────────────────────────────────────────
26
+ cmd
27
+ .command("list")
28
+ .alias("ls")
29
+ .description("List discovered subagents (project + personal)")
30
+ .option("--json", "Output as JSON")
31
+ .action(async (options) => {
32
+ try {
33
+ const { discoverAgents } = await import("../lib/agents.js");
34
+ const all = discoverAgents(process.cwd());
35
+ if (options.json) {
36
+ console.log(JSON.stringify(all, null, 2));
37
+ return;
38
+ }
39
+ if (all.length === 0) {
40
+ logger.log(
41
+ chalk.gray(
42
+ "No subagents found. Create one with: cc agents new <name>",
43
+ ),
44
+ );
45
+ return;
46
+ }
47
+ logger.log(chalk.bold(`Subagents (${all.length})`));
48
+ for (const a of all) {
49
+ const tools = a.tools ? a.tools.join(",") : "(all)";
50
+ logger.log(
51
+ ` ${chalk.cyan(a.name.padEnd(22))} ${chalk.gray(`[${a.scope}]`)} ` +
52
+ `${a.description || ""}`,
53
+ );
54
+ logger.log(
55
+ chalk.gray(
56
+ ` tools: ${tools}${a.model ? ` · model: ${a.model}` : ""}`,
57
+ ),
58
+ );
59
+ }
60
+ } catch (err) {
61
+ logger.error(chalk.red(`agents list failed: ${err.message}`));
62
+ process.exitCode = 1;
63
+ }
64
+ });
65
+
66
+ // ── show ──────────────────────────────────────────────────────────────
67
+ cmd
68
+ .command("show <name>")
69
+ .description("Show an agent's metadata + system prompt")
70
+ .option("--json", "Output as JSON")
71
+ .action(async (name, options) => {
72
+ try {
73
+ const { getAgent } = await import("../lib/agents.js");
74
+ const a = getAgent(name, process.cwd());
75
+ if (!a) {
76
+ logger.error(chalk.red(`no such agent: ${name}`));
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+ if (options.json) {
81
+ console.log(JSON.stringify(a, null, 2));
82
+ return;
83
+ }
84
+ logger.log(chalk.bold(a.name) + chalk.gray(` [${a.scope}]`));
85
+ if (a.description) logger.log(chalk.gray(` ${a.description}`));
86
+ logger.log(
87
+ chalk.gray(
88
+ ` tools: ${a.tools ? a.tools.join(",") : "(all)"}` +
89
+ `${a.model ? ` · model: ${a.model}` : ""}`,
90
+ ),
91
+ );
92
+ logger.log(chalk.gray(` file: ${a.file}`));
93
+ logger.log("");
94
+ logger.log(a.systemPrompt || chalk.gray("(empty system prompt)"));
95
+ } catch (err) {
96
+ logger.error(chalk.red(`agents show failed: ${err.message}`));
97
+ process.exitCode = 1;
98
+ }
99
+ });
100
+
101
+ // ── run ───────────────────────────────────────────────────────────────
102
+ cmd
103
+ .command("run <name> [task...]")
104
+ .description("Run a task headlessly as the named subagent")
105
+ .option("--output-format <fmt>", "text | json | stream-json", "text")
106
+ .option("--model <model>", "Override the agent's model")
107
+ .option("--permission-mode <mode>", "ApprovalGate tier (see cc agent)")
108
+ .option("--add-dir <dir...>", "Extra workspace roots")
109
+ .action(async (name, task, options) => {
110
+ try {
111
+ const { getAgent } = await import("../lib/agents.js");
112
+ const a = getAgent(name, process.cwd());
113
+ if (!a) {
114
+ logger.error(chalk.red(`no such agent: ${name}`));
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ const prompt = Array.isArray(task) ? task.join(" ").trim() : "";
119
+ if (!prompt) {
120
+ logger.error(
121
+ chalk.red(`agents run requires a task, e.g. cc agents run ${name} "review @src/x.js"`),
122
+ );
123
+ process.exitCode = 1;
124
+ return;
125
+ }
126
+ const { runAgentHeadless } = await import("../runtime/headless-runner.js");
127
+ const outcome = await runAgentHeadless({
128
+ prompt,
129
+ // The agent file's body becomes the system prompt (its persona).
130
+ systemPrompt: a.systemPrompt || undefined,
131
+ // Frontmatter `tools` scopes the run; null = inherit all.
132
+ allowedTools: a.tools || undefined,
133
+ model: options.model || a.model || undefined,
134
+ outputFormat: options.outputFormat,
135
+ permissionMode: options.permissionMode,
136
+ additionalDirectories: Array.isArray(options.addDir)
137
+ ? options.addDir
138
+ : [],
139
+ });
140
+ process.exit(outcome.exitCode);
141
+ } catch (err) {
142
+ logger.error(chalk.red(`agents run failed: ${err.message}`));
143
+ process.exitCode = 1;
144
+ }
145
+ });
146
+
147
+ // ── new (scaffold) ────────────────────────────────────────────────────
148
+ cmd
149
+ .command("new <name>")
150
+ .description("Scaffold a new agent file (project-native .chainlesschain/agents/)")
151
+ .option("--description <d>", "Frontmatter description")
152
+ .option("--tools <list>", "Comma-separated tool allow-list")
153
+ .option("--claude", "Create under .claude/agents (Claude-Code-portable)")
154
+ .option("--personal", "Create under ~/.claude/agents instead of project")
155
+ .action(async (name, options) => {
156
+ try {
157
+ const fs = await import("node:fs");
158
+ const path = await import("node:path");
159
+ const { homedir } = await import("node:os");
160
+ const safe = String(name).replace(/^\//, "").replace(/:/g, "/");
161
+ // New project agents go to the native `.chainlesschain/agents/`; use
162
+ // --claude for the Claude-Code-portable `.claude/agents/`, or --personal
163
+ // for `~/.claude/agents/`. All three are read back by discoverAgents.
164
+ const root = options.personal
165
+ ? path.join(homedir(), ".claude", "agents")
166
+ : options.claude
167
+ ? path.join(process.cwd(), ".claude", "agents")
168
+ : path.join(process.cwd(), ".chainlesschain", "agents");
169
+ const file = path.join(root, `${safe}.md`);
170
+ if (fs.existsSync(file)) {
171
+ logger.error(chalk.red(`already exists: ${file}`));
172
+ process.exitCode = 1;
173
+ return;
174
+ }
175
+ fs.mkdirSync(path.dirname(file), { recursive: true });
176
+ const toolsLine = options.tools
177
+ ? `tools: ${options.tools}\n`
178
+ : "# tools: read_file, search_files # omit to inherit all tools\n";
179
+ const tpl = `---
180
+ name: ${safe.replace(/\//g, ":")}
181
+ description: ${options.description || name}
182
+ ${toolsLine}---
183
+
184
+ You are a focused subagent. Describe its role, constraints, and output format
185
+ here — this whole body becomes the system prompt for \`cc agents run ${safe.replace(/\//g, ":")}\`.
186
+ `;
187
+ fs.writeFileSync(file, tpl, "utf-8");
188
+ logger.log(chalk.green(`✓ created ${file}`));
189
+ logger.log(
190
+ chalk.gray(
191
+ ` run it with: cc agents run ${safe.replace(/\//g, ":")} "<task>"`,
192
+ ),
193
+ );
194
+ } catch (err) {
195
+ logger.error(chalk.red(`agents new failed: ${err.message}`));
196
+ process.exitCode = 1;
197
+ }
198
+ });
199
+ }
@@ -145,8 +145,9 @@ export function registerCommandCommand(program) {
145
145
  // ── new (scaffold) ────────────────────────────────────────────────────
146
146
  cmd
147
147
  .command("new <name>")
148
- .description("Scaffold a new command file under .claude/commands/")
148
+ .description("Scaffold a new command file (project-native .chainlesschain/commands/)")
149
149
  .option("--description <d>", "Frontmatter description")
150
+ .option("--claude", "Create under .claude/commands (Claude-Code-portable)")
150
151
  .option("--personal", "Create under ~/.claude/commands instead of project")
151
152
  .action(async (name, options) => {
152
153
  try {
@@ -154,9 +155,13 @@ export function registerCommandCommand(program) {
154
155
  const path = await import("node:path");
155
156
  const { homedir } = await import("node:os");
156
157
  const safe = String(name).replace(/^\//, "").replace(/:/g, "/");
158
+ // Project commands go to the native `.chainlesschain/commands/`; use
159
+ // --claude for the portable `.claude/commands/`, --personal for home.
157
160
  const root = options.personal
158
161
  ? path.join(homedir(), ".claude", "commands")
159
- : path.join(process.cwd(), ".claude", "commands");
162
+ : options.claude
163
+ ? path.join(process.cwd(), ".claude", "commands")
164
+ : path.join(process.cwd(), ".chainlesschain", "commands");
160
165
  const file = path.join(root, `${safe}.md`);
161
166
  if (fs.existsSync(file)) {
162
167
  logger.error(chalk.red(`already exists: ${file}`));
@@ -29,9 +29,10 @@ export function registerHookCommand(program) {
29
29
  // hook list
30
30
  hook
31
31
  .command("list", { isDefault: true })
32
- .description("List all registered hooks")
32
+ .description("List registered hooks (DB) + .claude/settings.json hooks")
33
33
  .option("--event <name>", "Filter by event name")
34
34
  .option("--enabled", "Show only enabled hooks")
35
+ .option("--settings <file>", "Also merge an explicit settings file")
35
36
  .option("--json", "Output as JSON")
36
37
  .action(async (options) => {
37
38
  try {
@@ -46,43 +47,76 @@ export function registerHookCommand(program) {
46
47
  enabledOnly: options.enabled,
47
48
  });
48
49
 
50
+ // .claude/settings.json `hooks` block (Claude-Code parity, decision-
51
+ // capable; distinct from the DB registry above which is observe-only).
52
+ const { loadHooks } = await import("../lib/settings-hooks.cjs");
53
+ const { hooks: settingsHooks, files: settingsFiles } = loadHooks({
54
+ cwd: process.cwd(),
55
+ settingsFile: options.settings,
56
+ });
57
+
49
58
  if (options.json) {
50
59
  console.log(
51
60
  JSON.stringify(
52
- hooks.map((h) => ({
53
- id: h.id,
54
- event: h.event,
55
- name: h.name,
56
- type: h.type,
57
- priority: h.priority,
58
- enabled: h.enabled === 1,
59
- matcher: h.matcher,
60
- description: h.description,
61
- })),
61
+ {
62
+ hooks: hooks.map((h) => ({
63
+ id: h.id,
64
+ event: h.event,
65
+ name: h.name,
66
+ type: h.type,
67
+ priority: h.priority,
68
+ enabled: h.enabled === 1,
69
+ matcher: h.matcher,
70
+ description: h.description,
71
+ })),
72
+ settingsHooks,
73
+ settingsFiles,
74
+ },
62
75
  null,
63
76
  2,
64
77
  ),
65
78
  );
66
- } else if (hooks.length === 0) {
67
- logger.info(
68
- 'No hooks registered. Add one with "chainlesschain hook add <event> <name>"',
69
- );
70
79
  } else {
71
- logger.log(chalk.bold(`Hooks (${hooks.length}):\n`));
72
- for (const h of hooks) {
73
- const status = h.enabled
74
- ? chalk.green("enabled")
75
- : chalk.gray("disabled");
76
- const pLabel = Object.entries(HookPriority).find(
77
- ([, v]) => v === h.priority,
80
+ if (hooks.length === 0) {
81
+ logger.info(
82
+ 'No DB hooks. Add one with "chainlesschain hook add <event> <name>"',
78
83
  );
79
- const priorityStr = pLabel ? pLabel[0] : String(h.priority);
80
- logger.log(
81
- ` ${chalk.cyan(h.name)} [${h.event}] priority=${priorityStr} type=${h.type} [${status}]`,
84
+ } else {
85
+ logger.log(chalk.bold(`DB hooks (${hooks.length}):\n`));
86
+ for (const h of hooks) {
87
+ const status = h.enabled
88
+ ? chalk.green("enabled")
89
+ : chalk.gray("disabled");
90
+ const pLabel = Object.entries(HookPriority).find(
91
+ ([, v]) => v === h.priority,
92
+ );
93
+ const priorityStr = pLabel ? pLabel[0] : String(h.priority);
94
+ logger.log(
95
+ ` ${chalk.cyan(h.name)} [${h.event}] priority=${priorityStr} type=${h.type} [${status}]`,
96
+ );
97
+ if (h.description) logger.log(` ${chalk.gray(h.description)}`);
98
+ if (h.matcher)
99
+ logger.log(` matcher: ${chalk.yellow(h.matcher)}`);
100
+ }
101
+ }
102
+ const events = Object.keys(settingsHooks);
103
+ if (events.length > 0) {
104
+ const n = events.reduce(
105
+ (a, e) => a + settingsHooks[e].length,
106
+ 0,
82
107
  );
83
- if (h.description) logger.log(` ${chalk.gray(h.description)}`);
84
- if (h.matcher)
85
- logger.log(` matcher: ${chalk.yellow(h.matcher)}`);
108
+ logger.log(chalk.bold(`\n.claude/settings.json hooks (${n}):`));
109
+ for (const ev of events) {
110
+ if (options.event && ev !== options.event) continue;
111
+ for (const g of settingsHooks[ev]) {
112
+ for (const h of g.hooks) {
113
+ logger.log(
114
+ ` ${chalk.cyan(ev)} matcher=${chalk.yellow(g.matcher || "*")} ${chalk.gray(h.command)}`,
115
+ );
116
+ }
117
+ }
118
+ }
119
+ logger.log(chalk.dim(` sources: ${settingsFiles.join(", ")}`));
86
120
  }
87
121
  }
88
122
 
@@ -93,6 +127,80 @@ export function registerHookCommand(program) {
93
127
  }
94
128
  });
95
129
 
130
+ // hook test — dry-run .claude/settings.json hooks for an event + tool
131
+ hook
132
+ .command("test <event> <tool> [args...]")
133
+ .description(
134
+ 'Show which settings.json hooks fire for an event+tool (e.g. hook test PreToolUse run_shell "git push"); --run executes them',
135
+ )
136
+ .option("--settings <file>", "Also merge an explicit settings file")
137
+ .option("--run", "Actually execute the matched hooks and show decisions")
138
+ .option("--json", "Output as JSON")
139
+ .action(async (event, tool, args, options) => {
140
+ try {
141
+ const { loadHooks, collectHooks } =
142
+ await import("../lib/settings-hooks.cjs");
143
+ const { hooks } = loadHooks({
144
+ cwd: process.cwd(),
145
+ settingsFile: options.settings,
146
+ });
147
+ const matched = collectHooks(hooks, event, tool);
148
+ const toolInput = args && args.length ? { command: args.join(" "), args } : {};
149
+ const payload = {
150
+ hook_event_name: event,
151
+ tool_name: tool,
152
+ tool_input: toolInput,
153
+ cwd: process.cwd(),
154
+ session_id: "test",
155
+ };
156
+
157
+ if (!options.run) {
158
+ if (options.json) {
159
+ console.log(JSON.stringify({ event, tool, matched, payload }, null, 2));
160
+ } else if (matched.length === 0) {
161
+ logger.log(
162
+ chalk.gray(`no settings.json hooks match ${event} / ${tool}`),
163
+ );
164
+ } else {
165
+ logger.log(
166
+ chalk.bold(`${matched.length} hook(s) would fire for ${event} / ${tool}:`),
167
+ );
168
+ for (const h of matched) logger.log(` ${chalk.gray(h.command)}`);
169
+ logger.log(chalk.dim(" (use --run to execute and see decisions)"));
170
+ }
171
+ return;
172
+ }
173
+
174
+ const { runHooks } = await import("../lib/hook-runner.cjs");
175
+ const outcome = runHooks(matched, payload, {
176
+ cwd: process.cwd(),
177
+ event,
178
+ });
179
+ if (options.json) {
180
+ console.log(JSON.stringify(outcome, null, 2));
181
+ return;
182
+ }
183
+ const color =
184
+ outcome.decision === "block"
185
+ ? chalk.red
186
+ : outcome.decision === "ask"
187
+ ? chalk.yellow
188
+ : chalk.green;
189
+ logger.log(`decision: ${color.bold(outcome.decision)}`);
190
+ if (outcome.reason) logger.log(`reason: ${outcome.reason}`);
191
+ if (outcome.hook) logger.log(`from: ${chalk.gray(outcome.hook)}`);
192
+ for (const r of outcome.results) {
193
+ logger.log(
194
+ ` ${chalk.gray(r.command)} → ${r.decision}` +
195
+ (r.exitCode != null ? ` (exit ${r.exitCode})` : ""),
196
+ );
197
+ }
198
+ } catch (err) {
199
+ logger.error(`hook test failed: ${err.message}`);
200
+ process.exitCode = 1;
201
+ }
202
+ });
203
+
96
204
  // hook add
97
205
  hook
98
206
  .command("add")