chainlesschain 0.162.30 → 0.162.32

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.
Files changed (179) hide show
  1. package/package.json +2 -2
  2. package/src/assets/web-panel/assets/{AIOps-CsNttUU7.js → AIOps-Cg_uWAVl.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-lgohjckQ.js → ActionButton-DSFtQ1c2.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-ccV3LAca.js → Analytics-BMxpkw8y.js} +3 -3
  5. package/src/assets/web-panel/assets/AppLayout-tgVxlmsx.js +9 -0
  6. package/src/assets/web-panel/assets/{Audit-B1gFM5U9.js → Audit-DwzGllcp.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-BeWE3ERo.js → Backup-BG28Y2MV.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-CDkPsNG2.js → BaseInput-TXthbazl.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-ztb9ia6e.js → Chat-D096SxaD.js} +4 -4
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-Dlw_6n3M.js → ChatBubbleRenderer-PIx0Eu9I.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BcfRBlIY.js → Checkbox-Czttw1JS.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-DOs99xkr.js → Codegen-DZtMgv4q.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-D1X6tYlj.js → Col-D3DnfExY.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-DTksIWtz.js → Community-Bj5AdwqY.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-DIJtAYBO.js → Compact-BQ8Zszub.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-BBf7LF_k.js → Compliance-DXacb34n.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-UBPXQ40s.js → Cowork-BgMUBTkw.js} +2 -2
  18. package/src/assets/web-panel/assets/{Cron-CkRm1jPB.js → Cron-fqBWOqlN.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-qALlTl7e.js → Crosschain-E4oa1MWy.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-CqyqVS6E.js → DID-pwgfYZaV.js} +2 -2
  21. package/src/assets/web-panel/assets/Dashboard-n8mdLFIR.js +3 -0
  22. package/src/assets/web-panel/assets/{Dropdown-Cb5UzbSZ.js → Dropdown--6DYqxk7.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CarBq8Fk.js → EmailListRenderer-CkjQluz3.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CSiGXaZz.js → FamilyGuardDashboard-u-QTQ-OC.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-DUxhVoBN.js → Federation-D219M5Qc.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-BoMQpkhx.js → FormItemContext-BBU_aopC.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-DTVqC_CX.js → GenericCardRenderer-pTMCIHcM.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-C_XuPtK5.js → Git-ClcCARWt.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-BZyqlqz-.js → Governance-CvUi3I93.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-DdZVUimI.js → Inference-DT-a4pVg.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-IzZ-jnCn.js → KnowledgeGraph-DHMs2LY8.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-koTK6eNc.js → Logs-D2s4eV1N.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-6zpJ1L8n.js → Marketplace-YC5-fx-6.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-Ywc4IVks.js → McpTools-7JHTEC4T.js} +3 -3
  35. package/src/assets/web-panel/assets/{Memory-C_zB9dUa.js → Memory-BudotVLD.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-Nc05r24L.js → MobileBridge-CAiRyLVU.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-BJGxL526.js → MobileProjects-CrJJOCFw.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-Im7SIcz1.js → Mtc-d0iY0CeK.js} +5 -5
  39. package/src/assets/web-panel/assets/{MtcAudit-BFFzvzMD.js → MtcAudit-aI2cG1UP.js} +4 -4
  40. package/src/assets/web-panel/assets/{Multisig-CcNEbycq.js → Multisig-4bF70khG.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-CDH6OTXN.js → NLProgramming-CwLib1S7.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-Dqg3QXcU.js → Notes-Wt7AuFRU.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-CDVmK1eU.js → NotificationSettings-D081vV_7.js} +1 -1
  44. package/src/assets/web-panel/assets/OrderTableRenderer-DCPei1L9.js +1 -0
  45. package/src/assets/web-panel/assets/{Organization-DJb9bRQS.js → Organization-BNEsUNdP.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CK7Q5dje.js → Overflow-B_1iUXDD.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-CJIyYfwc.js → P2P-Dbc-kNwJ.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-uqRULcuw.js → PdhVaultBrowser-D8Xh289k.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Crvwt6bq.js → Permissions-C77mM6-n.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DcN5OWzg.js → PersonalDataHub-Dj0J3r_K.js} +3 -3
  51. package/src/assets/web-panel/assets/{Pipeline-DfWJvvJW.js → Pipeline-B6F0WQ2C.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-DepD0S3v.js → Privacy-eDKOkyyq.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-B7OKhH27.js → ProjectInit-DAWwhr5_.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-BJ4ueRFv.js → ProjectSettings-DwdK8k6I.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-Cb3p5QAP.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-Dl0FT1S3.js → Providers--DcYxQfN.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-V2hYLhfp.js → QuickAsk-DU268niT.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-8Kaiodgv.js → Recommend-ChnflhV1.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-CsxB3JGg.js → Reputation-DSsY3bQG.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-6-x7tEYq.js → Row-Zb-EjmgQ.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-Buv6f5tw.js → RssFeed-CGLiixZB.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-ABrDz84n.js → Search-Dhr_po-U.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-DqOJmz18.js → Security-GMYNhGsR.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-Cq4Tda3q.js → Services-DiOpnVY0.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-n74QlyYq.js → Skeleton-DG3ez6ME.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CC0iozL5.js → Skills-DZGptytP.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-hwRgJ99Z.js → Sla-CtGpE3xA.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-B6Bs6_-8.js → SpeechSettings-DQFw6Cf9.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-CTp2dZ0z.js → SyncSettings-C8X78RpX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Tasks-D70Lis6S.js → Tasks-DtVkhWCV.js} +1 -1
  71. package/src/assets/web-panel/assets/{Templates-Cags0ssw.js → Templates-SF9_ZWsV.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-BxCMzzGt.js → Tenant-BbIQSVZz.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-v05SDqHd.js → Terminal-DKr5zDwu.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BLUDHbBL.js → TimelineRenderer-BtLaNaWr.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-D-xKLJYv.js → Tokens-CfYbk2NG.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-B47tVIbH.js → Trigger-BLX_XDP0.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-DmRU9kfs.js → Trust-BWxUv9PR.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-DzgSGs-c.js → UkeySign-DRwTyQD4.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-C6qu58up.js → VideoEditing-BsC4VOSo.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-Dh8ZWx8f.js → Wallet-CSsO1NJU.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DFHOVuAY.js → WebAuthn-z1MxiFzS.js} +4 -4
  82. package/src/assets/web-panel/assets/{WorkflowEditor-B_fyQ3Y_.js → WorkflowEditor-B1vV7uuJ.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-BR-WxnCQ.js → chat-C0NJRaL2.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-C-6RysQe.js → colors-CHRiteWF.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-B_9_SCKN.js → compact-item-2XmBBKPD.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-D6rklIbE.js → createContext-DkedHC38.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-DmNpkOdC.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-BrotgSvd.js → hasIn-Bpn9Xrlw.js} +1 -1
  89. package/src/assets/web-panel/assets/index-7nAysteg.js +1 -0
  90. package/src/assets/web-panel/assets/{index-MCmNzIC7.js → index-B5NGWgHp.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-GzuCTHVZ.js → index-BItcSqan.js} +3 -3
  92. package/src/assets/web-panel/assets/index-BKWSQilQ.js +1 -0
  93. package/src/assets/web-panel/assets/{index-DTCUOKu9.js → index-BN068mCR.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-Bv9BrnD2.js → index-BOsIgPge.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DfqUsPl2.js → index-BYUd69vM.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-Cn21XmDt.js → index-BYmwEaIk.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-CWmJukRW.js → index-BZ1gOoiG.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-Bwkg_EJk.js → index-BfY9U3X5.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-MBOwmoOi.js → index-BveL_4n3.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-CJ70GAW2.js → index-CCg6ZY4t.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B85rQNYG.js → index-CJOoo72F.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-Cn5ghmbB.js → index-CToQxpWz.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-rWiOF7Iu.js → index-CWgWrrWs.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-PzM_GlKb.js → index-CdR7RfRP.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-ZehgEQYa.js → index-Cljnfuxu.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-BsDNNDBN.js → index-CxvA72CP.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-D6KqyxG1.js → index-CyJpmSHZ.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-E_5VXq8H.js → index-D7U411hK.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CJgp_QFo.js → index-D9mNfpxi.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DTpElYJs.js → index-DAFLFMXQ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-DMnomft7.js → index-DAeHmElB.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B2yXH6vy.js → index-DDy_RDjs.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-kkjq_hwC.js → index-DE5Qm9UI.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DTh0fWI4.js → index-DM9JrnYi.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DigjvHuo.js → index-DMbF-Euw.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-DkpDFJRn.js → index-DUBsq_1G.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BIiCIC2j.js → index-De49R7TX.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-CsWVDOd2.js → index-De5vOO9V.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-CAfRNHna.js → index-Dk7P-q3n.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CdDmzoPE.js → index-DryKGM_t.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CTQkYbir.js → index-DtU4qZRF.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-CK8YwdNd.js → index-NuBsCRaR.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BaLhL3Tj.js → index-Sk3-3tKa.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-CTpxOc5s.js → index-alGjpoM1.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-CrGp-4E2.js → index-cfSUlOfY.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BbRl_gIW.js → index-i4W_EAuh.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CCWzUY8K.js → index-uHGxyZtQ.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-C2v_L5na.js → initDefaultProps-DlDE-QgI.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-DNDqGbfr.js → motion-CodUbIRF.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-xvpQ_6hJ.js → move-DaLwsHeR.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-Cb0FsfrO.js → omit-DdVg-3rL.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-BxhYpnum.js → pickAttrs-KLR1EVCo.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-B3soaW4h.js → placementArrow-ChV7HvNw.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-B-eRSLvd.js → responsiveObserve-BB_A8dBt.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide--cM2ZOx-.js → slide-Bc1tQnIK.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-DjBhfi8Q.js → statusUtils-CgrveSb0.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-C30mMh8o.js → styleChecker-vXAYhhjz.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-f7y2Qlzs.js → useFlexGapSupport-BCIMPfq9.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-iTCXoLoZ.js → useFs-DMZGdr6G.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BH0RXmVF.js → usePersonalDataHub-118tWI_Z.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-DQtmeDXM.js → vnode-Z7O2Y7JP.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-vw50zkLZ.js → zoom-BXym6zmD.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +333 -1
  145. package/src/commands/ask.js +35 -1
  146. package/src/commands/checkpoint.js +439 -0
  147. package/src/commands/compact.js +150 -0
  148. package/src/commands/cost.js +114 -0
  149. package/src/commands/goal.js +417 -0
  150. package/src/commands/hub.js +7 -0
  151. package/src/commands/session.js +22 -2
  152. package/src/harness/prompt-compressor.js +71 -1
  153. package/src/index.js +8 -0
  154. package/src/lib/agent-core.js +1 -0
  155. package/src/lib/checkpoint-store.js +523 -0
  156. package/src/lib/file-checkpoint.js +300 -0
  157. package/src/lib/goal-context.js +87 -0
  158. package/src/lib/goal-store.js +308 -0
  159. package/src/lib/llm-pricing.js +227 -0
  160. package/src/lib/personal-data-hub-wiring.js +30 -0
  161. package/src/lib/recent-session.js +72 -0
  162. package/src/lib/session-picker.js +68 -0
  163. package/src/repl/agent-repl.js +101 -9
  164. package/src/repl/chat-repl.js +16 -1
  165. package/src/runtime/agent-core.js +313 -32
  166. package/src/runtime/fallback-model.js +109 -0
  167. package/src/runtime/file-ref-expander.js +258 -0
  168. package/src/runtime/headless-runner.js +601 -0
  169. package/src/runtime/headless-stream.js +315 -0
  170. package/src/runtime/policies/agent-policy.js +7 -0
  171. package/src/runtime/quiet-stdout.js +35 -0
  172. package/src/runtime/system-prompt.js +60 -0
  173. package/src/assets/web-panel/assets/AppLayout-B0hl5cPk.js +0 -9
  174. package/src/assets/web-panel/assets/Dashboard-XlMpT7K_.js +0 -3
  175. package/src/assets/web-panel/assets/OrderTableRenderer-Bg0bkfjR.js +0 -1
  176. package/src/assets/web-panel/assets/Projects-Dl_hPdhU.js +0 -1
  177. package/src/assets/web-panel/assets/devWarning-BiN5HELJ.js +0 -1
  178. package/src/assets/web-panel/assets/index-BhxiT2LJ.js +0 -1
  179. package/src/assets/web-panel/assets/index-DBNSZ2oz.js +0 -1
@@ -25,9 +25,28 @@ import ora from "ora";
25
25
  import { logger } from "../lib/logger.js";
26
26
  import { BUILT_IN_PROVIDERS } from "../lib/llm-providers.js";
27
27
  import { loadConfig } from "../lib/config-manager.js";
28
+ import { expandFileRefs } from "../runtime/file-ref-expander.js";
28
29
 
29
30
  const ANDROID_LOCAL_OLLAMA_URL = "http://127.0.0.1:18484";
30
31
 
32
+ /**
33
+ * Expand `@path` file references in the question (Claude-Code parity), unless
34
+ * disabled. Returns the (possibly augmented) prompt plus any warnings for
35
+ * typo'd paths. Exported so the wiring is unit-testable without a live LLM.
36
+ *
37
+ * @param {string} question
38
+ * @param {object} [opts] { cwd, enabled }
39
+ * @returns {{ prompt:string, warnings:string[] }}
40
+ */
41
+ export function expandQuestionRefs(
42
+ question,
43
+ { cwd = process.cwd(), enabled = true } = {},
44
+ ) {
45
+ if (!enabled) return { prompt: question, warnings: [] };
46
+ const { prompt, warnings } = expandFileRefs(question, { cwd });
47
+ return { prompt, warnings };
48
+ }
49
+
31
50
  /**
32
51
  * Resolve effective Ollama base URL given CLI options, env, and config.
33
52
  * Exported for unit testing.
@@ -144,6 +163,10 @@ export function registerAskCommand(program) {
144
163
  "--prefer-android-local",
145
164
  "Route ollama provider at the Android in-APK LocalLlmServer (127.0.0.1:18484). One-shot equivalent of `cc config set llm.preferAndroidLocal true`.",
146
165
  )
166
+ .option(
167
+ "--no-file-refs",
168
+ "Do not expand @path file references in the question",
169
+ )
147
170
  .option("--json", "Output as JSON")
148
171
  .action(async (question, options) => {
149
172
  const config = loadConfig();
@@ -167,9 +190,20 @@ export function registerAskCommand(program) {
167
190
  resolvedOptions.baseUrl = options.baseUrl || config.llm?.baseUrl;
168
191
  }
169
192
 
193
+ // Expand @path references (e.g. `cc ask "summarize @notes.md"`) before
194
+ // sending to the model. The original question is preserved for --json
195
+ // output; only the LLM sees the injected file contents.
196
+ const { prompt: expandedQuestion, warnings } = expandQuestionRefs(
197
+ question,
198
+ { enabled: options.fileRefs !== false },
199
+ );
200
+ for (const w of warnings) {
201
+ process.stderr.write(`[@ref] ${w}\n`);
202
+ }
203
+
170
204
  const spinner = ora("Thinking...").start();
171
205
  try {
172
- const answer = await queryLLM(question, resolvedOptions);
206
+ const answer = await queryLLM(expandedQuestion, resolvedOptions);
173
207
 
174
208
  spinner.stop();
175
209
 
@@ -0,0 +1,439 @@
1
+ /**
2
+ * cc checkpoint — file-state snapshot / rewind (Claude-Code rewind parity).
3
+ *
4
+ * cc checkpoint create [paths...] [--label <l>] snapshot the work tree (git)
5
+ * or the given paths (fallback)
6
+ * cc checkpoint list list checkpoints
7
+ * cc checkpoint show <id> [--diff] manifest, or diff vs current
8
+ * cc checkpoint restore <id> [--dry-run] [--force] roll back (alias: rewind)
9
+ * cc checkpoint delete <id> [--force] remove a checkpoint
10
+ * cc checkpoint clear remove all (a session)
11
+ *
12
+ * Engine: inside a git work tree it uses git-plumbing shadow commits
13
+ * (whole-tree, content-addressed, .gitignore-aware, accurate add/modify/delete
14
+ * rewind — refs/cc-checkpoints/*). Outside git it falls back to the copy-based
15
+ * store (file-checkpoint.js) which snapshots the explicit paths you name.
16
+ *
17
+ * Restore takes an automatic safety snapshot of the current state first, so a
18
+ * rewind is itself reversible. Distinct from `cc workflow checkpoint`
19
+ * (workflow execution state, not files).
20
+ */
21
+
22
+ import chalk from "chalk";
23
+ import { resolve } from "path";
24
+ import { logger } from "../lib/logger.js";
25
+
26
+ /** git-plumbing engine adapter (normalized interface). */
27
+ function gitEngine(gs, dir, session) {
28
+ return {
29
+ kind: "git",
30
+ create: ({ label }) => {
31
+ const r = gs.createCheckpoint(dir, { session, label });
32
+ return {
33
+ id: r.id,
34
+ label: r.label,
35
+ createdAt: r.createdAt,
36
+ fileCount: r.files ?? r.fileCount,
37
+ };
38
+ },
39
+ list: () =>
40
+ gs.listCheckpoints(dir, { session }).map((r) => ({
41
+ id: r.id,
42
+ label: r.label,
43
+ createdAt: r.createdAt,
44
+ fileCount: null,
45
+ })),
46
+ show: (id) => gs.showCheckpoint(dir, id, { session }),
47
+ status: (id) => gs.statusAgainst(dir, id, { session }),
48
+ diffText: (id, o) => gs.diffCheckpoint(dir, id, { session, stat: o?.stat }),
49
+ restore: (id, o) => {
50
+ const r = gs.rewindTo(dir, id, { session, dryRun: o?.dryRun });
51
+ return {
52
+ dryRun: !!r.dryRun,
53
+ restoredCount: r.modified + r.recreated,
54
+ modified: r.modified,
55
+ recreated: r.recreated,
56
+ deleted: r.deleted,
57
+ safetyId: r.safetyId,
58
+ };
59
+ },
60
+ remove: (id) => gs.deleteCheckpoint(dir, id, { session }),
61
+ clear: () => gs.clearCheckpoints(dir, { session }),
62
+ };
63
+ }
64
+
65
+ /** copy-based engine adapter (normalized interface). */
66
+ function copyEngine(cs, dir) {
67
+ return {
68
+ kind: "copy",
69
+ create: ({ paths, label }) => {
70
+ const m = cs.createCheckpoint(paths, { cwd: dir, label });
71
+ return {
72
+ id: m.id,
73
+ label: m.label,
74
+ createdAt: m.createdAt,
75
+ fileCount: m.fileCount,
76
+ };
77
+ },
78
+ list: () =>
79
+ cs.listCheckpoints().map((c) => ({
80
+ id: c.id,
81
+ label: c.label,
82
+ createdAt: c.createdAt,
83
+ fileCount: c.fileCount,
84
+ })),
85
+ show: (id) => {
86
+ const m = cs.getCheckpoint(id);
87
+ if (!m) throw new Error(`no such checkpoint: ${id}`);
88
+ return {
89
+ id: m.id,
90
+ label: m.label,
91
+ createdAt: m.createdAt,
92
+ fileCount: m.fileCount,
93
+ files: m.files.map((f) => ({ rel: f.rel, bytes: f.bytes })),
94
+ };
95
+ },
96
+ status: (id) => {
97
+ const d = cs.diffCheckpoint(id);
98
+ return { modified: d.modified, added: [], deleted: d.deleted };
99
+ },
100
+ diffText: () => null, // copy engine has no raw patch — caller uses status()
101
+ restore: (id, o) => {
102
+ const r = cs.restoreCheckpoint(id, { cwd: dir, dryRun: o?.dryRun });
103
+ return {
104
+ dryRun: !!r.dryRun,
105
+ restoredCount: r.restored.length,
106
+ restored: r.restored,
107
+ missingBlob: r.missingBlob,
108
+ safetyId: r.safetyId,
109
+ };
110
+ },
111
+ remove: (id) => cs.deleteCheckpoint(id),
112
+ clear: () => {
113
+ const all = cs.listCheckpoints();
114
+ for (const c of all) cs.deleteCheckpoint(c.id);
115
+ return all.length;
116
+ },
117
+ };
118
+ }
119
+
120
+ /** Choose the engine for `dir`: git-plumbing when available, else copy-based. */
121
+ async function pickEngine(dir, session) {
122
+ const gs = await import("../lib/checkpoint-store.js");
123
+ if (gs.isCheckpointAvailable(dir)) return gitEngine(gs, dir, session);
124
+ const cs = await import("../lib/file-checkpoint.js");
125
+ return copyEngine(cs, dir);
126
+ }
127
+
128
+ const tag = (engine) => chalk.dim(engine.kind === "git" ? "[git]" : "[copy]");
129
+
130
+ export function registerCheckpointCommand(program) {
131
+ const cp = program
132
+ .command("checkpoint")
133
+ .description("Snapshot / rewind file state (git-plumbing, copy fallback)");
134
+
135
+ cp.command("create [paths...]")
136
+ .description(
137
+ "Snapshot the work tree (git) or the given files/dirs (fallback)",
138
+ )
139
+ .option("-d, --dir <dir>", "Target directory", ".")
140
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
141
+ .option("--label <label>", "Human label for this checkpoint")
142
+ .option("--json", "Output as JSON")
143
+ .action(async (paths, options) => {
144
+ try {
145
+ const dir = resolve(options.dir);
146
+ const engine = await pickEngine(dir, options.session);
147
+ if (engine.kind === "copy" && (!paths || paths.length === 0)) {
148
+ logger.error(
149
+ chalk.red(
150
+ "Not a git repo here — specify paths to snapshot: cc checkpoint create <paths...>",
151
+ ),
152
+ );
153
+ process.exitCode = 1;
154
+ return;
155
+ }
156
+ if (engine.kind === "git" && paths && paths.length > 0) {
157
+ logger.log(
158
+ chalk.gray(
159
+ " (git engine snapshots the whole work tree; paths ignored)",
160
+ ),
161
+ );
162
+ }
163
+ const m = engine.create({ paths, label: options.label });
164
+ if (options.json) {
165
+ console.log(JSON.stringify({ ...m, engine: engine.kind }, null, 2));
166
+ return;
167
+ }
168
+ logger.log(
169
+ chalk.green(`✓ checkpoint ${chalk.bold(m.id)}`) +
170
+ ` ${tag(engine)}` +
171
+ (m.label ? chalk.gray(` "${m.label}"`) : ""),
172
+ );
173
+ logger.log(chalk.gray(` ${m.fileCount} file(s) snapshotted`));
174
+ logger.log(
175
+ chalk.gray(
176
+ ` rewind with: cc checkpoint restore ${m.id}` +
177
+ (options.session !== "default" ? ` -s ${options.session}` : ""),
178
+ ),
179
+ );
180
+ } catch (err) {
181
+ logger.error(chalk.red(`checkpoint create failed: ${err.message}`));
182
+ process.exitCode = 1;
183
+ }
184
+ });
185
+
186
+ cp.command("list")
187
+ .alias("ls")
188
+ .description("List checkpoints (newest first)")
189
+ .option("-d, --dir <dir>", "Target directory", ".")
190
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
191
+ .option("--json", "Output as JSON")
192
+ .action(async (options) => {
193
+ try {
194
+ const dir = resolve(options.dir);
195
+ const engine = await pickEngine(dir, options.session);
196
+ const all = engine.list();
197
+ if (options.json) {
198
+ console.log(JSON.stringify(all, null, 2));
199
+ return;
200
+ }
201
+ if (all.length === 0) {
202
+ logger.log(
203
+ chalk.gray(
204
+ "No checkpoints. Create one: cc checkpoint create" +
205
+ (engine.kind === "copy" ? " <paths...>" : ""),
206
+ ),
207
+ );
208
+ return;
209
+ }
210
+ for (const c of all) {
211
+ const count =
212
+ c.fileCount == null
213
+ ? ""
214
+ : `${String(c.fileCount).padStart(4)} files`;
215
+ logger.log(
216
+ `${chalk.cyan(c.id.padEnd(22))} ${chalk.gray(c.createdAt)} ${count}` +
217
+ (c.label ? chalk.gray(` "${c.label}"`) : ""),
218
+ );
219
+ }
220
+ } catch (err) {
221
+ logger.error(chalk.red(`checkpoint list failed: ${err.message}`));
222
+ process.exitCode = 1;
223
+ }
224
+ });
225
+
226
+ cp.command("show <id>")
227
+ .description("Show a checkpoint's files, or its diff vs current state")
228
+ .option("-d, --dir <dir>", "Target directory", ".")
229
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
230
+ .option("--diff", "Compare snapshot against current on-disk files")
231
+ .option("--stat", "With --diff: summary (diffstat) instead of full patch")
232
+ .option("--json", "Output as JSON")
233
+ .action(async (id, options) => {
234
+ try {
235
+ const dir = resolve(options.dir);
236
+ const engine = await pickEngine(dir, options.session);
237
+
238
+ if (options.diff) {
239
+ const text = engine.diffText(id, { stat: options.stat });
240
+ if (text != null) {
241
+ if (options.json) {
242
+ console.log(JSON.stringify({ id, diff: text }, null, 2));
243
+ return;
244
+ }
245
+ if (!text.trim()) logger.info(`No changes since checkpoint ${id}.`);
246
+ else logger.log(text);
247
+ return;
248
+ }
249
+ const d = engine.status(id);
250
+ if (options.json) {
251
+ console.log(JSON.stringify({ id, ...d }, null, 2));
252
+ return;
253
+ }
254
+ logger.log(chalk.bold(`Diff vs current — ${id}`));
255
+ logger.log(` ${chalk.yellow("modified")}: ${d.modified.length}`);
256
+ d.modified.forEach((f) => logger.log(chalk.yellow(` M ${f}`)));
257
+ logger.log(` ${chalk.red("deleted")}: ${d.deleted.length}`);
258
+ d.deleted.forEach((f) => logger.log(chalk.red(` D ${f}`)));
259
+ return;
260
+ }
261
+
262
+ const m = engine.show(id);
263
+ if (options.json) {
264
+ console.log(JSON.stringify(m, null, 2));
265
+ return;
266
+ }
267
+ logger.log(chalk.bold(`Checkpoint ${m.id}`) + ` ${tag(engine)}`);
268
+ if (m.label) logger.log(chalk.gray(` label: ${m.label}`));
269
+ logger.log(
270
+ chalk.gray(` created: ${m.createdAt} files: ${m.fileCount}`),
271
+ );
272
+ for (const f of m.files) {
273
+ logger.log(` ${chalk.gray(String(f.bytes).padStart(8))} ${f.rel}`);
274
+ }
275
+ } catch (err) {
276
+ logger.error(chalk.red(`checkpoint show failed: ${err.message}`));
277
+ process.exitCode = 1;
278
+ }
279
+ });
280
+
281
+ cp.command("restore <id>")
282
+ .alias("rewind")
283
+ .description(
284
+ "Restore files from a checkpoint (auto-snapshots current state first)",
285
+ )
286
+ .option("-d, --dir <dir>", "Target directory", ".")
287
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
288
+ .option("--dry-run", "Show what would change without writing")
289
+ .option("--force", "Restore without the interactive confirm prompt")
290
+ .option("--json", "Output as JSON")
291
+ .action(async (id, options) => {
292
+ try {
293
+ const dir = resolve(options.dir);
294
+ const engine = await pickEngine(dir, options.session);
295
+
296
+ if (options.dryRun) {
297
+ const r = engine.restore(id, { dryRun: true });
298
+ if (options.json) {
299
+ console.log(JSON.stringify(r, null, 2));
300
+ return;
301
+ }
302
+ logger.log(chalk.bold(`Dry-run restore — ${id}`));
303
+ logger.log(` would restore: ${r.restoredCount} file(s)`);
304
+ if (typeof r.deleted === "number") {
305
+ logger.log(` would remove: ${r.deleted} file(s) created since`);
306
+ }
307
+ return;
308
+ }
309
+
310
+ // Destructive: overwrites current files. Require --force when not a TTY;
311
+ // prompt when interactive.
312
+ if (!options.force) {
313
+ const d = engine.status(id);
314
+ const willChange =
315
+ d.modified.length + d.deleted.length + (d.added?.length || 0);
316
+ if (process.stdin.isTTY) {
317
+ const { confirm } = await import("@inquirer/prompts");
318
+ const ok = await confirm({
319
+ message: `Restore ${id}? ${willChange} file(s) affected (a safety checkpoint is taken first).`,
320
+ default: false,
321
+ }).catch(() => false);
322
+ if (!ok) {
323
+ logger.log(chalk.gray("Aborted."));
324
+ return;
325
+ }
326
+ } else {
327
+ logger.error(
328
+ chalk.red(
329
+ `Refusing to restore without --force (non-interactive). ${willChange} file(s) would change. Re-run with --dry-run to preview or --force to proceed.`,
330
+ ),
331
+ );
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+ }
336
+
337
+ const r = engine.restore(id);
338
+ if (options.json) {
339
+ console.log(JSON.stringify(r, null, 2));
340
+ return;
341
+ }
342
+ logger.log(
343
+ chalk.green(`✓ restored ${r.restoredCount} file(s) from ${id}`) +
344
+ (typeof r.deleted === "number" && r.deleted > 0
345
+ ? chalk.gray(` (${r.deleted} removed)`)
346
+ : ""),
347
+ );
348
+ if (r.safetyId) {
349
+ logger.log(
350
+ chalk.gray(
351
+ ` safety checkpoint of prior state: ${r.safetyId}` +
352
+ ` (undo with: cc checkpoint restore ${r.safetyId}` +
353
+ (options.session !== "default"
354
+ ? ` -s ${options.session}`
355
+ : "") +
356
+ `)`,
357
+ ),
358
+ );
359
+ }
360
+ if (r.missingBlob && r.missingBlob.length) {
361
+ logger.log(
362
+ chalk.red(` missing blobs (skipped): ${r.missingBlob.join(", ")}`),
363
+ );
364
+ }
365
+ } catch (err) {
366
+ logger.error(chalk.red(`checkpoint restore failed: ${err.message}`));
367
+ process.exitCode = 1;
368
+ }
369
+ });
370
+
371
+ cp.command("delete <id>")
372
+ .alias("rm")
373
+ .description("Delete a checkpoint")
374
+ .option("-d, --dir <dir>", "Target directory", ".")
375
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
376
+ .option("--force", "Skip confirmation")
377
+ .action(async (id, options) => {
378
+ try {
379
+ const dir = resolve(options.dir);
380
+ const engine = await pickEngine(dir, options.session);
381
+ if (!options.force && process.stdin.isTTY) {
382
+ const { confirm } = await import("@inquirer/prompts");
383
+ const ok = await confirm({
384
+ message: `Delete checkpoint ${id}?`,
385
+ default: false,
386
+ }).catch(() => false);
387
+ if (!ok) {
388
+ logger.log(chalk.gray("Aborted."));
389
+ return;
390
+ }
391
+ }
392
+ const existed = engine.remove(id);
393
+ if (!existed) {
394
+ logger.error(chalk.red(`no such checkpoint: ${id}`));
395
+ process.exitCode = 1;
396
+ return;
397
+ }
398
+ logger.log(chalk.green(`✓ deleted ${id}`));
399
+ } catch (err) {
400
+ logger.error(chalk.red(`checkpoint delete failed: ${err.message}`));
401
+ process.exitCode = 1;
402
+ }
403
+ });
404
+
405
+ cp.command("clear")
406
+ .description("Delete all checkpoints (in a session, for the git engine)")
407
+ .option("-d, --dir <dir>", "Target directory", ".")
408
+ .option("-s, --session <id>", "Checkpoint session (git engine)", "default")
409
+ .option("--force", "Skip confirmation")
410
+ .option("--json", "Output as JSON")
411
+ .action(async (options) => {
412
+ try {
413
+ const dir = resolve(options.dir);
414
+ const engine = await pickEngine(dir, options.session);
415
+ if (!options.force && process.stdin.isTTY) {
416
+ const { confirm } = await import("@inquirer/prompts");
417
+ const ok = await confirm({
418
+ message: `Delete ALL checkpoints${engine.kind === "git" ? ` in session "${options.session}"` : ""}?`,
419
+ default: false,
420
+ }).catch(() => false);
421
+ if (!ok) {
422
+ logger.log(chalk.gray("Aborted."));
423
+ return;
424
+ }
425
+ }
426
+ const removed = engine.clear();
427
+ if (options.json) {
428
+ console.log(
429
+ JSON.stringify({ removed, engine: engine.kind }, null, 2),
430
+ );
431
+ return;
432
+ }
433
+ logger.log(chalk.green(`✓ removed ${removed} checkpoint(s)`));
434
+ } catch (err) {
435
+ logger.error(chalk.red(`checkpoint clear failed: ${err.message}`));
436
+ process.exitCode = 1;
437
+ }
438
+ });
439
+ }
@@ -0,0 +1,150 @@
1
+ /**
2
+ * cc compact — compact a stored session's history (Claude-Code `/compact` parity,
3
+ * headless). Complements the interactive agent REPL's `/compact`, which only
4
+ * works on the live in-memory conversation.
5
+ *
6
+ * cc compact <session-id> compact and persist (writes a `compact`
7
+ * checkpoint event the resume path honors)
8
+ * cc compact <session-id> --dry-run preview the reduction, write nothing
9
+ *
10
+ * Engine: the existing PromptCompressor (snip + dedup + collapse + truncate).
11
+ * Runs OFFLINE and deterministic — no LLM summarization is wired here, so a
12
+ * compaction never makes a network call and is reproducible. By default it
13
+ * sizes its thresholds to the session's recorded model/provider context window;
14
+ * override with --model/--provider or the hard --max-tokens/--max-messages.
15
+ *
16
+ * After compaction the new history is appended as a JSONL `compact` event;
17
+ * `rebuildMessages()` already rebuilds from the last such event, so a later
18
+ * `cc agent --resume <id>` picks up the shortened history automatically.
19
+ * Distinct from `cc checkpoint` (file state) and `cc workflow checkpoint`
20
+ * (execution state).
21
+ */
22
+
23
+ import chalk from "chalk";
24
+ import { logger } from "../lib/logger.js";
25
+ import {
26
+ sessionExists,
27
+ readEvents,
28
+ rebuildMessages,
29
+ appendCompactEvent,
30
+ } from "../harness/jsonl-session-store.js";
31
+ import { PromptCompressor } from "../harness/prompt-compressor.js";
32
+
33
+ /** Build a compressor sized to the session (or explicit overrides). */
34
+ function buildCompressor(options, recorded) {
35
+ const maxTokens = options.maxTokens ? Number(options.maxTokens) : undefined;
36
+ const maxMessages = options.maxMessages
37
+ ? Number(options.maxMessages)
38
+ : undefined;
39
+ if (maxTokens || maxMessages) {
40
+ // Hard thresholds win — adaptive sizing is bypassed by the constructor.
41
+ return new PromptCompressor({ maxTokens, maxMessages });
42
+ }
43
+ const model = options.model || recorded.model || undefined;
44
+ const provider = options.provider || recorded.provider || undefined;
45
+ // model/provider → adaptive context-window thresholds; neither → defaults.
46
+ return new PromptCompressor({ model, provider });
47
+ }
48
+
49
+ export function registerCompactCommand(program) {
50
+ program
51
+ .command("compact <session-id>")
52
+ .description(
53
+ "Compact a stored session's history (offline; persists for --resume)",
54
+ )
55
+ .option(
56
+ "-m, --model <model>",
57
+ "Model for adaptive context-window sizing (default: session's recorded model)",
58
+ )
59
+ .option(
60
+ "-p, --provider <provider>",
61
+ "Provider for adaptive sizing (default: session's recorded provider)",
62
+ )
63
+ .option("--max-tokens <n>", "Override the token threshold (skips adaptive)")
64
+ .option(
65
+ "--max-messages <n>",
66
+ "Override the message-count threshold (skips adaptive)",
67
+ )
68
+ .option("--dry-run", "Preview the reduction without writing")
69
+ .option("--json", "Output as JSON")
70
+ .action(async (sessionId, options) => {
71
+ try {
72
+ if (!sessionExists(sessionId)) {
73
+ logger.error(chalk.red(`no such session: ${sessionId}`));
74
+ logger.log(chalk.gray(" list sessions with: cc session list"));
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+
79
+ const events = readEvents(sessionId);
80
+ const start = events.find((e) => e.type === "session_start");
81
+ const recorded = {
82
+ model: start?.data?.model || "",
83
+ provider: start?.data?.provider || "",
84
+ };
85
+
86
+ const messages = rebuildMessages(sessionId);
87
+ const compressor = buildCompressor(options, recorded);
88
+ const { messages: compacted, stats } =
89
+ await compressor.compress(messages);
90
+
91
+ const reduced = stats.saved > 0 || compacted.length < messages.length;
92
+
93
+ if (!reduced) {
94
+ if (options.json) {
95
+ console.log(
96
+ JSON.stringify({ sessionId, compacted: false, stats }, null, 2),
97
+ );
98
+ return;
99
+ }
100
+ logger.log(
101
+ chalk.gray(
102
+ `Nothing to compact — ${messages.length} message(s), ${stats.originalTokens} tokens (under threshold).`,
103
+ ),
104
+ );
105
+ return;
106
+ }
107
+
108
+ if (!options.dryRun) {
109
+ appendCompactEvent(sessionId, { ...stats, messages: compacted });
110
+ }
111
+
112
+ if (options.json) {
113
+ console.log(
114
+ JSON.stringify(
115
+ { sessionId, dryRun: !!options.dryRun, stats },
116
+ null,
117
+ 2,
118
+ ),
119
+ );
120
+ return;
121
+ }
122
+
123
+ const verb = options.dryRun ? "Would compact" : "Compacted";
124
+ logger.log(
125
+ (options.dryRun ? chalk.cyan : chalk.green)(
126
+ `${options.dryRun ? "" : "✓ "}${verb} ${sessionId}`,
127
+ ),
128
+ );
129
+ logger.log(
130
+ chalk.gray(
131
+ ` ${stats.originalMessages} → ${stats.compressedMessages} messages` +
132
+ `, ${stats.originalTokens} → ${stats.compressedTokens} tokens` +
133
+ ` (saved ${stats.saved}, ${stats.strategy})`,
134
+ ),
135
+ );
136
+ if (options.dryRun) {
137
+ logger.log(
138
+ chalk.gray(` re-run without --dry-run to persist the compaction`),
139
+ );
140
+ } else {
141
+ logger.log(
142
+ chalk.gray(` resume with: cc agent --resume ${sessionId}`),
143
+ );
144
+ }
145
+ } catch (err) {
146
+ logger.error(chalk.red(`compact failed: ${err.message}`));
147
+ process.exitCode = 1;
148
+ }
149
+ });
150
+ }