chainlesschain 0.162.40 → 0.162.41

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 (162) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-CPmKv82o.js → AIOps-Ut7EevnG.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BNDYY7Qd.js → ActionButton-Dv6BlfJg.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-BgCMCOsk.js → Analytics-TQVQuJ7u.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-Dv4oJcqS.js → AppLayout-MSqLm2WK.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-5iV3yrGa.js → Audit-mw81HwVy.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-CHDhnbzF.js → Backup-BQcPWDb1.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-B6reFkra.js → BaseInput-BYo_pwBH.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DwS5YyE2.js → Chat-zi3YUKx2.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-CqXa87Hw.js → ChatBubbleRenderer-DWSm1XJJ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-yiW0M4RE.js → Checkbox-BvC8Erjt.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-DoiVuD_g.js → Codegen-C32vx0OP.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-BVASLexk.js → Col-DMBwmqyZ.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-D6KQ7JoU.js → Community-nDWncmKV.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-Bl9Uhb6v.js → Compact-lIc1HFn8.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-MM31-dba.js → Compliance-D14I_gd2.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-PjU_1ieD.js → Cowork-BiNI-_ZL.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-DorNtPZL.js → Cron-N13sFzHb.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-Bm5ts2Kw.js → Crosschain-Dlnl0-v6.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-7Y3jlFdY.js → DID-CxtYS31I.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-1oE532bG.js → Dashboard-G4UnHlTR.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-hJlOPs0s.js → Dropdown-BazlxFGY.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-BEqJxKaO.js → EmailListRenderer-BrpNdihm.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-BvCGwB6X.js → FamilyGuardDashboard-HD7jbOOR.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-CsXI72e5.js → Federation-Bz8lzAGI.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-Dh9SMul-.js → FormItemContext-CcyzGS00.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-9edWzrtG.js → GenericCardRenderer-DRo9cwmp.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-ZYhNL8Xk.js → Git-B7bn333J.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-BwAdp8QA.js → Governance-DZX9CWAM.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-5C-M1XsH.js → Inference-B3XhsL6W.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-zFAi-zCi.js → KnowledgeGraph-CxFRTlQe.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-BZsEdbgE.js → Logs-xuys6mKH.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-BP6gErRK.js → Marketplace-CXyxv4WU.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-CXVzoLrd.js → McpTools-BzZLQVI3.js} +5 -5
  35. package/src/assets/web-panel/assets/{Memory-BIpChb4-.js → Memory-BANtaBa7.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-B4O7wDT8.js → MobileBridge-BJIwjmxr.js} +2 -2
  37. package/src/assets/web-panel/assets/MobileProjects-B857uSAZ.js +1 -0
  38. package/src/assets/web-panel/assets/{Mtc-BTmEyTM5.js → Mtc-Cn7ceFEz.js} +6 -6
  39. package/src/assets/web-panel/assets/{MtcAudit-CsbG9LlV.js → MtcAudit-B0zE978G.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-CL8yoGon.js → Multisig-CQFT0wXW.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-C2cIlIp_.js → NLProgramming-DSxKdVY-.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-7aBk_n_M.js → Notes-DtlTfam8.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-BuhQk4rJ.js → NotificationSettings-CHQwayAg.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-mqMFZu0x.js → OrderTableRenderer-Brpmzh9n.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-CAdq-170.js → Organization-nF_tzZDT.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow--Xn0E787.js → Overflow-CgCSf_PH.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DYt3YAXI.js → P2P-Bvn46bLY.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-Bgb_v8WN.js → PdhVaultBrowser-Bzl9k7Gj.js} +5 -5
  49. package/src/assets/web-panel/assets/{Permissions-DoFlmoaW.js → Permissions-Dmezbuo8.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-C-FJB3a0.js → PersonalDataHub-lCKRxwZr.js} +2 -2
  51. package/src/assets/web-panel/assets/{Pipeline-3bL2RzzL.js → Pipeline-DDCGm9PA.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-c4igYUCF.js → Privacy-Cgu18Kjl.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-C0QS1UPR.js → ProjectInit-CkF1AeRY.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-CkYC0xkE.js → ProjectSettings-D0Q-orz1.js} +2 -2
  55. package/src/assets/web-panel/assets/Projects-KfGELrSY.js +1 -0
  56. package/src/assets/web-panel/assets/{Providers-41NySsLt.js → Providers-BACLV0z8.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-DHq9pD7z.js → QuickAsk-CPsZUqDl.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CLjgFPLv.js → Recommend-5jX0OI1-.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-EIrgErm3.js → Reputation-5JKv54z0.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-GAvKzKH7.js → Row-DLiTF5LY.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-CYCNsVmD.js → RssFeed-CFdGmCKW.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-DWOE32k8.js → Search-BjIOnmA7.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-Dgh8Jevn.js → Security-BujPqQSo.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-BxdgP67N.js → Services-ChciPnMu.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-D-xT4ZkA.js → Skeleton-Cwswp1Jv.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-BKN4lfSa.js → Skills-CtwR4vJV.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla--N1TudpS.js → Sla-pRIevich.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-B0vfJpEh.js → SpeechSettings-BRqB28Ai.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-BuBAbPAh.js → SyncSettings-BYyj58_h.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-DTLpT48U.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-DI2giLgc.js → Templates-Bbz_h7oW.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-BiTWvm0g.js → Tenant-D-H4E3cu.js} +1 -1
  73. package/src/assets/web-panel/assets/{Terminal-vV6AWGDi.js → Terminal-CLLi0-lV.js} +2 -2
  74. package/src/assets/web-panel/assets/{TimelineRenderer-BmgzKdAp.js → TimelineRenderer-BKI6eG0k.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-Nvupdm6p.js → Tokens-rsE_yDjM.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-DRfR77WJ.js → Trigger-8TpwuTGk.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-De0Jal_6.js → Trust-sMtZkHPs.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-Dzo4-VAM.js → UkeySign-BAy2bAdG.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-hg2ytiJB.js → VideoEditing-CBeR_DYK.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet--bU5-gRh.js → Wallet-BymDnBcq.js} +4 -4
  81. package/src/assets/web-panel/assets/{WebAuthn-DZptt-PV.js → WebAuthn-DQIjmqNz.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-Dy9223bY.js → WorkflowEditor-Cj7PB73f.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-DaxGeI9w.js → chat-DYnGj4vi.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-Cu2VEci3.js → colors-qOLKZNvN.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-CGolhyJq.js → compact-item-BpjCLPcW.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-DY7EFhkD.js → createContext-CfakUZVQ.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-DgtRXlrj.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-Bpc-NoFN.js → hasIn-C9RW1s7t.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DxXkr-NS.js → index-8Ia91vNV.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-DldaToUA.js → index-B4kS312z.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-CBSk_VrT.js → index-BE67I0SW.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-Bz83ngs0.js → index-BFOSDeeo.js} +1 -1
  93. package/src/assets/web-panel/assets/{index-1D4sfByw.js → index-BIz-pX0k.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DNX81oSR.js → index-BJoWi1aR.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-D63ObMdQ.js → index-B_K0YtG2.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DpRSzAFl.js → index-BdR8XRyF.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-eF9RV_4c.js → index-BfyRXPyV.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CJFYF8F9.js → index-Bl5LBZJM.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-CFAnEzRW.js → index-BlxRICmz.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-BTvwiqJE.js → index-BxiHBsfU.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-Ciw5-X1B.js → index-C2S1hUWG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C0_zeYnx.js → index-CEHyZ77C.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DfKmAEtE.js → index-CJZ2noI2.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-lfP8sdzB.js → index-COYEuArt.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-C-Hkl_2G.js → index-CVZTLSL1.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DAov-rJR.js → index-CbnJ6FsO.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CaKXhpEu.js → index-CvWFTG56.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-rkm7dHwG.js → index-D-RzTqlR.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CGqeHu_F.js → index-DA80prWe.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-BjfxHEmX.js → index-DAjszh8P.js} +1 -1
  111. package/src/assets/web-panel/assets/index-DIGTMmnW.js +1 -0
  112. package/src/assets/web-panel/assets/{index-BRAgl2J_.js → index-DQvVYNoJ.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-D0GN5tdM.js → index-DSWdpR3c.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-DexYD87j.js → index-DadPmrxI.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BP9P6chP.js → index-DgMJagCq.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-Bn5gM9Oy.js → index-DkmLJFE_.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-C2RpsAiO.js → index-DzXYG5YJ.js} +1 -1
  118. package/src/assets/web-panel/assets/index-Ef5jERRW.js +1 -0
  119. package/src/assets/web-panel/assets/{index-DElatOQ0.js → index-JkOMWGMX.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-oJQgRCrR.js → index-T3bIqK_p.js} +3 -3
  121. package/src/assets/web-panel/assets/{index-RumxOD0S.js → index-UiiqS5k2.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-BQ2z6Ky5.js → index-VYIJmPvJ.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DZ4Vm8dQ.js → index-ZCtDWP2C.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BlHq81Ow.js → index-f9yoj84i.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-8h9y5S6X.js → index-lPc7EzUi.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-VBRPxZeE.js → index-m9JeDv6B.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CLNqZF55.js → index-qf0fAus7.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-CkJZfCo8.js → initDefaultProps-DgsgQr1H.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BerbusV1.js → motion-TeUH7wzx.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-DyRzKPD4.js → move-DdkIeWQx.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-CCdrTUAs.js → omit-BH_PH6HT.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-mVDeZx2m.js → pickAttrs-CllCh-Nl.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-Bb_-Fs_o.js → placementArrow-BCjE2AzM.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-C6TMj1R_.js → responsiveObserve-BAVGAvRQ.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-CdCNsy1J.js → slide-D4ZW-Inn.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-Ccxd1rFd.js → statusUtils-j4pxhmKV.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-3IL-yw1V.js → styleChecker-DH2SLtPg.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-CH8DjUHl.js → useFlexGapSupport-CYMMs-_Q.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-Cn9nE2sp.js → useFs-BOX2ddKh.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BPyT0HO7.js → usePersonalDataHub-BwcnN5z_.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Mfm7vy07.js → vnode-Cwalh7Hj.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-CTpAiAE9.js → zoom-B2_q_nbu.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +38 -4
  145. package/src/commands/init.js +31 -0
  146. package/src/commands/mcp.js +57 -0
  147. package/src/commands/memory.js +62 -0
  148. package/src/commands/session.js +70 -0
  149. package/src/lib/agent-core.js +1 -0
  150. package/src/lib/init-ai-refine.js +66 -0
  151. package/src/lib/json-schema-output.js +181 -0
  152. package/src/lib/mcp-serve.js +259 -0
  153. package/src/lib/project-instructions.js +89 -0
  154. package/src/lib/repl-completer.js +9 -3
  155. package/src/lib/repl-rewind.js +107 -0
  156. package/src/repl/agent-repl.js +145 -1
  157. package/src/assets/web-panel/assets/MobileProjects-7VPMoHus.js +0 -1
  158. package/src/assets/web-panel/assets/Projects-Di17SYft.js +0 -1
  159. package/src/assets/web-panel/assets/Tasks-4XugjJ87.js +0 -1
  160. package/src/assets/web-panel/assets/devWarning-DV2BNd59.js +0 -1
  161. package/src/assets/web-panel/assets/index-BZqtTmyG.js +0 -1
  162. package/src/assets/web-panel/assets/index-DUpwdJt9.js +0 -1
@@ -0,0 +1,181 @@
1
+ /**
2
+ * `cc agent -p --json-schema <file>` — structured output for headless runs.
3
+ *
4
+ * The final answer must be JSON that validates against a (subset) JSON
5
+ * Schema; invalid replies are retried with a corrective prompt (up to
6
+ * MAX_ATTEMPTS total). Implemented entirely AROUND runAgentHeadless using its
7
+ * `deps.writeOut` capture seam — the runner itself is untouched: each attempt
8
+ * runs with output captured, the validated JSON is the only thing printed.
9
+ *
10
+ * Validator subset (enough for tool/script contracts, not full draft-2020):
11
+ * type (object/array/string/number/integer/boolean/null), properties,
12
+ * required, items, enum, const, additionalProperties:false. Zero deps.
13
+ */
14
+
15
+ import fsDefault from "fs";
16
+
17
+ export const MAX_ATTEMPTS = 3;
18
+ export const _deps = { fs: fsDefault };
19
+
20
+ /** Validate `value` against the schema subset. Returns error strings ([] = valid). */
21
+ export function validateAgainstSchema(value, schema, path = "$") {
22
+ const errors = [];
23
+ if (!schema || typeof schema !== "object") return errors;
24
+
25
+ const typeOf = (v) =>
26
+ v === null
27
+ ? "null"
28
+ : Array.isArray(v)
29
+ ? "array"
30
+ : typeof v === "number" && Number.isInteger(v)
31
+ ? "integer"
32
+ : typeof v;
33
+
34
+ if (schema.type) {
35
+ const want = Array.isArray(schema.type) ? schema.type : [schema.type];
36
+ const got = typeOf(value);
37
+ const ok = want.some((t) => t === got || (t === "number" && got === "integer"));
38
+ if (!ok) {
39
+ errors.push(`${path}: expected type ${want.join("|")}, got ${got}`);
40
+ return errors; // type mismatch — deeper checks are noise
41
+ }
42
+ }
43
+ if (schema.enum && !schema.enum.some((e) => JSON.stringify(e) === JSON.stringify(value))) {
44
+ errors.push(`${path}: value not in enum [${schema.enum.map((e) => JSON.stringify(e)).join(", ")}]`);
45
+ }
46
+ if (schema.const !== undefined && JSON.stringify(schema.const) !== JSON.stringify(value)) {
47
+ errors.push(`${path}: must equal const ${JSON.stringify(schema.const)}`);
48
+ }
49
+ if (typeOf(value) === "object" && !Array.isArray(value)) {
50
+ for (const req of schema.required || []) {
51
+ if (!(req in value)) errors.push(`${path}: missing required property "${req}"`);
52
+ }
53
+ const props = schema.properties || {};
54
+ for (const [k, v] of Object.entries(value)) {
55
+ if (props[k]) {
56
+ errors.push(...validateAgainstSchema(v, props[k], `${path}.${k}`));
57
+ } else if (schema.additionalProperties === false) {
58
+ errors.push(`${path}: unexpected property "${k}"`);
59
+ }
60
+ }
61
+ }
62
+ if (Array.isArray(value) && schema.items) {
63
+ value.forEach((item, i) => {
64
+ errors.push(...validateAgainstSchema(item, schema.items, `${path}[${i}]`));
65
+ });
66
+ }
67
+ return errors;
68
+ }
69
+
70
+ /** Pull a JSON payload out of an LLM reply (bare, fenced, or embedded). */
71
+ export function extractJsonPayload(text) {
72
+ const raw = String(text || "").trim();
73
+ const tries = [];
74
+ tries.push(raw);
75
+ const fence = /```(?:json)?\s*([\s\S]*?)```/i.exec(raw);
76
+ if (fence) tries.push(fence[1].trim());
77
+ const firstObj = raw.indexOf("{");
78
+ const lastObj = raw.lastIndexOf("}");
79
+ if (firstObj !== -1 && lastObj > firstObj) tries.push(raw.slice(firstObj, lastObj + 1));
80
+ const firstArr = raw.indexOf("[");
81
+ const lastArr = raw.lastIndexOf("]");
82
+ if (firstArr !== -1 && lastArr > firstArr) tries.push(raw.slice(firstArr, lastArr + 1));
83
+ for (const candidate of tries) {
84
+ if (!candidate) continue;
85
+ try {
86
+ return { ok: true, value: JSON.parse(candidate) };
87
+ } catch {
88
+ /* next candidate */
89
+ }
90
+ }
91
+ return { ok: false, error: "reply contains no parseable JSON" };
92
+ }
93
+
94
+ export function buildSchemaInstruction(schema) {
95
+ return [
96
+ "OUTPUT CONTRACT: your FINAL reply must be ONLY a JSON value (no prose, no markdown fences) that validates against this JSON Schema:",
97
+ JSON.stringify(schema),
98
+ ].join("\n");
99
+ }
100
+
101
+ export function buildRetryPrompt(originalPrompt, raw, errors) {
102
+ return [
103
+ originalPrompt,
104
+ "",
105
+ "Your previous reply failed JSON Schema validation:",
106
+ ...errors.slice(0, 10).map((e) => `- ${e}`),
107
+ "",
108
+ `Previous reply (for reference): ${String(raw).slice(0, 2000)}`,
109
+ "",
110
+ "Reply again with ONLY the corrected JSON.",
111
+ ].join("\n");
112
+ }
113
+
114
+ /**
115
+ * Run a headless turn constrained to a schema, retrying on validation
116
+ * failure. Prints the validated JSON to writeOut; returns the exit code.
117
+ *
118
+ * @param {object} cfg { schemaFile|schema, baseOptions, runHeadless,
119
+ * maxAttempts?, writeOut?, writeErr?, deps? }
120
+ */
121
+ export async function runJsonSchemaConstrained(cfg = {}) {
122
+ const fs = cfg.deps?.fs || _deps.fs;
123
+ const writeOut = cfg.writeOut || ((s) => process.stdout.write(s));
124
+ const writeErr = cfg.writeErr || ((s) => process.stderr.write(s));
125
+ const maxAttempts = cfg.maxAttempts || MAX_ATTEMPTS;
126
+
127
+ const schema =
128
+ cfg.schema || JSON.parse(fs.readFileSync(cfg.schemaFile, "utf-8"));
129
+ const instruction = buildSchemaInstruction(schema);
130
+ const base = cfg.baseOptions || {};
131
+
132
+ let prompt = base.prompt;
133
+ let lastRaw = "";
134
+ let lastErrors = ["no attempts ran"];
135
+
136
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
137
+ let captured = "";
138
+ const outcome = await cfg.runHeadless(
139
+ {
140
+ ...base,
141
+ prompt,
142
+ outputFormat: "text",
143
+ appendSystemPrompt: [base.appendSystemPrompt, instruction]
144
+ .filter(Boolean)
145
+ .join("\n\n"),
146
+ },
147
+ {
148
+ writeOut: (s) => {
149
+ captured += s;
150
+ },
151
+ writeErr,
152
+ },
153
+ );
154
+ const raw = String(outcome?.result ?? captured ?? "").trim() || captured.trim();
155
+ lastRaw = raw;
156
+ const parsed = extractJsonPayload(raw);
157
+ if (parsed.ok) {
158
+ const errors = validateAgainstSchema(parsed.value, schema);
159
+ if (errors.length === 0) {
160
+ writeOut(`${JSON.stringify(parsed.value, null, 2)}\n`);
161
+ return 0;
162
+ }
163
+ lastErrors = errors;
164
+ } else {
165
+ lastErrors = [parsed.error];
166
+ }
167
+ if (attempt < maxAttempts) {
168
+ writeErr(
169
+ `--json-schema: attempt ${attempt} failed validation (${lastErrors.length} error(s)) — retrying…\n`,
170
+ );
171
+ prompt = buildRetryPrompt(base.prompt, raw, lastErrors);
172
+ }
173
+ }
174
+
175
+ writeErr(
176
+ `--json-schema: reply failed validation after ${maxAttempts} attempts:\n${lastErrors
177
+ .map((e) => ` - ${e}`)
178
+ .join("\n")}\nLast reply:\n${lastRaw.slice(0, 1000)}\n`,
179
+ );
180
+ return 1;
181
+ }
@@ -0,0 +1,259 @@
1
+ /**
2
+ * `cc mcp serve` — expose cc's local file tools as an MCP server so OTHER
3
+ * MCP clients (Claude Desktop, another cc, any Streamable-HTTP client) can
4
+ * use this machine's workspace. Claude-Code `claude mcp serve` parity.
5
+ *
6
+ * Protocol: Streamable-HTTP MCP, same shape the IDE-bridge work verified
7
+ * against the real CLI MCPClient — every request is POST JSON-RPC answered
8
+ * with application/json (no persistent SSE GET needed): `initialize`,
9
+ * `notifications/initialized`, `tools/list`, `tools/call`; tool failures are
10
+ * `isError` results, transport failures JSON-RPC errors.
11
+ *
12
+ * Security: tools are CONFINED to the serve root (path resolves inside root
13
+ * or the call fails), Bearer-token auth is on by default (random token,
14
+ * printed once), `--read-only` drops write_file. Zero npm deps (node http).
15
+ */
16
+
17
+ import http from "http";
18
+ import fsDefault from "fs";
19
+ import pathDefault from "path";
20
+ import { randomBytes } from "crypto";
21
+
22
+ export const MAX_READ_BYTES = 200 * 1024;
23
+ export const MAX_LIST_ENTRIES = 500;
24
+ export const MAX_SEARCH_RESULTS = 200;
25
+ export const MAX_SEARCH_ENTRIES = 50_000;
26
+ const SKIP_DIRS = new Set(["node_modules", ".git", "dist", "build"]);
27
+
28
+ export const _deps = { fs: fsDefault, path: pathDefault };
29
+
30
+ /** Resolve `rel` inside `root`; throws on escape (.. traversal, abs paths out). */
31
+ export function confine(root, rel, deps = _deps) {
32
+ const abs = deps.path.resolve(root, rel || ".");
33
+ const normRoot = deps.path.resolve(root);
34
+ if (abs !== normRoot && !abs.startsWith(normRoot + deps.path.sep)) {
35
+ throw new Error(`path escapes serve root: ${rel}`);
36
+ }
37
+ return abs;
38
+ }
39
+
40
+ function ok(text) {
41
+ return { content: [{ type: "text", text: String(text) }] };
42
+ }
43
+ function fail(message) {
44
+ return { content: [{ type: "text", text: String(message) }], isError: true };
45
+ }
46
+
47
+ /** Build the tool table (name → {description, inputSchema, handler}). */
48
+ export function buildTools({ root, readOnly = false, deps = _deps }) {
49
+ const fs = deps.fs;
50
+ const tools = {
51
+ read_file: {
52
+ description: `Read a UTF-8 file under the serve root (${MAX_READ_BYTES} byte cap)`,
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: { path: { type: "string" } },
56
+ required: ["path"],
57
+ },
58
+ handler: ({ path: rel }) => {
59
+ const abs = confine(root, rel, deps);
60
+ const buf = fs.readFileSync(abs);
61
+ const truncated = buf.length > MAX_READ_BYTES;
62
+ const text = (truncated ? buf.slice(0, MAX_READ_BYTES) : buf).toString(
63
+ "utf-8",
64
+ );
65
+ return ok(truncated ? `${text}\n… [truncated ${buf.length} bytes]` : text);
66
+ },
67
+ },
68
+ list_dir: {
69
+ description: "List a directory under the serve root (dirs get trailing /)",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: { path: { type: "string" } },
73
+ },
74
+ handler: ({ path: rel } = {}) => {
75
+ const abs = confine(root, rel || ".", deps);
76
+ const entries = fs
77
+ .readdirSync(abs, { withFileTypes: true })
78
+ .slice(0, MAX_LIST_ENTRIES)
79
+ .map((e) => (e.isDirectory() ? `${e.name}/` : e.name));
80
+ return ok(entries.join("\n"));
81
+ },
82
+ },
83
+ search_files: {
84
+ description:
85
+ "Find files under the serve root whose RELATIVE PATH contains the query (case-insensitive, bounded walk)",
86
+ inputSchema: {
87
+ type: "object",
88
+ properties: {
89
+ query: { type: "string" },
90
+ dir: { type: "string" },
91
+ },
92
+ required: ["query"],
93
+ },
94
+ handler: ({ query, dir } = {}) => {
95
+ const base = confine(root, dir || ".", deps);
96
+ const q = String(query).toLowerCase();
97
+ const hits = [];
98
+ let seen = 0;
99
+ const walk = (d) => {
100
+ if (hits.length >= MAX_SEARCH_RESULTS || seen >= MAX_SEARCH_ENTRIES)
101
+ return;
102
+ let list;
103
+ try {
104
+ list = fs.readdirSync(d, { withFileTypes: true });
105
+ } catch {
106
+ return;
107
+ }
108
+ for (const e of list) {
109
+ if (hits.length >= MAX_SEARCH_RESULTS || ++seen >= MAX_SEARCH_ENTRIES)
110
+ return;
111
+ const abs = deps.path.join(d, e.name);
112
+ if (e.isDirectory()) {
113
+ if (!SKIP_DIRS.has(e.name) && !e.name.startsWith("."))
114
+ walk(abs);
115
+ } else {
116
+ const rel = deps.path.relative(root, abs).replace(/\\/g, "/");
117
+ if (rel.toLowerCase().includes(q)) hits.push(rel);
118
+ }
119
+ }
120
+ };
121
+ walk(base);
122
+ return ok(hits.join("\n") || "(no matches)");
123
+ },
124
+ },
125
+ };
126
+ if (!readOnly) {
127
+ tools.write_file = {
128
+ description: "Write a UTF-8 file under the serve root (creates parent dirs)",
129
+ inputSchema: {
130
+ type: "object",
131
+ properties: {
132
+ path: { type: "string" },
133
+ content: { type: "string" },
134
+ },
135
+ required: ["path", "content"],
136
+ },
137
+ handler: ({ path: rel, content }) => {
138
+ const abs = confine(root, rel, deps);
139
+ fs.mkdirSync(deps.path.dirname(abs), { recursive: true });
140
+ fs.writeFileSync(abs, String(content), "utf-8");
141
+ return ok(`wrote ${Buffer.byteLength(String(content))} bytes to ${rel}`);
142
+ },
143
+ };
144
+ }
145
+ return tools;
146
+ }
147
+
148
+ function rpcResult(id, result) {
149
+ return JSON.stringify({ jsonrpc: "2.0", id, result });
150
+ }
151
+ function rpcError(id, code, message) {
152
+ return JSON.stringify({ jsonrpc: "2.0", id, error: { code, message } });
153
+ }
154
+
155
+ /**
156
+ * Start the server. Returns { server, port, token, url, close() }.
157
+ *
158
+ * @param {object} opts { root, port=0, token (null → random, false → no auth),
159
+ * readOnly, deps }
160
+ */
161
+ export function startMcpServe(opts = {}) {
162
+ const deps = { ..._deps, ...(opts.deps || {}) };
163
+ const root = deps.path.resolve(opts.root || process.cwd());
164
+ const readOnly = Boolean(opts.readOnly);
165
+ const token =
166
+ opts.token === false
167
+ ? null
168
+ : opts.token || randomBytes(16).toString("hex");
169
+ const tools = buildTools({ root, readOnly, deps });
170
+
171
+ const server = http.createServer((req, res) => {
172
+ const send = (status, body) => {
173
+ res.writeHead(status, { "Content-Type": "application/json" });
174
+ res.end(body);
175
+ };
176
+ if (req.method !== "POST") {
177
+ return send(405, rpcError(null, -32600, "POST only"));
178
+ }
179
+ if (token) {
180
+ const auth = req.headers.authorization || "";
181
+ if (auth !== `Bearer ${token}`) {
182
+ return send(401, rpcError(null, -32001, "unauthorized"));
183
+ }
184
+ }
185
+ let raw = "";
186
+ req.on("data", (c) => {
187
+ raw += c;
188
+ });
189
+ req.on("end", () => {
190
+ let msg;
191
+ try {
192
+ msg = JSON.parse(raw);
193
+ } catch {
194
+ return send(400, rpcError(null, -32700, "parse error"));
195
+ }
196
+ const { id, method, params } = msg || {};
197
+ try {
198
+ if (method === "initialize") {
199
+ return send(
200
+ 200,
201
+ rpcResult(id, {
202
+ protocolVersion: params?.protocolVersion || "2025-03-26",
203
+ capabilities: { tools: {} },
204
+ serverInfo: { name: "cc-mcp-serve", version: "1.0.0" },
205
+ }),
206
+ );
207
+ }
208
+ if (method === "notifications/initialized") {
209
+ res.writeHead(202);
210
+ return res.end();
211
+ }
212
+ if (method === "tools/list") {
213
+ return send(
214
+ 200,
215
+ rpcResult(id, {
216
+ tools: Object.entries(tools).map(([name, t]) => ({
217
+ name,
218
+ description: t.description,
219
+ inputSchema: t.inputSchema,
220
+ })),
221
+ }),
222
+ );
223
+ }
224
+ if (method === "tools/call") {
225
+ const tool = tools[params?.name];
226
+ if (!tool) {
227
+ return send(200, rpcResult(id, fail(`unknown tool: ${params?.name}`)));
228
+ }
229
+ let result;
230
+ try {
231
+ result = tool.handler(params?.arguments || {});
232
+ } catch (err) {
233
+ result = fail(err.message);
234
+ }
235
+ return send(200, rpcResult(id, result));
236
+ }
237
+ return send(200, rpcError(id, -32601, `method not found: ${method}`));
238
+ } catch (err) {
239
+ return send(500, rpcError(id, -32603, err.message));
240
+ }
241
+ });
242
+ });
243
+
244
+ return new Promise((resolve, reject) => {
245
+ server.on("error", reject);
246
+ server.listen(opts.port || 0, "127.0.0.1", () => {
247
+ const port = server.address().port;
248
+ resolve({
249
+ server,
250
+ port,
251
+ token,
252
+ root,
253
+ readOnly,
254
+ url: `http://127.0.0.1:${port}/mcp`,
255
+ close: () => new Promise((r) => server.close(r)),
256
+ });
257
+ });
258
+ });
259
+ }
@@ -136,10 +136,88 @@ export function findInstructionFiles(opts = {}) {
136
136
  // Template-scaffolded project rules (`cc init -t` writes these) join the
137
137
  // chain too, so scaffold-flow and memory-flow projects both feed the agent.
138
138
  push(path.join(d, ".chainlesschain", "rules.md"), "rules");
139
+ // Path-scoped rule files (`.claude/rules/*.md`, YAML frontmatter `paths:`
140
+ // globs). Glob filtering happens at LOAD time where content is available.
141
+ try {
142
+ const rulesDir = path.join(d, ".claude", "rules");
143
+ for (const f of fs
144
+ .readdirSync(rulesDir)
145
+ .filter((n) => n.endsWith(".md"))
146
+ .sort()) {
147
+ push(path.join(rulesDir, f), "rule");
148
+ }
149
+ } catch {
150
+ /* no rules dir */
151
+ }
139
152
  }
