chainlesschain 0.162.35 → 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-CJn02U42.js → AIOps-vAVAFNJ4.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-ewURAAoy.js → ActionButton-BnRHFCKM.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BiSadESb.js → Analytics-BOjwqWqG.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BR0WOEug.js → AppLayout-Dc0D1Txn.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-CrqcYx0e.js → Audit-dd_2efaZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DtbSBn4e.js → Backup-HF1jgm8G.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BjSc9j0o.js → BaseInput-CCtzmoKe.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-ixzrlCJE.js → Chat-BNfH1c3p.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-B78nEq05.js → ChatBubbleRenderer-DCWFqmI4.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-UGYeSsgr.js → Checkbox-BOr-NscK.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B97OOAg4.js → Codegen-DE058N7-.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D9aGkaZ6.js → Col-SOREo1XE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-Dc2v2RGS.js → Community-sOvNZo9f.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-B_FYlUQR.js → Compact-DnBe558D.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-C4FiTHyC.js → Compliance-o-r6CUbg.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CQ8j3LIg.js → Cowork-D6_k9mHP.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-Dzjs9Z9Z.js → Cron-CEV3Xkrm.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BXI24uzI.js → Crosschain-eJ1lQWKU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-C-I4_d07.js → DID-B-WqM9Hp.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-BzzGh5mo.js → Dashboard-ZnKPcsHN.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Bh8H70De.js → Dropdown-B8uLWDIP.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DI_qybJP.js → EmailListRenderer-Jmj2Y7aH.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DkKTsfc4.js → FamilyGuardDashboard-Cb2xetG-.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DS7CmvVG.js → Federation-C_07GXoq.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CI97WsB5.js → FormItemContext-D3kbYrMU.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Da27EdR4.js → GenericCardRenderer-9xgqvGPg.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CEh0gR2W.js → Git-BlwWlMMB.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-kIr3tls2.js → Governance-DxN3wQZ_.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CC1GzyC1.js → Inference-ls7pSw_D.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BNgTiWOB.js → KnowledgeGraph-_n9hYuPI.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B2P10gB1.js → Logs-CvEVY5TK.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-HPfBvbFZ.js → Marketplace-C3qvQJT7.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-ByYotSKb.js → McpTools-DiwKpnKx.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BGIAzFVS.js → Memory-CIBPi_da.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CroNYTAH.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-BqhyIwo9.js → Mtc-BMFWrI65.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BpEKOvx9.js → MtcAudit-2s8LaHtR.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DST1d_Qo.js → Multisig-dL_nvj7d.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DlMsZcK_.js → NLProgramming-BbrJp06R.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-C734UJvD.js → Notes-jR9irwy3.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C0-pPxvk.js → NotificationSettings-Dk-STCIX.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C7zT9eFc.js → OrderTableRenderer-CqqfY6zq.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-C5iHC_yW.js → Organization-BCK5jylo.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CovuHHVR.js → Overflow-BRAY7Smt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Dx9QL-Gy.js → P2P-BltVRGjb.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-IP1dEt6-.js → PdhVaultBrowser-CV8UbXHe.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-BrR1XZG5.js → Permissions-_tNl47Qh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BgqxVE5m.js → PersonalDataHub-Cgc4HjpX.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-DzMk5HAz.js → Pipeline-Bn_QU4mu.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CDoLa6tk.js → Privacy-jzJowp5P.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Dy5gc6ve.js → ProjectInit-B_1pJ8qd.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DXy-k4hG.js → ProjectSettings-CPVZpXzs.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CQsHOWnT.js +1 -0
- package/src/assets/web-panel/assets/Providers-CzzMiLC0.js +1 -0
- package/src/assets/web-panel/assets/{QuickAsk-B8KEHCnd.js → QuickAsk-MxBKIn9o.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DNVHGYYZ.js → Recommend-D8lN6Lis.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-CaDhWP03.js → Reputation-CfYK-IrV.js} +1 -1
- package/src/assets/web-panel/assets/{Row-CrGLI02x.js → Row-Bg7NZDP9.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-BX7P8I6i.js → RssFeed-BOVNJhj0.js} +3 -3
- package/src/assets/web-panel/assets/Search-B38qzmhY.js +1 -0
- package/src/assets/web-panel/assets/{Security-B6J7IFc1.js → Security-CjqleZpe.js} +4 -4
- package/src/assets/web-panel/assets/{Services-vvdcO3mM.js → Services-Bu9JSJap.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BoAoPTzZ.js → Skeleton-B2RvRkaX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CyIQV5b3.js → Skills-_h42mxMN.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BAQVgdZV.js → Sla-BssLs56D.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Bxcn1Jkj.js → SpeechSettings-DCxFYHsd.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-Dpaj3hDM.js → SyncSettings-D2xQuNLE.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Bwqo89En.js → Tasks-DhpOGOlo.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Bowcqifn.js → Templates-CYG-R-aS.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DOkf85uG.js → Tenant-BQRYLsvP.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-v4MM9dCj.js → Terminal-imKU7N5j.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-B9A3zDXA.js → TimelineRenderer-BIZzBftk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-jtVVqKFr.js → Tokens-uMLH5p_a.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-26Iw-iIl.js → Trigger-BzS6XPqx.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DqY5ORrH.js → Trust-R4zhHufZ.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BFsbr3y7.js → UkeySign-DATQCoGe.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BtDbj3oa.js → VideoEditing-ClUmKOtS.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BAwmwHbk.js → Wallet-DzJTbQzD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DINJTsfq.js → WebAuthn-CrXrLmzQ.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-BEorm8SK.js → WorkflowEditor-CpvZ0Tma.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CE39-Dxg.js → chat-a6wpYmVL.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C_cLZ93a.js → colors-CXJADb1t.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BSioWA2c.js → compact-item-CL2pohS_.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CGTk4mhN.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-Dl1fRwS_.js → hasIn-Bchh1rAi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAhinBPR.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-BWpfxzVm.js → index-B4zNisy9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA6K7lZB.js → index-B6NehWty.js} +1 -1
- package/src/assets/web-panel/assets/index-B7Ek5iiY.js +1 -0
- package/src/assets/web-panel/assets/{index-DJyeeygd.js → index-B7knYOpm.js} +1 -1
- package/src/assets/web-panel/assets/{index-hv4jUdG3.js → index-B7wT5VRi.js} +1 -1
- package/src/assets/web-panel/assets/{index-9_mmaR42.js → index-BF4xx1_b.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cxw3p73X.js → index-BH9t10pe.js} +1 -1
- package/src/assets/web-panel/assets/{index-B016Fsqr.js → index-BPH5ESqs.js} +3 -3
- package/src/assets/web-panel/assets/{index-BmbVyhk1.js → index-BmsIKzyu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CST381Qf.js → index-BoaRB-4a.js} +1 -1
- package/src/assets/web-panel/assets/{index-D9D4q-qI.js → index-BrbJBnT-.js} +1 -1
- package/src/assets/web-panel/assets/{index-pngH1and.js → index-C2eMYASq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKEipmR8.js → index-C4yBRKT4.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2QiUEgK.js → index-CGq4HQno.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4JXchTG.js → index-CMybtJY6.js} +1 -1
- package/src/assets/web-panel/assets/{index-iiZfONfx.js → index-CR3kFPuC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTKEXyaW.js → index-CTRd7vkq.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2ly7sCw.js → index-CdU8BwRW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di6nvW1N.js → index-Cua_P8St.js} +1 -1
- package/src/assets/web-panel/assets/{index-BnEPB1Mz.js → index-CuehgDOp.js} +1 -1
- package/src/assets/web-panel/assets/{index-DrWERr8C.js → index-D-TT9Swq.js} +1 -1
- package/src/assets/web-panel/assets/{index-II3JhQu2.js → index-DEYcLAl7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbXnyoSO.js → index-DQ_hw_5P.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ceo9P9tQ.js → index-DTEu7TSF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDQx2YFc.js → index-DVo1GJoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-VVk1Jg.js → index-DjdOL159.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAwVwBOL.js → index-DsbMVBj1.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bvi14vJ7.js → index-DxahxRP7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhqOTuMW.js → index-EPERz4Pu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds2RzRG0.js → index-IkvkNxbc.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChwpS1f0.js → index-KCib1PTw.js} +1 -1
- package/src/assets/web-panel/assets/{index-OCxo0X6J.js → index-M8SZI11a.js} +1 -1
- package/src/assets/web-panel/assets/{index-Qj2x55mz.js → index-TxbHusq2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CisXVbSt.js → index-dsLc7t6W.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9tq8Da8.js → index-jMcv1u5o.js} +1 -1
- package/src/assets/web-panel/assets/{index--SWvw6yW.js → index-majCS3s2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTpCUi0m.js → index-u8K1y_lh.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-GOhLA2-f.js → initDefaultProps-DYn3Gc09.js} +1 -1
- package/src/assets/web-panel/assets/{motion-jqxFzHTx.js → motion-ZS3eolb9.js} +1 -1
- package/src/assets/web-panel/assets/{move-CSLsp6TA.js → move-CEw4uqr3.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Cnlrb25c.js → omit-DlHFZnPp.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CLqlxWWD.js → pickAttrs-eZQvV5fA.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BAWIWtul.js → placementArrow-B31jQwa-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CSR1DayS.js → responsiveObserve-DAsNmVto.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CNhoPJOp.js → slide-gPQPrYZC.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BZiYHRHW.js → statusUtils-DwWKX5co.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-BMoY-Fm5.js → styleChecker-B3VOtXuH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhtNdlaS.js → useFlexGapSupport-6ADctM2r.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DNPtDOZ4.js → useFs-6Zx1SSKs.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DTdjNvAI.js → usePersonalDataHub-BzReowln.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C9zW9IJ2.js → vnode-C8IpEQbD.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D-6RYJJr.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 +8 -2
- 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 -2
- package/src/lib/agent-core.js +7 -0
- package/src/lib/agents.js +5 -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 +4 -0
- 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-CH-qnGEV.js +0 -1
- package/src/assets/web-panel/assets/Projects-DvsaEbZR.js +0 -1
- package/src/assets/web-panel/assets/Providers-Demck9PO.js +0 -1
- package/src/assets/web-panel/assets/Search-laS6rz8M.js +0 -1
- package/src/assets/web-panel/assets/devWarning-PObcVnJR.js +0 -1
- package/src/assets/web-panel/assets/index-BNwIzLyX.js +0 -1
- package/src/assets/web-panel/assets/index-Dh6FxR9B.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
|
});
|
package/src/commands/agents.js
CHANGED
|
@@ -147,9 +147,10 @@ export function registerAgentsCommand(program) {
|
|
|
147
147
|
// ── new (scaffold) ────────────────────────────────────────────────────
|
|
148
148
|
cmd
|
|
149
149
|
.command("new <name>")
|
|
150
|
-
.description("Scaffold a new agent file
|
|
150
|
+
.description("Scaffold a new agent file (project-native .chainlesschain/agents/)")
|
|
151
151
|
.option("--description <d>", "Frontmatter description")
|
|
152
152
|
.option("--tools <list>", "Comma-separated tool allow-list")
|
|
153
|
+
.option("--claude", "Create under .claude/agents (Claude-Code-portable)")
|
|
153
154
|
.option("--personal", "Create under ~/.claude/agents instead of project")
|
|
154
155
|
.action(async (name, options) => {
|
|
155
156
|
try {
|
|
@@ -157,9 +158,14 @@ export function registerAgentsCommand(program) {
|
|
|
157
158
|
const path = await import("node:path");
|
|
158
159
|
const { homedir } = await import("node:os");
|
|
159
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.
|
|
160
164
|
const root = options.personal
|
|
161
165
|
? path.join(homedir(), ".claude", "agents")
|
|
162
|
-
:
|
|
166
|
+
: options.claude
|
|
167
|
+
? path.join(process.cwd(), ".claude", "agents")
|
|
168
|
+
: path.join(process.cwd(), ".chainlesschain", "agents");
|
|
163
169
|
const file = path.join(root, `${safe}.md`);
|
|
164
170
|
if (fs.existsSync(file)) {
|
|
165
171
|
logger.error(chalk.red(`already exists: ${file}`));
|
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")
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `cc ide` — inspect IDE bridge discovery (Phase 0).
|
|
3
|
+
*
|
|
4
|
+
* The agent auto-connects a running editor's MCP server when inside an IDE
|
|
5
|
+
* integrated terminal (or with `cc agent --ide`). These subcommands make that
|
|
6
|
+
* discovery visible and debuggable WITHOUT running an agent:
|
|
7
|
+
*
|
|
8
|
+
* cc ide list list live IDE lockfiles found
|
|
9
|
+
* cc ide status which server `cc agent` would connect to (+ its MCP config)
|
|
10
|
+
* cc ide doctor explain WHY discovery did / didn't pick a server
|
|
11
|
+
*
|
|
12
|
+
* Pure read-only over ~/.chainlesschain/ide/*.json — see
|
|
13
|
+
* docs/design/modules/98_IDE桥接对标方案.md.
|
|
14
|
+
*/
|
|
15
|
+
import chalk from "chalk";
|
|
16
|
+
import {
|
|
17
|
+
readIdeLocks,
|
|
18
|
+
discoverIdeServer,
|
|
19
|
+
ideServerToMcpConfig,
|
|
20
|
+
isInIdeTerminal,
|
|
21
|
+
ideLockDir,
|
|
22
|
+
diagnoseIde,
|
|
23
|
+
} from "../lib/ide-bridge.js";
|
|
24
|
+
|
|
25
|
+
function emit(obj) {
|
|
26
|
+
console.log(JSON.stringify(obj, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerIdeCommand(program) {
|
|
30
|
+
const ide = program
|
|
31
|
+
.command("ide")
|
|
32
|
+
.description("Inspect IDE bridge discovery (editor MCP server auto-connect)");
|
|
33
|
+
|
|
34
|
+
ide
|
|
35
|
+
.command("list")
|
|
36
|
+
.description("List live IDE lockfiles (editor MCP servers advertised)")
|
|
37
|
+
.option("--json", "Machine-readable output")
|
|
38
|
+
.action((options) => {
|
|
39
|
+
const locks = readIdeLocks();
|
|
40
|
+
if (options.json) {
|
|
41
|
+
// Never surface the raw bearer token or internal _file path.
|
|
42
|
+
const safe = locks.map(({ token, _file, ...rest }) => ({
|
|
43
|
+
...rest,
|
|
44
|
+
hasToken: !!token,
|
|
45
|
+
}));
|
|
46
|
+
emit({ lockDir: ideLockDir(), count: safe.length, locks: safe });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!locks.length) {
|
|
50
|
+
console.log(
|
|
51
|
+
chalk.gray(`No live IDE lockfiles in ${ideLockDir()}`),
|
|
52
|
+
);
|
|
53
|
+
console.log(
|
|
54
|
+
chalk.gray(
|
|
55
|
+
"Start an IDE extension that advertises one, or run `cc ide doctor`.",
|
|
56
|
+
),
|
|
57
|
+
);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
console.log(chalk.bold(`IDE servers (${locks.length}) in ${ideLockDir()}:`));
|
|
61
|
+
for (const l of locks) {
|
|
62
|
+
console.log(
|
|
63
|
+
` ${chalk.cyan(l.ide)} port ${l.port} ${l.transport} ` +
|
|
64
|
+
`${l.token ? chalk.green("token") : chalk.yellow("no-token")}`,
|
|
65
|
+
);
|
|
66
|
+
console.log(chalk.gray(` url ${l.url}`));
|
|
67
|
+
if (l.workspaceFolders.length) {
|
|
68
|
+
console.log(chalk.gray(` ws ${l.workspaceFolders.join(", ")}`));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
ide
|
|
74
|
+
.command("status")
|
|
75
|
+
.description("Show the IDE server `cc agent` would connect to right now")
|
|
76
|
+
.option("--ide", "Force selection even outside an IDE terminal")
|
|
77
|
+
.option("--json", "Machine-readable output")
|
|
78
|
+
.action((options) => {
|
|
79
|
+
const env = process.env;
|
|
80
|
+
const cwd = process.cwd();
|
|
81
|
+
const inIde = isInIdeTerminal(env);
|
|
82
|
+
const lock = discoverIdeServer({ cwd, env, force: options.ide === true });
|
|
83
|
+
const cfg = lock ? ideServerToMcpConfig(lock) : null;
|
|
84
|
+
// Redact the bearer token in the previewed config.
|
|
85
|
+
const safeCfg = cfg
|
|
86
|
+
? {
|
|
87
|
+
...cfg,
|
|
88
|
+
headers: cfg.headers?.Authorization
|
|
89
|
+
? { ...cfg.headers, Authorization: "Bearer ***" }
|
|
90
|
+
: cfg.headers,
|
|
91
|
+
}
|
|
92
|
+
: null;
|
|
93
|
+
if (options.json) {
|
|
94
|
+
// Strip the raw token + internal _file from the chosen lock.
|
|
95
|
+
const safeChosen = lock
|
|
96
|
+
? (({ token, _file, ...rest }) => ({ ...rest, hasToken: !!token }))(
|
|
97
|
+
lock,
|
|
98
|
+
)
|
|
99
|
+
: null;
|
|
100
|
+
emit({
|
|
101
|
+
inIdeTerminal: inIde,
|
|
102
|
+
chosen: safeChosen,
|
|
103
|
+
mcpConfig: safeCfg,
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
console.log(
|
|
108
|
+
`In IDE terminal: ${inIde ? chalk.green("yes") : chalk.gray("no")}`,
|
|
109
|
+
);
|
|
110
|
+
if (!lock) {
|
|
111
|
+
console.log(
|
|
112
|
+
chalk.gray(
|
|
113
|
+
"No IDE server would be connected. Try `cc ide doctor` for why.",
|
|
114
|
+
),
|
|
115
|
+
);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
console.log(
|
|
119
|
+
`Would connect: ${chalk.cyan(lock.ide)} on port ${lock.port} ` +
|
|
120
|
+
`(${lock.transport}) as MCP server ${chalk.bold("ide")}`,
|
|
121
|
+
);
|
|
122
|
+
console.log(chalk.gray("MCP config:"));
|
|
123
|
+
console.log(chalk.gray(" " + JSON.stringify(safeCfg)));
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
ide
|
|
127
|
+
.command("doctor")
|
|
128
|
+
.description("Explain why IDE discovery did / didn't pick a server")
|
|
129
|
+
.option("--ide", "Diagnose as if --ide (force) were passed")
|
|
130
|
+
.option("--json", "Machine-readable output")
|
|
131
|
+
.action((options) => {
|
|
132
|
+
const diag = diagnoseIde({
|
|
133
|
+
cwd: process.cwd(),
|
|
134
|
+
env: process.env,
|
|
135
|
+
force: options.ide === true,
|
|
136
|
+
});
|
|
137
|
+
if (options.json) {
|
|
138
|
+
emit(diag);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
console.log(chalk.bold("IDE bridge discovery"));
|
|
142
|
+
console.log(` lock dir : ${diag.lockDir}`);
|
|
143
|
+
console.log(
|
|
144
|
+
` in IDE terminal: ${diag.inIdeTerminal ? chalk.green("yes") : chalk.gray("no")}`,
|
|
145
|
+
);
|
|
146
|
+
console.log(` live locks : ${diag.locks.length}`);
|
|
147
|
+
for (const l of diag.locks) {
|
|
148
|
+
const m =
|
|
149
|
+
l.matchScore >= 0
|
|
150
|
+
? chalk.green(`workspace-match(${l.matchScore})`)
|
|
151
|
+
: chalk.yellow("no-workspace-match");
|
|
152
|
+
console.log(
|
|
153
|
+
` - ${chalk.cyan(l.ide)} port ${l.port} ${l.transport} ` +
|
|
154
|
+
`${l.hasToken ? "token" : chalk.yellow("no-token")} ${m}`,
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
console.log(
|
|
158
|
+
` result : ${
|
|
159
|
+
diag.chosen
|
|
160
|
+
? chalk.green(`connect ${diag.chosen.ide}:${diag.chosen.port}`)
|
|
161
|
+
: chalk.yellow("nothing to connect")
|
|
162
|
+
}`,
|
|
163
|
+
);
|
|
164
|
+
console.log(` reason : ${diag.reason}`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return ide;
|
|
168
|
+
}
|