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,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc permissions — inspect, dry-run, and edit the `.claude/settings.json`
|
|
3
|
+
* permission ruleset (Claude-Code `permissions.{allow,ask,deny}` parity).
|
|
4
|
+
*
|
|
5
|
+
* cc permissions list [--json] merged ruleset + source file
|
|
6
|
+
* cc permissions test <tool> <args...> dry-run: which rule decides?
|
|
7
|
+
* cc permissions add <allow|ask|deny> <rule> append a rule to a settings file
|
|
8
|
+
* [--local | --user] (default target: project)
|
|
9
|
+
*
|
|
10
|
+
* The ruleset is loaded by settings-loader (user < project < local < env) and
|
|
11
|
+
* evaluated by permission-rules (deny > ask > allow). This command only reads
|
|
12
|
+
* and edits the files / runs the engine — it does not yet gate the agent tool
|
|
13
|
+
* loop (that wiring is a separate, riskier step).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { logger } from "../lib/logger.js";
|
|
18
|
+
|
|
19
|
+
const KIND_COLOR = {
|
|
20
|
+
allow: chalk.green,
|
|
21
|
+
ask: chalk.yellow,
|
|
22
|
+
deny: chalk.red,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Resolve an umbrella token (Bash) to a concrete tool name for dry-run. */
|
|
26
|
+
function resolveConcreteTool(token, groups) {
|
|
27
|
+
const t = String(token || "");
|
|
28
|
+
const lower = t.toLowerCase();
|
|
29
|
+
if (Object.prototype.hasOwnProperty.call(groups, lower)) {
|
|
30
|
+
return groups[lower][0];
|
|
31
|
+
}
|
|
32
|
+
return t;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Build the tool args object a dry-run target needs from positional args. */
|
|
36
|
+
function buildArgs(tool, positional, rulesMod) {
|
|
37
|
+
const joined = positional.join(" ").trim();
|
|
38
|
+
if (rulesMod.COMMAND_TOOLS.has(tool)) return { command: joined };
|
|
39
|
+
if (rulesMod.PATH_TOOLS.has(tool)) return { path: positional[0] || "" };
|
|
40
|
+
if (rulesMod.URL_TOOLS.has(tool)) return { url: positional[0] || "" };
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function registerPermissionsCommand(program) {
|
|
45
|
+
const cmd = program
|
|
46
|
+
.command("permissions")
|
|
47
|
+
.alias("perms")
|
|
48
|
+
.description(
|
|
49
|
+
"Inspect / dry-run / edit .claude/settings.json permission rules",
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// ── list ──────────────────────────────────────────────────────────────
|
|
53
|
+
cmd
|
|
54
|
+
.command("list")
|
|
55
|
+
.alias("ls")
|
|
56
|
+
.description("Show the merged permission ruleset and where each rule came from")
|
|
57
|
+
.option("--json", "Output as JSON")
|
|
58
|
+
.option("--settings <file>", "Also merge an explicit settings file")
|
|
59
|
+
.action(async (options) => {
|
|
60
|
+
try {
|
|
61
|
+
const { loadSettings } = await import("../lib/settings-loader.cjs");
|
|
62
|
+
const { rules, sources, files } = loadSettings({
|
|
63
|
+
cwd: process.cwd(),
|
|
64
|
+
settingsFile: options.settings,
|
|
65
|
+
});
|
|
66
|
+
if (options.json) {
|
|
67
|
+
console.log(JSON.stringify({ rules, sources, files }, null, 2));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const total =
|
|
71
|
+
rules.allow.length + rules.ask.length + rules.deny.length;
|
|
72
|
+
if (total === 0) {
|
|
73
|
+
logger.log(
|
|
74
|
+
chalk.gray(
|
|
75
|
+
"No permission rules. Add one: cc permissions add deny \"Bash(rm:*)\"\n" +
|
|
76
|
+
"(or create .claude/settings.json with a permissions block)",
|
|
77
|
+
),
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
for (const kind of ["deny", "ask", "allow"]) {
|
|
82
|
+
if (rules[kind].length === 0) continue;
|
|
83
|
+
logger.log(KIND_COLOR[kind].bold(`${kind} (${rules[kind].length})`));
|
|
84
|
+
for (const rule of rules[kind]) {
|
|
85
|
+
const src = sources[`${kind}:${rule}`] || "?";
|
|
86
|
+
logger.log(` ${rule.padEnd(34)} ${chalk.gray(src)}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (files.length) {
|
|
90
|
+
logger.log(chalk.dim(`\nsources: ${files.join(", ")}`));
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger.error(chalk.red(`permissions list failed: ${err.message}`));
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── test (dry-run) ─────────────────────────────────────────────────────
|
|
99
|
+
cmd
|
|
100
|
+
.command("test <tool> [args...]")
|
|
101
|
+
.description(
|
|
102
|
+
'Dry-run a tool call against the ruleset, e.g. permissions test run_shell "git push"',
|
|
103
|
+
)
|
|
104
|
+
.option("--json", "Output as JSON")
|
|
105
|
+
.option("--settings <file>", "Also merge an explicit settings file")
|
|
106
|
+
.action(async (tool, args, options) => {
|
|
107
|
+
try {
|
|
108
|
+
const { loadSettings, ruleSource } =
|
|
109
|
+
await import("../lib/settings-loader.cjs");
|
|
110
|
+
// .cjs default-export interop: module.exports surfaces as `.default`.
|
|
111
|
+
const rulesMod = await import("../lib/permission-rules.cjs");
|
|
112
|
+
const mod = rulesMod.default || rulesMod;
|
|
113
|
+
|
|
114
|
+
const { rules, sources } = loadSettings({
|
|
115
|
+
cwd: process.cwd(),
|
|
116
|
+
settingsFile: options.settings,
|
|
117
|
+
});
|
|
118
|
+
const concrete = resolveConcreteTool(tool, mod.TOOL_GROUPS);
|
|
119
|
+
const toolArgs = buildArgs(concrete, args || [], mod);
|
|
120
|
+
const result = mod.evaluatePermissionRules({
|
|
121
|
+
tool: concrete,
|
|
122
|
+
args: toolArgs,
|
|
123
|
+
cwd: process.cwd(),
|
|
124
|
+
rules,
|
|
125
|
+
});
|
|
126
|
+
const decision = result.decision || "fallthrough";
|
|
127
|
+
const source = result.rule
|
|
128
|
+
? ruleSource(sources, result.decision, result.rule)
|
|
129
|
+
: null;
|
|
130
|
+
|
|
131
|
+
if (options.json) {
|
|
132
|
+
console.log(
|
|
133
|
+
JSON.stringify(
|
|
134
|
+
{ tool: concrete, args: toolArgs, decision, rule: result.rule, source },
|
|
135
|
+
null,
|
|
136
|
+
2,
|
|
137
|
+
),
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const color = KIND_COLOR[result.decision] || chalk.gray;
|
|
143
|
+
logger.log(
|
|
144
|
+
`${chalk.bold(concrete)} ${chalk.gray(JSON.stringify(toolArgs))}`,
|
|
145
|
+
);
|
|
146
|
+
logger.log(` decision: ${color.bold(decision)}`);
|
|
147
|
+
if (result.rule) {
|
|
148
|
+
logger.log(` rule: ${result.rule}`);
|
|
149
|
+
if (source) logger.log(` source: ${chalk.gray(source)}`);
|
|
150
|
+
} else {
|
|
151
|
+
logger.log(
|
|
152
|
+
chalk.gray(
|
|
153
|
+
" no rule matched → falls back to risk-tier / --permission-mode logic",
|
|
154
|
+
),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
logger.error(chalk.red(`permissions test failed: ${err.message}`));
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// ── add ────────────────────────────────────────────────────────────────
|
|
164
|
+
cmd
|
|
165
|
+
.command("add <decision> <rule>")
|
|
166
|
+
.description("Append a rule (allow|ask|deny) to a settings file")
|
|
167
|
+
.option("--local", "Write to .claude/settings.local.json (personal, gitignored)")
|
|
168
|
+
.option("--user", "Write to ~/.claude/settings.json (all projects)")
|
|
169
|
+
.action(async (decision, rule, options) => {
|
|
170
|
+
try {
|
|
171
|
+
const kind = String(decision || "").toLowerCase();
|
|
172
|
+
if (!["allow", "ask", "deny"].includes(kind)) {
|
|
173
|
+
logger.error(
|
|
174
|
+
chalk.red(`decision must be allow | ask | deny (got "${decision}")`),
|
|
175
|
+
);
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const rulesMod = await import("../lib/permission-rules.cjs");
|
|
180
|
+
const mod = rulesMod.default || rulesMod;
|
|
181
|
+
if (!mod.parseRule(rule)) {
|
|
182
|
+
logger.error(
|
|
183
|
+
chalk.red(
|
|
184
|
+
`not a valid rule: "${rule}" (expected Tool or Tool(pattern), e.g. Bash(rm:*))`,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
process.exitCode = 1;
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const scope = options.user ? "user" : options.local ? "local" : "project";
|
|
192
|
+
const { addRule } = await import("../lib/settings-loader.cjs");
|
|
193
|
+
const { file, added } = addRule({
|
|
194
|
+
cwd: process.cwd(),
|
|
195
|
+
kind,
|
|
196
|
+
rule,
|
|
197
|
+
scope,
|
|
198
|
+
});
|
|
199
|
+
if (!added) {
|
|
200
|
+
logger.log(chalk.gray(`already present in ${file}: ${kind} ${rule}`));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
logger.log(
|
|
204
|
+
`${KIND_COLOR[kind].bold("✓ " + kind)} ${rule} ${chalk.gray("→ " + file)}`,
|
|
205
|
+
);
|
|
206
|
+
} catch (err) {
|
|
207
|
+
logger.error(chalk.red(`permissions add failed: ${err.message}`));
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc statusline — preview the configured `statusLine` (Claude-Code parity).
|
|
3
|
+
*
|
|
4
|
+
* cc statusline preview the rendered status line
|
|
5
|
+
* cc statusline show show the resolved config (+ source)
|
|
6
|
+
*
|
|
7
|
+
* Configure in .claude/settings.json:
|
|
8
|
+
* { "statusLine": { "type": "command", "command": "~/.claude/status.sh" } }
|
|
9
|
+
* The command receives a JSON context on stdin and its first stdout line is the
|
|
10
|
+
* status line (shown above the REPL prompt each turn).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import chalk from "chalk";
|
|
14
|
+
import { logger } from "../lib/logger.js";
|
|
15
|
+
|
|
16
|
+
export function registerStatuslineCommand(program) {
|
|
17
|
+
const cmd = program
|
|
18
|
+
.command("statusline")
|
|
19
|
+
.alias("status-line")
|
|
20
|
+
.description("Preview / show the .claude/settings.json statusLine");
|
|
21
|
+
|
|
22
|
+
cmd
|
|
23
|
+
.command("preview", { isDefault: true })
|
|
24
|
+
.description("Render the configured status line once")
|
|
25
|
+
.option("--model <m>", "Model to pass in the context", "opus")
|
|
26
|
+
.option("--json", "Output as JSON")
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
const { loadStatusLineConfig, buildContext, renderStatusLine } =
|
|
30
|
+
await import("../lib/status-line.cjs");
|
|
31
|
+
const cfg = loadStatusLineConfig({ cwd: process.cwd() });
|
|
32
|
+
if (!cfg) {
|
|
33
|
+
logger.log(
|
|
34
|
+
chalk.gray(
|
|
35
|
+
'No statusLine configured. Add to .claude/settings.json:\n' +
|
|
36
|
+
' { "statusLine": { "type": "command", "command": "./status.sh" } }',
|
|
37
|
+
),
|
|
38
|
+
);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const line = renderStatusLine(
|
|
42
|
+
cfg,
|
|
43
|
+
buildContext({
|
|
44
|
+
sessionId: "preview",
|
|
45
|
+
model: options.model,
|
|
46
|
+
cwd: process.cwd(),
|
|
47
|
+
}),
|
|
48
|
+
{ cwd: process.cwd() },
|
|
49
|
+
);
|
|
50
|
+
if (options.json) {
|
|
51
|
+
console.log(JSON.stringify({ config: cfg, line }, null, 2));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (line == null) {
|
|
55
|
+
logger.error(
|
|
56
|
+
chalk.red(
|
|
57
|
+
`statusLine command produced no output (or failed): ${cfg.command}`,
|
|
58
|
+
),
|
|
59
|
+
);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
process.stdout.write(line + "\n");
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error(chalk.red(`statusline preview failed: ${err.message}`));
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
cmd
|
|
71
|
+
.command("show")
|
|
72
|
+
.description("Show the resolved statusLine config")
|
|
73
|
+
.option("--json", "Output as JSON")
|
|
74
|
+
.action(async (options) => {
|
|
75
|
+
try {
|
|
76
|
+
const { loadStatusLineConfig } = await import("../lib/status-line.cjs");
|
|
77
|
+
const cfg = loadStatusLineConfig({ cwd: process.cwd() });
|
|
78
|
+
if (options.json) {
|
|
79
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
if (!cfg) {
|
|
83
|
+
logger.log(chalk.gray("No statusLine configured."));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
logger.log(`command: ${chalk.cyan(cfg.command)}`);
|
|
87
|
+
logger.log(chalk.gray(`type: ${cfg.type} padding: ${cfg.padding}`));
|
|
88
|
+
} catch (err) {
|
|
89
|
+
logger.error(chalk.red(`statusline show failed: ${err.message}`));
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
package/src/index.js
CHANGED
|
@@ -58,11 +58,15 @@ import { registerRCacheCommand } from "./commands/rcache.js";
|
|
|
58
58
|
import { registerSessionCommand } from "./commands/session.js";
|
|
59
59
|
import { registerCostCommand } from "./commands/cost.js";
|
|
60
60
|
import { registerContextCommand } from "./commands/context.js";
|
|
61
|
-
import { registerAgentsCommand } from "./commands/agents.js";
|
|
62
61
|
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
63
62
|
import { registerGoalCommand } from "./commands/goal.js";
|
|
64
63
|
import { registerCommandCommand } from "./commands/command.js";
|
|
65
64
|
import { registerCompactCommand } from "./commands/compact.js";
|
|
65
|
+
import { registerLoopCommand } from "./commands/loop.js";
|
|
66
|
+
import { registerPermissionsCommand } from "./commands/permissions.js";
|
|
67
|
+
import { registerOutputStyleCommand } from "./commands/output-style.js";
|
|
68
|
+
import { registerStatuslineCommand } from "./commands/statusline.js";
|
|
69
|
+
import { registerIdeCommand } from "./commands/ide.js";
|
|
66
70
|
import { registerConsolCommand } from "./commands/consol.js";
|
|
67
71
|
import { registerImportCommand } from "./commands/import.js";
|
|
68
72
|
import { registerExportCommand } from "./commands/export.js";
|
|
@@ -457,11 +461,15 @@ export function createProgram(opts = {}) {
|
|
|
457
461
|
registerSessionCommand(program);
|
|
458
462
|
registerCostCommand(program);
|
|
459
463
|
registerContextCommand(program);
|
|
460
|
-
registerAgentsCommand(program);
|
|
461
464
|
registerCheckpointCommand(program);
|
|
462
465
|
registerGoalCommand(program);
|
|
463
466
|
registerCommandCommand(program);
|
|
464
467
|
registerCompactCommand(program);
|
|
468
|
+
registerLoopCommand(program);
|
|
469
|
+
registerPermissionsCommand(program);
|
|
470
|
+
registerOutputStyleCommand(program);
|
|
471
|
+
registerStatuslineCommand(program);
|
|
472
|
+
registerIdeCommand(program);
|
|
465
473
|
registerConsolCommand(program);
|
|
466
474
|
|
|
467
475
|
// Phase 2: Knowledge & content management
|
package/src/lib/agent-core.js
CHANGED
|
@@ -24,5 +24,12 @@ export {
|
|
|
24
24
|
agentLoop,
|
|
25
25
|
formatToolArgs,
|
|
26
26
|
getActiveMcpServers,
|
|
27
|
+
listBackgroundShellTasks,
|
|
28
|
+
killAllBackgroundShellTasks,
|
|
27
29
|
_accumulateOllamaStream,
|
|
30
|
+
_accumulateOpenAIStream,
|
|
31
|
+
_accumulateAnthropicStream,
|
|
32
|
+
_toAnthropicMessages,
|
|
33
|
+
_anthropicThinkingParams,
|
|
34
|
+
_normalizeAnthropicResponse,
|
|
28
35
|
} from "../runtime/agent-core.js";
|
package/src/lib/agents.js
CHANGED
|
@@ -57,7 +57,12 @@ export function normalizeTools(tools) {
|
|
|
57
57
|
export function agentDirs(cwd = process.cwd(), opts = {}) {
|
|
58
58
|
const path = opts.deps?.path || _deps.path;
|
|
59
59
|
const home = opts.home || homedir();
|
|
60
|
+
// Project-native first (highest precedence), then the Claude-Code-portable
|
|
61
|
+
// location (so existing `.claude/agents/*.md` work unchanged), then personal.
|
|
62
|
+
// discoverAgents reverses + last-write-wins, so `.chainlesschain/agents/`
|
|
63
|
+
// shadows `.claude/agents/` shadows `~/.claude/agents/` on a name clash.
|
|
60
64
|
return [
|
|
65
|
+
{ dir: path.join(cwd, ".chainlesschain", "agents"), scope: "project" },
|
|
61
66
|
{ dir: path.join(cwd, ".claude", "agents"), scope: "project" },
|
|
62
67
|
{ dir: path.join(home, ".claude", "agents"), scope: "personal" },
|
|
63
68
|
];
|
package/src/lib/hook-manager.js
CHANGED
|
@@ -40,6 +40,7 @@ export const HookEvents = {
|
|
|
40
40
|
SessionEnd: "SessionEnd",
|
|
41
41
|
PreCompact: "PreCompact",
|
|
42
42
|
PostCompact: "PostCompact",
|
|
43
|
+
Notification: "Notification",
|
|
43
44
|
UserPromptSubmit: "UserPromptSubmit",
|
|
44
45
|
AssistantResponse: "AssistantResponse",
|
|
45
46
|
AgentStart: "AgentStart",
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* hook-runner — execute a Claude-Code `command` hook with the JSON protocol.
|
|
5
|
+
*
|
|
6
|
+
* Protocol (Claude-Code parity):
|
|
7
|
+
* - the hook event payload is written to the command's STDIN as JSON;
|
|
8
|
+
* - exit code 2 → BLOCK (reason = stderr) — the canonical "deny" path;
|
|
9
|
+
* - exit code 0 + JSON stdout → honored decision:
|
|
10
|
+
* { "decision": "block"|"approve"|"ask", "reason": "..." }
|
|
11
|
+
* { "hookSpecificOutput": { "permissionDecision": "deny"|"allow"|"ask",
|
|
12
|
+
* "permissionDecisionReason": "..." } }
|
|
13
|
+
* { "continue": false, "stopReason": "..." } → block
|
|
14
|
+
* { "additionalContext": "..." } → continue
|
|
15
|
+
* - any other non-zero → non-blocking error (surfaced, never blocks);
|
|
16
|
+
* - spawn failure / timeout → non-blocking (a broken hook must not wedge the
|
|
17
|
+
* agent — only an explicit block decision blocks).
|
|
18
|
+
*
|
|
19
|
+
* Returns a normalized `{ decision, reason, exitCode, stdout, stderr, ... }`.
|
|
20
|
+
* `_deps.spawnSync` is injected for unit tests (no real process needed).
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const cpDefault = require("node:child_process");
|
|
24
|
+
|
|
25
|
+
const _deps = { spawnSync: cpDefault.spawnSync };
|
|
26
|
+
|
|
27
|
+
const HOOK_DECISIONS = Object.freeze({
|
|
28
|
+
BLOCK: "block",
|
|
29
|
+
ALLOW: "allow",
|
|
30
|
+
ASK: "ask",
|
|
31
|
+
CONTINUE: "continue",
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
/** Parse a hook's stdout JSON into a normalized decision, or null if not JSON. */
|
|
35
|
+
function tryParseDecision(stdout) {
|
|
36
|
+
const text = String(stdout || "").trim();
|
|
37
|
+
if (!text || text[0] !== "{") return null;
|
|
38
|
+
let obj;
|
|
39
|
+
try {
|
|
40
|
+
obj = JSON.parse(text);
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
// PreToolUse-specific permission decision
|
|
45
|
+
const hso = obj.hookSpecificOutput;
|
|
46
|
+
if (hso && hso.permissionDecision) {
|
|
47
|
+
const pd = String(hso.permissionDecision).toLowerCase();
|
|
48
|
+
const decision =
|
|
49
|
+
pd === "deny"
|
|
50
|
+
? HOOK_DECISIONS.BLOCK
|
|
51
|
+
: pd === "ask"
|
|
52
|
+
? HOOK_DECISIONS.ASK
|
|
53
|
+
: pd === "allow"
|
|
54
|
+
? HOOK_DECISIONS.ALLOW
|
|
55
|
+
: HOOK_DECISIONS.CONTINUE;
|
|
56
|
+
return { decision, reason: hso.permissionDecisionReason || null };
|
|
57
|
+
}
|
|
58
|
+
// Generic decision field
|
|
59
|
+
if (obj.decision) {
|
|
60
|
+
const d = String(obj.decision).toLowerCase();
|
|
61
|
+
const decision =
|
|
62
|
+
d === "block" || d === "deny"
|
|
63
|
+
? HOOK_DECISIONS.BLOCK
|
|
64
|
+
: d === "approve" || d === "allow"
|
|
65
|
+
? HOOK_DECISIONS.ALLOW
|
|
66
|
+
: d === "ask"
|
|
67
|
+
? HOOK_DECISIONS.ASK
|
|
68
|
+
: HOOK_DECISIONS.CONTINUE;
|
|
69
|
+
return { decision, reason: obj.reason || null };
|
|
70
|
+
}
|
|
71
|
+
// continue:false → stop/block
|
|
72
|
+
if (obj.continue === false) {
|
|
73
|
+
return {
|
|
74
|
+
decision: HOOK_DECISIONS.BLOCK,
|
|
75
|
+
reason: obj.stopReason || obj.reason || "hook requested stop",
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
decision: HOOK_DECISIONS.CONTINUE,
|
|
80
|
+
reason: null,
|
|
81
|
+
additionalContext: obj.additionalContext || null,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Run one command hook. `input` is JSON-serialized to the hook's stdin.
|
|
87
|
+
*
|
|
88
|
+
* @param {string} command
|
|
89
|
+
* @param {object} [input] the hook event payload (tool_name, tool_input, …)
|
|
90
|
+
* @param {object} [opts] { timeout=60000, cwd, event }
|
|
91
|
+
* @returns {{ decision:string, reason:string|null, exitCode:number|null,
|
|
92
|
+
* stdout?:string, stderr?:string, additionalContext?:string,
|
|
93
|
+
* nonBlockingError?:boolean, error?:string }}
|
|
94
|
+
*/
|
|
95
|
+
function runCommandHook(command, input = {}, opts = {}) {
|
|
96
|
+
const { timeout = 60000, cwd, event } = opts;
|
|
97
|
+
if (!command) {
|
|
98
|
+
return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode: 0 };
|
|
99
|
+
}
|
|
100
|
+
let res;
|
|
101
|
+
try {
|
|
102
|
+
res = _deps.spawnSync(command, {
|
|
103
|
+
input: JSON.stringify(input),
|
|
104
|
+
cwd: cwd || process.cwd(),
|
|
105
|
+
encoding: "utf-8",
|
|
106
|
+
timeout,
|
|
107
|
+
shell: true,
|
|
108
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
109
|
+
env: {
|
|
110
|
+
...process.env,
|
|
111
|
+
CLAUDE_HOOK_EVENT: event || input.hook_event_name || "",
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return {
|
|
116
|
+
decision: HOOK_DECISIONS.CONTINUE,
|
|
117
|
+
reason: `hook spawn failed: ${err.message}`,
|
|
118
|
+
exitCode: null,
|
|
119
|
+
error: err.message,
|
|
120
|
+
nonBlockingError: true,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
// spawnSync surfaces timeout/ENOENT on res.error (status null) — non-blocking.
|
|
124
|
+
if (res.error) {
|
|
125
|
+
return {
|
|
126
|
+
decision: HOOK_DECISIONS.CONTINUE,
|
|
127
|
+
reason: `hook error: ${res.error.message}`,
|
|
128
|
+
exitCode: null,
|
|
129
|
+
stdout: String(res.stdout || ""),
|
|
130
|
+
stderr: String(res.stderr || ""),
|
|
131
|
+
error: res.error.message,
|
|
132
|
+
nonBlockingError: true,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const exitCode = res.status;
|
|
136
|
+
const stdout = String(res.stdout || "");
|
|
137
|
+
const stderr = String(res.stderr || "");
|
|
138
|
+
|
|
139
|
+
if (exitCode === 2) {
|
|
140
|
+
return {
|
|
141
|
+
decision: HOOK_DECISIONS.BLOCK,
|
|
142
|
+
reason: stderr.trim() || "hook exited 2 (blocked)",
|
|
143
|
+
exitCode,
|
|
144
|
+
stdout,
|
|
145
|
+
stderr,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (exitCode === 0) {
|
|
149
|
+
const parsed = tryParseDecision(stdout);
|
|
150
|
+
if (parsed) return { ...parsed, exitCode, stdout, stderr };
|
|
151
|
+
return { decision: HOOK_DECISIONS.CONTINUE, reason: null, exitCode, stdout, stderr };
|
|
152
|
+
}
|
|
153
|
+
// Other non-zero → non-blocking error.
|
|
154
|
+
return {
|
|
155
|
+
decision: HOOK_DECISIONS.CONTINUE,
|
|
156
|
+
reason: stderr.trim() || `hook exited ${exitCode}`,
|
|
157
|
+
exitCode,
|
|
158
|
+
stdout,
|
|
159
|
+
stderr,
|
|
160
|
+
nonBlockingError: true,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Run a list of command hooks in order; first BLOCK (or ASK) short-circuits.
|
|
166
|
+
* @returns {{ decision, reason, hook?, results }}
|
|
167
|
+
*/
|
|
168
|
+
function runHooks(commandHooks, input = {}, opts = {}) {
|
|
169
|
+
const results = [];
|
|
170
|
+
for (const h of commandHooks || []) {
|
|
171
|
+
const r = runCommandHook(h.command, input, {
|
|
172
|
+
...opts,
|
|
173
|
+
timeout: h.timeout != null ? h.timeout * 1000 : opts.timeout,
|
|
174
|
+
});
|
|
175
|
+
results.push({ command: h.command, ...r });
|
|
176
|
+
if (r.decision === HOOK_DECISIONS.BLOCK || r.decision === HOOK_DECISIONS.ASK) {
|
|
177
|
+
return { decision: r.decision, reason: r.reason, hook: h.command, results };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return { decision: HOOK_DECISIONS.CONTINUE, reason: null, results };
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
module.exports = { runCommandHook, runHooks, tryParseDecision, HOOK_DECISIONS, _deps };
|