140
153
  return out;
141
154
  }
142
155
 
156
+ /**
157
+ * Parse a rule file's YAML-ish frontmatter (zero-dep): `paths:`/`globs:` as a
158
+ * dash-list or inline value. Returns { globs, body } with frontmatter
159
+ * stripped from body; files without frontmatter pass through unchanged.
160
+ */
161
+ export function parseRuleFrontmatter(text) {
162
+ const str = String(text);
163
+ const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(str);
164
+ if (!m) return { globs: [], body: str };
165
+ const globs = [];
166
+ let inPaths = false;
167
+ const take = (raw) => {
168
+ const v = raw.trim().replace(/^["']|["']$/g, "");
169
+ if (v) globs.push(v);
170
+ };
171
+ for (const rawLine of m[1].split(/\r?\n/)) {
172
+ const line = rawLine.trim();
173
+ const key = /^(paths|globs)\s*:\s*(.*)$/.exec(line);
174
+ if (key) {
175
+ inPaths = true;
176
+ const inline = key[2].trim();
177
+ if (inline) {
178
+ for (const g of inline.startsWith("[")
179
+ ? inline.replace(/^\[|\]$/g, "").split(",")
180
+ : [inline]) {
181
+ take(g);
182
+ }
183
+ }
184
+ continue;
185
+ }
186
+ if (inPaths) {
187
+ const item = /^-\s*(.+)$/.exec(line);
188
+ if (item) take(item[1]);
189
+ else if (line) inPaths = false;
190
+ }
191
+ }
192
+ return { globs, body: str.slice(m[0].length) };
193
+ }
194
+
195
+ /**
196
+ * Does a path-scoped rule apply when the agent runs at `relCwd` (cwd relative
197
+ * to the dir holding `.claude/`)? v1 prefix-overlap semantics: the glob's
198
+ * literal prefix and the cwd must sit on the same path line — running at the
199
+ * project root loads every rule; running inside packages/cli loads rules
200
+ * whose glob prefix is packages/cli plus prefixless globs (star-star
201
+ * patterns). Finer tool-time injection is a later phase (module 99 §5.3).
202
+ */
203
+ export function ruleApplies(globs, relCwd) {
204
+ if (!globs || globs.length === 0) return true;
205
+ const cwd = String(relCwd || "")
206
+ .replace(/\\/g, "/")
207
+ .replace(/^\.\/?/, "");
208
+ if (!cwd) return true; // at the project root every rule is in play
209
+ for (const glob of globs) {
210
+ const g = String(glob).replace(/\\/g, "/");
211
+ const star = g.search(/[*?[]/);
212
+ const prefix = (star === -1 ? g : g.slice(0, star)).replace(/\/+$/, "");
213
+ if (!prefix) return true; // "**/*.js" — applies everywhere
214
+ if (cwd === prefix || cwd.startsWith(`${prefix}/`) || prefix.startsWith(`${cwd}/`)) {
215
+ return true;
216
+ }
217
+ }
218
+ return false;
219
+ }
220
+
143
221
  /**
144
222
  * Collect `@path` import tokens from instruction text, skipping fenced code
145
223
  * blocks (``` / ~~~). Line-level scanning is good enough for memory files,
@@ -212,6 +290,17 @@ export function loadProjectInstructions(opts = {}) {
212
290
  warnings.push(`${abs} — cannot read: ${err.message}`);
213
291
  continue;
214
292
  }
293
+ if (scope === "rule") {
294
+ // <base>/.claude/rules/<file>.md → base is three dirs up.
295
+ const base = path.dirname(path.dirname(path.dirname(abs)));
296
+ const relCwd = path.relative(
297
+ base,
298
+ path.resolve(opts.cwd || process.cwd()),
299
+ );
300
+ const { globs, body } = parseRuleFrontmatter(entry.content);
301
+ if (!ruleApplies(globs, relCwd)) continue; // out of scope for this cwd
302
+ entry = { ...entry, content: body };
303
+ }
215
304
  total += Math.min(entry.bytes, maxFileBytes);
216
305
  out.push({ path: abs, scope, ...entry });
217
306
 
@@ -107,8 +107,12 @@ export function makeAtCompleter(opts = {}) {
107
107
  .map((f) => {
108
108
  const rel = path.relative(cwd, f);
109
109
  // Keep workspace files relative (the natural @ref form);
110
- // out-of-workspace files keep their absolute path.
111
- return rel && !rel.startsWith("..") ? fwd(rel) : fwd(f);
110
+ // out-of-workspace files keep their absolute path. On
111
+ // Windows a cross-drive relative() returns an *absolute*
112
+ // path (no ".." prefix), so isAbsolute must also gate it.
113
+ return rel && !rel.startsWith("..") && !path.isAbsolute(rel)
114
+ ? fwd(rel)
115
+ : fwd(f);
112
116
  })
113
117
  : [];
114
118
  ideFetchedAt = now();
@@ -131,7 +135,9 @@ export function makeAtCompleter(opts = {}) {
131
135
  const slash = /^\/([A-Za-z_-]*)$/.exec(line);
132
136
  if (slash && slashCommands.length) {
133
137
  const pref = `/${slash[1].toLowerCase()}`;
134
- const hits = slashCommands.filter((c) => c.toLowerCase().startsWith(pref));
138
+ const hits = slashCommands.filter((c) =>
139
+ c.toLowerCase().startsWith(pref),
140
+ );
135
141
  return [hits, line];
136
142
  }
137
143
  const at = extractAtPrefix(line);
@@ -0,0 +1,107 @@
1
+ /**
2
+ * REPL conversation rewind — Claude-Code double-Esc parity (v1: conversation
3
+ * state; file state stays on `cc checkpoint restore`, hinted alongside).
4
+ *
5
+ * Pure helpers over the REPL's live `messages` array so the picker logic is
6
+ * unit-testable without readline:
7
+ * - listUserTurns(): newest-first numbered list of user messages
8
+ * - rewindToTurn(): truncate the conversation BACK TO BEFORE turn #n and
9
+ * return the original text so the caller can prefill the input line
10
+ * (edit-and-resend, like Claude Code's rewind).
11
+ *
12
+ * Trigger surfaces (wired in agent-repl): `/rewind` lists, `/rewind <n>`
13
+ * rewinds, and a double-Esc while idle prints the same list as a shortcut.
14
+ */
15
+
16
+ export const DEFAULT_LIST_LIMIT = 10;
17
+ export const PREVIEW_CHARS = 60;
18
+
19
+ function previewOf(content) {
20
+ const text =
21
+ typeof content === "string" ? content : JSON.stringify(content || "");
22
+ const flat = text.replace(/\s+/g, " ").trim();
23
+ return flat.length > PREVIEW_CHARS
24
+ ? `${flat.slice(0, PREVIEW_CHARS)}…`
25
+ : flat;
26
+ }
27
+
28
+ /**
29
+ * Newest-first user turns.
30
+ * @returns {Array<{n:number, index:number, preview:string, content:any}>}
31
+ * n is the 1-based pick number (1 = most recent user message).
32
+ */
33
+ export function listUserTurns(messages, { limit = DEFAULT_LIST_LIMIT } = {}) {
34
+ const turns = [];
35
+ for (let i = (messages || []).length - 1; i >= 0; i--) {
36
+ const m = messages[i];
37
+ if (!m || m.role !== "user") continue;
38
+ turns.push({
39
+ n: turns.length + 1,
40
+ index: i,
41
+ preview: previewOf(m.content),
42
+ content: m.content,
43
+ });
44
+ if (turns.length >= limit) break;
45
+ }
46
+ return turns;
47
+ }
48
+
49
+ /**
50
+ * Rewind the conversation to BEFORE the picked user turn (mutates `messages`
51
+ * in place — everything from that user message onward is dropped).
52
+ *
53
+ * @param {Array} messages live conversation array
54
+ * @param {number} n 1-based pick from listUserTurns
55
+ * @returns {{ removed:number, text:string|null }|null} null on bad pick;
56
+ * `text` is the original user text when it was a plain string
57
+ * (caller prefills the input line with it).
58
+ */
59
+ export function rewindToTurn(messages, n) {
60
+ const turns = listUserTurns(messages, { limit: 1000 });
61
+ const turn = turns.find((t) => t.n === Number(n));
62
+ if (!turn) return null;
63
+ const removed = messages.length - turn.index;
64
+ messages.splice(turn.index);
65
+ return {
66
+ removed,
67
+ text: typeof turn.content === "string" ? turn.content : null,
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Offline extractive recap for a resumed conversation ("where were we") —
73
+ * no LLM call: turn counts + last ask + last reply previews.
74
+ * @returns {string[]|null} lines to print, or null when nothing to recap.
75
+ */
76
+ export function buildResumeRecap(messages, { previewChars = 160 } = {}) {
77
+ const list = messages || [];
78
+ const flat = (c) =>
79
+ (typeof c === "string" ? c : JSON.stringify(c || ""))
80
+ .replace(/\s+/g, " ")
81
+ .trim();
82
+ const cap = (s) =>
83
+ s.length > previewChars ? `${s.slice(0, previewChars)}…` : s;
84
+ const lastOf = (role) => {
85
+ for (let i = list.length - 1; i >= 0; i--) {
86
+ if (list[i]?.role === role) return list[i].content;
87
+ }
88
+ return null;
89
+ };
90
+ const users = list.filter((m) => m?.role === "user").length;
91
+ const assistants = list.filter((m) => m?.role === "assistant").length;
92
+ if (!users && !assistants) return null;
93
+ const lines = [`${users} user / ${assistants} assistant turns`];
94
+ const lu = lastOf("user");
95
+ if (lu) lines.push(`last ask : ${cap(flat(lu))}`);
96
+ const la = lastOf("assistant");
97
+ if (la) lines.push(`last reply: ${cap(flat(la))}`);
98
+ return lines;
99
+ }
100
+
101
+ /** Render the picker list (shared by /rewind and double-Esc). */
102
+ export function renderTurnList(turns) {
103
+ if (!turns.length) return " (no user turns yet)";
104
+ return turns
105
+ .map((t) => ` ${String(t.n).padStart(2)}. ${t.preview}`)
106
+ .join("\n");
107
+ }