@armadra/agent 0.0.0-stage → 0.2.1

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 (407) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +426 -2
  4. package/dist/agent/agent.d.ts +56 -0
  5. package/dist/agent/agent.js +129 -0
  6. package/dist/agent/loop.d.ts +51 -0
  7. package/dist/agent/loop.js +158 -0
  8. package/dist/agent/queue.d.ts +25 -0
  9. package/dist/agent/queue.js +55 -0
  10. package/dist/agent/retry.d.ts +22 -0
  11. package/dist/agent/retry.js +92 -0
  12. package/dist/agent/schema.d.ts +27 -0
  13. package/dist/agent/schema.js +186 -0
  14. package/dist/agent/session-cache.d.ts +100 -0
  15. package/dist/agent/session-cache.js +511 -0
  16. package/dist/agent/session-compaction.d.ts +49 -0
  17. package/dist/agent/session-compaction.js +251 -0
  18. package/dist/agent/session-core.d.ts +103 -0
  19. package/dist/agent/session-core.js +7 -0
  20. package/dist/agent/session-run.d.ts +55 -0
  21. package/dist/agent/session-run.js +246 -0
  22. package/dist/agent/session-state.d.ts +39 -0
  23. package/dist/agent/session-state.js +118 -0
  24. package/dist/agent/session-subagent.d.ts +54 -0
  25. package/dist/agent/session-subagent.js +157 -0
  26. package/dist/agent/session-sync.d.ts +20 -0
  27. package/dist/agent/session-sync.js +104 -0
  28. package/dist/agent/session-tools.d.ts +24 -0
  29. package/dist/agent/session-tools.js +277 -0
  30. package/dist/agent/session.d.ts +117 -0
  31. package/dist/agent/session.js +484 -0
  32. package/dist/agent/system-prompt.d.ts +49 -0
  33. package/dist/agent/system-prompt.js +121 -0
  34. package/dist/agent/tool-runner.d.ts +64 -0
  35. package/dist/agent/tool-runner.js +309 -0
  36. package/dist/agent/transform.d.ts +26 -0
  37. package/dist/agent/transform.js +171 -0
  38. package/dist/agent/types.d.ts +378 -0
  39. package/dist/agent/types.js +21 -0
  40. package/dist/ai/apis/anthropic-messages.d.ts +26 -0
  41. package/dist/ai/apis/anthropic-messages.js +214 -0
  42. package/dist/ai/apis/anthropic-request.d.ts +42 -0
  43. package/dist/ai/apis/anthropic-request.js +282 -0
  44. package/dist/ai/apis/api.d.ts +28 -0
  45. package/dist/ai/apis/api.js +102 -0
  46. package/dist/ai/apis/cache-params.d.ts +48 -0
  47. package/dist/ai/apis/cache-params.js +163 -0
  48. package/dist/ai/apis/google-generative-ai.d.ts +32 -0
  49. package/dist/ai/apis/google-generative-ai.js +249 -0
  50. package/dist/ai/apis/google-request.d.ts +40 -0
  51. package/dist/ai/apis/google-request.js +255 -0
  52. package/dist/ai/apis/openai-compat.d.ts +35 -0
  53. package/dist/ai/apis/openai-compat.js +142 -0
  54. package/dist/ai/apis/openai-completions.d.ts +28 -0
  55. package/dist/ai/apis/openai-completions.js +246 -0
  56. package/dist/ai/apis/openai-request.d.ts +35 -0
  57. package/dist/ai/apis/openai-request.js +303 -0
  58. package/dist/ai/apis/openai-responses-request.d.ts +52 -0
  59. package/dist/ai/apis/openai-responses-request.js +266 -0
  60. package/dist/ai/apis/openai-responses.d.ts +28 -0
  61. package/dist/ai/apis/openai-responses.js +303 -0
  62. package/dist/ai/apis/shared.d.ts +56 -0
  63. package/dist/ai/apis/shared.js +179 -0
  64. package/dist/ai/cache/economics.d.ts +24 -0
  65. package/dist/ai/cache/economics.js +69 -0
  66. package/dist/ai/cache/fingerprint.d.ts +17 -0
  67. package/dist/ai/cache/fingerprint.js +39 -0
  68. package/dist/ai/cache/miss.d.ts +49 -0
  69. package/dist/ai/cache/miss.js +96 -0
  70. package/dist/ai/cache/reporting.d.ts +39 -0
  71. package/dist/ai/cache/reporting.js +102 -0
  72. package/dist/ai/cache/types.d.ts +94 -0
  73. package/dist/ai/cache/types.js +19 -0
  74. package/dist/ai/cache/warmer.d.ts +94 -0
  75. package/dist/ai/cache/warmer.js +224 -0
  76. package/dist/ai/context.d.ts +35 -0
  77. package/dist/ai/context.js +114 -0
  78. package/dist/ai/cost.d.ts +21 -0
  79. package/dist/ai/cost.js +60 -0
  80. package/dist/ai/event-stream.d.ts +33 -0
  81. package/dist/ai/event-stream.js +101 -0
  82. package/dist/ai/fake/fake-provider.d.ts +68 -0
  83. package/dist/ai/fake/fake-provider.js +278 -0
  84. package/dist/ai/fake/fake-script.d.ts +69 -0
  85. package/dist/ai/fake/fake-script.js +142 -0
  86. package/dist/ai/http.d.ts +54 -0
  87. package/dist/ai/http.js +212 -0
  88. package/dist/ai/json-partial.d.ts +17 -0
  89. package/dist/ai/json-partial.js +243 -0
  90. package/dist/ai/overflow.d.ts +22 -0
  91. package/dist/ai/overflow.js +61 -0
  92. package/dist/ai/providers/auth.d.ts +71 -0
  93. package/dist/ai/providers/auth.js +202 -0
  94. package/dist/ai/providers/builtin.d.ts +23 -0
  95. package/dist/ai/providers/builtin.js +148 -0
  96. package/dist/ai/providers/catalog-data.d.ts +5 -0
  97. package/dist/ai/providers/catalog-data.js +19 -0
  98. package/dist/ai/providers/catalog.d.ts +28 -0
  99. package/dist/ai/providers/catalog.js +166 -0
  100. package/dist/ai/providers/registry.d.ts +83 -0
  101. package/dist/ai/providers/registry.js +305 -0
  102. package/dist/ai/sse.d.ts +32 -0
  103. package/dist/ai/sse.js +140 -0
  104. package/dist/ai/thinking.d.ts +42 -0
  105. package/dist/ai/thinking.js +101 -0
  106. package/dist/ai/types.d.ts +428 -0
  107. package/dist/ai/types.js +19 -0
  108. package/dist/bundle/ama-sandbox.cjs +324 -0
  109. package/dist/bundle/ama.cjs +25770 -0
  110. package/dist/bundle.d.ts +9 -0
  111. package/dist/bundle.js +15 -0
  112. package/dist/cli/args.d.ts +82 -0
  113. package/dist/cli/args.js +381 -0
  114. package/dist/cli/bootstrap.d.ts +18 -0
  115. package/dist/cli/bootstrap.js +398 -0
  116. package/dist/cli/compose-providers.d.ts +37 -0
  117. package/dist/cli/compose-providers.js +104 -0
  118. package/dist/cli/compose-session.d.ts +69 -0
  119. package/dist/cli/compose-session.js +370 -0
  120. package/dist/cli/compose-store.d.ts +24 -0
  121. package/dist/cli/compose-store.js +125 -0
  122. package/dist/cli/compose.d.ts +106 -0
  123. package/dist/cli/compose.js +206 -0
  124. package/dist/cli/default-model.d.ts +16 -0
  125. package/dist/cli/default-model.js +24 -0
  126. package/dist/cli/deps.d.ts +205 -0
  127. package/dist/cli/deps.js +12 -0
  128. package/dist/cli/exit-codes.d.ts +29 -0
  129. package/dist/cli/exit-codes.js +40 -0
  130. package/dist/cli/main.d.ts +26 -0
  131. package/dist/cli/main.js +165 -0
  132. package/dist/cli/runtime.d.ts +90 -0
  133. package/dist/cli/runtime.js +8 -0
  134. package/dist/cli/startup-screen.d.ts +15 -0
  135. package/dist/cli/startup-screen.js +56 -0
  136. package/dist/cli/startup-steps.d.ts +35 -0
  137. package/dist/cli/startup-steps.js +197 -0
  138. package/dist/cli/subcommands/auth.d.ts +12 -0
  139. package/dist/cli/subcommands/auth.js +91 -0
  140. package/dist/cli/subcommands/config.d.ts +44 -0
  141. package/dist/cli/subcommands/config.js +201 -0
  142. package/dist/cli/subcommands/context.d.ts +21 -0
  143. package/dist/cli/subcommands/context.js +41 -0
  144. package/dist/cli/subcommands/doctor.d.ts +11 -0
  145. package/dist/cli/subcommands/doctor.js +246 -0
  146. package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
  147. package/dist/cli/subcommands/models-cache-probe.js +190 -0
  148. package/dist/cli/subcommands/models-discover.d.ts +40 -0
  149. package/dist/cli/subcommands/models-discover.js +204 -0
  150. package/dist/cli/subcommands/models.d.ts +40 -0
  151. package/dist/cli/subcommands/models.js +144 -0
  152. package/dist/cli/subcommands/sessions.d.ts +11 -0
  153. package/dist/cli/subcommands/sessions.js +100 -0
  154. package/dist/codemode/capability.d.ts +37 -0
  155. package/dist/codemode/capability.js +55 -0
  156. package/dist/codemode/declarations.d.ts +44 -0
  157. package/dist/codemode/declarations.js +111 -0
  158. package/dist/codemode/host-side.d.ts +59 -0
  159. package/dist/codemode/host-side.js +253 -0
  160. package/dist/codemode/modes.d.ts +23 -0
  161. package/dist/codemode/modes.js +40 -0
  162. package/dist/codemode/protocol.d.ts +91 -0
  163. package/dist/codemode/protocol.js +114 -0
  164. package/dist/codemode/sandbox-entry.d.ts +25 -0
  165. package/dist/codemode/sandbox-entry.js +323 -0
  166. package/dist/codemode/store.d.ts +26 -0
  167. package/dist/codemode/store.js +53 -0
  168. package/dist/codemode/tool.d.ts +105 -0
  169. package/dist/codemode/tool.js +283 -0
  170. package/dist/compaction/branch-summary.d.ts +30 -0
  171. package/dist/compaction/branch-summary.js +79 -0
  172. package/dist/compaction/breaker.d.ts +32 -0
  173. package/dist/compaction/breaker.js +65 -0
  174. package/dist/compaction/cut-point.d.ts +23 -0
  175. package/dist/compaction/cut-point.js +70 -0
  176. package/dist/compaction/estimate.d.ts +30 -0
  177. package/dist/compaction/estimate.js +114 -0
  178. package/dist/compaction/prune-tier.d.ts +31 -0
  179. package/dist/compaction/prune-tier.js +83 -0
  180. package/dist/compaction/serialize.d.ts +20 -0
  181. package/dist/compaction/serialize.js +115 -0
  182. package/dist/compaction/summarize-tier.d.ts +111 -0
  183. package/dist/compaction/summarize-tier.js +305 -0
  184. package/dist/config/auth-file.d.ts +39 -0
  185. package/dist/config/auth-file.js +92 -0
  186. package/dist/config/context-files.d.ts +30 -0
  187. package/dist/config/context-files.js +77 -0
  188. package/dist/config/load.d.ts +45 -0
  189. package/dist/config/load.js +97 -0
  190. package/dist/config/merge.d.ts +73 -0
  191. package/dist/config/merge.js +248 -0
  192. package/dist/config/paths.d.ts +38 -0
  193. package/dist/config/paths.js +108 -0
  194. package/dist/config/profile.d.ts +27 -0
  195. package/dist/config/profile.js +65 -0
  196. package/dist/config/schema.d.ts +34 -0
  197. package/dist/config/schema.js +413 -0
  198. package/dist/config/trust.d.ts +42 -0
  199. package/dist/config/trust.js +122 -0
  200. package/dist/config/types.d.ts +172 -0
  201. package/dist/config/types.js +27 -0
  202. package/dist/config/write.d.ts +10 -0
  203. package/dist/config/write.js +25 -0
  204. package/dist/errors.d.ts +28 -0
  205. package/dist/errors.js +30 -0
  206. package/dist/hooks/config.d.ts +49 -0
  207. package/dist/hooks/config.js +102 -0
  208. package/dist/hooks/dispatcher.d.ts +41 -0
  209. package/dist/hooks/dispatcher.js +104 -0
  210. package/dist/hooks/matcher.d.ts +22 -0
  211. package/dist/hooks/matcher.js +108 -0
  212. package/dist/hooks/protocol.d.ts +33 -0
  213. package/dist/hooks/protocol.js +240 -0
  214. package/dist/hooks/runner.d.ts +30 -0
  215. package/dist/hooks/runner.js +143 -0
  216. package/dist/hooks/types.d.ts +147 -0
  217. package/dist/hooks/types.js +20 -0
  218. package/dist/host/api-impl.d.ts +75 -0
  219. package/dist/host/api-impl.js +173 -0
  220. package/dist/host/loader.d.ts +31 -0
  221. package/dist/host/loader.js +147 -0
  222. package/dist/host/types.d.ts +169 -0
  223. package/dist/host/types.js +14 -0
  224. package/dist/host.d.ts +5 -0
  225. package/dist/host.js +4 -0
  226. package/dist/index.d.ts +41 -0
  227. package/dist/index.js +28 -0
  228. package/dist/modes/commands-core.d.ts +44 -0
  229. package/dist/modes/commands-core.js +171 -0
  230. package/dist/modes/interactive/approval-dialog.d.ts +48 -0
  231. package/dist/modes/interactive/approval-dialog.js +202 -0
  232. package/dist/modes/interactive/commands.d.ts +41 -0
  233. package/dist/modes/interactive/commands.js +202 -0
  234. package/dist/modes/interactive/completion.d.ts +53 -0
  235. package/dist/modes/interactive/completion.js +157 -0
  236. package/dist/modes/interactive/interactive-mode.d.ts +46 -0
  237. package/dist/modes/interactive/interactive-mode.js +487 -0
  238. package/dist/modes/interactive/key-dispatch.d.ts +29 -0
  239. package/dist/modes/interactive/key-dispatch.js +107 -0
  240. package/dist/modes/interactive/line/line-editor.d.ts +50 -0
  241. package/dist/modes/interactive/line/line-editor.js +232 -0
  242. package/dist/modes/interactive/line/line-mode.d.ts +20 -0
  243. package/dist/modes/interactive/line/line-mode.js +216 -0
  244. package/dist/modes/interactive/line/line-render.d.ts +35 -0
  245. package/dist/modes/interactive/line/line-render.js +171 -0
  246. package/dist/modes/interactive/line/paste-state.d.ts +26 -0
  247. package/dist/modes/interactive/line/paste-state.js +72 -0
  248. package/dist/modes/interactive/message-view.d.ts +77 -0
  249. package/dist/modes/interactive/message-view.js +269 -0
  250. package/dist/modes/interactive/pickers.d.ts +42 -0
  251. package/dist/modes/interactive/pickers.js +115 -0
  252. package/dist/modes/interactive/startup-ui.d.ts +28 -0
  253. package/dist/modes/interactive/startup-ui.js +238 -0
  254. package/dist/modes/interactive/status-bar.d.ts +48 -0
  255. package/dist/modes/interactive/status-bar.js +139 -0
  256. package/dist/modes/interactive/tool-view.d.ts +95 -0
  257. package/dist/modes/interactive/tool-view.js +344 -0
  258. package/dist/modes/print/json-event.d.ts +15 -0
  259. package/dist/modes/print/json-event.js +39 -0
  260. package/dist/modes/print/print-mode.d.ts +15 -0
  261. package/dist/modes/print/print-mode.js +96 -0
  262. package/dist/modes/rpc/commands.d.ts +40 -0
  263. package/dist/modes/rpc/commands.js +258 -0
  264. package/dist/modes/rpc/jsonl.d.ts +16 -0
  265. package/dist/modes/rpc/jsonl.js +55 -0
  266. package/dist/modes/rpc/rpc-mode.d.ts +22 -0
  267. package/dist/modes/rpc/rpc-mode.js +153 -0
  268. package/dist/modes/session-report.d.ts +55 -0
  269. package/dist/modes/session-report.js +278 -0
  270. package/dist/modes/shared.d.ts +13 -0
  271. package/dist/modes/shared.js +52 -0
  272. package/dist/modes/startup-ui-text.d.ts +16 -0
  273. package/dist/modes/startup-ui-text.js +103 -0
  274. package/dist/permissions/broker.d.ts +47 -0
  275. package/dist/permissions/broker.js +107 -0
  276. package/dist/permissions/dangerous.d.ts +47 -0
  277. package/dist/permissions/dangerous.js +370 -0
  278. package/dist/permissions/pipeline.d.ts +51 -0
  279. package/dist/permissions/pipeline.js +181 -0
  280. package/dist/permissions/preview.d.ts +43 -0
  281. package/dist/permissions/preview.js +463 -0
  282. package/dist/permissions/rules.d.ts +58 -0
  283. package/dist/permissions/rules.js +249 -0
  284. package/dist/permissions/types.d.ts +100 -0
  285. package/dist/permissions/types.js +18 -0
  286. package/dist/rpc.d.ts +171 -0
  287. package/dist/rpc.js +13 -0
  288. package/dist/sdk.d.ts +98 -0
  289. package/dist/sdk.js +195 -0
  290. package/dist/session/manager.d.ts +70 -0
  291. package/dist/session/manager.js +278 -0
  292. package/dist/session/migrate.d.ts +19 -0
  293. package/dist/session/migrate.js +53 -0
  294. package/dist/session/projection.d.ts +50 -0
  295. package/dist/session/projection.js +180 -0
  296. package/dist/session/store.d.ts +47 -0
  297. package/dist/session/store.js +210 -0
  298. package/dist/session/tree.d.ts +21 -0
  299. package/dist/session/tree.js +88 -0
  300. package/dist/session/types.d.ts +199 -0
  301. package/dist/session/types.js +18 -0
  302. package/dist/skills/discover.d.ts +60 -0
  303. package/dist/skills/discover.js +160 -0
  304. package/dist/skills/expand.d.ts +35 -0
  305. package/dist/skills/expand.js +46 -0
  306. package/dist/skills/frontmatter.d.ts +27 -0
  307. package/dist/skills/frontmatter.js +157 -0
  308. package/dist/skills/index-prompt.d.ts +13 -0
  309. package/dist/skills/index-prompt.js +42 -0
  310. package/dist/skills/templates.d.ts +52 -0
  311. package/dist/skills/templates.js +177 -0
  312. package/dist/tools/bash.d.ts +49 -0
  313. package/dist/tools/bash.js +222 -0
  314. package/dist/tools/edit-fuzzy.d.ts +36 -0
  315. package/dist/tools/edit-fuzzy.js +129 -0
  316. package/dist/tools/edit.d.ts +38 -0
  317. package/dist/tools/edit.js +241 -0
  318. package/dist/tools/file-mutex.d.ts +8 -0
  319. package/dist/tools/file-mutex.js +29 -0
  320. package/dist/tools/glob.d.ts +44 -0
  321. package/dist/tools/glob.js +217 -0
  322. package/dist/tools/grep.d.ts +39 -0
  323. package/dist/tools/grep.js +179 -0
  324. package/dist/tools/ignore.d.ts +40 -0
  325. package/dist/tools/ignore.js +174 -0
  326. package/dist/tools/ls.d.ts +21 -0
  327. package/dist/tools/ls.js +96 -0
  328. package/dist/tools/output-accumulator.d.ts +48 -0
  329. package/dist/tools/output-accumulator.js +106 -0
  330. package/dist/tools/paths.d.ts +20 -0
  331. package/dist/tools/paths.js +58 -0
  332. package/dist/tools/presets.d.ts +71 -0
  333. package/dist/tools/presets.js +128 -0
  334. package/dist/tools/process-tree.d.ts +35 -0
  335. package/dist/tools/process-tree.js +125 -0
  336. package/dist/tools/read.d.ts +28 -0
  337. package/dist/tools/read.js +181 -0
  338. package/dist/tools/registry.d.ts +43 -0
  339. package/dist/tools/registry.js +106 -0
  340. package/dist/tools/shell.d.ts +38 -0
  341. package/dist/tools/shell.js +91 -0
  342. package/dist/tools/task.d.ts +29 -0
  343. package/dist/tools/task.js +107 -0
  344. package/dist/tools/todo.d.ts +28 -0
  345. package/dist/tools/todo.js +107 -0
  346. package/dist/tools/truncate.d.ts +44 -0
  347. package/dist/tools/truncate.js +175 -0
  348. package/dist/tools/types.d.ts +135 -0
  349. package/dist/tools/types.js +19 -0
  350. package/dist/tools/write.d.ts +20 -0
  351. package/dist/tools/write.js +84 -0
  352. package/dist/tui/ansi.d.ts +29 -0
  353. package/dist/tui/ansi.js +420 -0
  354. package/dist/tui/component.d.ts +38 -0
  355. package/dist/tui/component.js +13 -0
  356. package/dist/tui/components/box.d.ts +26 -0
  357. package/dist/tui/components/box.js +65 -0
  358. package/dist/tui/components/container.d.ts +14 -0
  359. package/dist/tui/components/container.js +39 -0
  360. package/dist/tui/components/editor-buffer.d.ts +67 -0
  361. package/dist/tui/components/editor-buffer.js +300 -0
  362. package/dist/tui/components/editor-paste.d.ts +29 -0
  363. package/dist/tui/components/editor-paste.js +64 -0
  364. package/dist/tui/components/editor.d.ts +114 -0
  365. package/dist/tui/components/editor.js +530 -0
  366. package/dist/tui/components/key-value.d.ts +29 -0
  367. package/dist/tui/components/key-value.js +48 -0
  368. package/dist/tui/components/loader.d.ts +35 -0
  369. package/dist/tui/components/loader.js +71 -0
  370. package/dist/tui/components/markdown.d.ts +90 -0
  371. package/dist/tui/components/markdown.js +462 -0
  372. package/dist/tui/components/meter.d.ts +29 -0
  373. package/dist/tui/components/meter.js +47 -0
  374. package/dist/tui/components/overlay.d.ts +24 -0
  375. package/dist/tui/components/overlay.js +52 -0
  376. package/dist/tui/components/select-list.d.ts +57 -0
  377. package/dist/tui/components/select-list.js +179 -0
  378. package/dist/tui/components/spacer.d.ts +11 -0
  379. package/dist/tui/components/spacer.js +16 -0
  380. package/dist/tui/components/text.d.ts +30 -0
  381. package/dist/tui/components/text.js +65 -0
  382. package/dist/tui/keybindings.d.ts +66 -0
  383. package/dist/tui/keybindings.js +120 -0
  384. package/dist/tui/keys.d.ts +38 -0
  385. package/dist/tui/keys.js +275 -0
  386. package/dist/tui/stdin-buffer.d.ts +41 -0
  387. package/dist/tui/stdin-buffer.js +206 -0
  388. package/dist/tui/terminal.d.ts +79 -0
  389. package/dist/tui/terminal.js +153 -0
  390. package/dist/tui/theme.d.ts +31 -0
  391. package/dist/tui/theme.js +199 -0
  392. package/dist/tui/tui.d.ts +103 -0
  393. package/dist/tui/tui.js +363 -0
  394. package/dist/tui/vt-screen.d.ts +46 -0
  395. package/dist/tui/vt-screen.js +251 -0
  396. package/dist/tui.d.ts +30 -0
  397. package/dist/tui.js +31 -0
  398. package/dist/version.d.ts +8 -0
  399. package/dist/version.js +24 -0
  400. package/docs/codemode.md +102 -0
  401. package/docs/hooks.md +196 -0
  402. package/docs/host-api.md +161 -0
  403. package/docs/providers.md +316 -0
  404. package/docs/rpc.md +253 -0
  405. package/docs/session-format.md +119 -0
  406. package/docs/tui.md +168 -0
  407. package/package.json +101 -4
