chainlesschain 0.162.33 → 0.162.34
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-3TazCYWE.js → AIOps-BYfi9NYS.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DUPN0PST.js → ActionButton-BiS_tAN7.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CemvhkzD.js → Analytics-jiWl_p-B.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BL_tAU3M.js → AppLayout-m4sIzDot.js} +3 -3
- package/src/assets/web-panel/assets/{Audit-Dl9l-cxF.js → Audit-CPla3Erm.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-BKDDX75m.js → Backup-BGeQzTaB.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CDYePvMI.js → BaseInput-DTf7Z1iU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CGtR0sg3.js → Chat-DPTlQlD-.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-BgRXce4e.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-CwYIHOOo.js → Checkbox-DY-XuQMu.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CIF5tbtd.js → Codegen-B6oxPiZI.js} +1 -1
- package/src/assets/web-panel/assets/{Col-z7d4kxeP.js → Col-Dqxb4wSE.js} +1 -1
- package/src/assets/web-panel/assets/{Community-DUlDrqF7.js → Community-DCIX514p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CJ1o8QQR.js → Compact-BGtCzDoJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-D3i9d_uO.js → Compliance-zcOYd55o.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Wm7JTkfB.js → Cowork-DVTtdIdM.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-B0QnHhZx.js → Cron-CPUaR69k.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-3yPrnNgd.js → Crosschain-DnjUS6QH.js} +1 -1
- package/src/assets/web-panel/assets/{DID-cfdkiDWF.js → DID-Dnz8VDmx.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DFkgM4gT.js → Dashboard-CtWf27j7.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-YYWE81DL.js → Dropdown-B4GC1ZV4.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BXfHK1Bn.js → EmailListRenderer-wjij3kzr.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DInUxJ2G.js → FamilyGuardDashboard-rS-2W4u5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DNUYeFsv.js → Federation-90p5Tnoz.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cr7eVEBB.js → FormItemContext-Cnrw7gzq.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-_gF4cmDa.js → GenericCardRenderer-C85NsWa3.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BqldmUbO.js → Git-BFAVM9F8.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BF59ZiQ8.js → Governance-DBoRonpq.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Cy7y1eb9.js → Inference-DHRyD66j.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-B3fVocTO.js → KnowledgeGraph-CTvUKecD.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BDirsUVk.js → Logs-CB0dv_Ts.js} +2 -2
- package/src/assets/web-panel/assets/{Marketplace-GhXpZgp2.js → Marketplace-CN7Hm5Uw.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-0VvfIhKx.js → McpTools-q5H25_8L.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-CJLBgAUT.js → Memory-BCV3pZ1d.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BMedY9Yg.js → MobileBridge-C04Mngt4.js} +2 -2
- package/src/assets/web-panel/assets/MobileProjects-CUxONYre.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CgEuUg0g.js → Mtc-ByAMz2DN.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-1pWNe_xi.js → MtcAudit-B7V7byJq.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DPIQ7oZL.js → Multisig-DtKmcVQV.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-W__P_P4Z.js → NLProgramming-CaMbT5SC.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-C_MCDhFk.js → Notes-DRjbSTCU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-CDFotapL.js → NotificationSettings-B9YbJID5.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BcI_-vGS.js +1 -0
- package/src/assets/web-panel/assets/{Organization-D6lMumhD.js → Organization-oTask4BE.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BMOvUMW6.js → Overflow-Bab06ey7.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DsQTEw1t.js → P2P--wlBeU0N.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CncRtN1Z.js → PdhVaultBrowser-D4t77Pwc.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DDC-DkUl.js → Permissions-B3sf6CJ3.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DVKY_NnT.js → PersonalDataHub-BXOojk63.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-C7oDVTl-.js → Pipeline-DReqtBFN.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DReGvTEJ.js → Privacy-cT1GwKLx.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C-j2dzxJ.js → ProjectInit-BhTAzVhH.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DcUsvFnc.js → ProjectSettings-CK-D8Fyj.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CbHiwen6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-DIpohWG5.js → Providers-B-ftiXa8.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DdvLtpEU.js → QuickAsk-CT5XPwTF.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DPAi2zo3.js → Recommend-CohhlBZ_.js} +1 -1
- package/src/assets/web-panel/assets/{Reputation-DJD7qXSI.js → Reputation-CrgbixFz.js} +1 -1
- package/src/assets/web-panel/assets/{Row-XERdPDHk.js → Row-ClExmBn3.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cl_VlCLg.js → RssFeed-VV0qizCJ.js} +2 -2
- package/src/assets/web-panel/assets/{Search-C-poG9P5.js → Search-CqJapSiL.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DjjCrw8v.js → Security-DY66Zie6.js} +2 -2
- package/src/assets/web-panel/assets/{Services-BuWeB4YJ.js → Services-RQwxat7-.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-VZXOKwC_.js → Skeleton-0v37UTU_.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-B76ONTfP.js → Skills-B4Vm4DxN.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DIj1KREq.js → Sla-CggphTlo.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BrAp3Yk3.js → SpeechSettings-BAOU08C7.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings--mJcpccF.js → SyncSettings-DmtC4J1w.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-DM8cMr83.js → Tasks-CExqxzL6.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-kOBK6m1Z.js → Templates-C1QK0YoU.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BjSzYPzn.js → Tenant-CieOfmqp.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DwpY-Ay7.js → Terminal-DWdhrxRq.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-aoI0DazM.js → TimelineRenderer-CjFVUUDU.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-YwE0LqSZ.js → Tokens-Bwbk3id9.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CwSKzvlX.js → Trigger-uJle_yj4.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-B__Jqdzn.js → Trust-BcOuxAA5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-mty0jwmx.js → UkeySign-DUu7Ufg6.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Ddsx_OQ6.js → VideoEditing-Ck8JtQ2n.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-D4Q8yXZm.js → Wallet-B3jw43on.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CLUaKUr5.js → WebAuthn-Baf9K0y7.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-Di5pOaeC.js → WorkflowEditor-CTEDl_83.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CELatHkT.js → chat-CKV51quV.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CawDLjXV.js → colors-BO_RP_yz.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-DeMp-K0j.js → compact-item-BZsxw_ZG.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-zY9kXivd.js → createContext-CAbvtzVL.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DQYatsRR.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-VEBMW8E4.js → hasIn-QmHT8zDz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjctklSd.js → index-5hlO2-JQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DClGYjBM.js → index-8BMLlHCv.js} +1 -1
- package/src/assets/web-panel/assets/{index-KcOEkUCM.js → index-9IqJODII.js} +1 -1
- package/src/assets/web-panel/assets/{index-fBNVDEf2.js → index-B2aiE8jk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BqJ2r12F.js → index-B3fwyCjJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-VJnHvkv2.js → index-B5zhcul9.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHqvj9uz.js → index-B9Z83FTS.js} +1 -1
- package/src/assets/web-panel/assets/{index-8kqE_cVD.js → index-BCsZiq4i.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr7lnIeI.js → index-BEJa1FiF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSiL_W2n.js → index-BL7gQAuB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DPHe9NYG.js → index-BNvTNZ1V.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8AZpx7d.js → index-BPZHeug4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DALuVdhu.js → index-BRNYA0BV.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHxHLv2b.js → index-BnPBG3Tr.js} +1 -1
- package/src/assets/web-panel/assets/index-Bv_y1Ud7.js +1 -0
- package/src/assets/web-panel/assets/{index-CbJZzK9B.js → index-C3K1eHDd.js} +1 -1
- package/src/assets/web-panel/assets/{index-V3K9gvKR.js → index-C6AA-xB2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfGGKoo8.js → index-C6i3reUS.js} +1 -1
- package/src/assets/web-panel/assets/{index-GRNVdvoA.js → index-CEh2Ry_A.js} +1 -1
- package/src/assets/web-panel/assets/{index-TfXODan7.js → index-CSaI8R_7.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWbbB1MI.js → index-CVoYeZ5Q.js} +1 -1
- package/src/assets/web-panel/assets/index-CZZnSJEX.js +1 -0
- package/src/assets/web-panel/assets/{index-u_1aiNTA.js → index-CqiKnXtL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtoauqWt.js → index-CsBx0u5G.js} +1 -1
- package/src/assets/web-panel/assets/{index-b3ZuAreb.js → index-D8CHQnPl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVkrfyuk.js → index-DBCYOypV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXNe_zIP.js → index-DC1CFfQU.js} +1 -1
- package/src/assets/web-panel/assets/{index-S9JZDSaa.js → index-DKnngF_f.js} +1 -1
- package/src/assets/web-panel/assets/{index-XFyv3Sg_.js → index-DKquNxL2.js} +3 -3
- package/src/assets/web-panel/assets/{index-vaD1iHg5.js → index-DRK0oAV5.js} +1 -1
- package/src/assets/web-panel/assets/{index-SrQIPYq8.js → index-DeC7lehI.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFc0vBN9.js → index-DjrDGJP2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfZV3FXN.js → index-Dln_vjSY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C0GhuYLk.js → index-Dob6B6qS.js} +1 -1
- package/src/assets/web-panel/assets/{index-JseP3-5X.js → index-GPY0LjCu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhsfyHcg.js → index-Ha2_56mf.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtLZammH.js → index-fnDgExTu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyeYs7SG.js → index-jd2r-T4p.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dna2psGz.js → index-qPafbZmr.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Sd7Eayz4.js → initDefaultProps-Bc2GWeWe.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DlToY72q.js → motion-BI-Rxw6o.js} +1 -1
- package/src/assets/web-panel/assets/{move-DvS7EmAP.js → move-DRPdwDQB.js} +1 -1
- package/src/assets/web-panel/assets/{omit-CzLq4QKW.js → omit-B4XTl3jW.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BcM75Jx_.js → pickAttrs-Do5d86Wr.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-B7xXXiwd.js → placementArrow-B8VGZ0ZF.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CrYPRB-g.js → responsiveObserve-Cf0kI_vN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-CSYTtsRt.js → slide-Cb0psjSL.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CeSuOVT_.js → statusUtils-Bjuo5Oal.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-KiQethca.js → styleChecker-BLMhoHJ5.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CSQnQdiv.js → useFlexGapSupport-BdCwAfNU.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Br8Kr1pr.js → useFs-9Jhaz5gG.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DGJtDcMm.js → usePersonalDataHub-xYFyXKwD.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C-jVtGka.js → vnode-CVhepE6Z.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CeWySTPF.js → zoom-IbbtJ4Zr.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agent.js +19 -0
- package/src/commands/command.js +187 -0
- package/src/commands/context.js +189 -0
- package/src/index.js +4 -0
- package/src/lib/slash-commands.js +197 -0
- package/src/repl/agent-repl.js +42 -1
- package/src/runtime/agent-core.js +253 -0
- package/src/runtime/headless-runner.js +30 -1
- package/src/runtime/headless-stream.js +65 -0
- package/src/runtime/mcp-config.js +100 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DZjc9uKn.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-mdohgRlL.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Dtht0cEs.js +0 -1
- package/src/assets/web-panel/assets/Projects-jSjWnmr6.js +0 -1
- package/src/assets/web-panel/assets/devWarning-zLjV7g6r.js +0 -1
- package/src/assets/web-panel/assets/index-CDtUWCtX.js +0 -1
- package/src/assets/web-panel/assets/index-d_RPqH7u.js +0 -1
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc command — user-defined slash-command macros (Claude-Code parity).
|
|
3
|
+
*
|
|
4
|
+
* cc command list [--json] list discovered commands
|
|
5
|
+
* cc command show <name> show metadata + template body
|
|
6
|
+
* cc command run <name> [args...] [opts] expand the template and run it
|
|
7
|
+
* headlessly (or --print-prompt)
|
|
8
|
+
* cc command new <name> [--description <d>] scaffold a command file
|
|
9
|
+
*
|
|
10
|
+
* Commands live in `.claude/commands/` (project, recursive) or
|
|
11
|
+
* `~/.claude/commands/` (personal). A file `git/commit.md` → command
|
|
12
|
+
* `git:commit`. Template body
|
|
13
|
+
* supports $ARGUMENTS / $1..$9, !`bash` bang execution, and @path file refs.
|
|
14
|
+
* Distinct from skills (AI-invoked); these are macros you run explicitly.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import chalk from "chalk";
|
|
18
|
+
import { logger } from "../lib/logger.js";
|
|
19
|
+
|
|
20
|
+
export function registerCommandCommand(program) {
|
|
21
|
+
const cmd = program
|
|
22
|
+
.command("command")
|
|
23
|
+
.alias("cmd")
|
|
24
|
+
.description("User-defined slash-command macros (.claude/commands/*.md)");
|
|
25
|
+
|
|
26
|
+
// ── list ──────────────────────────────────────────────────────────────
|
|
27
|
+
cmd
|
|
28
|
+
.command("list")
|
|
29
|
+
.alias("ls")
|
|
30
|
+
.description("List discovered command macros (project + personal)")
|
|
31
|
+
.option("--json", "Output as JSON")
|
|
32
|
+
.action(async (options) => {
|
|
33
|
+
try {
|
|
34
|
+
const { discoverCommands } = await import("../lib/slash-commands.js");
|
|
35
|
+
const all = discoverCommands(process.cwd());
|
|
36
|
+
if (options.json) {
|
|
37
|
+
console.log(JSON.stringify(all, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (all.length === 0) {
|
|
41
|
+
logger.log(
|
|
42
|
+
chalk.gray(
|
|
43
|
+
"No command macros. Create one: cc command new <name> " +
|
|
44
|
+
"(or add .claude/commands/<name>.md)",
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
for (const c of all) {
|
|
50
|
+
const tag =
|
|
51
|
+
c.scope === "project" ? chalk.cyan("[proj]") : chalk.gray("[pers]");
|
|
52
|
+
logger.log(
|
|
53
|
+
`${chalk.bold("/" + c.name.padEnd(22))} ${tag} ${chalk.gray(c.description || "")}` +
|
|
54
|
+
(c.argumentHint ? chalk.dim(` ${c.argumentHint}`) : ""),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
logger.error(chalk.red(`command list failed: ${err.message}`));
|
|
59
|
+
process.exitCode = 1;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ── show ──────────────────────────────────────────────────────────────
|
|
64
|
+
cmd
|
|
65
|
+
.command("show <name>")
|
|
66
|
+
.description("Show a command's metadata and template body")
|
|
67
|
+
.option("--json", "Output as JSON")
|
|
68
|
+
.action(async (name, options) => {
|
|
69
|
+
try {
|
|
70
|
+
const { getCommand } = await import("../lib/slash-commands.js");
|
|
71
|
+
const c = getCommand(name, process.cwd());
|
|
72
|
+
if (!c) {
|
|
73
|
+
logger.error(chalk.red(`no such command: ${name}`));
|
|
74
|
+
process.exitCode = 1;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (options.json) {
|
|
78
|
+
console.log(JSON.stringify(c, null, 2));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
logger.log(
|
|
82
|
+
chalk.bold(`/${c.name}`) + ` ${chalk.gray(`[${c.scope}]`)}`,
|
|
83
|
+
);
|
|
84
|
+
if (c.description) logger.log(chalk.gray(` ${c.description}`));
|
|
85
|
+
if (c.argumentHint) logger.log(chalk.gray(` args: ${c.argumentHint}`));
|
|
86
|
+
if (c.model) logger.log(chalk.gray(` model: ${c.model}`));
|
|
87
|
+
if (c.allowedTools)
|
|
88
|
+
logger.log(chalk.gray(` allowed-tools: ${c.allowedTools}`));
|
|
89
|
+
logger.log(chalk.gray(` file: ${c.file}`));
|
|
90
|
+
logger.log(chalk.dim(" ───"));
|
|
91
|
+
logger.log(c.body);
|
|
92
|
+
} catch (err) {
|
|
93
|
+
logger.error(chalk.red(`command show failed: ${err.message}`));
|
|
94
|
+
process.exitCode = 1;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// ── run ───────────────────────────────────────────────────────────────
|
|
99
|
+
cmd
|
|
100
|
+
.command("run <name> [args...]")
|
|
101
|
+
.description("Expand a command template and run it headlessly")
|
|
102
|
+
.option("--print-prompt", "Print the expanded prompt without running it")
|
|
103
|
+
.option("--no-bang", "Do not execute !`cmd` bang segments")
|
|
104
|
+
.option("--output-format <fmt>", "text | json | stream-json", "text")
|
|
105
|
+
.option("--model <model>", "Override the model")
|
|
106
|
+
.option("--permission-mode <mode>", "ApprovalGate tier (see cc agent)")
|
|
107
|
+
.action(async (name, args, options) => {
|
|
108
|
+
try {
|
|
109
|
+
const { getCommand, expandCommand } =
|
|
110
|
+
await import("../lib/slash-commands.js");
|
|
111
|
+
const c = getCommand(name, process.cwd());
|
|
112
|
+
if (!c) {
|
|
113
|
+
logger.error(chalk.red(`no such command: ${name}`));
|
|
114
|
+
process.exitCode = 1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const { prompt, warnings } = expandCommand(c, args, {
|
|
118
|
+
cwd: process.cwd(),
|
|
119
|
+
allowBang: options.bang !== false, // commander maps --no-bang → bang:false
|
|
120
|
+
});
|
|
121
|
+
for (const w of warnings) process.stderr.write(` @ref: ${w}\n`);
|
|
122
|
+
|
|
123
|
+
if (options.printPrompt) {
|
|
124
|
+
console.log(prompt);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const { runAgentHeadless, parseToolList } =
|
|
129
|
+
await import("../runtime/headless-runner.js");
|
|
130
|
+
const outcome = await runAgentHeadless({
|
|
131
|
+
prompt,
|
|
132
|
+
outputFormat: options.outputFormat,
|
|
133
|
+
model: options.model || c.model || undefined,
|
|
134
|
+
permissionMode: options.permissionMode,
|
|
135
|
+
// A command's frontmatter allowed-tools scopes the run.
|
|
136
|
+
allowedTools: parseToolList(c.allowedTools),
|
|
137
|
+
});
|
|
138
|
+
process.exit(outcome.exitCode);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
logger.error(chalk.red(`command run failed: ${err.message}`));
|
|
141
|
+
process.exitCode = 1;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ── new (scaffold) ────────────────────────────────────────────────────
|
|
146
|
+
cmd
|
|
147
|
+
.command("new <name>")
|
|
148
|
+
.description("Scaffold a new command file under .claude/commands/")
|
|
149
|
+
.option("--description <d>", "Frontmatter description")
|
|
150
|
+
.option("--personal", "Create under ~/.claude/commands instead of project")
|
|
151
|
+
.action(async (name, options) => {
|
|
152
|
+
try {
|
|
153
|
+
const fs = await import("node:fs");
|
|
154
|
+
const path = await import("node:path");
|
|
155
|
+
const { homedir } = await import("node:os");
|
|
156
|
+
const safe = String(name).replace(/^\//, "").replace(/:/g, "/");
|
|
157
|
+
const root = options.personal
|
|
158
|
+
? path.join(homedir(), ".claude", "commands")
|
|
159
|
+
: path.join(process.cwd(), ".claude", "commands");
|
|
160
|
+
const file = path.join(root, `${safe}.md`);
|
|
161
|
+
if (fs.existsSync(file)) {
|
|
162
|
+
logger.error(chalk.red(`already exists: ${file}`));
|
|
163
|
+
process.exitCode = 1;
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
167
|
+
const tpl = `---
|
|
168
|
+
description: ${options.description || name}
|
|
169
|
+
argument-hint: "[args]"
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
Describe the task here. Use $ARGUMENTS for all args, $1/$2 for positional,
|
|
173
|
+
@path to inline file contents, and !\`git status\` to splice in command output.
|
|
174
|
+
`;
|
|
175
|
+
fs.writeFileSync(file, tpl, "utf-8");
|
|
176
|
+
logger.log(chalk.green(`✓ created ${file}`));
|
|
177
|
+
logger.log(
|
|
178
|
+
chalk.gray(
|
|
179
|
+
` run it with: cc command run ${safe.replace(/\//g, ":")} <args>`,
|
|
180
|
+
),
|
|
181
|
+
);
|
|
182
|
+
} catch (err) {
|
|
183
|
+
logger.error(chalk.red(`command new failed: ${err.message}`));
|
|
184
|
+
process.exitCode = 1;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cc context [sessionId] — context-window token breakdown for a session.
|
|
3
|
+
*
|
|
4
|
+
* Shows how a session's stored conversation fills the model context window,
|
|
5
|
+
* grouped by role (system / user / assistant / tool), with the share each takes
|
|
6
|
+
* and the headroom remaining. Reuses the SAME token estimator + window table as
|
|
7
|
+
* the headless auto-compactor (prompt-compressor.js) and the JSONL session store
|
|
8
|
+
* (rebuildMessages) — no new data is collected, this is purely a reporting view.
|
|
9
|
+
*
|
|
10
|
+
* Complements `cc cost` ($ spend) and `cc session usage` (raw token counts):
|
|
11
|
+
* this is the "how full is the window right now" view (Claude-Code `/context`).
|
|
12
|
+
*
|
|
13
|
+
* cc context # most-recent headless session
|
|
14
|
+
* cc context <sessionId>
|
|
15
|
+
* cc context --model claude-sonnet-4-6 # size against a specific window
|
|
16
|
+
* cc context --json
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import chalk from "chalk";
|
|
20
|
+
import { logger } from "../lib/logger.js";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Bucket message tokens by role. tool_calls carried on assistant messages are
|
|
24
|
+
* counted separately so the breakdown reflects what actually fills the window.
|
|
25
|
+
*/
|
|
26
|
+
export function categorizeContext(messages, estimateTokens) {
|
|
27
|
+
const buckets = { system: 0, user: 0, assistant: 0, tool: 0, toolCalls: 0 };
|
|
28
|
+
const counts = { system: 0, user: 0, assistant: 0, tool: 0 };
|
|
29
|
+
for (const m of messages) {
|
|
30
|
+
if (!m) continue;
|
|
31
|
+
const role =
|
|
32
|
+
m.role === "system" || m.role === "user" || m.role === "tool"
|
|
33
|
+
? m.role
|
|
34
|
+
: "assistant";
|
|
35
|
+
const content =
|
|
36
|
+
typeof m.content === "string"
|
|
37
|
+
? m.content
|
|
38
|
+
: JSON.stringify(m.content || "");
|
|
39
|
+
buckets[role] += estimateTokens(content);
|
|
40
|
+
counts[role] += 1;
|
|
41
|
+
if (Array.isArray(m.tool_calls) && m.tool_calls.length) {
|
|
42
|
+
buckets.toolCalls += estimateTokens(JSON.stringify(m.tool_calls));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const total =
|
|
46
|
+
buckets.system +
|
|
47
|
+
buckets.user +
|
|
48
|
+
buckets.assistant +
|
|
49
|
+
buckets.tool +
|
|
50
|
+
buckets.toolCalls;
|
|
51
|
+
return { buckets, counts, total };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function bar(frac, width = 24) {
|
|
55
|
+
const f = Math.max(0, Math.min(1, frac || 0));
|
|
56
|
+
const filled = Math.round(f * width);
|
|
57
|
+
return "█".repeat(filled) + "░".repeat(width - filled);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function registerContextCommand(program) {
|
|
61
|
+
program
|
|
62
|
+
.command("context")
|
|
63
|
+
.description(
|
|
64
|
+
"Context-window token breakdown for a session (Claude-Code /context parity)",
|
|
65
|
+
)
|
|
66
|
+
.argument("[id]", "Session ID (omit for the most-recent headless session)")
|
|
67
|
+
.option("--model <model>", "Size against this model's context window")
|
|
68
|
+
.option("--provider <provider>", "Provider (for the window default)")
|
|
69
|
+
.option("--json", "Output as JSON")
|
|
70
|
+
.action(async (id, options) => {
|
|
71
|
+
const {
|
|
72
|
+
rebuildMessages,
|
|
73
|
+
getLastSessionId,
|
|
74
|
+
sessionExists,
|
|
75
|
+
readEvents,
|
|
76
|
+
} = await import("../harness/jsonl-session-store.js");
|
|
77
|
+
const { estimateTokens, getContextWindow } = await import(
|
|
78
|
+
"../harness/prompt-compressor.js"
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const sessionId = id || getLastSessionId();
|
|
82
|
+
if (!sessionId) {
|
|
83
|
+
logger.error(
|
|
84
|
+
'No session found. Run a headless agent with `--session <id>` first, or pass a session id.',
|
|
85
|
+
);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!sessionExists(sessionId)) {
|
|
90
|
+
logger.error(
|
|
91
|
+
`No headless transcript for session "${sessionId}" (headless sessions are JSONL-only).`,
|
|
92
|
+
);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const messages = rebuildMessages(sessionId) || [];
|
|
98
|
+
|
|
99
|
+
// Auto-detect the session's model/provider from its session_start header,
|
|
100
|
+
// overridable by flags so you can size the same transcript against a
|
|
101
|
+
// different window.
|
|
102
|
+
let recordedModel = "";
|
|
103
|
+
let recordedProvider = "";
|
|
104
|
+
try {
|
|
105
|
+
const start = (readEvents(sessionId) || []).find(
|
|
106
|
+
(e) => e.type === "session_start",
|
|
107
|
+
);
|
|
108
|
+
recordedModel = start?.data?.model || "";
|
|
109
|
+
recordedProvider = start?.data?.provider || "";
|
|
110
|
+
} catch {
|
|
111
|
+
// header optional — fall through to flags/defaults
|
|
112
|
+
}
|
|
113
|
+
const model = options.model || recordedModel || null;
|
|
114
|
+
const provider = options.provider || recordedProvider || "ollama";
|
|
115
|
+
const window = getContextWindow(model, provider);
|
|
116
|
+
|
|
117
|
+
const { buckets, counts, total } = categorizeContext(
|
|
118
|
+
messages,
|
|
119
|
+
estimateTokens,
|
|
120
|
+
);
|
|
121
|
+
const used = window > 0 ? total / window : 0;
|
|
122
|
+
const remaining = Math.max(0, window - total);
|
|
123
|
+
|
|
124
|
+
if (options.json) {
|
|
125
|
+
console.log(
|
|
126
|
+
JSON.stringify(
|
|
127
|
+
{
|
|
128
|
+
sessionId,
|
|
129
|
+
model: model || null,
|
|
130
|
+
provider,
|
|
131
|
+
contextWindow: window,
|
|
132
|
+
totalTokens: total,
|
|
133
|
+
usedFraction: Number(used.toFixed(4)),
|
|
134
|
+
remainingTokens: remaining,
|
|
135
|
+
messageCount: messages.length,
|
|
136
|
+
breakdown: buckets,
|
|
137
|
+
counts,
|
|
138
|
+
overflows: total > window,
|
|
139
|
+
},
|
|
140
|
+
null,
|
|
141
|
+
2,
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
logger.log(chalk.bold(`Context — session ${chalk.gray(sessionId.slice(0, 28))}`));
|
|
148
|
+
logger.log(
|
|
149
|
+
chalk.gray(
|
|
150
|
+
` model ${model || "(default)"} · provider ${provider} · ` +
|
|
151
|
+
`window ${window.toLocaleString()} tokens · ${messages.length} messages`,
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
logger.log("");
|
|
155
|
+
|
|
156
|
+
const rows = [
|
|
157
|
+
["system", buckets.system, counts.system],
|
|
158
|
+
["user", buckets.user, counts.user],
|
|
159
|
+
["assistant", buckets.assistant, counts.assistant],
|
|
160
|
+
["tool results", buckets.tool, counts.tool],
|
|
161
|
+
["tool calls", buckets.toolCalls, null],
|
|
162
|
+
];
|
|
163
|
+
for (const [label, tok, cnt] of rows) {
|
|
164
|
+
if (!tok) continue;
|
|
165
|
+
const frac = total ? tok / total : 0;
|
|
166
|
+
logger.log(
|
|
167
|
+
` ${label.padEnd(13)} ${String(tok).padStart(7)} ` +
|
|
168
|
+
`${chalk.cyan(bar(frac))} ${String(Math.round(frac * 100)).padStart(3)}%` +
|
|
169
|
+
`${cnt != null ? chalk.gray(` (${cnt})`) : ""}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
logger.log("");
|
|
174
|
+
const pct = (used * 100).toFixed(1);
|
|
175
|
+
const color = used > 0.9 ? chalk.red : used > 0.7 ? chalk.yellow : chalk.green;
|
|
176
|
+
logger.log(
|
|
177
|
+
` ${chalk.bold("total".padEnd(11))} ${String(total).padStart(7)} ` +
|
|
178
|
+
`${color(bar(used))} ${color(`${pct}% of window`)}`,
|
|
179
|
+
);
|
|
180
|
+
logger.log(
|
|
181
|
+
chalk.gray(` headroom ${String(remaining).padStart(7)} tokens remaining`),
|
|
182
|
+
);
|
|
183
|
+
if (total > window) {
|
|
184
|
+
logger.log(
|
|
185
|
+
chalk.red(" ⚠ exceeds the model context window — compaction required"),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
package/src/index.js
CHANGED
|
@@ -57,8 +57,10 @@ import { registerPermMemCommand } from "./commands/permmem.js";
|
|
|
57
57
|
import { registerRCacheCommand } from "./commands/rcache.js";
|
|
58
58
|
import { registerSessionCommand } from "./commands/session.js";
|
|
59
59
|
import { registerCostCommand } from "./commands/cost.js";
|
|
60
|
+
import { registerContextCommand } from "./commands/context.js";
|
|
60
61
|
import { registerCheckpointCommand } from "./commands/checkpoint.js";
|
|
61
62
|
import { registerGoalCommand } from "./commands/goal.js";
|
|
63
|
+
import { registerCommandCommand } from "./commands/command.js";
|
|
62
64
|
import { registerCompactCommand } from "./commands/compact.js";
|
|
63
65
|
import { registerConsolCommand } from "./commands/consol.js";
|
|
64
66
|
import { registerImportCommand } from "./commands/import.js";
|
|
@@ -453,8 +455,10 @@ export function createProgram(opts = {}) {
|
|
|
453
455
|
registerRCacheCommand(program);
|
|
454
456
|
registerSessionCommand(program);
|
|
455
457
|
registerCostCommand(program);
|
|
458
|
+
registerContextCommand(program);
|
|
456
459
|
registerCheckpointCommand(program);
|
|
457
460
|
registerGoalCommand(program);
|
|
461
|
+
registerCommandCommand(program);
|
|
458
462
|
registerCompactCommand(program);
|
|
459
463
|
registerConsolCommand(program);
|
|
460
464
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* slash-commands — user-defined command templates (Claude-Code parity).
|
|
3
|
+
*
|
|
4
|
+
* Markdown files under `.claude/commands/` (project) or `~/.claude/commands/`
|
|
5
|
+
* (personal) become reusable command macros. Distinct from skills (skills are
|
|
6
|
+
* AI-invoked capability bundles; these are user-authored prompt macros you run
|
|
7
|
+
* explicitly). A file `git/commit.md` is the command `git:commit`.
|
|
8
|
+
*
|
|
9
|
+
* Frontmatter (all optional): `description`, `argument-hint`, `allowed-tools`,
|
|
10
|
+
* `model`. Body is the prompt template, with substitutions applied at run time:
|
|
11
|
+
* $ARGUMENTS → all args joined by space
|
|
12
|
+
* $1 $2 … $9 → positional args (missing → empty string)
|
|
13
|
+
* !`<cmd>` → run <cmd> in a shell, splice in its stdout (bang exec)
|
|
14
|
+
* @path → splice in file/dir contents (via file-ref-expander)
|
|
15
|
+
*
|
|
16
|
+
* Project scope shadows personal scope on a name clash. Discovery + parse +
|
|
17
|
+
* expand are pure (inject fs/exec/cwd) so the whole thing is unit-testable.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import fsDefault from "node:fs";
|
|
21
|
+
import pathDefault from "node:path";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { execSync as execSyncDefault } from "node:child_process";
|
|
24
|
+
import yaml from "js-yaml";
|
|
25
|
+
import { expandFileRefs } from "../runtime/file-ref-expander.js";
|
|
26
|
+
|
|
27
|
+
const _deps = { fs: fsDefault, path: pathDefault, execSync: execSyncDefault };
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Split `--- ... ---` YAML frontmatter from the body and camelCase the keys
|
|
31
|
+
* (so `argument-hint` → `argumentHint`). Self-contained (no skill-loader, whose
|
|
32
|
+
* import chain drags in heavy native deps). Returns `{ data, body }`.
|
|
33
|
+
*/
|
|
34
|
+
function parseFrontmatter(content) {
|
|
35
|
+
const text = String(content || "");
|
|
36
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
37
|
+
if (!m) return { data: {}, body: text.trim() };
|
|
38
|
+
let raw = {};
|
|
39
|
+
try {
|
|
40
|
+
raw = yaml.load(m[1]) || {};
|
|
41
|
+
} catch {
|
|
42
|
+
raw = {};
|
|
43
|
+
}
|
|
44
|
+
const data = {};
|
|
45
|
+
for (const [k, v] of Object.entries(raw)) {
|
|
46
|
+
const camel = k.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
47
|
+
data[camel] = v;
|
|
48
|
+
}
|
|
49
|
+
return { data, body: (m[2] || "").trim() };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Default shell timeout for `!`cmd`` bang execution. */
|
|
53
|
+
export const BANG_TIMEOUT_MS = 10_000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The directories scanned for command files, project first (it shadows personal
|
|
57
|
+
* on a name clash). `opts.home` overrides the personal root for tests.
|
|
58
|
+
*/
|
|
59
|
+
export function commandDirs(cwd = process.cwd(), opts = {}) {
|
|
60
|
+
const path = opts.deps?.path || _deps.path;
|
|
61
|
+
const home = opts.home || homedir();
|
|
62
|
+
return [
|
|
63
|
+
{ dir: path.join(cwd, ".claude", "commands"), scope: "project" },
|
|
64
|
+
{ dir: path.join(home, ".claude", "commands"), scope: "personal" },
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Recursively collect `*.md` files under `dir` as `{file, rel}` (rel uses /). */
|
|
69
|
+
function walkMd(dir, { fs, path }, base = dir, acc = []) {
|
|
70
|
+
let entries;
|
|
71
|
+
try {
|
|
72
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
73
|
+
} catch {
|
|
74
|
+
return acc;
|
|
75
|
+
}
|
|
76
|
+
for (const e of entries) {
|
|
77
|
+
const full = path.join(dir, e.name);
|
|
78
|
+
if (e.isDirectory()) {
|
|
79
|
+
walkMd(full, { fs, path }, base, acc);
|
|
80
|
+
} else if (e.isFile() && e.name.endsWith(".md")) {
|
|
81
|
+
const rel = path.relative(base, full).replace(/\\/g, "/");
|
|
82
|
+
acc.push({ file: full, rel });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return acc;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Command name from a relative path: `git/commit.md` → `git:commit`. */
|
|
89
|
+
function nameFromRel(rel) {
|
|
90
|
+
return rel.replace(/\.md$/, "").replace(/\//g, ":");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Parse one command file into its metadata + body (no expansion yet). */
|
|
94
|
+
export function parseCommandFile(file, scope, opts = {}) {
|
|
95
|
+
const fs = opts.deps?.fs || _deps.fs;
|
|
96
|
+
let content;
|
|
97
|
+
try {
|
|
98
|
+
content = fs.readFileSync(file, "utf-8");
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const { data, body } = parseFrontmatter(content);
|
|
103
|
+
return {
|
|
104
|
+
file,
|
|
105
|
+
scope,
|
|
106
|
+
description: data.description || "",
|
|
107
|
+
argumentHint: data.argumentHint || "",
|
|
108
|
+
allowedTools: data.allowedTools || null,
|
|
109
|
+
model: data.model || null,
|
|
110
|
+
body: body || "",
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Discover all commands across both scopes. Project shadows personal by name.
|
|
116
|
+
* @returns {Array<{name, scope, file, description, argumentHint, allowedTools, model}>}
|
|
117
|
+
*/
|
|
118
|
+
export function discoverCommands(cwd = process.cwd(), opts = {}) {
|
|
119
|
+
const fs = opts.deps?.fs || _deps.fs;
|
|
120
|
+
const path = opts.deps?.path || _deps.path;
|
|
121
|
+
const byName = new Map();
|
|
122
|
+
// Personal first, then project — so project overwrites on clash.
|
|
123
|
+
const dirs = commandDirs(cwd, opts).reverse();
|
|
124
|
+
for (const { dir, scope } of dirs) {
|
|
125
|
+
for (const { file, rel } of walkMd(dir, { fs, path })) {
|
|
126
|
+
const meta = parseCommandFile(file, scope, opts);
|
|
127
|
+
if (!meta) continue;
|
|
128
|
+
const name = nameFromRel(rel);
|
|
129
|
+
byName.set(name, { name, ...meta });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Look up one command by name (accepts `git:commit` or `git/commit`). */
|
|
136
|
+
export function getCommand(name, cwd = process.cwd(), opts = {}) {
|
|
137
|
+
const wanted = String(name || "")
|
|
138
|
+
.replace(/^\//, "")
|
|
139
|
+
.replace(/\//g, ":");
|
|
140
|
+
return discoverCommands(cwd, opts).find((c) => c.name === wanted) || null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Substitute $ARGUMENTS and $1..$9 in `text`. */
|
|
144
|
+
export function substituteArgs(text, args = []) {
|
|
145
|
+
const list = Array.isArray(args) ? args : [];
|
|
146
|
+
let out = text.replace(/\$ARGUMENTS/g, list.join(" "));
|
|
147
|
+
out = out.replace(/\$([1-9])/g, (_, d) => list[Number(d) - 1] ?? "");
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Run every `!`cmd`` and replace it with the command's stdout (best-effort). */
|
|
152
|
+
function runBangs(text, { cwd, execSync }) {
|
|
153
|
+
return text.replace(/!`([^`]+)`/g, (_, cmd) => {
|
|
154
|
+
try {
|
|
155
|
+
const out = execSync(cmd, {
|
|
156
|
+
cwd,
|
|
157
|
+
encoding: "utf-8",
|
|
158
|
+
timeout: BANG_TIMEOUT_MS,
|
|
159
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
160
|
+
});
|
|
161
|
+
return String(out).trim();
|
|
162
|
+
} catch (err) {
|
|
163
|
+
return `[command failed: ${cmd} — ${err.message?.split("\n")[0] || err}]`;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Expand a command into a final prompt string.
|
|
170
|
+
* 1. $ARGUMENTS / $1..$9 substitution
|
|
171
|
+
* 2. !`cmd` bang execution (skipped when opts.allowBang === false)
|
|
172
|
+
* 3. @path file references (via file-ref-expander)
|
|
173
|
+
*
|
|
174
|
+
* @param {object} command output of getCommand/parseCommandFile (needs .body)
|
|
175
|
+
* @param {string[]} args
|
|
176
|
+
* @param {object} [opts] { cwd, allowBang, deps:{ execSync } }
|
|
177
|
+
* @returns {{ prompt:string, warnings:string[] }}
|
|
178
|
+
*/
|
|
179
|
+
export function expandCommand(command, args = [], opts = {}) {
|
|
180
|
+
const cwd = opts.cwd || process.cwd();
|
|
181
|
+
const execSync = opts.deps?.execSync || _deps.execSync;
|
|
182
|
+
const warnings = [];
|
|
183
|
+
|
|
184
|
+
let text = substituteArgs(command.body || "", args);
|
|
185
|
+
|
|
186
|
+
if (opts.allowBang !== false) {
|
|
187
|
+
text = runBangs(text, { cwd, execSync });
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// @file expansion appends a <referenced-files> block when anything resolves.
|
|
191
|
+
const expanded = expandFileRefs(text, { cwd });
|
|
192
|
+
for (const w of expanded.warnings) warnings.push(w);
|
|
193
|
+
|
|
194
|
+
return { prompt: expanded.prompt, warnings };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export { _deps };
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from "../lib/session-hooks.js";
|
|
59
59
|
import { HookEvents } from "../lib/hook-manager.js";
|
|
60
60
|
import { IterationBudget } from "../lib/iteration-budget.js";
|
|
61
|
+
import { loadMcpConfig } from "../runtime/mcp-config.js";
|
|
61
62
|
import {
|
|
62
63
|
AGENT_TOOLS,
|
|
63
64
|
buildSystemPrompt,
|
|
@@ -354,6 +355,10 @@ export async function startAgentRepl(options = {}) {
|
|
|
354
355
|
// and applies bundle manifest metadata (model/provider override, agentId).
|
|
355
356
|
let _bundleResolved = null;
|
|
356
357
|
let _bundleMcpClient = null;
|
|
358
|
+
// --mcp-config (interactive parity with headless): ad-hoc MCP servers loaded
|
|
359
|
+
// for this session via the shared mcp-config engine. Holds {mcpClient,
|
|
360
|
+
// extraToolDefinitions, externalToolExecutors, externalToolDescriptors}.
|
|
361
|
+
let _adhocMcp = null;
|
|
357
362
|
if (options.bundlePath) {
|
|
358
363
|
try {
|
|
359
364
|
const { loadBundle } =
|
|
@@ -446,6 +451,28 @@ export async function startAgentRepl(options = {}) {
|
|
|
446
451
|
}
|
|
447
452
|
}
|
|
448
453
|
|
|
454
|
+
// --mcp-config: connect ad-hoc MCP servers for this interactive session and
|
|
455
|
+
// expose their tools to the LLM (Claude-Code parity with headless). Reuses the
|
|
456
|
+
// shared engine, so tools surface as mcp__<server>__<tool>. Best-effort: a bad
|
|
457
|
+
// config is reported but never aborts the REPL.
|
|
458
|
+
if (options.mcpConfig) {
|
|
459
|
+
try {
|
|
460
|
+
_adhocMcp = await loadMcpConfig(options.mcpConfig, {
|
|
461
|
+
writeErr: (s) => process.stderr.write(s),
|
|
462
|
+
});
|
|
463
|
+
const toolCount = _adhocMcp.extraToolDefinitions.length;
|
|
464
|
+
logger.log(
|
|
465
|
+
chalk.gray(
|
|
466
|
+
`MCP: ${_adhocMcp.connected.length} server(s), ${toolCount} tool(s) ` +
|
|
467
|
+
`(mcp__<server>__<tool>)`,
|
|
468
|
+
),
|
|
469
|
+
);
|
|
470
|
+
} catch (mcpErr) {
|
|
471
|
+
logger.log(chalk.yellow(`MCP: --mcp-config failed — ${mcpErr.message}`));
|
|
472
|
+
_adhocMcp = null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
449
476
|
// Apply bundle approval policy to this session (after both gate and sessionId are ready)
|
|
450
477
|
if (_bundleResolved?.approvalPolicy?.default && _approvalGate && sessionId) {
|
|
451
478
|
try {
|
|
@@ -1567,7 +1594,12 @@ export async function startAgentRepl(options = {}) {
|
|
|
1567
1594
|
checkpointSession: sessionId,
|
|
1568
1595
|
prepareCall,
|
|
1569
1596
|
approvalGate: _approvalGate,
|
|
1570
|
-
|
|
1597
|
+
// MCP: --mcp-config (ad-hoc) wins; bundle MCP is the fallback. The 3
|
|
1598
|
+
// tool channels expose --mcp-config servers' tools to the LLM directly.
|
|
1599
|
+
mcpClient: _adhocMcp?.mcpClient || _bundleMcpClient || undefined,
|
|
1600
|
+
extraToolDefinitions: _adhocMcp?.extraToolDefinitions,
|
|
1601
|
+
externalToolExecutors: _adhocMcp?.externalToolExecutors,
|
|
1602
|
+
externalToolDescriptors: _adhocMcp?.externalToolDescriptors,
|
|
1571
1603
|
chatFn: _fallbackChatFn,
|
|
1572
1604
|
});
|
|
1573
1605
|
|
|
@@ -1778,6 +1810,15 @@ export async function startAgentRepl(options = {}) {
|
|
|
1778
1810
|
}
|
|
1779
1811
|
}
|
|
1780
1812
|
|
|
1813
|
+
// Disconnect ad-hoc (--mcp-config) MCP servers
|
|
1814
|
+
if (_adhocMcp?.mcpClient) {
|
|
1815
|
+
try {
|
|
1816
|
+
await _adhocMcp.mcpClient.disconnectAll();
|
|
1817
|
+
} catch (_e) {
|
|
1818
|
+
// Non-critical
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1781
1822
|
// Shutdown runtime
|
|
1782
1823
|
try {
|
|
1783
1824
|
await shutdown();
|