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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BYfi9NYS.js → AIOps-vAVAFNJ4.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BiS_tAN7.js → ActionButton-BnRHFCKM.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-jiWl_p-B.js → Analytics-BOjwqWqG.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-m4sIzDot.js → AppLayout-Dc0D1Txn.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CPla3Erm.js → Audit-dd_2efaZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BGeQzTaB.js → Backup-HF1jgm8G.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DTf7Z1iU.js → BaseInput-CCtzmoKe.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DPTlQlD-.js → Chat-BNfH1c3p.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BgRXce4e.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DY-XuQMu.js → Checkbox-BOr-NscK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B6oxPiZI.js → Codegen-DE058N7-.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Dqxb4wSE.js → Col-SOREo1XE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DCIX514p.js → Community-sOvNZo9f.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGtCzDoJ.js → Compact-DnBe558D.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-zcOYd55o.js → Compliance-o-r6CUbg.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DVTtdIdM.js → Cowork-D6_k9mHP.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-CPUaR69k.js → Cron-CEV3Xkrm.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DnjUS6QH.js → Crosschain-eJ1lQWKU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Dnz8VDmx.js → DID-B-WqM9Hp.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CtWf27j7.js → Dashboard-ZnKPcsHN.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B4GC1ZV4.js → Dropdown-B8uLWDIP.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-wjij3kzr.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-rS-2W4u5.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-90p5Tnoz.js → Federation-C_07GXoq.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cnrw7gzq.js → FormItemContext-D3kbYrMU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C85NsWa3.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BFAVM9F8.js → Git-BlwWlMMB.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DBoRonpq.js → Governance-DxN3wQZ_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DHRyD66j.js → Inference-ls7pSw_D.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-CTvUKecD.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CB0dv_Ts.js → Logs-CvEVY5TK.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CN7Hm5Uw.js → Marketplace-C3qvQJT7.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-q5H25_8L.js → McpTools-DiwKpnKx.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-BCV3pZ1d.js → Memory-CIBPi_da.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-C04Mngt4.js → MobileBridge-D-v0Se8y.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-cP1apTQD.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-ByAMz2DN.js → Mtc-BMFWrI65.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-B7V7byJq.js → MtcAudit-2s8LaHtR.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DtKmcVQV.js → Multisig-dL_nvj7d.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CaMbT5SC.js → NLProgramming-BbrJp06R.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DRjbSTCU.js → Notes-jR9irwy3.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-B9YbJID5.js → NotificationSettings-Dk-STCIX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BcI_-vGS.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-oTask4BE.js → Organization-BCK5jylo.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Bab06ey7.js → Overflow-BRAY7Smt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P--wlBeU0N.js → P2P-BltVRGjb.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-D4t77Pwc.js → PdhVaultBrowser-CV8UbXHe.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B3sf6CJ3.js → Permissions-_tNl47Qh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BXOojk63.js → PersonalDataHub-Cgc4HjpX.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DReqtBFN.js → Pipeline-Bn_QU4mu.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-cT1GwKLx.js → Privacy-jzJowp5P.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BhTAzVhH.js → ProjectInit-B_1pJ8qd.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CK-D8Fyj.js → ProjectSettings-CPVZpXzs.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CbHiwen6.js → Projects-CQsHOWnT.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B-ftiXa8.js → Providers-CzzMiLC0.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CT5XPwTF.js → QuickAsk-MxBKIn9o.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CohhlBZ_.js → Recommend-D8lN6Lis.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-CrgbixFz.js → Reputation-CfYK-IrV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ClExmBn3.js → Row-Bg7NZDP9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-VV0qizCJ.js → RssFeed-BOVNJhj0.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CqJapSiL.js → Search-B38qzmhY.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DY66Zie6.js → Security-CjqleZpe.js} +4 -4
- package/src/assets/web-panel/assets/{Services-RQwxat7-.js → Services-Bu9JSJap.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-0v37UTU_.js → Skeleton-B2RvRkaX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B4Vm4DxN.js → Skills-_h42mxMN.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CggphTlo.js → Sla-BssLs56D.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BAOU08C7.js → SpeechSettings-DCxFYHsd.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DmtC4J1w.js → SyncSettings-D2xQuNLE.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DhpOGOlo.js +1 -0
- package/src/assets/web-panel/assets/{Templates-C1QK0YoU.js → Templates-CYG-R-aS.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CieOfmqp.js → Tenant-BQRYLsvP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DWdhrxRq.js → Terminal-imKU7N5j.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CjFVUUDU.js → TimelineRenderer-BIZzBftk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bwbk3id9.js → Tokens-uMLH5p_a.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-uJle_yj4.js → Trigger-BzS6XPqx.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-BcOuxAA5.js → Trust-R4zhHufZ.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-DUu7Ufg6.js → UkeySign-DATQCoGe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Ck8JtQ2n.js → VideoEditing-ClUmKOtS.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-B3jw43on.js → Wallet-DzJTbQzD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Baf9K0y7.js → WebAuthn-CrXrLmzQ.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CTEDl_83.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CKV51quV.js → chat-a6wpYmVL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BO_RP_yz.js → colors-CXJADb1t.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BZsxw_ZG.js → compact-item-CL2pohS_.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CAbvtzVL.js → createContext-xFi_1G5_.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BtmELbtB.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-QmHT8zDz.js → hasIn-Bchh1rAi.js} +1 -1
- package/src/assets/web-panel/assets/{index-fnDgExTu.js → index-B3Tpv7-d.js} +1 -1
- package/src/assets/web-panel/assets/index-B4l4vLTB.js +1 -0
- package/src/assets/web-panel/assets/{index-BEJa1FiF.js → index-B4zNisy9.js} +1 -1
- package/src/assets/web-panel/assets/{index-jd2r-T4p.js → index-B6NehWty.js} +1 -1
- package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
- package/src/assets/web-panel/assets/{index-BPZHeug4.js → index-B7knYOpm.js} +1 -1
- package/src/assets/web-panel/assets/{index-GPY0LjCu.js → index-B7wT5VRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKnngF_f.js → index-BF4xx1_b.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRNYA0BV.js → index-BH9t10pe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKquNxL2.js → index-BPH5ESqs.js} +3 -3
- package/src/assets/web-panel/assets/{index-CEh2Ry_A.js → index-BmsIKzyu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dob6B6qS.js → index-BoaRB-4a.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ha2_56mf.js → index-BrbJBnT-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dln_vjSY.js → index-C2eMYASq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqiKnXtL.js → index-C4yBRKT4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3fwyCjJ.js → index-CGq4HQno.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2aiE8jk.js → index-CMybtJY6.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5zhcul9.js → index-CR3kFPuC.js} +1 -1
- package/src/assets/web-panel/assets/{index-8BMLlHCv.js → index-CTRd7vkq.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6i3reUS.js → index-CdU8BwRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNvTNZ1V.js → index-Cua_P8St.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjrDGJP2.js → index-CuehgDOp.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCsZiq4i.js → index-D-TT9Swq.js} +1 -1
- package/src/assets/web-panel/assets/{index-qPafbZmr.js → index-DEYcLAl7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DeC7lehI.js → index-DQ_hw_5P.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnPBG3Tr.js → index-DTEu7TSF.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8CHQnPl.js → index-DVo1GJoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-9IqJODII.js → index-DjdOL159.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBCYOypV.js → index-DsbMVBj1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BL7gQAuB.js → index-DxahxRP7.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC1CFfQU.js → index-EPERz4Pu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVoYeZ5Q.js → index-IkvkNxbc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CsBx0u5G.js → index-KCib1PTw.js} +1 -1
- package/src/assets/web-panel/assets/{index-5hlO2-JQ.js → index-M8SZI11a.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSaI8R_7.js → index-TxbHusq2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6AA-xB2.js → index-dsLc7t6W.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRK0oAV5.js → index-jMcv1u5o.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9Z83FTS.js → index-majCS3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3K1eHDd.js → index-u8K1y_lh.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Bc2GWeWe.js → initDefaultProps-DYn3Gc09.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BI-Rxw6o.js → motion-ZS3eolb9.js} +1 -1
- package/src/assets/web-panel/assets/{move-DRPdwDQB.js → move-CEw4uqr3.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B4XTl3jW.js → omit-DlHFZnPp.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Do5d86Wr.js → pickAttrs-eZQvV5fA.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B8VGZ0ZF.js → placementArrow-B31jQwa-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cf0kI_vN.js → responsiveObserve-DAsNmVto.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Cb0psjSL.js → slide-gPQPrYZC.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Bjuo5Oal.js → statusUtils-DwWKX5co.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BLMhoHJ5.js → styleChecker-B3VOtXuH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BdCwAfNU.js → useFlexGapSupport-6ADctM2r.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-9Jhaz5gG.js → useFs-6Zx1SSKs.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-xYFyXKwD.js → usePersonalDataHub-BzReowln.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVhepE6Z.js → vnode-C8IpEQbD.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-IbbtJ4Zr.js → zoom-ruc9vHr0.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +161 -6
- package/src/commands/agents.js +199 -0
- package/src/commands/command.js +7 -2
- package/src/commands/hook.js +136 -28
- package/src/commands/ide.js +168 -0
- package/src/commands/mcp.js +92 -0
- package/src/commands/output-style.js +127 -0
- package/src/commands/permissions.js +211 -0
- package/src/commands/statusline.js +93 -0
- package/src/index.js +8 -0
- package/src/lib/agent-core.js +7 -0
- package/src/lib/agents.js +147 -0
- package/src/lib/hook-manager.js +1 -0
- package/src/lib/hook-runner.cjs +183 -0
- package/src/lib/ide-bridge.js +310 -0
- package/src/lib/image-input.js +156 -0
- package/src/lib/mcp-oauth.js +415 -0
- package/src/lib/output-styles.js +179 -0
- package/src/lib/permission-rules.cjs +325 -0
- package/src/lib/provider-options.js +11 -7
- package/src/lib/settings-hook-events.cjs +102 -0
- package/src/lib/settings-hooks.cjs +163 -0
- package/src/lib/settings-loader.cjs +244 -0
- package/src/lib/slash-commands.js +21 -13
- package/src/lib/status-line.cjs +204 -0
- package/src/lib/sub-agent-profiles.js +3 -0
- package/src/lib/web-search.js +487 -0
- package/src/repl/agent-repl.js +445 -35
- package/src/repl/slash-macro.js +45 -0
- package/src/runtime/agent-core.js +799 -21
- package/src/runtime/coding-agent-contract-shared.cjs +94 -4
- package/src/runtime/coding-agent-policy.cjs +24 -0
- package/src/runtime/headless-runner.js +162 -6
- package/src/runtime/headless-stream.js +133 -7
- package/src/runtime/mcp-config.js +161 -15
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/runtime/system-prompt.js +6 -1
- package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CExqxzL6.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +0 -1
- package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +0 -1
- package/src/assets/web-panel/assets/index-CZZnSJEX.js +0 -1
package/src/commands/agent.js
CHANGED
|
@@ -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
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
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
|
|
422
|
-
//
|
|
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
|
+
}
|
package/src/commands/command.js
CHANGED
|
@@ -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
|
|
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
|
-
:
|
|
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}`));
|
package/src/commands/hook.js
CHANGED
|
@@ -29,9 +29,10 @@ export function registerHookCommand(program) {
|
|
|
29
29
|
// hook list
|
|
30
30
|
hook
|
|
31
31
|
.command("list", { isDefault: true })
|
|
32
|
-
.description("List
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
80
|
-
logger.log(
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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")
|