@@ -0,0 +1,71 @@
1
+ /**
2
+ * 旋转指示器(设计 §12.1、§12.6:运行中显示 Loader 与已用时)。[B4]
3
+ *
4
+ * 帧由定时器推进并调用 `requestRender`;`stop()` 后不再触发渲染。时钟可注入便于测试。
5
+ */
6
+ import { truncateToWidth } from "../ansi.js";
7
+ export const LOADER_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
8
+ export function formatElapsed(ms) {
9
+ const total = Math.max(0, Math.floor(ms / 1000));
10
+ if (total < 60)
11
+ return `${total}s`;
12
+ const m = Math.floor(total / 60);
13
+ const s = total % 60;
14
+ return `${m}m${String(s).padStart(2, "0")}s`;
15
+ }
16
+ export class Loader {
17
+ requestRender;
18
+ options;
19
+ frame = 0;
20
+ timer = null;
21
+ startedAt;
22
+ message;
23
+ now;
24
+ constructor(requestRender, options = {}) {
25
+ this.requestRender = requestRender;
26
+ this.options = options;
27
+ this.now = options.now ?? Date.now;
28
+ this.message = options.message ?? "";
29
+ this.startedAt = this.now();
30
+ }
31
+ get running() {
32
+ return this.timer !== null;
33
+ }
34
+ setMessage(message) {
35
+ this.message = message;
36
+ this.requestRender();
37
+ }
38
+ start() {
39
+ if (this.timer !== null)
40
+ return;
41
+ this.startedAt = this.now();
42
+ this.frame = 0;
43
+ this.timer = setInterval(() => this.tick(), this.options.intervalMs ?? 80);
44
+ this.timer.unref?.();
45
+ this.requestRender();
46
+ }
47
+ stop() {
48
+ if (this.timer === null)
49
+ return;
50
+ clearInterval(this.timer);
51
+ this.timer = null;
52
+ }
53
+ /** 推进一帧(定时器调用;测试可直接调用)。 */
54
+ tick() {
55
+ this.frame = (this.frame + 1) % LOADER_FRAMES.length;
56
+ this.requestRender();
57
+ }
58
+ render(width) {
59
+ const theme = this.options.theme;
60
+ const spinner = LOADER_FRAMES[this.frame];
61
+ const parts = [theme ? theme.fg("accent", spinner) : spinner];
62
+ if (this.message !== "")
63
+ parts.push(this.message);
64
+ if (this.options.showElapsed !== false) {
65
+ const elapsed = `(${formatElapsed(this.now() - this.startedAt)})`;
66
+ parts.push(theme ? theme.fg("dim", elapsed) : elapsed);
67
+ }
68
+ return [truncateToWidth(parts.join(" "), width)];
69
+ }
70
+ invalidate() { }
71
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Markdown → 行(设计 §12.5)。[B4]
3
+ *
4
+ * 支持:ATX 标题、段落(软换行合并为空格,行尾两个空格或 `\` 为硬换行)、无序 / 有序列表(缩进嵌套)、
5
+ * 围栏代码块(边框 + 语言标签;流式时未闭合的围栏按代码块渲染)、引用、分隔线、
6
+ * 行内 `code` / **粗体** / *斜体* / ~~删除线~~ / [链接](url) / <自动链接> / 反斜杠转义。
7
+ * 表格降级为等宽对齐文本(不换行、超宽截断);不做语法高亮(§12.9)。
8
+ *
9
+ * 缓存:按块缓存渲染结果(键 = 宽度 + 块源文本),流式追加时只有末块重新渲染;解析也是增量的
10
+ * (`Markdown.append` 只从尾部可能变化的块重新解析)。
11
+ */
12
+ import type { Component, Theme } from "../component.js";
13
+ export type MarkdownBlock = {
14
+ kind: "heading";
15
+ level: number;
16
+ text: string;
17
+ source: string;
18
+ } | {
19
+ kind: "paragraph";
20
+ text: string;
21
+ source: string;
22
+ } | {
23
+ kind: "code";
24
+ lang: string;
25
+ lines: string[];
26
+ source: string;
27
+ } | {
28
+ kind: "list";
29
+ items: ListItem[];
30
+ source: string;
31
+ } | {
32
+ kind: "quote";
33
+ text: string;
34
+ source: string;
35
+ } | {
36
+ kind: "table";
37
+ rows: string[][];
38
+ source: string;
39
+ } | {
40
+ kind: "hr";
41
+ source: string;
42
+ };
43
+ export interface ListItem {
44
+ depth: number;
45
+ marker: string;
46
+ text: string;
47
+ }
48
+ /** 块占的行区间 [start, end)(行号,相对整篇)。 */
49
+ export interface BlockSpan {
50
+ start: number;
51
+ end: number;
52
+ }
53
+ /** 把源文本切成块。 */
54
+ export declare function parseMarkdown(source: string): MarkdownBlock[];
55
+ /** 行内标记 → 带样式文本。 */
56
+ export declare function renderInline(text: string, theme: Theme): string;
57
+ /** 单个块 → 行(不含块间空行)。 */
58
+ export declare function renderBlock(block: MarkdownBlock, width: number, theme: Theme): string[];
59
+ export interface MarkdownOptions {
60
+ theme?: Theme;
61
+ /** 左右内边距。 */
62
+ paddingX?: number;
63
+ }
64
+ export declare class Markdown implements Component {
65
+ private text;
66
+ private readonly options;
67
+ private blocks;
68
+ /** 与 blocks 一一对应的行区间(增量解析用)。 */
69
+ private spans;
70
+ /** 规范化(\r\n → \n)后的行。 */
71
+ private lines;
72
+ private cacheWidth;
73
+ private readonly blockCache;
74
+ private lastLines;
75
+ private readonly theme;
76
+ /** 累计解析过的行数(诊断与测试:流式追加应只重解析尾部)。 */
77
+ parsedLines: number;
78
+ constructor(text?: string, options?: MarkdownOptions);
79
+ getText(): string;
80
+ setText(text: string): void;
81
+ /**
82
+ * 流式追加:只有最后一行会变长,此前的行不变。一个块的结束取决于它之后的一行(列表再多看一行),
83
+ * 所以「结束行 + 1」早于旧的最后一行的块保持不变,从第一个不满足的块的起始行重新解析
84
+ * (未闭合的围栏就是从围栏起点)。含 `\r` 时回落全量(`\r\n` 可能被拆在两次追加之间)。
85
+ */
86
+ append(delta: string): void;
87
+ private reparseFrom;
88
+ render(width: number): string[];
89
+ invalidate(): void;
90
+ }
@@ -0,0 +1,462 @@
1
+ /**
2
+ * Markdown → 行(设计 §12.5)。[B4]
3
+ *
4
+ * 支持:ATX 标题、段落(软换行合并为空格,行尾两个空格或 `\` 为硬换行)、无序 / 有序列表(缩进嵌套)、
5
+ * 围栏代码块(边框 + 语言标签;流式时未闭合的围栏按代码块渲染)、引用、分隔线、
6
+ * 行内 `code` / **粗体** / *斜体* / ~~删除线~~ / [链接](url) / <自动链接> / 反斜杠转义。
7
+ * 表格降级为等宽对齐文本(不换行、超宽截断);不做语法高亮(§12.9)。
8
+ *
9
+ * 缓存:按块缓存渲染结果(键 = 宽度 + 块源文本),流式追加时只有末块重新渲染;解析也是增量的
10
+ * (`Markdown.append` 只从尾部可能变化的块重新解析)。
11
+ */
12
+ import { padToWidth, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "../ansi.js";
13
+ import { plainTheme } from "../theme.js";
14
+ const HEADING_RE = /^ {0,3}(#{1,6})(?:\s+(.*?))?\s*#*\s*$/;
15
+ const FENCE_RE = /^ {0,3}(`{3,}|~{3,})\s*([^\s`]*)/;
16
+ const HR_RE = /^ {0,3}([-*_])(?:\s*\1){2,}\s*$/;
17
+ const LIST_RE = /^(\s*)([-*+]|\d{1,9}[.)])\s+(.*)$/;
18
+ const QUOTE_RE = /^ {0,3}>\s?(.*)$/;
19
+ const TABLE_RE = /^\s*\|.*\|\s*$/;
20
+ const TABLE_SEP_RE = /^\s*\|?\s*:?-{2,}:?\s*(\|\s*:?-{2,}:?\s*)*\|?\s*$/;
21
+ function isBlockStart(line) {
22
+ return (HEADING_RE.test(line) ||
23
+ FENCE_RE.test(line) ||
24
+ HR_RE.test(line) ||
25
+ LIST_RE.test(line) ||
26
+ QUOTE_RE.test(line) ||
27
+ TABLE_RE.test(line));
28
+ }
29
+ function splitLines(source) {
30
+ return source.replace(/\r\n?/g, "\n").split("\n");
31
+ }
32
+ /** 把源文本切成块。 */
33
+ export function parseMarkdown(source) {
34
+ return parseLines(splitLines(source), 0, [], []);
35
+ }
36
+ /**
37
+ * 从第 `from` 行起解析,块与行区间分别追加到 `blocks` / `spans`。块与块之间不带状态(空行跳过),
38
+ * 所以从任一块的起始行重新解析,结果与全量解析的对应部分相同(增量解析的前提)。
39
+ */
40
+ function parseLines(lines, from, blocks, spans) {
41
+ let i = from;
42
+ while (i < lines.length) {
43
+ const line = lines[i];
44
+ if (line.trim() === "") {
45
+ i++;
46
+ continue;
47
+ }
48
+ const start = i;
49
+ const before = blocks.length;
50
+ i = parseBlock(lines, i, blocks);
51
+ for (let k = before; k < blocks.length; k++)
52
+ spans.push({ start, end: i });
53
+ }
54
+ return blocks;
55
+ }
56
+ /** 解析从 `i` 起(非空行)的一个块,返回下一行号。 */
57
+ function parseBlock(lines, i, blocks) {
58
+ const line = lines[i];
59
+ const start = i;
60
+ const fence = FENCE_RE.exec(line);
61
+ if (fence) {
62
+ const marker = fence[1];
63
+ const body = [];
64
+ i++;
65
+ while (i < lines.length && !lines[i].trimStart().startsWith(marker))
66
+ body.push(lines[i++]);
67
+ if (i < lines.length)
68
+ i++;
69
+ const src = lines.slice(start, i).join("\n");
70
+ blocks.push({ kind: "code", lang: fence[2] ?? "", lines: body, source: src });
71
+ return i;
72
+ }
73
+ const heading = HEADING_RE.exec(line);
74
+ if (heading) {
75
+ blocks.push({
76
+ kind: "heading",
77
+ level: heading[1].length,
78
+ text: heading[2] ?? "",
79
+ source: line,
80
+ });
81
+ i++;
82
+ return i;
83
+ }
84
+ if (HR_RE.test(line)) {
85
+ blocks.push({ kind: "hr", source: line });
86
+ i++;
87
+ return i;
88
+ }
89
+ if (TABLE_RE.test(line)) {
90
+ const rows = [];
91
+ while (i < lines.length && TABLE_RE.test(lines[i])) {
92
+ const row = lines[i++];
93
+ if (TABLE_SEP_RE.test(row))
94
+ continue;
95
+ rows.push(splitTableRow(row));
96
+ }
97
+ blocks.push({ kind: "table", rows, source: lines.slice(start, i).join("\n") });
98
+ return i;
99
+ }
100
+ if (QUOTE_RE.test(line)) {
101
+ const body = [];
102
+ while (i < lines.length && QUOTE_RE.test(lines[i]))
103
+ body.push(QUOTE_RE.exec(lines[i++])[1]);
104
+ blocks.push({
105
+ kind: "quote",
106
+ text: body.join("\n"),
107
+ source: lines.slice(start, i).join("\n"),
108
+ });
109
+ return i;
110
+ }
111
+ if (LIST_RE.test(line)) {
112
+ return parseList(lines, i, blocks);
113
+ }
114
+ const body = [line];
115
+ i++;
116
+ while (i < lines.length && lines[i].trim() !== "" && !isBlockStart(lines[i])) {
117
+ body.push(lines[i++]);
118
+ }
119
+ blocks.push({ kind: "paragraph", text: joinSoftBreaks(body), source: body.join("\n") });
120
+ return i;
121
+ }
122
+ function parseList(lines, start, blocks) {
123
+ const items = [];
124
+ const indents = [];
125
+ let i = start;
126
+ while (i < lines.length) {
127
+ const line = lines[i];
128
+ const m = LIST_RE.exec(line);
129
+ if (m) {
130
+ const indent = m[1].replace(/\t/g, " ").length;
131
+ while (indents.length > 0 && indent < indents[indents.length - 1])
132
+ indents.pop();
133
+ if (indents.length === 0 || indent > indents[indents.length - 1])
134
+ indents.push(indent);
135
+ items.push({ depth: indents.length - 1, marker: m[2], text: m[3] });
136
+ i++;
137
+ continue;
138
+ }
139
+ // 续行:缩进的非空行并入上一项;空行后接列表项则继续,否则结束
140
+ if (line.trim() !== "" && /^\s+/.test(line) && items.length > 0) {
141
+ const last = items[items.length - 1];
142
+ last.text = joinSoftBreaks([last.text, line.trim()]);
143
+ i++;
144
+ continue;
145
+ }
146
+ if (line.trim() === "" && i + 1 < lines.length && LIST_RE.test(lines[i + 1])) {
147
+ i++;
148
+ continue;
149
+ }
150
+ break;
151
+ }
152
+ blocks.push({ kind: "list", items, source: lines.slice(start, i).join("\n") });
153
+ return i;
154
+ }
155
+ function joinSoftBreaks(lines) {
156
+ let out = "";
157
+ lines.forEach((line, index) => {
158
+ const text = index === lines.length - 1 ? line.trim() : line.trimStart();
159
+ if (index === 0) {
160
+ out = text;
161
+ return;
162
+ }
163
+ const hard = / {2,}$/.test(out) || out.endsWith("\\");
164
+ out = out.replace(/(?: {2,}|\\)$/, "").trimEnd() + (hard ? "\n" : " ") + text;
165
+ });
166
+ return out.trimEnd();
167
+ }
168
+ function splitTableRow(row) {
169
+ const trimmed = row.trim().replace(/^\|/, "").replace(/\|$/, "");
170
+ return trimmed.split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, "|"));
171
+ }
172
+ /** 行内标记 → 带样式文本。 */
173
+ export function renderInline(text, theme) {
174
+ let out = "";
175
+ let i = 0;
176
+ while (i < text.length) {
177
+ const ch = text[i];
178
+ if (ch === "\\" && i + 1 < text.length && /[\\`*_{}[\]()#+\-.!~|<>]/.test(text[i + 1])) {
179
+ out += text[i + 1];
180
+ i += 2;
181
+ continue;
182
+ }
183
+ if (ch === "`") {
184
+ const ticks = /^`+/.exec(text.slice(i))[0];
185
+ const end = text.indexOf(ticks, i + ticks.length);
186
+ if (end !== -1) {
187
+ out += theme.fg("code", text.slice(i + ticks.length, end).trim() || " ");
188
+ i = end + ticks.length;
189
+ continue;
190
+ }
191
+ }
192
+ const emphasis = matchDelimited(text, i, ["**", "__", "~~", "*", "_"]);
193
+ if (emphasis) {
194
+ const inner = renderInline(emphasis.inner, theme);
195
+ if (emphasis.delim === "**" || emphasis.delim === "__")
196
+ out += theme.bold(inner);
197
+ else if (emphasis.delim === "~~")
198
+ out += `\x1b[9m${inner}\x1b[29m`;
199
+ else
200
+ out += theme.italic(inner);
201
+ i = emphasis.end;
202
+ continue;
203
+ }
204
+ if (ch === "[") {
205
+ const link = /^\[([^\]]*)\]\(([^)\s]*)(?:\s+"[^"]*")?\)/.exec(text.slice(i));
206
+ if (link) {
207
+ const label = renderInline(link[1], theme);
208
+ const url = link[2];
209
+ out +=
210
+ link[1] === url || url === ""
211
+ ? theme.underline(label || url)
212
+ : theme.underline(label) + theme.fg("dim", ` (${url})`);
213
+ i += link[0].length;
214
+ continue;
215
+ }
216
+ }
217
+ if (ch === "<") {
218
+ const auto = /^<((?:https?|mailto):[^>\s]+)>/.exec(text.slice(i));
219
+ if (auto) {
220
+ out += theme.underline(auto[1]);
221
+ i += auto[0].length;
222
+ continue;
223
+ }
224
+ }
225
+ out += ch;
226
+ i++;
227
+ }
228
+ return out;
229
+ }
230
+ function matchDelimited(text, i, delims) {
231
+ for (const delim of delims) {
232
+ if (!text.startsWith(delim, i))
233
+ continue;
234
+ const after = text[i + delim.length];
235
+ if (after === undefined || /\s/.test(after))
236
+ continue;
237
+ // 下划线只在词边界起作用(snake_case 不当斜体)
238
+ if (delim.startsWith("_") && i > 0 && /[\p{L}\p{N}]/u.test(text[i - 1]))
239
+ continue;
240
+ let search = i + delim.length + 1;
241
+ while (search <= text.length) {
242
+ const close = text.indexOf(delim, search);
243
+ if (close === -1)
244
+ break;
245
+ const before = text[close - 1];
246
+ const next = text[close + delim.length];
247
+ const wordAfter = next !== undefined && /[\p{L}\p{N}]/u.test(next);
248
+ if (!/\s/.test(before) && !(delim.startsWith("_") && wordAfter)) {
249
+ // `*` 不吃 `**` 的一半
250
+ if (delim.length === 1 && text[close + 1] === delim) {
251
+ search = close + 2;
252
+ continue;
253
+ }
254
+ return { delim, inner: text.slice(i + delim.length, close), end: close + delim.length };
255
+ }
256
+ search = close + 1;
257
+ }
258
+ }
259
+ return null;
260
+ }
261
+ /** 单个块 → 行(不含块间空行)。 */
262
+ export function renderBlock(block, width, theme) {
263
+ switch (block.kind) {
264
+ case "heading": {
265
+ const text = renderInline(block.text, theme);
266
+ const styled = block.level <= 2 ? theme.fg("accent", theme.bold(text)) : theme.bold(text);
267
+ return wrapTextWithAnsi(styled, width);
268
+ }
269
+ case "paragraph":
270
+ return wrapTextWithAnsi(renderInline(block.text, theme), width);
271
+ case "hr":
272
+ return [theme.fg("border", "─".repeat(Math.max(1, width)))];
273
+ case "quote": {
274
+ const bar = theme.fg("border", "│ ");
275
+ const inner = wrapTextWithAnsi(theme.italic(renderInline(block.text, theme)), width - 2);
276
+ return inner.map((line) => bar + line);
277
+ }
278
+ case "list":
279
+ return renderList(block.items, width, theme);
280
+ case "code":
281
+ return renderCode(block.lang, block.lines, width, theme);
282
+ case "table":
283
+ return renderTable(block.rows, width, theme);
284
+ }
285
+ }
286
+ const BULLETS = ["•", "◦", "▪"];
287
+ function renderList(items, width, theme) {
288
+ const lines = [];
289
+ for (const item of items) {
290
+ const indent = " ".repeat(item.depth);
291
+ const ordered = /\d/.test(item.marker);
292
+ const bullet = ordered ? item.marker : BULLETS[item.depth % BULLETS.length];
293
+ const head = indent + theme.fg("accent", bullet) + " ";
294
+ const headWidth = visibleWidth(head);
295
+ const body = wrapTextWithAnsi(renderInline(item.text, theme), Math.max(1, width - headWidth));
296
+ body.forEach((line, index) => {
297
+ lines.push((index === 0 ? head : " ".repeat(headWidth)) + line);
298
+ });
299
+ }
300
+ return lines;
301
+ }
302
+ function renderCode(lang, body, width, theme) {
303
+ const code = body.map((line) => line.replace(/\t/g, " "));
304
+ if (width < 8)
305
+ return code.map((line) => truncateToWidth(line, width));
306
+ const inner = width - 4;
307
+ const border = (s) => theme.fg("border", s);
308
+ const label = lang === "" ? "" : truncateToWidth(` ${lang} `, inner - 1);
309
+ const top = border("╭─") +
310
+ theme.fg("dim", label) +
311
+ border("─".repeat(width - 3 - visibleWidth(label)) + "╮");
312
+ const lines = [top];
313
+ for (const line of code) {
314
+ for (const piece of wrapCode(line, inner)) {
315
+ lines.push(border("│ ") + padToWidth(theme.fg("code", piece), inner) + border(" │"));
316
+ }
317
+ }
318
+ lines.push(border("╰" + "─".repeat(width - 2) + "╯"));
319
+ return lines;
320
+ }
321
+ /** 代码按列硬断行(不按词),空行保留。 */
322
+ function wrapCode(line, width) {
323
+ if (visibleWidth(line) <= width)
324
+ return [line];
325
+ const out = [];
326
+ let current = "";
327
+ let w = 0;
328
+ for (const ch of line) {
329
+ const cw = visibleWidth(ch);
330
+ if (w + cw > width) {
331
+ out.push(current);
332
+ current = "";
333
+ w = 0;
334
+ }
335
+ current += ch;
336
+ w += cw;
337
+ }
338
+ out.push(current);
339
+ return out;
340
+ }
341
+ function renderTable(rows, width, theme) {
342
+ const cols = Math.max(0, ...rows.map((r) => r.length));
343
+ const widths = new Array(cols).fill(0);
344
+ const rendered = rows.map((row) => row.map((cell) => renderInline(cell, theme)));
345
+ for (const row of rendered) {
346
+ row.forEach((cell, c) => (widths[c] = Math.max(widths[c], visibleWidth(cell))));
347
+ }
348
+ const sep = theme.fg("border", " │ ");
349
+ return rendered.map((row, r) => {
350
+ const cells = widths.map((w, c) => padToWidth(row[c] ?? "", w));
351
+ const line = cells.join(sep).replace(/\s+$/, "");
352
+ return truncateToWidth(r === 0 && rows.length > 1 ? theme.bold(line) : line, width);
353
+ });
354
+ }
355
+ export class Markdown {
356
+ text;
357
+ options;
358
+ blocks = [];
359
+ /** 与 blocks 一一对应的行区间(增量解析用)。 */
360
+ spans = [];
361
+ /** 规范化(\r\n → \n)后的行。 */
362
+ lines = [];
363
+ cacheWidth = -1;
364
+ blockCache = new Map();
365
+ lastLines = null;
366
+ theme;
367
+ /** 累计解析过的行数(诊断与测试:流式追加应只重解析尾部)。 */
368
+ parsedLines = 0;
369
+ constructor(text = "", options = {}) {
370
+ this.text = text;
371
+ this.options = options;
372
+ this.theme = options.theme ?? plainTheme();
373
+ this.reparseFrom(0, splitLines(text));
374
+ }
375
+ getText() {
376
+ return this.text;
377
+ }
378
+ setText(text) {
379
+ if (text === this.text)
380
+ return;
381
+ if (text.startsWith(this.text) && this.text !== "") {
382
+ this.append(text.slice(this.text.length));
383
+ return;
384
+ }
385
+ this.text = text;
386
+ this.reparseFrom(0, splitLines(text));
387
+ }
388
+ /**
389
+ * 流式追加:只有最后一行会变长,此前的行不变。一个块的结束取决于它之后的一行(列表再多看一行),
390
+ * 所以「结束行 + 1」早于旧的最后一行的块保持不变,从第一个不满足的块的起始行重新解析
391
+ * (未闭合的围栏就是从围栏起点)。含 `\r` 时回落全量(`\r\n` 可能被拆在两次追加之间)。
392
+ */
393
+ append(delta) {
394
+ if (delta === "")
395
+ return;
396
+ const previous = this.text;
397
+ this.text = previous + delta;
398
+ if (delta.includes("\r") || previous.endsWith("\r")) {
399
+ this.reparseFrom(0, splitLines(this.text));
400
+ return;
401
+ }
402
+ const lines = this.lines;
403
+ const oldLast = lines.length - 1;
404
+ const pieces = delta.split("\n");
405
+ lines[oldLast] = (lines[oldLast] ?? "") + pieces[0];
406
+ for (let k = 1; k < pieces.length; k++)
407
+ lines.push(pieces[k]);
408
+ let keep = this.spans.findIndex((span) => span.end + 1 >= oldLast);
409
+ if (keep === -1)
410
+ keep = this.spans.length;
411
+ const from = keep < this.spans.length ? this.spans[keep].start : (this.spans.at(-1)?.end ?? 0);
412
+ this.blocks.length = keep;
413
+ this.spans.length = keep;
414
+ this.reparseFrom(from, lines);
415
+ }
416
+ reparseFrom(from, lines) {
417
+ if (from === 0) {
418
+ this.blocks = [];
419
+ this.spans = [];
420
+ }
421
+ this.lines = lines;
422
+ parseLines(lines, from, this.blocks, this.spans);
423
+ this.parsedLines += lines.length - from;
424
+ this.lastLines = null;
425
+ }
426
+ render(width) {
427
+ if (width !== this.cacheWidth) {
428
+ this.blockCache.clear();
429
+ this.cacheWidth = width;
430
+ this.lastLines = null;
431
+ }
432
+ if (this.lastLines)
433
+ return this.lastLines;
434
+ const padX = this.options.paddingX ?? 0;
435
+ const inner = Math.max(1, width - padX * 2);
436
+ const pad = " ".repeat(padX);
437
+ const lines = [];
438
+ const used = new Set();
439
+ this.blocks.forEach((block, index) => {
440
+ const key = `${block.kind}\u0000${block.source}`;
441
+ used.add(key);
442
+ let rendered = this.blockCache.get(key);
443
+ if (!rendered) {
444
+ rendered = renderBlock(block, inner, this.theme).map((line) => pad + line);
445
+ this.blockCache.set(key, rendered);
446
+ }
447
+ if (index > 0)
448
+ lines.push("");
449
+ for (const line of rendered)
450
+ lines.push(line);
451
+ });
452
+ for (const key of [...this.blockCache.keys()])
453
+ if (!used.has(key))
454
+ this.blockCache.delete(key);
455
+ this.lastLines = lines;
456
+ return lines;
457
+ }
458
+ invalidate() {
459
+ this.blockCache.clear();
460
+ this.lastLines = null;
461
+ }
462
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * 单行余量表(第三波 A8):`ctx ▮▮▮▮▮▮▮▯▯▯ 72%`。[W3-B9a-2]
3
+ *
4
+ * - `value` 是 0–1 的比例(越界夹紧);undefined 显示 `—`,格子全空;
5
+ * - 阈值着色(需要 theme):≥ `dangerAt`(缺省 0.9)error,≥ `warnAt`(缺省 0.7)warning,否则 success;
6
+ * 空格子 dim;
7
+ * - 宽度不够时先去掉格子,只剩「标签 百分比」,再不够就截断。
8
+ */
9
+ import type { Component, SemanticColor, Theme } from "../component.js";
10
+ export interface MeterOptions {
11
+ label?: string;
12
+ /** 格子数,缺省 10。 */
13
+ cells?: number;
14
+ warnAt?: number;
15
+ dangerAt?: number;
16
+ theme?: Theme;
17
+ }
18
+ export declare const METER_FULL = "\u25AE";
19
+ export declare const METER_EMPTY = "\u25AF";
20
+ export declare class Meter implements Component {
21
+ private value;
22
+ private readonly options;
23
+ constructor(value?: number | undefined, options?: MeterOptions);
24
+ setValue(value: number | undefined): void;
25
+ /** 当前阈值档位对应的语义色。 */
26
+ level(): SemanticColor;
27
+ render(width: number): string[];
28
+ invalidate(): void;
29
+ }