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