chainlesschain 0.162.49 → 0.162.60
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/bin/chainlesschain.js +1 -1
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-DmdtNmWd.js → AIOps-a2cSbSEu.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DkuYVafg.js → ActionButton-DwvSB5Pp.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-Ba_h8Tub.js → Analytics-BqaRaBDD.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-yb8Zm9MX.js → AppLayout-Beck7v8t.js} +5 -5
- package/src/assets/web-panel/assets/{Audit-BGjex2fm.js → Audit-UtJhPdXJ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-IFQ2hOF2.js → Backup-HVZhcdll.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-W8AkPkrV.js → BaseInput-DRY_ZGmj.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BgI7t-iW.js → Chat-D7Vuwfe2.js} +6 -6
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BS2q_hPX.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-DuWsZP4g.js → Checkbox-B406i7N1.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-DyoTNmYg.js → Codegen-BvTCqHi3.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DttmlDRk.js → Col-DRiyxTQP.js} +1 -1
- package/src/assets/web-panel/assets/{Community-D9nnIdKn.js → Community-DWmhxHQa.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-C8KVQaHb.js → Compact-DO1HBZEz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-R2owqgjj.js → Compliance-D4j-VHwS.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DwGMMjRn.js → Cowork-BGBkWtat.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BSTcN_lK.js → Cron-Xa9PtMUQ.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CTNuIbFD.js → Crosschain-wVWs4lqN.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CgApGsFP.js → DID-DTkqiRuT.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-D_OJ3UN5.js → Dashboard-d9STUbrr.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-B84Jwra_.js → Dropdown-CrdxS-C8.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bv-YO-6y.js → EmailListRenderer-D78XHUEp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-drgZ408Y.js → FamilyGuardDashboard-iAeSETIP.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CtzFkdW2.js → Federation-CTV1Sxqs.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-BFAvNhl9.js → FormItemContext-BtwNuQKK.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DnuEyz_l.js → GenericCardRenderer-CdEgHjkl.js} +1 -1
- package/src/assets/web-panel/assets/{Git-jlHajmRJ.js → Git-BTo-PJr_.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DmJC7PGL.js → Governance-DquOG94r.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-B-u7xD2n.js → Inference-DDtcBxRB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BaYCA2Cd.js → KnowledgeGraph-KUOmNj5C.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DTNYQWfp.js → Logs-HKm7kRs7.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-CUu1xYvo.js → Marketplace-IxrOcbFB.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BmoeTyrC.js → McpTools-D6a1LM3S.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-DxTU3QU7.js → Memory-lFkD2ZuM.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CpcOlKAD.js → MobileBridge-xwuQTps5.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-BYr1D3WO.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-LfxwOm0x.js → Mtc-BXpJGrjm.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-D6A9Gjkh.js → MtcAudit-CWttaim1.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Ch_jofPV.js → Multisig-jKgTuVLS.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bkvogg0I.js → NLProgramming-xl4RDzQj.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-C5t5Xihm.js → Notes-DPBOvscE.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CTpDUNCb.js → NotificationSettings-8TkIkRo3.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dwa-XtoE.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Dr37BaXa.js → Organization-CJ0xVwZM.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-ZGjsdP7N.js → Overflow-V7VuUslt.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-bWJU5Vxd.js → P2P-BxuccEGq.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-DaP2Q5kU.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-BOSnFZaC.js → Permissions-CPJFF0zU.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-X4SgjP6P.js → PersonalDataHub-Cmn2uiuw.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-DoJhB9bj.js → Pipeline-0zX89_iz.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-OM9lDj-R.js → Privacy-DROUg3XE.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BXQEOmLn.js → ProjectInit-c5KESOK4.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DBXo3K5u.js → ProjectSettings-BfiCcnb_.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CJ4DBJlS.js → Projects-BtZH5-Eh.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Tk9SawmO.js → Providers-C9Rr_dOk.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DRI1-nTC.js → QuickAsk-Du4p90W6.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DtrIVTu9.js → Recommend-B-DQenTl.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DkH8ImwZ.js → Reputation-DvwlAVRZ.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DpA9dlvi.js → Row-rTnbvkP-.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DV3OhxWd.js → RssFeed-DwvsqWbB.js} +3 -3
- package/src/assets/web-panel/assets/{Search-QxdntiQx.js → Search-U_Xj5SvF.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CGuEnrD2.js → Security-CdjsVDQ8.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BvwSSD8b.js → Services-DUd3mFXk.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-sx_8L3-5.js → Skeleton-CA2gCJmY.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-dWOwxRsu.js → Skills-BIw7Rb-m.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-zxXnfKrT.js → Sla-vySPesC0.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CmFlcNjr.js → SpeechSettings-EniuTjBJ.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BeXeqURL.js → SyncSettings-DkrqbzYS.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-oyPnWRbw.js +1 -0
- package/src/assets/web-panel/assets/{Templates-DlgR3XFH.js → Templates-C2Kvn60U.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-0P8HgQaM.js → Tenant-x6jVMx4D.js} +1 -1
- package/src/assets/web-panel/assets/Terminal-C2nZbPBs.js +3 -0
- package/src/assets/web-panel/assets/{TimelineRenderer-hbO7agZs.js → TimelineRenderer-BF6HAETd.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CsmhgTBO.js → Tokens-B-KcVAin.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DnaF_2PP.js → Trigger-B-Caiptm.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-C1oafGj1.js → Trust-_8sq7pJp.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-eLL4DOmC.js → UkeySign-CLveUEgo.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CX45sVq7.js → VideoEditing-iVc9jxt9.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-aWPqpHdQ.js → Wallet-D1n5pidD.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DMYV1MAo.js → WebAuthn-CA8kubXb.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-D9uRIJvH.js → WorkflowEditor-BXWwd_fB.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BmWYfCxG.js → chat-DUNkQr1A.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DqvTCkBe.js → colors-Dz5ozTcp.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Bh0L0ejI.js → compact-item-CZ5-JSLh.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-r2qgp1mn.js → createContext-CFxlcPug.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BMRVR8Xp.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BcffXa-S.js → hasIn-CqWIkHJm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DutDlDUF.js → index-3elHm6lI.js} +1 -1
- package/src/assets/web-panel/assets/{index-XwbSqOB2.js → index-8l5LLWxH.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2-02rrp.js → index-BBq1ySIt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY8RXaZR.js → index-BI2EU3hC.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6pAm1iJ.js → index-BJt6sNTF.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dcjol7ot.js → index-BLLSLAC3.js} +1 -1
- package/src/assets/web-panel/assets/{index-U86pxDyR.js → index-BLNgGXeg.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPFT7J7I.js → index-BO-yo7Jv.js} +1 -1
- package/src/assets/web-panel/assets/index-BT2s_zxU.js +1 -0
- package/src/assets/web-panel/assets/{index-DRXcGa5y.js → index-BiAcVeea.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7z0qK1W.js → index-C2SoMbLc.js} +1 -1
- package/src/assets/web-panel/assets/{index-DW1y18GR.js → index-C5XUilwu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCBqTRWH.js → index-C5zhjact.js} +1 -1
- package/src/assets/web-panel/assets/{index-BMn_luHQ.js → index-CBeASfAD.js} +1 -1
- package/src/assets/web-panel/assets/{index-COrfHebA.js → index-CD7UjlnE.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxajFkK2.js → index-CDq8IVvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJt0iuep.js → index-CE-gpaY9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtyXyl3t.js → index-CL6wt2JN.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbj6C3pA.js → index-CLu3Oyef.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKnEtlZD.js → index-C_WWTpLE.js} +1 -1
- package/src/assets/web-panel/assets/{index-De36_UgR.js → index-CbcHBDYj.js} +1 -1
- package/src/assets/web-panel/assets/{index-CAlxkpnv.js → index-CguUaiiY.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ct8qhPZe.js → index-Ci1-8q-g.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbMox24t.js → index-CvMZxZOn.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLN-neIf.js → index-D6Nkerss.js} +1 -1
- package/src/assets/web-panel/assets/{index-h4O0AcBt.js → index-DEzYXMgc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXXxkeij.js → index-DF0hXW5L.js} +1 -1
- package/src/assets/web-panel/assets/{index-B3mmDuOv.js → index-DGAjS_D9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDR3GmaO.js → index-DO6mf95c.js} +1 -1
- package/src/assets/web-panel/assets/index-DOTL6NDc.js +1 -0
- package/src/assets/web-panel/assets/{index-BeV-KoQl.js → index-DUyhvh0L.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bma_yHcC.js → index-DW18L-o6.js} +1 -1
- package/src/assets/web-panel/assets/{index-585fuGAN.js → index-Dc-5Ulgt.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCyB-RK5.js → index-DvUlrMy-.js} +3 -3
- package/src/assets/web-panel/assets/{index-DWlDSE0F.js → index-FsYDYVUk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXae4ZyX.js → index-GVbsyUQm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9nh3ANl.js → index-noQc_RpT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQlAPNSU.js → index-rR060KAF.js} +1 -1
- package/src/assets/web-panel/assets/{index-S4E77Aer.js → index-xaZX6ZDL.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C1d8I-BX.js → initDefaultProps-PIetywTX.js} +1 -1
- package/src/assets/web-panel/assets/{motion-Dq7fiy4Y.js → motion-gQJEK3wO.js} +1 -1
- package/src/assets/web-panel/assets/{move-Bqb2dySM.js → move-ML1nRxts.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BUYqb4My.js → omit-wUWsw3YL.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DeytiKlZ.js → pickAttrs-A0Wlomih.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-xrXZWCqG.js → placementArrow-C5RKYdxT.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CcL2K-YY.js → responsiveObserve-DIxNVSJl.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DmCWaic7.js → slide-B-tNesVu.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CqNrFif7.js → statusUtils-CftzO200.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C436m5Xy.js → styleChecker-CMgvWu90.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CVhutCN8.js → useFlexGapSupport-sxqoDNhZ.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DUd49Bui.js → useFs-CowEXz4v.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-fuS9raic.js → usePersonalDataHub-6ISRG7V-.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C3kmDmk-.js → vnode-C2mnXfmw.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-hX-F1dT-.js → zoom-DMsur0jx.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +66 -28
- package/src/harness/mcp-client.js +48 -2
- package/src/lib/llm-pricing.js +9 -0
- package/src/lib/permission-rules.cjs +11 -1
- package/src/lib/personal-data-hub-wiring.js +24 -0
- package/src/lib/repl-multiline.js +64 -0
- package/src/lib/repl-rewind.js +65 -2
- package/src/lib/repl-vim.js +445 -0
- package/src/lib/safe-mode.js +17 -3
- package/src/lib/skill-loader.js +45 -1
- package/src/lib/slash-commands.js +13 -3
- package/src/lib/status-line.cjs +33 -3
- package/src/repl/agent-repl.js +253 -27
- package/src/repl/session-cost.js +98 -1
- package/src/runtime/agent-core.js +23 -8
- package/src/runtime/fallback-model.js +125 -30
- package/src/runtime/headless-runner.js +2 -0
- package/src/runtime/headless-stream.js +2 -0
- package/src/runtime/mcp-config.js +14 -3
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CfpKEQUF.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Bjh_z16l.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ST2lr-Bi.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-BRVoW-ye.js +0 -7
- package/src/assets/web-panel/assets/Tasks-iImd8xSO.js +0 -1
- package/src/assets/web-panel/assets/Terminal-B5VDEEHD.js +0 -3
- package/src/assets/web-panel/assets/devWarning-CusWDjWW.js +0 -1
- package/src/assets/web-panel/assets/index-BhYltBvN.js +0 -1
- package/src/assets/web-panel/assets/index-CZiIHw4e.js +0 -1
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vim-mode line editing for the agent REPL (Claude-Code `/vim` parity).
|
|
3
|
+
*
|
|
4
|
+
* Node's readline owns rich INSERT-mode editing (history, kill-ring, tab
|
|
5
|
+
* completion), so this module is a pure NORMAL-mode command interpreter — the
|
|
6
|
+
* thing readline does NOT give you. The REPL wiring (agent-repl.js) consults it
|
|
7
|
+
* only while in normal mode; insert↔normal transitions seed state from the live
|
|
8
|
+
* `rl.line` / `rl.cursor`, so insert-mode edits never need to round-trip here.
|
|
9
|
+
*
|
|
10
|
+
* Everything is a pure function over an explicit `{ line, cursor, ... }` state so
|
|
11
|
+
* the whole keymap is unit-testable without a TTY. `feedNormalKey()` takes one
|
|
12
|
+
* keypress and returns the next state plus, when relevant, `mode:"insert"`
|
|
13
|
+
* (hand control back to readline, seeded with the returned line/cursor) or
|
|
14
|
+
* `submit:true` (Enter in normal mode → run the line).
|
|
15
|
+
*
|
|
16
|
+
* Supported (a deliberately useful subset, not all of vim):
|
|
17
|
+
* motions h l 0 ^ $ w b e, f<c> F<c> t<c> T<c>, numeric counts (3w, 2l)
|
|
18
|
+
* enter ins i I a A, and o/O → A/I (the input is a single line)
|
|
19
|
+
* edits x X D C s S ~ r<c>, and operators d/c/y with the motions above
|
|
20
|
+
* plus doubled dd/cc/yy (whole line); p/P paste the register
|
|
21
|
+
* Not (yet): undo/redo, registers beyond the unnamed one, visual mode.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** Fresh state. mode starts "insert" so a just-toggled REPL keeps typing. */
|
|
25
|
+
export function createVimState(line = "", cursor = 0) {
|
|
26
|
+
return {
|
|
27
|
+
mode: "insert",
|
|
28
|
+
line,
|
|
29
|
+
cursor,
|
|
30
|
+
register: { text: "", linewise: false },
|
|
31
|
+
pending: null, // { op:"d"|"c"|"y", count:number } awaiting a motion
|
|
32
|
+
count: "", // accumulating numeric prefix
|
|
33
|
+
awaitChar: null, // { kind:"f"|"F"|"t"|"T"|"r" } awaiting one more char
|
|
34
|
+
message: null, // transient note for the wiring (e.g. unknown key → bell)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const isWord = (c) => !!c && /[A-Za-z0-9_]/.test(c);
|
|
39
|
+
const isSpace = (c) => !!c && /\s/.test(c);
|
|
40
|
+
const isPunct = (c) => !!c && !isWord(c) && !isSpace(c);
|
|
41
|
+
const clamp = (n, lo, hi) => Math.max(lo, Math.min(hi, n));
|
|
42
|
+
|
|
43
|
+
/** First non-blank column (vim `^`). */
|
|
44
|
+
function firstNonBlank(line) {
|
|
45
|
+
const i = line.search(/\S/);
|
|
46
|
+
return i === -1 ? 0 : i;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Start of the next word (vim `w`). */
|
|
50
|
+
function wordForward(line, cursor) {
|
|
51
|
+
const n = line.length;
|
|
52
|
+
let i = cursor;
|
|
53
|
+
if (i >= n) return n;
|
|
54
|
+
const cls = isWord(line[i]) ? isWord : isPunct(line[i]) ? isPunct : null;
|
|
55
|
+
if (cls) while (i < n && cls(line[i])) i++;
|
|
56
|
+
while (i < n && isSpace(line[i])) i++;
|
|
57
|
+
return i;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Start of the previous word (vim `b`). */
|
|
61
|
+
function wordBackward(line, cursor) {
|
|
62
|
+
let i = cursor;
|
|
63
|
+
if (i <= 0) return 0;
|
|
64
|
+
i--;
|
|
65
|
+
while (i > 0 && isSpace(line[i])) i--;
|
|
66
|
+
if (i <= 0) return 0;
|
|
67
|
+
const cls = isWord(line[i]) ? isWord : isPunct;
|
|
68
|
+
while (i > 0 && cls(line[i - 1])) i--;
|
|
69
|
+
return i;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** End of the current/next word (vim `e`, inclusive). */
|
|
73
|
+
function wordEnd(line, cursor) {
|
|
74
|
+
const n = line.length;
|
|
75
|
+
let i = cursor;
|
|
76
|
+
if (i >= n - 1) return Math.max(0, n - 1);
|
|
77
|
+
i++;
|
|
78
|
+
while (i < n && isSpace(line[i])) i++;
|
|
79
|
+
if (i >= n) return n - 1;
|
|
80
|
+
const cls = isWord(line[i]) ? isWord : isPunct;
|
|
81
|
+
while (i + 1 < n && cls(line[i + 1])) i++;
|
|
82
|
+
return i;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Resolve a motion key to a target column + whether it's inclusive (the target
|
|
87
|
+
* char is part of an operator's range). Returns null for "not a motion".
|
|
88
|
+
*
|
|
89
|
+
* @returns {{ target:number, inclusive:boolean }|null}
|
|
90
|
+
*/
|
|
91
|
+
function resolveMotion(state, ch, count, charArg) {
|
|
92
|
+
const { line, cursor } = state;
|
|
93
|
+
const n = line.length;
|
|
94
|
+
const rep = Math.max(1, count);
|
|
95
|
+
let pos;
|
|
96
|
+
switch (ch) {
|
|
97
|
+
case "h":
|
|
98
|
+
return { target: Math.max(0, cursor - rep), inclusive: false };
|
|
99
|
+
case "l":
|
|
100
|
+
case " ":
|
|
101
|
+
return { target: Math.min(n, cursor + rep), inclusive: false };
|
|
102
|
+
case "0":
|
|
103
|
+
return { target: 0, inclusive: false };
|
|
104
|
+
case "^":
|
|
105
|
+
return { target: firstNonBlank(line), inclusive: false };
|
|
106
|
+
case "$":
|
|
107
|
+
return { target: n, inclusive: false };
|
|
108
|
+
case "w":
|
|
109
|
+
pos = cursor;
|
|
110
|
+
for (let k = 0; k < rep; k++) pos = wordForward(line, pos);
|
|
111
|
+
return { target: pos, inclusive: false };
|
|
112
|
+
case "b":
|
|
113
|
+
pos = cursor;
|
|
114
|
+
for (let k = 0; k < rep; k++) pos = wordBackward(line, pos);
|
|
115
|
+
return { target: pos, inclusive: false };
|
|
116
|
+
case "e":
|
|
117
|
+
pos = cursor;
|
|
118
|
+
for (let k = 0; k < rep; k++) pos = wordEnd(line, pos);
|
|
119
|
+
return { target: pos, inclusive: true };
|
|
120
|
+
case "f":
|
|
121
|
+
case "t": {
|
|
122
|
+
if (charArg == null) return null;
|
|
123
|
+
let from = cursor;
|
|
124
|
+
for (let k = 0; k < rep; k++) {
|
|
125
|
+
const idx = line.indexOf(charArg, from + 1);
|
|
126
|
+
if (idx === -1) return { target: cursor, inclusive: false, fail: true };
|
|
127
|
+
from = idx;
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
target: ch === "t" ? Math.max(cursor, from - 1) : from,
|
|
131
|
+
inclusive: true,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
case "F":
|
|
135
|
+
case "T": {
|
|
136
|
+
if (charArg == null) return null;
|
|
137
|
+
let from = cursor;
|
|
138
|
+
for (let k = 0; k < rep; k++) {
|
|
139
|
+
const idx = line.lastIndexOf(charArg, from - 1);
|
|
140
|
+
if (idx === -1) return { target: cursor, inclusive: false, fail: true };
|
|
141
|
+
from = idx;
|
|
142
|
+
}
|
|
143
|
+
return { target: ch === "T" ? from + 1 : from, inclusive: false };
|
|
144
|
+
}
|
|
145
|
+
default:
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const NORMAL_CURSOR_MAX = (line) => Math.max(0, line.length - 1);
|
|
151
|
+
|
|
152
|
+
/** Delete [from,to) from line, stash into register (charwise). */
|
|
153
|
+
function cutRange(state, from, to) {
|
|
154
|
+
const a = Math.min(from, to);
|
|
155
|
+
const b = Math.max(from, to);
|
|
156
|
+
const text = state.line.slice(a, b);
|
|
157
|
+
return {
|
|
158
|
+
line: state.line.slice(0, a) + state.line.slice(b),
|
|
159
|
+
cursor: a,
|
|
160
|
+
register: { text, linewise: false },
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Apply an operator (d/c/y) over a resolved motion span. */
|
|
165
|
+
function applyOperator(state, op, span) {
|
|
166
|
+
const { line, cursor } = state;
|
|
167
|
+
let to = span.target;
|
|
168
|
+
if (span.inclusive) to = Math.min(line.length, to + 1);
|
|
169
|
+
if (op === "y") {
|
|
170
|
+
const a = Math.min(cursor, to);
|
|
171
|
+
const b = Math.max(cursor, to);
|
|
172
|
+
const next = {
|
|
173
|
+
...state,
|
|
174
|
+
register: { text: line.slice(a, b), linewise: false },
|
|
175
|
+
cursor: a,
|
|
176
|
+
pending: null,
|
|
177
|
+
count: "",
|
|
178
|
+
awaitChar: null,
|
|
179
|
+
};
|
|
180
|
+
return next;
|
|
181
|
+
}
|
|
182
|
+
const cut = cutRange(state, cursor, to);
|
|
183
|
+
const base = {
|
|
184
|
+
...state,
|
|
185
|
+
line: cut.line,
|
|
186
|
+
register: cut.register,
|
|
187
|
+
pending: null,
|
|
188
|
+
count: "",
|
|
189
|
+
awaitChar: null,
|
|
190
|
+
};
|
|
191
|
+
if (op === "c") {
|
|
192
|
+
return { ...base, cursor: cut.cursor, mode: "insert" };
|
|
193
|
+
}
|
|
194
|
+
// d: keep cursor in-bounds for normal mode
|
|
195
|
+
return { ...base, cursor: clamp(cut.cursor, 0, NORMAL_CURSOR_MAX(cut.line)) };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Whole-line operator (dd / cc / yy). */
|
|
199
|
+
function applyLinewiseOperator(state, op) {
|
|
200
|
+
const reg = { text: state.line, linewise: true };
|
|
201
|
+
if (op === "y") {
|
|
202
|
+
return {
|
|
203
|
+
...state,
|
|
204
|
+
register: reg,
|
|
205
|
+
pending: null,
|
|
206
|
+
count: "",
|
|
207
|
+
awaitChar: null,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const cleared = {
|
|
211
|
+
...state,
|
|
212
|
+
line: "",
|
|
213
|
+
cursor: 0,
|
|
214
|
+
register: reg,
|
|
215
|
+
pending: null,
|
|
216
|
+
count: "",
|
|
217
|
+
awaitChar: null,
|
|
218
|
+
};
|
|
219
|
+
return op === "c" ? { ...cleared, mode: "insert" } : cleared;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const enterInsert = (state, patch = {}) => ({
|
|
223
|
+
...state,
|
|
224
|
+
...patch,
|
|
225
|
+
mode: "insert",
|
|
226
|
+
pending: null,
|
|
227
|
+
count: "",
|
|
228
|
+
awaitChar: null,
|
|
229
|
+
message: null,
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const bell = (state, patch = {}) => ({
|
|
233
|
+
...state,
|
|
234
|
+
...patch,
|
|
235
|
+
pending: null,
|
|
236
|
+
count: "",
|
|
237
|
+
awaitChar: null,
|
|
238
|
+
message: "bell",
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Feed one keypress to the NORMAL-mode interpreter.
|
|
243
|
+
*
|
|
244
|
+
* @param {object} state from createVimState (mode is assumed "normal")
|
|
245
|
+
* @param {string} ch printable character (may be "" for special keys)
|
|
246
|
+
* @param {object} [key] node readline key descriptor ({ name, ctrl, ... })
|
|
247
|
+
* @returns {object} next state. `mode:"insert"` ⇒ hand back to readline seeded
|
|
248
|
+
* with line/cursor; `submit:true` ⇒ run the line.
|
|
249
|
+
*/
|
|
250
|
+
export function feedNormalKey(state, ch, key = {}) {
|
|
251
|
+
const s = { ...state, message: null };
|
|
252
|
+
const line = s.line;
|
|
253
|
+
|
|
254
|
+
// Enter → submit the line (parity with insert-mode Enter).
|
|
255
|
+
if (key.name === "return" || key.name === "enter") {
|
|
256
|
+
return { ...s, submit: true, pending: null, count: "", awaitChar: null };
|
|
257
|
+
}
|
|
258
|
+
// Esc clears any pending state but stays in normal mode.
|
|
259
|
+
if (key.name === "escape") {
|
|
260
|
+
return { ...s, pending: null, count: "", awaitChar: null };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Awaiting a single char (f/F/t/T target, or r replacement).
|
|
264
|
+
if (s.awaitChar) {
|
|
265
|
+
const { kind, forOp } = s.awaitChar;
|
|
266
|
+
if (!ch) return { ...s, awaitChar: null }; // a non-char key cancels
|
|
267
|
+
if (kind === "r") {
|
|
268
|
+
if (s.cursor >= line.length) return bell(s);
|
|
269
|
+
const next = line.slice(0, s.cursor) + ch + line.slice(s.cursor + 1);
|
|
270
|
+
return { ...s, line: next, awaitChar: null };
|
|
271
|
+
}
|
|
272
|
+
const span = resolveMotion(s, kind, parseInt(s.count || "1", 10), ch);
|
|
273
|
+
if (!span || span.fail) return bell(s, { awaitChar: null });
|
|
274
|
+
// Operator+find (e.g. df,) → apply the operator over the span; otherwise
|
|
275
|
+
// f/F/t/T is a standalone cursor motion.
|
|
276
|
+
if (forOp) {
|
|
277
|
+
return applyOperator({ ...s, awaitChar: null }, forOp, span);
|
|
278
|
+
}
|
|
279
|
+
return {
|
|
280
|
+
...s,
|
|
281
|
+
cursor: clamp(span.target, 0, NORMAL_CURSOR_MAX(line)),
|
|
282
|
+
awaitChar: null,
|
|
283
|
+
count: "",
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Count prefix: digits 1-9 always; 0 only continues an existing count
|
|
288
|
+
// (otherwise 0 is the "start of line" motion).
|
|
289
|
+
if (/[0-9]/.test(ch) && !(ch === "0" && !s.count)) {
|
|
290
|
+
return { ...s, count: s.count + ch };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const count = parseInt(s.count || "1", 10);
|
|
294
|
+
|
|
295
|
+
// Operator pending (d/c/y already pressed): this key is the motion.
|
|
296
|
+
if (s.pending) {
|
|
297
|
+
const op = s.pending.op;
|
|
298
|
+
// Doubled operator → linewise (dd, cc, yy).
|
|
299
|
+
if (ch === op) return applyLinewiseOperator(s, op);
|
|
300
|
+
// f/F/t/T need one more char before they can resolve.
|
|
301
|
+
if ("fFtT".includes(ch)) {
|
|
302
|
+
return { ...s, awaitChar: { kind: ch, forOp: op }, count: String(count) };
|
|
303
|
+
}
|
|
304
|
+
const span = resolveMotion(s, ch, count, null);
|
|
305
|
+
if (!span || span.fail) return bell(s);
|
|
306
|
+
return applyOperator(s, op, span);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// No pending operator — top-level normal commands.
|
|
310
|
+
switch (ch) {
|
|
311
|
+
case "i":
|
|
312
|
+
return enterInsert(s);
|
|
313
|
+
case "I":
|
|
314
|
+
return enterInsert(s, { cursor: firstNonBlank(line) });
|
|
315
|
+
case "a":
|
|
316
|
+
return enterInsert(s, { cursor: Math.min(line.length, s.cursor + 1) });
|
|
317
|
+
case "A":
|
|
318
|
+
return enterInsert(s, { cursor: line.length });
|
|
319
|
+
case "o": // single-line input: open ≈ append at end
|
|
320
|
+
return enterInsert(s, { cursor: line.length });
|
|
321
|
+
case "O":
|
|
322
|
+
return enterInsert(s, { cursor: firstNonBlank(line) });
|
|
323
|
+
case "x": {
|
|
324
|
+
if (s.cursor >= line.length) return bell(s);
|
|
325
|
+
const to = Math.min(line.length, s.cursor + count);
|
|
326
|
+
const cut = cutRange(s, s.cursor, to);
|
|
327
|
+
return {
|
|
328
|
+
...s,
|
|
329
|
+
line: cut.line,
|
|
330
|
+
register: cut.register,
|
|
331
|
+
cursor: clamp(cut.cursor, 0, NORMAL_CURSOR_MAX(cut.line)),
|
|
332
|
+
count: "",
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
case "X": {
|
|
336
|
+
if (s.cursor <= 0) return bell(s);
|
|
337
|
+
const from = Math.max(0, s.cursor - count);
|
|
338
|
+
const cut = cutRange(s, from, s.cursor);
|
|
339
|
+
return {
|
|
340
|
+
...s,
|
|
341
|
+
line: cut.line,
|
|
342
|
+
register: cut.register,
|
|
343
|
+
cursor: cut.cursor,
|
|
344
|
+
count: "",
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
case "D": {
|
|
348
|
+
const cut = cutRange(s, s.cursor, line.length);
|
|
349
|
+
return {
|
|
350
|
+
...s,
|
|
351
|
+
line: cut.line,
|
|
352
|
+
register: cut.register,
|
|
353
|
+
cursor: clamp(cut.cursor, 0, NORMAL_CURSOR_MAX(cut.line)),
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
case "C": {
|
|
357
|
+
const cut = cutRange(s, s.cursor, line.length);
|
|
358
|
+
return enterInsert(s, {
|
|
359
|
+
line: cut.line,
|
|
360
|
+
register: cut.register,
|
|
361
|
+
cursor: cut.cursor,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
case "s": {
|
|
365
|
+
if (s.cursor >= line.length) return enterInsert(s);
|
|
366
|
+
const cut = cutRange(
|
|
367
|
+
s,
|
|
368
|
+
s.cursor,
|
|
369
|
+
Math.min(line.length, s.cursor + count),
|
|
370
|
+
);
|
|
371
|
+
return enterInsert(s, {
|
|
372
|
+
line: cut.line,
|
|
373
|
+
register: cut.register,
|
|
374
|
+
cursor: cut.cursor,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
case "S":
|
|
378
|
+
return applyLinewiseOperator({ ...s }, "c");
|
|
379
|
+
case "~": {
|
|
380
|
+
if (s.cursor >= line.length) return bell(s);
|
|
381
|
+
const c = line[s.cursor];
|
|
382
|
+
const flipped = c === c.toLowerCase() ? c.toUpperCase() : c.toLowerCase();
|
|
383
|
+
const next = line.slice(0, s.cursor) + flipped + line.slice(s.cursor + 1);
|
|
384
|
+
return {
|
|
385
|
+
...s,
|
|
386
|
+
line: next,
|
|
387
|
+
cursor: clamp(s.cursor + 1, 0, NORMAL_CURSOR_MAX(next)),
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
case "r":
|
|
391
|
+
return { ...s, awaitChar: { kind: "r" } };
|
|
392
|
+
case "d":
|
|
393
|
+
case "c":
|
|
394
|
+
case "y":
|
|
395
|
+
return { ...s, pending: { op: ch, count }, count: "" };
|
|
396
|
+
case "p": {
|
|
397
|
+
const { text, linewise } = s.register;
|
|
398
|
+
if (!text) return bell(s);
|
|
399
|
+
const at = linewise ? line.length : Math.min(line.length, s.cursor + 1);
|
|
400
|
+
const next = line.slice(0, at) + text + line.slice(at);
|
|
401
|
+
return {
|
|
402
|
+
...s,
|
|
403
|
+
line: next,
|
|
404
|
+
cursor: clamp(at + text.length - 1, 0, NORMAL_CURSOR_MAX(next)),
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
case "P": {
|
|
408
|
+
const { text } = s.register;
|
|
409
|
+
if (!text) return bell(s);
|
|
410
|
+
const at = s.cursor;
|
|
411
|
+
const next = line.slice(0, at) + text + line.slice(at);
|
|
412
|
+
return {
|
|
413
|
+
...s,
|
|
414
|
+
line: next,
|
|
415
|
+
cursor: clamp(at + text.length - 1, 0, NORMAL_CURSOR_MAX(next)),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
case "f":
|
|
419
|
+
case "F":
|
|
420
|
+
case "t":
|
|
421
|
+
case "T":
|
|
422
|
+
return { ...s, awaitChar: { kind: ch }, count: String(count) };
|
|
423
|
+
default: {
|
|
424
|
+
// Pure motions (h l 0 ^ $ w b e) and Backspace/arrows mapped to h/l.
|
|
425
|
+
let motionCh = ch;
|
|
426
|
+
if (key.name === "backspace") motionCh = "h";
|
|
427
|
+
else if (key.name === "left") motionCh = "h";
|
|
428
|
+
else if (key.name === "right") motionCh = "l";
|
|
429
|
+
const span = resolveMotion(s, motionCh, count, null);
|
|
430
|
+
if (span && !span.fail) {
|
|
431
|
+
return {
|
|
432
|
+
...s,
|
|
433
|
+
cursor: clamp(span.target, 0, NORMAL_CURSOR_MAX(line)),
|
|
434
|
+
count: "",
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
return bell(s);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Short label for the prompt/status indicator. */
|
|
443
|
+
export function modeLabel(state) {
|
|
444
|
+
return state.mode === "normal" ? "NORMAL" : "INSERT";
|
|
445
|
+
}
|
package/src/lib/safe-mode.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `cc agent --safe-mode` — Claude-Code 2.1.169 parity ("--safe-mode flag
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* the agent bare.
|
|
3
|
+
* AND CLAUDE_CODE_SAFE_MODE env var disable customizations"): one flag (or an
|
|
4
|
+
* ambient env var) flips every customization kill-switch so a misbehaving
|
|
5
|
+
* hook / memory file / persona can be isolated by running the agent bare.
|
|
6
6
|
*
|
|
7
7
|
* Deliberate divergence from Claude Code: settings PERMISSION RULES stay
|
|
8
8
|
* active — deny rules are a safety surface, and "debug my customizations"
|
|
@@ -29,3 +29,17 @@ export function applySafeMode(env = process.env) {
|
|
|
29
29
|
}
|
|
30
30
|
return Object.keys(SAFE_MODE_SWITCHES);
|
|
31
31
|
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether safe mode is requested — via the explicit `--safe-mode` flag OR an
|
|
35
|
+
* ambient env var: `CC_SAFE_MODE` (native) or `CLAUDE_CODE_SAFE_MODE`
|
|
36
|
+
* (Claude-Code 2.1.169 portability). Truthy env values: 1 / true / yes / on.
|
|
37
|
+
* @param {{ safeMode?: boolean }} [opts]
|
|
38
|
+
* @param {object} [env=process.env]
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
export function safeModeRequested(opts = {}, env = process.env) {
|
|
42
|
+
if (opts && opts.safeMode === true) return true;
|
|
43
|
+
const raw = env.CC_SAFE_MODE || env.CLAUDE_CODE_SAFE_MODE;
|
|
44
|
+
return raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim());
|
|
45
|
+
}
|
package/src/lib/skill-loader.js
CHANGED
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
* ".claude/skills auto-load" behavior): a repo carrying Claude-Code skills
|
|
14
14
|
* works in cc unchanged — same SKILL.md format — while native
|
|
15
15
|
* `.chainlesschain/skills` still wins on name collisions.
|
|
16
|
+
*
|
|
17
|
+
* The bundled (layer 0) built-ins can be hidden via Claude-Code 2.1.169's
|
|
18
|
+
* `disableBundledSkills` (settings.json) or the `CC_DISABLE_BUNDLED_SKILLS`
|
|
19
|
+
* env var — see {@link bundledSkillsDisabled}.
|
|
16
20
|
*/
|
|
17
21
|
|
|
18
22
|
import fs from "fs";
|
|
@@ -23,9 +27,45 @@ import { getElectronUserDataDir } from "./paths.js";
|
|
|
23
27
|
import { findProjectRoot } from "./project-detector.js";
|
|
24
28
|
import { findProjectRoot as findGitRoot } from "./project-instructions.js";
|
|
25
29
|
import { parseSkillMcpServers } from "./skill-mcp.js";
|
|
30
|
+
import settingsLoader from "./settings-loader.cjs";
|
|
26
31
|
|
|
27
32
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
28
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Whether the bundled (built-in) skill layer should be hidden — Claude-Code
|
|
36
|
+
* 2.1.169 `disableBundledSkills` parity. Precedence: explicit option >
|
|
37
|
+
* `CC_DISABLE_BUNDLED_SKILLS` env > `.claude/settings.json` `disableBundledSkills`
|
|
38
|
+
* (last layer wins). Best-effort: any read failure leaves bundled skills visible.
|
|
39
|
+
*
|
|
40
|
+
* @param {{ disableBundledSkills?: boolean, env?: object, cwd?: string,
|
|
41
|
+
* settingsFile?: string }} [opts]
|
|
42
|
+
* @returns {boolean}
|
|
43
|
+
*/
|
|
44
|
+
export function bundledSkillsDisabled(opts = {}) {
|
|
45
|
+
if (typeof opts.disableBundledSkills === "boolean") {
|
|
46
|
+
return opts.disableBundledSkills;
|
|
47
|
+
}
|
|
48
|
+
const env = (opts.env || process.env).CC_DISABLE_BUNDLED_SKILLS;
|
|
49
|
+
if (env != null && String(env).trim() !== "") {
|
|
50
|
+
return /^(1|true|yes|on)$/i.test(String(env).trim());
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
let disabled = false;
|
|
54
|
+
for (const file of settingsLoader.settingsPaths(
|
|
55
|
+
opts.cwd || process.cwd(),
|
|
56
|
+
opts.settingsFile,
|
|
57
|
+
)) {
|
|
58
|
+
const data = settingsLoader.readSettingsFile(file);
|
|
59
|
+
if (data && typeof data.disableBundledSkills === "boolean") {
|
|
60
|
+
disabled = data.disableBundledSkills; // last (highest) layer wins
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return disabled;
|
|
64
|
+
} catch {
|
|
65
|
+
return false; // never fail skill loading over a settings read
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
29
69
|
/** Layer names in priority order (lowest → highest) */
|
|
30
70
|
export const LAYER_NAMES = [
|
|
31
71
|
"bundled",
|
|
@@ -349,13 +389,17 @@ export class CLISkillLoader {
|
|
|
349
389
|
* Higher-priority layers override same-name skills from lower layers.
|
|
350
390
|
* @returns {object[]} Resolved skill list
|
|
351
391
|
*/
|
|
352
|
-
loadAll() {
|
|
392
|
+
loadAll(options = {}) {
|
|
353
393
|
const layers = this.getLayerPaths();
|
|
354
394
|
const skillMap = new Map();
|
|
395
|
+
// Claude-Code `disableBundledSkills`: hide the built-in layer so only
|
|
396
|
+
// user/workspace/Claude-Code-portable skills load.
|
|
397
|
+
const dropBundled = bundledSkillsDisabled(options);
|
|
355
398
|
|
|
356
399
|
// Process in priority order (lowest first, so higher layers overwrite)
|
|
357
400
|
for (const { layer, path: layerPath, exists } of layers) {
|
|
358
401
|
if (!exists) continue;
|
|
402
|
+
if (dropBundled && layer === "bundled") continue;
|
|
359
403
|
const skills = this._loadFromDir(layerPath, layer);
|
|
360
404
|
for (const skill of skills) {
|
|
361
405
|
skillMap.set(skill.id, skill);
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* `model`. Body is the prompt template, with substitutions applied at run time:
|
|
11
11
|
* $ARGUMENTS → all args joined by space
|
|
12
12
|
* $1 $2 … $9 → positional args (missing → empty string)
|
|
13
|
+
* \$ARGUMENTS \$1 → escaped: kept literal (the backslash is dropped)
|
|
13
14
|
* !`<cmd>` → run <cmd> in a shell, splice in its stdout (bang exec)
|
|
14
15
|
* @path → splice in file/dir contents (via file-ref-expander)
|
|
15
16
|
*
|
|
@@ -148,11 +149,20 @@ export function getCommand(name, cwd = process.cwd(), opts = {}) {
|
|
|
148
149
|
return discoverCommands(cwd, opts).find((c) => c.name === wanted) || null;
|
|
149
150
|
}
|
|
150
151
|
|
|
151
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Substitute $ARGUMENTS and $1..$9 in `text`. A backslash escapes the `$` so a
|
|
154
|
+
* literal token survives (Claude-Code 2.1.163 `\$` escape): `\$5` → `$5`,
|
|
155
|
+
* `\$ARGUMENTS` → `$ARGUMENTS`. The negative lookbehind skips an escaped `$`
|
|
156
|
+
* during substitution; a final pass strips the escaping backslash.
|
|
157
|
+
*/
|
|
152
158
|
export function substituteArgs(text, args = []) {
|
|
153
159
|
const list = Array.isArray(args) ? args : [];
|
|
154
|
-
let out = text.replace(
|
|
155
|
-
out = out.replace(
|
|
160
|
+
let out = text.replace(/(?<!\\)\$ARGUMENTS/g, list.join(" "));
|
|
161
|
+
out = out.replace(/(?<!\\)\$([1-9])/g, (_, d) => list[Number(d) - 1] ?? "");
|
|
162
|
+
// Unescape: a backslash immediately before $ARGUMENTS or $<digit> was an
|
|
163
|
+
// explicit "keep literal" marker — drop the backslash now that substitution
|
|
164
|
+
// has skipped it. Other backslashes are left untouched.
|
|
165
|
+
out = out.replace(/\\(\$(?:ARGUMENTS|[1-9]))/g, "$1");
|
|
156
166
|
return out;
|
|
157
167
|
}
|
|
158
168
|
|
package/src/lib/status-line.cjs
CHANGED
|
@@ -102,6 +102,22 @@ function formatTokens(n) {
|
|
|
102
102
|
return Number((v / 1_000_000).toFixed(1)) + "M";
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Resolve the terminal size to advertise to the status-line command, as
|
|
107
|
+
* positive integers or null. Explicit `columns`/`rows` options win (testing /
|
|
108
|
+
* non-REPL callers); otherwise the live `process.stdout` dimensions are used.
|
|
109
|
+
* Non-TTY (piped) stdout has no dimensions → null (env var simply omitted).
|
|
110
|
+
*/
|
|
111
|
+
function terminalSize({ columns, rows } = {}) {
|
|
112
|
+
const out = process.stdout || {};
|
|
113
|
+
const cols = Number.isFinite(columns) ? columns : out.columns;
|
|
114
|
+
const rws = Number.isFinite(rows) ? rows : out.rows;
|
|
115
|
+
return {
|
|
116
|
+
columns: Number.isFinite(cols) && cols > 0 ? Math.floor(cols) : null,
|
|
117
|
+
rows: Number.isFinite(rws) && rws > 0 ? Math.floor(rws) : null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
105
121
|
/** Home-relative compact path: the home dir collapses to "~". */
|
|
106
122
|
function shortenPath(p) {
|
|
107
123
|
const cwd = String(p || "");
|
|
@@ -159,13 +175,26 @@ function isStatusLineDisabled({ cwd = process.cwd(), settingsFile } = {}) {
|
|
|
159
175
|
* Render the status line by running the command with the JSON context on stdin.
|
|
160
176
|
* Returns the first stdout line (trimmed, ANSI preserved) or null.
|
|
161
177
|
*/
|
|
162
|
-
function renderStatusLine(
|
|
178
|
+
function renderStatusLine(
|
|
179
|
+
config,
|
|
180
|
+
context = {},
|
|
181
|
+
{ cwd, timeout = 5000, columns, rows } = {},
|
|
182
|
+
) {
|
|
163
183
|
if (!config || !config.command) return null;
|
|
184
|
+
// Claude-Code parity (2.1.153): hand the command the terminal width/height so
|
|
185
|
+
// a script can right-size its output. process.env is spread first so the
|
|
186
|
+
// command (run via shell) keeps PATH etc.; COLUMNS/LINES are only added when
|
|
187
|
+
// a real size is known (TTY) so a non-TTY run doesn't fake a width.
|
|
188
|
+
const { columns: cols, rows: rws } = terminalSize({ columns, rows });
|
|
189
|
+
const env = { ...process.env };
|
|
190
|
+
if (cols != null) env.COLUMNS = String(cols);
|
|
191
|
+
if (rws != null) env.LINES = String(rws);
|
|
164
192
|
let res;
|
|
165
193
|
try {
|
|
166
194
|
res = _deps.spawnSync(config.command, {
|
|
167
195
|
input: JSON.stringify(context),
|
|
168
196
|
cwd: cwd || process.cwd(),
|
|
197
|
+
env,
|
|
169
198
|
encoding: "utf-8",
|
|
170
199
|
timeout,
|
|
171
200
|
shell: true,
|
|
@@ -184,11 +213,11 @@ function renderStatusLine(config, context = {}, { cwd, timeout = 5000 } = {}) {
|
|
|
184
213
|
}
|
|
185
214
|
|
|
186
215
|
/** Convenience: load + render in one call (used by the REPL each turn). */
|
|
187
|
-
function getStatusLine({ cwd, settingsFile, sessionId, model, provider, projectDir, timeout } = {}) {
|
|
216
|
+
function getStatusLine({ cwd, settingsFile, sessionId, model, provider, projectDir, timeout, columns, rows } = {}) {
|
|
188
217
|
const config = loadStatusLineConfig({ cwd, settingsFile });
|
|
189
218
|
if (!config) return null;
|
|
190
219
|
const context = buildContext({ sessionId, model, provider, cwd, projectDir });
|
|
191
|
-
return renderStatusLine(config, context, { cwd, timeout });
|
|
220
|
+
return renderStatusLine(config, context, { cwd, timeout, columns, rows });
|
|
192
221
|
}
|
|
193
222
|
|
|
194
223
|
module.exports = {
|
|
@@ -200,5 +229,6 @@ module.exports = {
|
|
|
200
229
|
shortenPath,
|
|
201
230
|
renderDefaultStatusLine,
|
|
202
231
|
isStatusLineDisabled,
|
|
232
|
+
terminalSize,
|
|
203
233
|
_deps,
|
|
204
234
|
};
|