@pugi/cli 0.1.0-beta.9 → 0.1.0-beta.90

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 (409) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/LICENSE +1 -1
  3. package/assets/pugi-prozr2-mascot.ansi +9 -0
  4. package/bin/run.js +33 -1
  5. package/dist/commands/deploy.js +40 -40
  6. package/dist/commands/flatten.js +191 -0
  7. package/dist/commands/jobs-watch.js +201 -0
  8. package/dist/commands/jobs.js +42 -27
  9. package/dist/commands/smoke.js +133 -0
  10. package/dist/core/agent-progress/cleanup.js +134 -0
  11. package/dist/core/agent-progress/schema.js +144 -0
  12. package/dist/core/agent-progress/writer.js +101 -0
  13. package/dist/core/agents/adaptive-router.js +330 -0
  14. package/dist/core/agents/query-decomposer.js +297 -0
  15. package/dist/core/agents/registry.js +3 -3
  16. package/dist/core/approvals/shortcut-resolver.js +98 -0
  17. package/dist/core/artifact-chain/dispatcher.js +148 -0
  18. package/dist/core/artifact-chain/exporter.js +164 -0
  19. package/dist/core/artifact-chain/state.js +243 -0
  20. package/dist/core/artifact-chain/steps.js +169 -0
  21. package/dist/core/ask-user/question.js +92 -0
  22. package/dist/core/audit/audit-trail.js +275 -0
  23. package/dist/core/auth/ensure-authenticated.js +129 -0
  24. package/dist/core/auth/env-provider.js +238 -0
  25. package/dist/core/auto-open-browser.js +4 -4
  26. package/dist/core/auto-update/channels.js +122 -0
  27. package/dist/core/auto-update/checker.js +241 -0
  28. package/dist/core/auto-update/state.js +235 -0
  29. package/dist/core/bare-mode/index.js +107 -0
  30. package/dist/core/bash/redirect.js +281 -0
  31. package/dist/core/bash-classifier.js +436 -40
  32. package/dist/core/checkpoint/resumer.js +149 -0
  33. package/dist/core/checkpoint/rewinder.js +291 -0
  34. package/dist/core/checkpoints/shadow-git.js +670 -0
  35. package/dist/core/citations/parser.js +109 -0
  36. package/dist/core/classifier/yolo-classifier.js +88 -0
  37. package/dist/core/codegraph/decision-store.js +248 -0
  38. package/dist/core/codegraph/detect-repo.js +459 -0
  39. package/dist/core/codegraph/install.js +134 -0
  40. package/dist/core/codegraph/offer-hook.js +220 -0
  41. package/dist/core/compact/auto-trigger.js +96 -0
  42. package/dist/core/compact/buffer-rewriter.js +115 -0
  43. package/dist/core/compact/summarizer.js +208 -0
  44. package/dist/core/compact/token-counter.js +108 -0
  45. package/dist/core/consensus/anvil-fanout.js +25 -25
  46. package/dist/core/consensus/diff-capture.js +121 -12
  47. package/dist/core/consensus/rubric.js +21 -21
  48. package/dist/core/context/builder.js +6 -6
  49. package/dist/core/context/compaction-events.js +8 -8
  50. package/dist/core/context/compaction.js +31 -31
  51. package/dist/core/context/index.js +15 -8
  52. package/dist/core/context/invariants.js +51 -51
  53. package/dist/core/context/markdown-loader.js +28 -10
  54. package/dist/core/context/markdown-traverse.js +255 -0
  55. package/dist/core/context/pugiignore.js +41 -41
  56. package/dist/core/context/repo-skeleton.js +37 -37
  57. package/dist/core/context/tool-eviction.js +55 -0
  58. package/dist/core/context/watcher.js +32 -32
  59. package/dist/core/context/working-set.js +23 -23
  60. package/dist/core/coordinator/agent-tools.js +77 -0
  61. package/dist/core/coordinator/agent-toolset.js +65 -0
  62. package/dist/core/coordinator/fsm.js +73 -0
  63. package/dist/core/coordinator/mode-fsm.js +70 -0
  64. package/dist/core/cost/rate-card.js +129 -0
  65. package/dist/core/cost/tracker.js +221 -0
  66. package/dist/core/credentials.js +13 -13
  67. package/dist/core/cron/scheduler.js +138 -0
  68. package/dist/core/denial-tracking/index.js +8 -0
  69. package/dist/core/denial-tracking/state.js +264 -0
  70. package/dist/core/diagnostics/probe-runner.js +93 -0
  71. package/dist/core/diagnostics/probes/api.js +46 -0
  72. package/dist/core/diagnostics/probes/auth.js +93 -0
  73. package/dist/core/diagnostics/probes/bare-mode.js +42 -0
  74. package/dist/core/diagnostics/probes/cli-version.js +127 -0
  75. package/dist/core/diagnostics/probes/config.js +72 -0
  76. package/dist/core/diagnostics/probes/denial-tracking.js +57 -0
  77. package/dist/core/diagnostics/probes/disk.js +81 -0
  78. package/dist/core/diagnostics/probes/engine-live.js +46 -0
  79. package/dist/core/diagnostics/probes/git.js +65 -0
  80. package/dist/core/diagnostics/probes/hooks.js +118 -0
  81. package/dist/core/diagnostics/probes/mcp.js +75 -0
  82. package/dist/core/diagnostics/probes/node.js +59 -0
  83. package/dist/core/diagnostics/probes/pnpm.js +36 -0
  84. package/dist/core/diagnostics/probes/pugi-md.js +89 -0
  85. package/dist/core/diagnostics/probes/sandbox.js +40 -0
  86. package/dist/core/diagnostics/probes/session.js +74 -0
  87. package/dist/core/diagnostics/probes/status-snapshot.js +488 -0
  88. package/dist/core/diagnostics/probes/workspace.js +63 -0
  89. package/dist/core/diagnostics/types.js +70 -0
  90. package/dist/core/dispatch/cache-cleanup.js +197 -0
  91. package/dist/core/dispatch/cache-handoff.js +295 -0
  92. package/dist/core/edits/apply-patch-layer-e.js +189 -0
  93. package/dist/core/edits/dispatch.js +333 -7
  94. package/dist/core/edits/format-detector.js +260 -0
  95. package/dist/core/edits/format-matrix.js +26 -0
  96. package/dist/core/edits/fuzzy-ladder.js +650 -0
  97. package/dist/core/edits/index.js +5 -1
  98. package/dist/core/edits/journal.js +199 -0
  99. package/dist/core/edits/layer-a-apply.js +15 -15
  100. package/dist/core/edits/layer-a-fuzzy-apply.js +198 -0
  101. package/dist/core/edits/layer-b-apply.js +9 -9
  102. package/dist/core/edits/layer-c-apply.js +6 -6
  103. package/dist/core/edits/layer-d-ast.js +557 -14
  104. package/dist/core/edits/marker-parser.js +12 -12
  105. package/dist/core/edits/security-gate.js +27 -27
  106. package/dist/core/edits/verify-hook.js +273 -0
  107. package/dist/core/edits/worktree.js +29 -29
  108. package/dist/core/engine/anvil-client.js +214 -26
  109. package/dist/core/engine/auto-compact.js +179 -0
  110. package/dist/core/engine/budgets.js +186 -0
  111. package/dist/core/engine/context-prefix.js +155 -0
  112. package/dist/core/engine/index.js +1 -1
  113. package/dist/core/engine/intensity.js +158 -0
  114. package/dist/core/engine/intent.js +260 -0
  115. package/dist/core/engine/native-pugi.js +1295 -227
  116. package/dist/core/engine/prompts.js +129 -19
  117. package/dist/core/engine/strip-internal-fields.js +124 -0
  118. package/dist/core/engine/tool-bridge.js +1731 -59
  119. package/dist/core/evaluation/golden-dataset.js +293 -0
  120. package/dist/core/feedback/queue.js +177 -0
  121. package/dist/core/feedback/submitter.js +145 -0
  122. package/dist/core/file-cache.js +113 -1
  123. package/dist/core/flatten/flatten-repo.js +439 -0
  124. package/dist/core/format/osc8-link.js +28 -0
  125. package/dist/core/hook-chains.js +392 -0
  126. package/dist/core/hooks/citation-verify-hook.js +138 -0
  127. package/dist/core/hooks/citation-verify.js +112 -0
  128. package/dist/core/hooks/events.js +46 -0
  129. package/dist/core/hooks/index.js +15 -0
  130. package/dist/core/hooks/registry.js +216 -0
  131. package/dist/core/hooks/runner.js +236 -0
  132. package/dist/core/hooks/v2/event-emitter.js +115 -0
  133. package/dist/core/hooks/v2/executor.js +282 -0
  134. package/dist/core/hooks/v2/index.js +25 -0
  135. package/dist/core/hooks/v2/lifecycle.js +104 -0
  136. package/dist/core/hooks/v2/loader.js +216 -0
  137. package/dist/core/hooks/v2/matcher.js +125 -0
  138. package/dist/core/hooks/v2/trust.js +143 -0
  139. package/dist/core/hooks/v2/types.js +86 -0
  140. package/dist/core/hooks/worktree-events.js +158 -0
  141. package/dist/core/image/renderer.js +71 -0
  142. package/dist/core/init/detector.js +582 -0
  143. package/dist/core/init/template-renderer.js +242 -0
  144. package/dist/core/jobs/registry.js +18 -18
  145. package/dist/core/ledger/results-tsv.js +142 -0
  146. package/dist/core/log-discipline/stdout-redirect.js +51 -0
  147. package/dist/core/lsp/cache.js +105 -0
  148. package/dist/core/lsp/client.js +551 -41
  149. package/dist/core/lsp/language-detect.js +66 -0
  150. package/dist/core/lsp/post-edit-diagnostics.js +171 -0
  151. package/dist/core/lsp/server-detect.js +173 -0
  152. package/dist/core/lsp/symbol-cache.js +162 -0
  153. package/dist/core/lsp/symbol-tools.js +664 -0
  154. package/dist/core/mcp/client.js +97 -28
  155. package/dist/core/mcp/http-server.js +553 -0
  156. package/dist/core/mcp/orchestrator-tools.js +662 -0
  157. package/dist/core/mcp/permission.js +190 -0
  158. package/dist/core/mcp/registry.js +39 -17
  159. package/dist/core/mcp/server-tools.js +219 -0
  160. package/dist/core/mcp/server.js +397 -0
  161. package/dist/core/mcp/trust.js +10 -10
  162. package/dist/core/memory/dual-write.js +416 -0
  163. package/dist/core/memory/passive-extract.js +130 -0
  164. package/dist/core/memory/phase1-kinds.js +20 -0
  165. package/dist/core/memory/secret-scanner.js +304 -0
  166. package/dist/core/memory-sync/queue.js +170 -0
  167. package/dist/core/metrics/extract.js +113 -0
  168. package/dist/core/modes/roo-modes.js +68 -0
  169. package/dist/core/onboarding/ensure-initialized.js +133 -0
  170. package/dist/core/onboarding/marker.js +111 -0
  171. package/dist/core/onboarding/telemetry-state.js +108 -0
  172. package/dist/core/output-style/presets.js +176 -0
  173. package/dist/core/output-style/state.js +185 -0
  174. package/dist/core/path-security.js +287 -5
  175. package/dist/core/permission.js +82 -22
  176. package/dist/core/permissions/auto-classifier.js +124 -0
  177. package/dist/core/permissions/bash-parser.js +371 -0
  178. package/dist/core/permissions/circuit-breaker.js +83 -0
  179. package/dist/core/permissions/constrained-edit.js +91 -0
  180. package/dist/core/permissions/gate.js +278 -0
  181. package/dist/core/permissions/index.js +20 -0
  182. package/dist/core/permissions/mode.js +174 -0
  183. package/dist/core/permissions/network-egress.js +137 -0
  184. package/dist/core/permissions/state.js +241 -0
  185. package/dist/core/permissions/tool-class.js +93 -0
  186. package/dist/core/plan-mode/ui-state.js +51 -0
  187. package/dist/core/plans/plan-artifact.js +721 -0
  188. package/dist/core/policy-limits/etag-store.js +122 -0
  189. package/dist/core/prd-check/parser.js +215 -0
  190. package/dist/core/prd-check/reporter.js +127 -0
  191. package/dist/core/prd-check/session-review.js +557 -0
  192. package/dist/core/prd-check/verifiers.js +223 -0
  193. package/dist/core/prompt-cache/client-cache.js +99 -0
  194. package/dist/core/prompts/assembly.js +29 -0
  195. package/dist/core/prompts/registry.js +364 -0
  196. package/dist/core/pugi-md/cc-compat-rules.js +735 -0
  197. package/dist/core/pugi-md/context-injector.js +76 -0
  198. package/dist/core/pugi-md/walk-up.js +207 -0
  199. package/dist/core/python/uv-installer.js +270 -0
  200. package/dist/core/python/uv-resolver.js +83 -0
  201. package/dist/core/rate-limit/narrator.js +146 -0
  202. package/dist/core/recipes/cli-types.js +20 -0
  203. package/dist/core/recipes/loader.js +103 -0
  204. package/dist/core/recipes/runner.js +345 -0
  205. package/dist/core/recipes/schema.js +587 -0
  206. package/dist/core/release-notes/parser.js +241 -0
  207. package/dist/core/release-notes/state.js +116 -0
  208. package/dist/core/repl/ask.js +37 -37
  209. package/dist/core/repl/cancellation.js +26 -26
  210. package/dist/core/repl/cap-warning.js +4 -4
  211. package/dist/core/repl/clipboard-read.js +11 -11
  212. package/dist/core/repl/dispatch-fsm.js +12 -12
  213. package/dist/core/repl/history-search.js +15 -15
  214. package/dist/core/repl/history.js +28 -18
  215. package/dist/core/repl/kill-ring.js +5 -5
  216. package/dist/core/repl/model-pricing.js +135 -0
  217. package/dist/core/repl/privacy-banner.js +22 -22
  218. package/dist/core/repl/session.js +2148 -217
  219. package/dist/core/repl/slash-commands.js +501 -41
  220. package/dist/core/repl/store/index.js +1 -1
  221. package/dist/core/repl/store/jsonl-log.js +22 -22
  222. package/dist/core/repl/store/lockfile.js +10 -10
  223. package/dist/core/repl/store/session-store.js +136 -107
  224. package/dist/core/repl/store/types.js +15 -15
  225. package/dist/core/repl/store/uuid-v7.js +12 -12
  226. package/dist/core/repl/workspace-context.js +43 -21
  227. package/dist/core/repo-map/build.js +125 -0
  228. package/dist/core/repo-map/cache.js +185 -0
  229. package/dist/core/repo-map/extractor.js +254 -0
  230. package/dist/core/repo-map/formatter.js +145 -0
  231. package/dist/core/repo-map/page-rank.js +105 -0
  232. package/dist/core/repo-map/scanner.js +211 -0
  233. package/dist/core/retry-budget/budget.js +284 -0
  234. package/dist/core/retry-budget/index.js +5 -0
  235. package/dist/core/retry-budget/retry-cap.js +74 -0
  236. package/dist/core/routing/lead-worker.js +43 -0
  237. package/dist/core/routing/pre-flight-estimator.js +108 -0
  238. package/dist/core/runs/run-tree.js +103 -0
  239. package/dist/core/security/injection-scanner.js +367 -0
  240. package/dist/core/security/output-filter.js +418 -0
  241. package/dist/core/session/env-file.js +105 -0
  242. package/dist/core/session/section-budgets.js +140 -0
  243. package/dist/core/session.js +92 -0
  244. package/dist/core/settings.js +324 -5
  245. package/dist/core/share/formatter.js +271 -0
  246. package/dist/core/share/redactor.js +221 -0
  247. package/dist/core/share/uploader.js +267 -0
  248. package/dist/core/skills/defaults.js +30 -30
  249. package/dist/core/skills/loader.js +22 -22
  250. package/dist/core/skills/sources.js +27 -27
  251. package/dist/core/smoke/headless-driver.js +174 -0
  252. package/dist/core/smoke/orchestrator.js +194 -0
  253. package/dist/core/smoke/runner.js +238 -0
  254. package/dist/core/smoke/scenario-parser.js +316 -0
  255. package/dist/core/statusline.js +99 -0
  256. package/dist/core/subagents/dispatcher-real.js +600 -0
  257. package/dist/core/subagents/dispatcher.js +132 -43
  258. package/dist/core/subagents/index.js +19 -6
  259. package/dist/core/subagents/isolation-matrix.js +213 -0
  260. package/dist/core/subagents/spawn.js +19 -4
  261. package/dist/core/telemetry/emitter.js +229 -0
  262. package/dist/core/telemetry/queue.js +251 -0
  263. package/dist/core/theme/context.js +91 -0
  264. package/dist/core/theme/presets.js +228 -0
  265. package/dist/core/theme/state.js +181 -0
  266. package/dist/core/todos/invariant.js +10 -0
  267. package/dist/core/todos/state.js +177 -0
  268. package/dist/core/tool-schema/compressor.js +89 -0
  269. package/dist/core/transport/version-interceptor.js +166 -0
  270. package/dist/core/trust.js +2 -2
  271. package/dist/core/tui/thinking-block.js +64 -0
  272. package/dist/core/vim/keymap.js +288 -0
  273. package/dist/core/vim/state.js +92 -0
  274. package/dist/core/watch-markers/marker-watcher.js +133 -0
  275. package/dist/core/worktree/include-parser.js +249 -0
  276. package/dist/core/worktree-manager/cleanup.js +123 -0
  277. package/dist/core/worktree-manager/manager.js +303 -0
  278. package/dist/index.js +36 -0
  279. package/dist/runtime/bootstrap.js +190 -0
  280. package/dist/runtime/cli.js +4185 -549
  281. package/dist/runtime/commands/agents.js +31 -31
  282. package/dist/runtime/commands/budget.js +5 -5
  283. package/dist/runtime/commands/cancel.js +231 -0
  284. package/dist/runtime/commands/chain.js +489 -0
  285. package/dist/runtime/commands/codegraph-status.js +227 -0
  286. package/dist/runtime/commands/compact.js +297 -0
  287. package/dist/runtime/commands/config.js +73 -39
  288. package/dist/runtime/commands/cost.js +199 -0
  289. package/dist/runtime/commands/delegate.js +27 -4
  290. package/dist/runtime/commands/dispatch.js +126 -0
  291. package/dist/runtime/commands/doctor.js +579 -0
  292. package/dist/runtime/commands/feedback.js +184 -0
  293. package/dist/runtime/commands/hooks.js +187 -0
  294. package/dist/runtime/commands/init.js +254 -0
  295. package/dist/runtime/commands/lsp.js +200 -38
  296. package/dist/runtime/commands/mcp.js +879 -0
  297. package/dist/runtime/commands/memory.js +582 -0
  298. package/dist/runtime/commands/model.js +237 -0
  299. package/dist/runtime/commands/onboarding.js +275 -0
  300. package/dist/runtime/commands/patch.js +12 -12
  301. package/dist/runtime/commands/permissions.js +112 -0
  302. package/dist/runtime/commands/plan.js +143 -0
  303. package/dist/runtime/commands/prd-check.js +285 -0
  304. package/dist/runtime/commands/privacy.js +17 -17
  305. package/dist/runtime/commands/recipe.js +325 -0
  306. package/dist/runtime/commands/redo-blob-store.js +92 -0
  307. package/dist/runtime/commands/redo.js +361 -0
  308. package/dist/runtime/commands/release-notes.js +229 -0
  309. package/dist/runtime/commands/repo-map.js +95 -0
  310. package/dist/runtime/commands/report.js +299 -0
  311. package/dist/runtime/commands/resume.js +118 -0
  312. package/dist/runtime/commands/review-consensus.js +68 -53
  313. package/dist/runtime/commands/rewind.js +333 -0
  314. package/dist/runtime/commands/roster.js +14 -14
  315. package/dist/runtime/commands/sessions.js +163 -0
  316. package/dist/runtime/commands/share.js +316 -0
  317. package/dist/runtime/commands/skills.js +31 -31
  318. package/dist/runtime/commands/status.js +186 -0
  319. package/dist/runtime/commands/stickers.js +82 -0
  320. package/dist/runtime/commands/style.js +194 -0
  321. package/dist/runtime/commands/theme.js +196 -0
  322. package/dist/runtime/commands/undo.js +54 -22
  323. package/dist/runtime/commands/update.js +289 -0
  324. package/dist/runtime/commands/vim.js +140 -0
  325. package/dist/runtime/commands/worktree.js +8 -8
  326. package/dist/runtime/commands/worktrees.js +155 -0
  327. package/dist/runtime/headless-repl.js +195 -0
  328. package/dist/runtime/headless.js +543 -0
  329. package/dist/runtime/load-hooks-or-exit.js +71 -0
  330. package/dist/runtime/plan-decompose.js +22 -22
  331. package/dist/runtime/sigint-guard.js +272 -0
  332. package/dist/runtime/update-check.js +28 -28
  333. package/dist/runtime/version.js +65 -0
  334. package/dist/runtime/worktree-bootstrap.js +579 -0
  335. package/dist/skills/bundled/batch.js +617 -0
  336. package/dist/skills/bundled/index.js +45 -0
  337. package/dist/skills/bundled/loop.js +358 -0
  338. package/dist/skills/bundled/remember.js +383 -0
  339. package/dist/skills/bundled/simplify.js +289 -0
  340. package/dist/skills/bundled/skillify.js +373 -0
  341. package/dist/skills/bundled/stuck.js +558 -0
  342. package/dist/skills/bundled/verify.js +439 -0
  343. package/dist/testing/vcr.js +486 -0
  344. package/dist/tools/agent-tool.js +229 -0
  345. package/dist/tools/apply-patch.js +89 -28
  346. package/dist/tools/ask-user-question.js +337 -0
  347. package/dist/tools/ask-user.js +115 -0
  348. package/dist/tools/bash.js +624 -46
  349. package/dist/tools/brief.js +224 -0
  350. package/dist/tools/enter-worktree.js +250 -0
  351. package/dist/tools/exit-worktree.js +147 -0
  352. package/dist/tools/file-tools.js +161 -44
  353. package/dist/tools/lsp-tools.js +377 -1
  354. package/dist/tools/mcp-tool.js +260 -0
  355. package/dist/tools/multi-edit.js +361 -0
  356. package/dist/tools/powershell.js +268 -0
  357. package/dist/tools/registry.js +86 -4
  358. package/dist/tools/skill-tool.js +96 -0
  359. package/dist/tools/sleep.js +99 -0
  360. package/dist/tools/synthetic-output.js +133 -0
  361. package/dist/tools/tasks.js +208 -0
  362. package/dist/tools/todo-write.js +184 -0
  363. package/dist/tools/verify-plan-execution.js +295 -0
  364. package/dist/tools/web-fetch-injection-scanner.js +207 -0
  365. package/dist/tools/web-fetch.js +195 -10
  366. package/dist/tools/web-search.js +458 -0
  367. package/dist/tui/agent-progress-card.js +111 -0
  368. package/dist/tui/agent-tree.js +11 -1
  369. package/dist/tui/ask-modal.js +14 -14
  370. package/dist/tui/ask-user-question-chips.js +315 -0
  371. package/dist/tui/ask-user-question-prompt.js +203 -0
  372. package/dist/tui/compact-banner.js +81 -0
  373. package/dist/tui/conversation-pane.js +85 -11
  374. package/dist/tui/cost-table.js +111 -0
  375. package/dist/tui/device-flow.js +2 -2
  376. package/dist/tui/doctor-table.js +46 -0
  377. package/dist/tui/feedback-prompt.js +156 -0
  378. package/dist/tui/input-box.js +247 -32
  379. package/dist/tui/login-picker.js +3 -3
  380. package/dist/tui/markdown-render.js +6 -6
  381. package/dist/tui/onboarding-wizard.js +240 -0
  382. package/dist/tui/permissions-picker.js +86 -0
  383. package/dist/tui/render.js +36 -1
  384. package/dist/tui/repl-render.js +176 -25
  385. package/dist/tui/repl-splash-art.js +16 -16
  386. package/dist/tui/repl-splash-mascot.js +48 -24
  387. package/dist/tui/repl-splash.js +22 -22
  388. package/dist/tui/repl.js +125 -45
  389. package/dist/tui/slash-palette.js +6 -6
  390. package/dist/tui/splash.js +2 -2
  391. package/dist/tui/status-bar.js +109 -31
  392. package/dist/tui/status-table.js +7 -0
  393. package/dist/tui/stickers-art.js +136 -0
  394. package/dist/tui/style-table.js +28 -0
  395. package/dist/tui/theme-table.js +29 -0
  396. package/dist/tui/thinking-spinner.js +123 -0
  397. package/dist/tui/tool-stream-pane.js +53 -4
  398. package/dist/tui/update-banner.js +27 -2
  399. package/dist/tui/vim-input.js +267 -0
  400. package/dist/tui/welcome-banner.js +107 -0
  401. package/dist/tui/welcome-data.js +293 -0
  402. package/dist/tui/workspace-context.js +2 -2
  403. package/package.json +31 -16
  404. package/test/scenarios/codegen-create-file.scenario.txt +13 -0
  405. package/test/scenarios/compact-force.scenario.txt +12 -0
  406. package/test/scenarios/identity.scenario.txt +12 -0
  407. package/test/scenarios/persona-handoff.scenario.txt +12 -0
  408. package/test/scenarios/walkback.scenario.txt +12 -0
  409. package/dist/core/engine/compaction-hook.js +0 -154
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Pugi hooks v2 - matcher grammar .
3
+ *
4
+ * The matcher string is compared against the tool name for tool events
5
+ * (PreToolUse / PostToolUse / PostToolUseFailure) and against the empty
6
+ * string for non-tool events (matcher is effectively ignored unless it
7
+ * is the wildcard).
8
+ *
9
+ * Grammar (evaluated in order):
10
+ *
11
+ * 1. wildcard `*` - matches anything.
12
+ * 2. slash-regex literal - `/pattern/flags`. Flags subset of gimsuy.
13
+ * 3. prefix regex - starts with `^` (e.g. `^mcp__`).
14
+ * 4. alternation - `bash|grep|read`. Each side compiled
15
+ * recursively so `mcp__*|bash` works.
16
+ * 5. wildcard glob - any string containing `*` not handled
17
+ * by the regex branches (e.g. `mcp__*`).
18
+ * 6. exact match - case-sensitive equality.
19
+ *
20
+ * Operators in the wild write `^mcp__` expecting "starts with mcp__".
21
+ * The the upstream tool docs document this behaviour.
22
+ *
23
+ * Brand voice: ASCII only.
24
+ */
25
+ // Constructed regexes used internally. We avoid `/.../` literals when
26
+ // the pattern contains `${` because some TypeScript parser
27
+ // configurations mis-treat the dollar-brace sequence inside a regex
28
+ // character class as a template-literal opener.
29
+ const REGEX_META_CHARS = new RegExp('[.*+?^${}()|[\\]\\\\]');
30
+ const ESCAPE_REGEX_META = new RegExp('[.*+?^${}()|[\\]\\\\]', 'g');
31
+ const SLASH_REGEX_LITERAL = /^\/(.+)\/([A-Za-z]*)$/;
32
+ const ALLOWED_REGEX_FLAGS = /^[gimsuy]*$/;
33
+ /**
34
+ * Compile a matcher string into a predicate over a single candidate.
35
+ * The candidate is the tool name for tool events, or `''` for
36
+ * non-tool events.
37
+ *
38
+ * Throws on a malformed regex literal so the operator sees a clear
39
+ * error at config-load time rather than at hook-fire time.
40
+ */
41
+ export function compileMatcher(matcher) {
42
+ const trimmed = matcher.trim();
43
+ if (trimmed === '' || trimmed === '*') {
44
+ return () => true;
45
+ }
46
+ // 1. Slash-delimited regex literal.
47
+ const slashMatch = SLASH_REGEX_LITERAL.exec(trimmed);
48
+ if (slashMatch) {
49
+ const body = slashMatch[1] ?? '';
50
+ const flags = slashMatch[2] ?? '';
51
+ if (!ALLOWED_REGEX_FLAGS.test(flags)) {
52
+ throw new Error(`pugi hooks v2: matcher '${matcher}' has unsupported regex flags '${flags}'`);
53
+ }
54
+ try {
55
+ const re = new RegExp(body, flags);
56
+ return (candidate) => re.test(candidate);
57
+ }
58
+ catch (error) {
59
+ throw new Error(`pugi hooks v2: matcher '${matcher}' is not a valid regex: ${error.message}`);
60
+ }
61
+ }
62
+ // 2. Prefix-rooted regex shortcut: starts with `^` OR ends with `$`
63
+ // AND no `|` (alternation handled below). Also a string
64
+ // containing regex meta but no `|` and not pure wildcard glob.
65
+ const looksLikeRegex = (trimmed.startsWith('^') || trimmed.endsWith('$')) &&
66
+ !trimmed.includes('|');
67
+ const looksLikeMetaRegex = REGEX_META_CHARS.test(trimmed) &&
68
+ !trimmed.includes('|') &&
69
+ !isPureWildcardGlob(trimmed);
70
+ if (looksLikeRegex || looksLikeMetaRegex) {
71
+ try {
72
+ const re = new RegExp(trimmed);
73
+ return (candidate) => re.test(candidate);
74
+ }
75
+ catch (error) {
76
+ throw new Error(`pugi hooks v2: matcher '${matcher}' is not a valid regex: ${error.message}`);
77
+ }
78
+ }
79
+ // 3. Pipe alternation.
80
+ if (trimmed.includes('|')) {
81
+ const alts = trimmed
82
+ .split('|')
83
+ .map((s) => s.trim())
84
+ .filter((s) => s.length > 0);
85
+ const predicates = alts.map((alt) => compileMatcher(alt));
86
+ return (candidate) => predicates.some((p) => p(candidate));
87
+ }
88
+ // 4. Wildcard glob.
89
+ if (trimmed.includes('*')) {
90
+ const pattern = trimmed
91
+ .split('*')
92
+ .map((part) => escapeRegex(part))
93
+ .join('.*');
94
+ const re = new RegExp(`^${pattern}$`);
95
+ return (candidate) => re.test(candidate);
96
+ }
97
+ // 5. Exact match.
98
+ return (candidate) => candidate === trimmed;
99
+ }
100
+ /**
101
+ * True iff the matcher uses ONLY the wildcard sub-grammar (literal
102
+ * chars + `*`) - no other regex meta. Used to disambiguate `mcp__*`
103
+ * from `mcp__\d+`.
104
+ */
105
+ function isPureWildcardGlob(matcher) {
106
+ for (const ch of matcher) {
107
+ if (ch === '*')
108
+ continue;
109
+ if (REGEX_META_CHARS.test(ch))
110
+ return false;
111
+ }
112
+ return matcher.includes('*');
113
+ }
114
+ function escapeRegex(value) {
115
+ return value.replace(ESCAPE_REGEX_META, '\\$&');
116
+ }
117
+ /**
118
+ * Quick predicate over a matcher + candidate. Compiles + executes in
119
+ * one shot. Use `compileMatcher` directly when the same matcher is
120
+ * applied to many candidates.
121
+ */
122
+ export function matches(matcher, candidate) {
123
+ return compileMatcher(matcher)(candidate);
124
+ }
125
+ //# sourceMappingURL=matcher.js.map
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Pugi hooks v2 — trust ledger .
3
+ *
4
+ * Mirrors `core/mcp/trust.ts`. First-run interactive prompt fires
5
+ * before any hook executes; the operator's decision (allow / deny /
6
+ * always-allow) is persisted to `~/.pugi/hooks-trust.json` so the
7
+ * prompt does not re-fire on every REPL boot.
8
+ *
9
+ * Why a separate ledger from MCP trust:
10
+ * - Hook commands run arbitrary shell as the operator. A trust
11
+ * decision for a hook script does not transfer to MCP servers and
12
+ * vice-versa; the surfaces should not collide.
13
+ * - The hook ledger is keyed by command STRING (with the project
14
+ * path scope) — two projects with the same `.pugi/hooks.json`
15
+ * content trust independently. This prevents a malicious upstream
16
+ * `pugi init` template from inheriting a trust decision from an
17
+ * unrelated project on the same machine.
18
+ *
19
+ * Schema:
20
+ * {
21
+ * "schema": 1,
22
+ * "entries": {
23
+ * "<workspaceRoot>::<command-hash>": {
24
+ * "state": "trusted" | "denied" | "pending",
25
+ * "decidedAt": "<iso8601>",
26
+ * "command": "<truncated command>",
27
+ * "workspaceRoot": "<absolute path>"
28
+ * }
29
+ * }
30
+ * }
31
+ *
32
+ * Brand voice: ASCII only.
33
+ */
34
+ import { createHash } from 'node:crypto';
35
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
36
+ import { homedir } from 'node:os';
37
+ import { dirname, resolve } from 'node:path';
38
+ import { z } from 'zod';
39
+ const TRUST_LEDGER_FILENAME = 'hooks-trust.json';
40
+ const ledgerEntrySchema = z.object({
41
+ state: z.enum(['pending', 'trusted', 'denied']),
42
+ decidedAt: z.string().datetime(),
43
+ command: z.string().min(1),
44
+ workspaceRoot: z.string().min(1),
45
+ });
46
+ const ledgerSchema = z.object({
47
+ schema: z.literal(1).default(1),
48
+ entries: z.record(ledgerEntrySchema).default({}),
49
+ });
50
+ /** Override-friendly path resolution. Honors `PUGI_HOME`. */
51
+ export function trustLedgerPath(homeOverride) {
52
+ const home = homeOverride ?? process.env.PUGI_HOME ?? resolve(homedir(), '.pugi');
53
+ return resolve(home, TRUST_LEDGER_FILENAME);
54
+ }
55
+ /** Stable key for a (workspace, command) pair. */
56
+ export function trustKey(workspaceRoot, command) {
57
+ const hash = createHash('sha256')
58
+ .update(`${workspaceRoot}\0${command}`)
59
+ .digest('hex')
60
+ .slice(0, 16);
61
+ return `${workspaceRoot}::${hash}`;
62
+ }
63
+ function readLedger(homeOverride) {
64
+ const path = trustLedgerPath(homeOverride);
65
+ if (!existsSync(path)) {
66
+ return { schema: 1, entries: {} };
67
+ }
68
+ const raw = readFileSync(path, 'utf8');
69
+ if (raw.trim() === '') {
70
+ return { schema: 1, entries: {} };
71
+ }
72
+ return ledgerSchema.parse(JSON.parse(raw));
73
+ }
74
+ function writeLedger(ledger, homeOverride) {
75
+ const path = trustLedgerPath(homeOverride);
76
+ mkdirSync(dirname(path), { recursive: true });
77
+ writeFileSync(path, `${JSON.stringify(ledger, null, 2)}\n`, {
78
+ encoding: 'utf8',
79
+ mode: 0o600,
80
+ });
81
+ }
82
+ /**
83
+ * Look up the trust state for (workspace, command). Returns `pending`
84
+ * when no decision has been recorded.
85
+ */
86
+ export function getHookTrust(workspaceRoot, command, homeOverride) {
87
+ const ledger = readLedger(homeOverride);
88
+ const entry = ledger.entries[trustKey(workspaceRoot, command)];
89
+ return entry ? entry.state : 'pending';
90
+ }
91
+ /** Persist a trust decision. */
92
+ export function setHookTrust(workspaceRoot, command, state, homeOverride) {
93
+ const ledger = readLedger(homeOverride);
94
+ ledger.entries[trustKey(workspaceRoot, command)] = {
95
+ state,
96
+ decidedAt: new Date().toISOString(),
97
+ command: command.slice(0, 200),
98
+ workspaceRoot,
99
+ };
100
+ writeLedger(ledger, homeOverride);
101
+ }
102
+ /** Bulk listing for `pugi hooks trust list`. */
103
+ export function listHookTrust(homeOverride) {
104
+ const ledger = readLedger(homeOverride);
105
+ return Object.values(ledger.entries)
106
+ .map((entry) => ({
107
+ workspaceRoot: entry.workspaceRoot,
108
+ command: entry.command,
109
+ state: entry.state,
110
+ decidedAt: entry.decidedAt,
111
+ }))
112
+ .sort((a, b) => a.workspaceRoot.localeCompare(b.workspaceRoot));
113
+ }
114
+ /**
115
+ * Resolve trust for a hook, prompting the operator on first encounter.
116
+ * `once` answers do NOT persist — the hook runs but the ledger stays
117
+ * pending so the next session re-prompts.
118
+ *
119
+ * The headless behaviour: when `promptFn` is undefined, the result is
120
+ * `denied`. Non-interactive sessions must NOT execute unknown hooks.
121
+ */
122
+ export async function ensureHookTrust(input, promptFn, homeOverride) {
123
+ const known = getHookTrust(input.workspaceRoot, input.command, homeOverride);
124
+ if (known !== 'pending') {
125
+ return known;
126
+ }
127
+ if (!promptFn) {
128
+ // Headless mode without prompt = refuse for safety.
129
+ return 'denied';
130
+ }
131
+ const answer = await promptFn(input);
132
+ if (answer === 'trust') {
133
+ setHookTrust(input.workspaceRoot, input.command, 'trusted', homeOverride);
134
+ return 'trusted';
135
+ }
136
+ if (answer === 'deny') {
137
+ setHookTrust(input.workspaceRoot, input.command, 'denied', homeOverride);
138
+ return 'denied';
139
+ }
140
+ // `once` -> run this time but do NOT persist.
141
+ return 'trusted';
142
+ }
143
+ //# sourceMappingURL=trust.js.map
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pugi hooks v2 — the upstream tool parity types .
3
+ *
4
+ * The v2 surface is a fresh implementation alongside the existing
5
+ * `core/hooks/` MVP module. It is NOT a replacement — both surfaces
6
+ * co-exist so the operator's legacy `~/.pugi/hooks-mvp.json` configs
7
+ * keep working while v2 grows the matcher grammar, the JSON decision
8
+ * protocol, and the per-project trust ledger.
9
+ *
10
+ * Why v2 instead of evolving v1 in-place:
11
+ * - The v1 runner spawns hooks via `/bin/sh -c` with the payload in
12
+ * env vars + stdin. v2 ships a richer stdin contract (schema_version,
13
+ * transcript_path, cwd, permission_mode, agent_id, agent_type) that
14
+ * would silently break v1 scripts if we mutated the existing surface.
15
+ * - The v1 matcher is exact/star only. v2 adds regex, |-alternation,
16
+ * and MCP wildcards. A clean module is easier to reason about than
17
+ * a backward-compat branch inside the existing matcher.
18
+ * - The v1 trust model is implicit (operator runs `pugi hooks doctor`
19
+ * to surface config errors). v2 introduces a first-run interactive
20
+ * prompt with a persisted ledger, matching MCP's trust pattern.
21
+ *
22
+ * Phase 1 wires 9 of the 31 events the upstream tool exposes. The remaining
23
+ * 22 land in Phase 2 along with hook chains. The type union below
24
+ * carries ALL 31 names so chains + future events compile against the
25
+ * same surface without churn.
26
+ *
27
+ * Brand voice: ASCII only, no emoji, no em-dashes.
28
+ */
29
+ /** All 31 events the v2 surface understands. */
30
+ export const ALL_HOOK_EVENTS_V2 = [
31
+ 'SessionStart',
32
+ 'SessionEnd',
33
+ 'UserPromptSubmit',
34
+ 'PreToolUse',
35
+ 'PostToolUse',
36
+ 'PostToolUseFailure',
37
+ 'Stop',
38
+ 'PreCompact',
39
+ 'PostCompact',
40
+ 'Notification',
41
+ 'SubagentStart',
42
+ 'SubagentStop',
43
+ 'SubagentToolUse',
44
+ 'PermissionRequest',
45
+ 'PermissionGranted',
46
+ 'PermissionDenied',
47
+ 'PreEdit',
48
+ 'PostEdit',
49
+ 'PreWrite',
50
+ 'PostWrite',
51
+ 'PreRead',
52
+ 'PostRead',
53
+ 'PreBash',
54
+ 'PostBash',
55
+ 'McpServerConnect',
56
+ 'McpServerDisconnect',
57
+ 'McpToolCall',
58
+ 'McpToolResult',
59
+ 'AgentSpawn',
60
+ 'AgentComplete',
61
+ 'TaskCompleted',
62
+ 'PromptInjection',
63
+ ];
64
+ /**
65
+ * The 9 events emitted in Phase 1. A separate constant from
66
+ * `ALL_HOOK_EVENTS_V2` so callers can reason about "what fires today"
67
+ * without coupling to the entire reservation.
68
+ */
69
+ export const PHASE_1_HOOK_EVENTS = [
70
+ 'SessionStart',
71
+ 'SessionEnd',
72
+ 'UserPromptSubmit',
73
+ 'PreToolUse',
74
+ 'PostToolUse',
75
+ 'PostToolUseFailure',
76
+ 'Stop',
77
+ 'PreCompact',
78
+ 'PostCompact',
79
+ ];
80
+ /** Default per-hook timeout in ms. */
81
+ export const DEFAULT_HOOK_TIMEOUT_MS_V2 = 5_000;
82
+ /** Hard upper bound on per-hook timeout. */
83
+ export const MAX_HOOK_TIMEOUT_MS_V2 = 60_000;
84
+ /** Hook stdout/stderr cap. Hooks emitting more are killed. */
85
+ export const HOOK_OUTPUT_CAP_BYTES_V2 = 1024 * 1024;
86
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Worktree lifecycle hooks - PUGI-487.
3
+ *
4
+ * Extends the existing hook lifecycle (`SessionStart` / `PreToolUse` /
5
+ * etc.) with two new events targeted at the user-facing `--worktree`
6
+ * flag:
7
+ *
8
+ * - `WorktreeCreate`: fired when the operator runs
9
+ * `pugi --worktree <name>` BEFORE git has touched the filesystem.
10
+ * Receives a JSON document on stdin describing the requested
11
+ * worktree. If the hook's stdout has a JSON envelope with a
12
+ * `directory` field, the runtime uses that path instead of the
13
+ * default `.claude/worktrees/<name>/` location. Non-zero exit aborts
14
+ * worktree creation when `blocking: true` is set.
15
+ *
16
+ * - `WorktreeRemove`: fired when the operator (or the cleanup sweep)
17
+ * is about to delete a worktree. Receives a JSON document on stdin
18
+ * describing the target. Non-zero exit aborts removal when
19
+ * `blocking: true`.
20
+ *
21
+ * Both events follow the JSON-on-stdin pattern of the existing v2
22
+ * hook contract. The dispatcher is intentionally small because the
23
+ * heavy lifting (file load, schema validation, timeout enforcement)
24
+ * lives in `core/hooks/registry.ts` and `core/hooks/runner.ts`.
25
+ *
26
+ * Brand voice: ASCII only, no emoji, no banned words.
27
+ */
28
+ import { spawnSync } from 'node:child_process';
29
+ import { DEFAULT_HOOK_TIMEOUT_MS } from './registry.js';
30
+ /**
31
+ * Fire every hook configured for the given worktree event. Returns the
32
+ * aggregated outcome including any directory override and the
33
+ * blocking-failure short circuit.
34
+ *
35
+ * Each hook receives the JSON payload on stdin and is expected to
36
+ * produce its response on stdout. Hooks are run sequentially (not in
37
+ * parallel) so that ordering across a multi-hook chain stays
38
+ * deterministic - the first directory override wins.
39
+ */
40
+ export function fireWorktreeHooks(config, event, payload, options = {}) {
41
+ const entries = config.list(event);
42
+ const spawn = options.spawn ?? defaultSpawn;
43
+ const results = [];
44
+ let directoryOverride;
45
+ let anyBlocked = false;
46
+ const json = JSON.stringify(payload);
47
+ for (const entry of entries) {
48
+ const start = Date.now();
49
+ const ret = spawn(entry.command, {
50
+ input: json,
51
+ encoding: 'utf8',
52
+ shell: '/bin/sh',
53
+ timeout: entry.timeoutMs ?? DEFAULT_HOOK_TIMEOUT_MS,
54
+ maxBuffer: 1 * 1024 * 1024,
55
+ });
56
+ const elapsedMs = Date.now() - start;
57
+ const stdout = bufToString(ret.stdout);
58
+ const stderr = bufToString(ret.stderr);
59
+ const ok = ret.status === 0;
60
+ const blocked = !ok && entry.blocking === true;
61
+ if (blocked)
62
+ anyBlocked = true;
63
+ let dirOverride;
64
+ if (event === 'WorktreeCreate' && ok && stdout.length > 0) {
65
+ dirOverride = extractDirectoryOverride(stdout);
66
+ if (dirOverride && !directoryOverride) {
67
+ directoryOverride = dirOverride;
68
+ }
69
+ }
70
+ results.push({
71
+ command: entry.command.slice(0, 200),
72
+ exitCode: ret.status,
73
+ stdout,
74
+ stderr,
75
+ elapsedMs,
76
+ ok,
77
+ blocked,
78
+ ...(dirOverride ? { directoryOverride: dirOverride } : {}),
79
+ });
80
+ if (blocked)
81
+ break;
82
+ }
83
+ return {
84
+ event,
85
+ results,
86
+ anyBlocked,
87
+ ...(directoryOverride ? { directoryOverride } : {}),
88
+ };
89
+ }
90
+ /**
91
+ * Extract a `directory` override from hook stdout. Two accepted shapes:
92
+ *
93
+ * 1. JSON object with `{ "directory": "<path>" }`.
94
+ * 2. Bare path string (trimmed first non-empty line) when the hook
95
+ * writes plain text. We accept the trimmed line as the override
96
+ * ONLY if it looks like a non-flag path (no leading `-`, no `\n`,
97
+ * no shell metacharacters).
98
+ *
99
+ * Returns undefined when no override is detected.
100
+ */
101
+ export function extractDirectoryOverride(stdout) {
102
+ const trimmed = stdout.trim();
103
+ if (trimmed.length === 0)
104
+ return undefined;
105
+ if (trimmed.startsWith('{')) {
106
+ try {
107
+ const parsed = JSON.parse(trimmed);
108
+ if (parsed &&
109
+ typeof parsed === 'object' &&
110
+ !Array.isArray(parsed) &&
111
+ typeof parsed.directory === 'string') {
112
+ const dir = parsed.directory.trim();
113
+ if (isSafePathToken(dir))
114
+ return dir;
115
+ }
116
+ }
117
+ catch {
118
+ // fall through to bare-path detection
119
+ }
120
+ }
121
+ const firstLine = trimmed.split(/\r?\n/, 1)[0] ?? '';
122
+ if (firstLine.length === 0)
123
+ return undefined;
124
+ if (isSafePathToken(firstLine))
125
+ return firstLine;
126
+ return undefined;
127
+ }
128
+ /**
129
+ * Cheap defence-in-depth check: a directory override must not look
130
+ * like a shell injection vector. The override is later validated for
131
+ * containment (the runtime refuses paths that escape the repo root),
132
+ * but rejecting obvious metacharacters here surfaces a cleaner error.
133
+ */
134
+ function isSafePathToken(value) {
135
+ if (value.length === 0)
136
+ return false;
137
+ if (value.startsWith('-'))
138
+ return false;
139
+ if (/[;`$&|<>\n\r\t]/.test(value))
140
+ return false;
141
+ return true;
142
+ }
143
+ function defaultSpawn(command, options) {
144
+ const result = spawnSync(command, [], options);
145
+ return {
146
+ status: result.status,
147
+ stdout: result.stdout ?? '',
148
+ stderr: result.stderr ?? '',
149
+ };
150
+ }
151
+ function bufToString(v) {
152
+ if (v === undefined || v === null)
153
+ return '';
154
+ if (typeof v === 'string')
155
+ return v;
156
+ return v.toString('utf8');
157
+ }
158
+ //# sourceMappingURL=worktree-events.js.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Inline image renderer for Pugi TUI .
3
+ *
4
+ * Wraps `terminal-image` (sindresorhus, MIT, ~150K weekly DLs) which
5
+ * detects iTerm2 / Kitty inline-image protocols, falls back к
6
+ * Sixel-capable terminals, и finally к chafa-style ANSI block art.
7
+ *
8
+ * What this module adds:
9
+ * - File-size cap (default 8 MB) — guards against accidental
10
+ * screen-fill from a 50 MB raw PNG drop
11
+ * - Width/height defaults aligned with Pugi TUI column budget
12
+ * - Safe degradation: returns a single-line `[image: <path>]`
13
+ * placeholder when the host stream is not a TTY (e.g. piped output,
14
+ * CI runs, log captures) instead of dumping raw escape sequences
15
+ * - Format allowlist — refuses non-image extensions early без
16
+ * decoding the entire file
17
+ *
18
+ * Pure-ish: no globals, no shared state. Caller owns the file path и
19
+ * decides where the rendered string lands (Ink Text node, raw stdout,
20
+ * fallback chat log).
21
+ */
22
+ import { readFile, stat } from 'node:fs/promises';
23
+ import path from 'node:path';
24
+ import terminalImage from 'terminal-image';
25
+ const DEFAULT_MAX_BYTES = 8 * 1024 * 1024;
26
+ const DEFAULT_TUI_WIDTH = 80;
27
+ const DEFAULT_TUI_HEIGHT = 30;
28
+ const ALLOWED_EXTENSIONS = new Set([
29
+ '.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp', '.tiff', '.tif',
30
+ ]);
31
+ export function isImagePath(filePath) {
32
+ return ALLOWED_EXTENSIONS.has(path.extname(filePath).toLowerCase());
33
+ }
34
+ export async function renderImageFile(filePath, options = {}) {
35
+ if (!isImagePath(filePath)) {
36
+ return placeholder(filePath, 'unsupported-format');
37
+ }
38
+ const ttyFlag = options.isTTY ?? process.stdout.isTTY ?? false;
39
+ if (!ttyFlag) {
40
+ return placeholder(filePath, 'not-a-tty');
41
+ }
42
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
43
+ if (maxBytes <= 0) {
44
+ throw new RangeError(`maxBytes must be > 0, got ${maxBytes}`);
45
+ }
46
+ try {
47
+ const stats = await stat(filePath);
48
+ if (stats.size > maxBytes) {
49
+ return placeholder(filePath, 'too-large');
50
+ }
51
+ const buf = await readFile(filePath);
52
+ const termOptions = {
53
+ width: options.width ?? DEFAULT_TUI_WIDTH,
54
+ height: options.height ?? DEFAULT_TUI_HEIGHT,
55
+ preserveAspectRatio: options.preserveAspectRatio ?? true,
56
+ };
57
+ const content = await terminalImage.buffer(buf, termOptions);
58
+ return { content, rendered: true, reason: 'rendered' };
59
+ }
60
+ catch {
61
+ return placeholder(filePath, 'read-error');
62
+ }
63
+ }
64
+ function placeholder(filePath, reason) {
65
+ return {
66
+ content: `[image: ${path.basename(filePath)}]`,
67
+ rendered: false,
68
+ reason,
69
+ };
70
+ }
71
+ //# sourceMappingURL=renderer.js.map