whale-code 6.4.0

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 (319) hide show
  1. package/README.md +95 -0
  2. package/bin/swag-agent.js +9 -0
  3. package/bin/swagmanager-mcp.js +321 -0
  4. package/dist/cli/app.d.ts +26 -0
  5. package/dist/cli/app.js +64 -0
  6. package/dist/cli/chat/AgentSelector.d.ts +14 -0
  7. package/dist/cli/chat/AgentSelector.js +14 -0
  8. package/dist/cli/chat/ChatApp.d.ts +9 -0
  9. package/dist/cli/chat/ChatApp.js +267 -0
  10. package/dist/cli/chat/ChatInput.d.ts +39 -0
  11. package/dist/cli/chat/ChatInput.js +509 -0
  12. package/dist/cli/chat/MarkdownText.d.ts +10 -0
  13. package/dist/cli/chat/MarkdownText.js +20 -0
  14. package/dist/cli/chat/MessageList.d.ts +37 -0
  15. package/dist/cli/chat/MessageList.js +80 -0
  16. package/dist/cli/chat/ModelSelector.d.ts +20 -0
  17. package/dist/cli/chat/ModelSelector.js +73 -0
  18. package/dist/cli/chat/RewindViewer.d.ts +26 -0
  19. package/dist/cli/chat/RewindViewer.js +185 -0
  20. package/dist/cli/chat/StoreSelector.d.ts +14 -0
  21. package/dist/cli/chat/StoreSelector.js +24 -0
  22. package/dist/cli/chat/StreamingText.d.ts +12 -0
  23. package/dist/cli/chat/StreamingText.js +12 -0
  24. package/dist/cli/chat/SubagentPanel.d.ts +45 -0
  25. package/dist/cli/chat/SubagentPanel.js +110 -0
  26. package/dist/cli/chat/TeamPanel.d.ts +21 -0
  27. package/dist/cli/chat/TeamPanel.js +42 -0
  28. package/dist/cli/chat/ToolIndicator.d.ts +25 -0
  29. package/dist/cli/chat/ToolIndicator.js +436 -0
  30. package/dist/cli/chat/hooks/useAgentLoop.d.ts +39 -0
  31. package/dist/cli/chat/hooks/useAgentLoop.js +382 -0
  32. package/dist/cli/chat/hooks/useSlashCommands.d.ts +37 -0
  33. package/dist/cli/chat/hooks/useSlashCommands.js +387 -0
  34. package/dist/cli/commands/config-cmd.d.ts +10 -0
  35. package/dist/cli/commands/config-cmd.js +99 -0
  36. package/dist/cli/commands/doctor.d.ts +14 -0
  37. package/dist/cli/commands/doctor.js +172 -0
  38. package/dist/cli/commands/init.d.ts +16 -0
  39. package/dist/cli/commands/init.js +278 -0
  40. package/dist/cli/commands/mcp.d.ts +12 -0
  41. package/dist/cli/commands/mcp.js +162 -0
  42. package/dist/cli/login/LoginApp.d.ts +7 -0
  43. package/dist/cli/login/LoginApp.js +157 -0
  44. package/dist/cli/print-mode.d.ts +31 -0
  45. package/dist/cli/print-mode.js +202 -0
  46. package/dist/cli/serve-mode.d.ts +37 -0
  47. package/dist/cli/serve-mode.js +636 -0
  48. package/dist/cli/services/agent-definitions.d.ts +25 -0
  49. package/dist/cli/services/agent-definitions.js +91 -0
  50. package/dist/cli/services/agent-events.d.ts +178 -0
  51. package/dist/cli/services/agent-events.js +175 -0
  52. package/dist/cli/services/agent-loop.d.ts +90 -0
  53. package/dist/cli/services/agent-loop.js +762 -0
  54. package/dist/cli/services/agent-worker-base.d.ts +97 -0
  55. package/dist/cli/services/agent-worker-base.js +220 -0
  56. package/dist/cli/services/auth-service.d.ts +30 -0
  57. package/dist/cli/services/auth-service.js +160 -0
  58. package/dist/cli/services/background-processes.d.ts +126 -0
  59. package/dist/cli/services/background-processes.js +318 -0
  60. package/dist/cli/services/browser-auth.d.ts +24 -0
  61. package/dist/cli/services/browser-auth.js +180 -0
  62. package/dist/cli/services/claude-md-loader.d.ts +16 -0
  63. package/dist/cli/services/claude-md-loader.js +58 -0
  64. package/dist/cli/services/config-store.d.ts +47 -0
  65. package/dist/cli/services/config-store.js +79 -0
  66. package/dist/cli/services/debug-log.d.ts +10 -0
  67. package/dist/cli/services/debug-log.js +52 -0
  68. package/dist/cli/services/error-logger.d.ts +58 -0
  69. package/dist/cli/services/error-logger.js +269 -0
  70. package/dist/cli/services/file-history.d.ts +21 -0
  71. package/dist/cli/services/file-history.js +83 -0
  72. package/dist/cli/services/format-server-response.d.ts +16 -0
  73. package/dist/cli/services/format-server-response.js +440 -0
  74. package/dist/cli/services/git-context.d.ts +11 -0
  75. package/dist/cli/services/git-context.js +66 -0
  76. package/dist/cli/services/hooks.d.ts +85 -0
  77. package/dist/cli/services/hooks.js +258 -0
  78. package/dist/cli/services/interactive-tools.d.ts +125 -0
  79. package/dist/cli/services/interactive-tools.js +260 -0
  80. package/dist/cli/services/keybinding-manager.d.ts +52 -0
  81. package/dist/cli/services/keybinding-manager.js +115 -0
  82. package/dist/cli/services/local-tools.d.ts +22 -0
  83. package/dist/cli/services/local-tools.js +697 -0
  84. package/dist/cli/services/lsp-manager.d.ts +18 -0
  85. package/dist/cli/services/lsp-manager.js +717 -0
  86. package/dist/cli/services/mcp-client.d.ts +48 -0
  87. package/dist/cli/services/mcp-client.js +157 -0
  88. package/dist/cli/services/memory-manager.d.ts +16 -0
  89. package/dist/cli/services/memory-manager.js +57 -0
  90. package/dist/cli/services/model-manager.d.ts +18 -0
  91. package/dist/cli/services/model-manager.js +71 -0
  92. package/dist/cli/services/model-router.d.ts +26 -0
  93. package/dist/cli/services/model-router.js +149 -0
  94. package/dist/cli/services/permission-modes.d.ts +13 -0
  95. package/dist/cli/services/permission-modes.js +43 -0
  96. package/dist/cli/services/rewind.d.ts +84 -0
  97. package/dist/cli/services/rewind.js +194 -0
  98. package/dist/cli/services/ripgrep.d.ts +28 -0
  99. package/dist/cli/services/ripgrep.js +138 -0
  100. package/dist/cli/services/sandbox.d.ts +29 -0
  101. package/dist/cli/services/sandbox.js +97 -0
  102. package/dist/cli/services/server-tools.d.ts +61 -0
  103. package/dist/cli/services/server-tools.js +543 -0
  104. package/dist/cli/services/session-persistence.d.ts +23 -0
  105. package/dist/cli/services/session-persistence.js +99 -0
  106. package/dist/cli/services/subagent-worker.d.ts +19 -0
  107. package/dist/cli/services/subagent-worker.js +41 -0
  108. package/dist/cli/services/subagent.d.ts +47 -0
  109. package/dist/cli/services/subagent.js +647 -0
  110. package/dist/cli/services/system-prompt.d.ts +7 -0
  111. package/dist/cli/services/system-prompt.js +198 -0
  112. package/dist/cli/services/team-lead.d.ts +73 -0
  113. package/dist/cli/services/team-lead.js +512 -0
  114. package/dist/cli/services/team-state.d.ts +77 -0
  115. package/dist/cli/services/team-state.js +398 -0
  116. package/dist/cli/services/teammate.d.ts +31 -0
  117. package/dist/cli/services/teammate.js +689 -0
  118. package/dist/cli/services/telemetry.d.ts +61 -0
  119. package/dist/cli/services/telemetry.js +209 -0
  120. package/dist/cli/services/tools/agent-tools.d.ts +14 -0
  121. package/dist/cli/services/tools/agent-tools.js +347 -0
  122. package/dist/cli/services/tools/file-ops.d.ts +15 -0
  123. package/dist/cli/services/tools/file-ops.js +487 -0
  124. package/dist/cli/services/tools/search-tools.d.ts +8 -0
  125. package/dist/cli/services/tools/search-tools.js +186 -0
  126. package/dist/cli/services/tools/shell-exec.d.ts +10 -0
  127. package/dist/cli/services/tools/shell-exec.js +168 -0
  128. package/dist/cli/services/tools/task-manager.d.ts +28 -0
  129. package/dist/cli/services/tools/task-manager.js +209 -0
  130. package/dist/cli/services/tools/web-tools.d.ts +11 -0
  131. package/dist/cli/services/tools/web-tools.js +395 -0
  132. package/dist/cli/setup/SetupApp.d.ts +9 -0
  133. package/dist/cli/setup/SetupApp.js +191 -0
  134. package/dist/cli/shared/MatrixIntro.d.ts +4 -0
  135. package/dist/cli/shared/MatrixIntro.js +83 -0
  136. package/dist/cli/shared/Theme.d.ts +74 -0
  137. package/dist/cli/shared/Theme.js +127 -0
  138. package/dist/cli/shared/WhaleBanner.d.ts +10 -0
  139. package/dist/cli/shared/WhaleBanner.js +12 -0
  140. package/dist/cli/shared/markdown.d.ts +21 -0
  141. package/dist/cli/shared/markdown.js +756 -0
  142. package/dist/cli/status/StatusApp.d.ts +4 -0
  143. package/dist/cli/status/StatusApp.js +105 -0
  144. package/dist/cli/stores/StoreApp.d.ts +7 -0
  145. package/dist/cli/stores/StoreApp.js +81 -0
  146. package/dist/index.d.ts +15 -0
  147. package/dist/index.js +538 -0
  148. package/dist/local-agent/connection.d.ts +48 -0
  149. package/dist/local-agent/connection.js +332 -0
  150. package/dist/local-agent/discovery.d.ts +18 -0
  151. package/dist/local-agent/discovery.js +146 -0
  152. package/dist/local-agent/executor.d.ts +34 -0
  153. package/dist/local-agent/executor.js +241 -0
  154. package/dist/local-agent/index.d.ts +14 -0
  155. package/dist/local-agent/index.js +198 -0
  156. package/dist/node/adapters/base.d.ts +35 -0
  157. package/dist/node/adapters/base.js +10 -0
  158. package/dist/node/adapters/discord.d.ts +29 -0
  159. package/dist/node/adapters/discord.js +299 -0
  160. package/dist/node/adapters/email.d.ts +23 -0
  161. package/dist/node/adapters/email.js +218 -0
  162. package/dist/node/adapters/imessage.d.ts +17 -0
  163. package/dist/node/adapters/imessage.js +118 -0
  164. package/dist/node/adapters/slack.d.ts +26 -0
  165. package/dist/node/adapters/slack.js +259 -0
  166. package/dist/node/adapters/sms.d.ts +23 -0
  167. package/dist/node/adapters/sms.js +161 -0
  168. package/dist/node/adapters/telegram.d.ts +17 -0
  169. package/dist/node/adapters/telegram.js +101 -0
  170. package/dist/node/adapters/webchat.d.ts +27 -0
  171. package/dist/node/adapters/webchat.js +160 -0
  172. package/dist/node/adapters/whatsapp.d.ts +28 -0
  173. package/dist/node/adapters/whatsapp.js +230 -0
  174. package/dist/node/cli.d.ts +2 -0
  175. package/dist/node/cli.js +325 -0
  176. package/dist/node/config.d.ts +17 -0
  177. package/dist/node/config.js +31 -0
  178. package/dist/node/runtime.d.ts +50 -0
  179. package/dist/node/runtime.js +351 -0
  180. package/dist/server/handlers/__test-utils__/mock-supabase.d.ts +11 -0
  181. package/dist/server/handlers/__test-utils__/mock-supabase.js +393 -0
  182. package/dist/server/handlers/analytics.d.ts +17 -0
  183. package/dist/server/handlers/analytics.js +266 -0
  184. package/dist/server/handlers/api-keys.d.ts +6 -0
  185. package/dist/server/handlers/api-keys.js +221 -0
  186. package/dist/server/handlers/billing.d.ts +33 -0
  187. package/dist/server/handlers/billing.js +272 -0
  188. package/dist/server/handlers/browser.d.ts +10 -0
  189. package/dist/server/handlers/browser.js +517 -0
  190. package/dist/server/handlers/catalog.d.ts +99 -0
  191. package/dist/server/handlers/catalog.js +976 -0
  192. package/dist/server/handlers/comms.d.ts +254 -0
  193. package/dist/server/handlers/comms.js +588 -0
  194. package/dist/server/handlers/creations.d.ts +6 -0
  195. package/dist/server/handlers/creations.js +479 -0
  196. package/dist/server/handlers/crm.d.ts +89 -0
  197. package/dist/server/handlers/crm.js +538 -0
  198. package/dist/server/handlers/discovery.d.ts +6 -0
  199. package/dist/server/handlers/discovery.js +288 -0
  200. package/dist/server/handlers/embeddings.d.ts +92 -0
  201. package/dist/server/handlers/embeddings.js +197 -0
  202. package/dist/server/handlers/enrichment.d.ts +8 -0
  203. package/dist/server/handlers/enrichment.js +768 -0
  204. package/dist/server/handlers/image-gen.d.ts +6 -0
  205. package/dist/server/handlers/image-gen.js +409 -0
  206. package/dist/server/handlers/inventory.d.ts +319 -0
  207. package/dist/server/handlers/inventory.js +447 -0
  208. package/dist/server/handlers/kali.d.ts +10 -0
  209. package/dist/server/handlers/kali.js +210 -0
  210. package/dist/server/handlers/llm-providers.d.ts +6 -0
  211. package/dist/server/handlers/llm-providers.js +673 -0
  212. package/dist/server/handlers/local-agent.d.ts +6 -0
  213. package/dist/server/handlers/local-agent.js +118 -0
  214. package/dist/server/handlers/meta-ads.d.ts +111 -0
  215. package/dist/server/handlers/meta-ads.js +2279 -0
  216. package/dist/server/handlers/nodes.d.ts +33 -0
  217. package/dist/server/handlers/nodes.js +699 -0
  218. package/dist/server/handlers/operations.d.ts +138 -0
  219. package/dist/server/handlers/operations.js +131 -0
  220. package/dist/server/handlers/platform.d.ts +23 -0
  221. package/dist/server/handlers/platform.js +227 -0
  222. package/dist/server/handlers/supply-chain.d.ts +19 -0
  223. package/dist/server/handlers/supply-chain.js +327 -0
  224. package/dist/server/handlers/transcription.d.ts +17 -0
  225. package/dist/server/handlers/transcription.js +121 -0
  226. package/dist/server/handlers/video-gen.d.ts +6 -0
  227. package/dist/server/handlers/video-gen.js +466 -0
  228. package/dist/server/handlers/voice.d.ts +8 -0
  229. package/dist/server/handlers/voice.js +1146 -0
  230. package/dist/server/handlers/workflow-steps.d.ts +86 -0
  231. package/dist/server/handlers/workflow-steps.js +2349 -0
  232. package/dist/server/handlers/workflows.d.ts +7 -0
  233. package/dist/server/handlers/workflows.js +989 -0
  234. package/dist/server/index.d.ts +1 -0
  235. package/dist/server/index.js +2427 -0
  236. package/dist/server/lib/batch-client.d.ts +80 -0
  237. package/dist/server/lib/batch-client.js +467 -0
  238. package/dist/server/lib/code-worker-pool.d.ts +31 -0
  239. package/dist/server/lib/code-worker-pool.js +224 -0
  240. package/dist/server/lib/code-worker.d.ts +1 -0
  241. package/dist/server/lib/code-worker.js +188 -0
  242. package/dist/server/lib/compaction-service.d.ts +32 -0
  243. package/dist/server/lib/compaction-service.js +162 -0
  244. package/dist/server/lib/logger.d.ts +19 -0
  245. package/dist/server/lib/logger.js +46 -0
  246. package/dist/server/lib/otel.d.ts +38 -0
  247. package/dist/server/lib/otel.js +126 -0
  248. package/dist/server/lib/pg-rate-limiter.d.ts +21 -0
  249. package/dist/server/lib/pg-rate-limiter.js +86 -0
  250. package/dist/server/lib/prompt-sanitizer.d.ts +37 -0
  251. package/dist/server/lib/prompt-sanitizer.js +177 -0
  252. package/dist/server/lib/provider-capabilities.d.ts +85 -0
  253. package/dist/server/lib/provider-capabilities.js +190 -0
  254. package/dist/server/lib/provider-failover.d.ts +74 -0
  255. package/dist/server/lib/provider-failover.js +210 -0
  256. package/dist/server/lib/rate-limiter.d.ts +39 -0
  257. package/dist/server/lib/rate-limiter.js +147 -0
  258. package/dist/server/lib/server-agent-loop.d.ts +107 -0
  259. package/dist/server/lib/server-agent-loop.js +667 -0
  260. package/dist/server/lib/server-subagent.d.ts +78 -0
  261. package/dist/server/lib/server-subagent.js +203 -0
  262. package/dist/server/lib/session-checkpoint.d.ts +51 -0
  263. package/dist/server/lib/session-checkpoint.js +145 -0
  264. package/dist/server/lib/ssrf-guard.d.ts +13 -0
  265. package/dist/server/lib/ssrf-guard.js +240 -0
  266. package/dist/server/lib/supabase-client.d.ts +7 -0
  267. package/dist/server/lib/supabase-client.js +78 -0
  268. package/dist/server/lib/template-resolver.d.ts +31 -0
  269. package/dist/server/lib/template-resolver.js +215 -0
  270. package/dist/server/lib/utils.d.ts +16 -0
  271. package/dist/server/lib/utils.js +147 -0
  272. package/dist/server/local-agent-gateway.d.ts +82 -0
  273. package/dist/server/local-agent-gateway.js +426 -0
  274. package/dist/server/providers/anthropic.d.ts +20 -0
  275. package/dist/server/providers/anthropic.js +199 -0
  276. package/dist/server/providers/bedrock.d.ts +20 -0
  277. package/dist/server/providers/bedrock.js +194 -0
  278. package/dist/server/providers/gemini.d.ts +24 -0
  279. package/dist/server/providers/gemini.js +486 -0
  280. package/dist/server/providers/openai.d.ts +24 -0
  281. package/dist/server/providers/openai.js +522 -0
  282. package/dist/server/providers/registry.d.ts +32 -0
  283. package/dist/server/providers/registry.js +58 -0
  284. package/dist/server/providers/shared.d.ts +32 -0
  285. package/dist/server/providers/shared.js +124 -0
  286. package/dist/server/providers/types.d.ts +92 -0
  287. package/dist/server/providers/types.js +12 -0
  288. package/dist/server/proxy-handlers.d.ts +6 -0
  289. package/dist/server/proxy-handlers.js +89 -0
  290. package/dist/server/tool-router.d.ts +149 -0
  291. package/dist/server/tool-router.js +803 -0
  292. package/dist/server/validation.d.ts +24 -0
  293. package/dist/server/validation.js +301 -0
  294. package/dist/server/worker.d.ts +19 -0
  295. package/dist/server/worker.js +201 -0
  296. package/dist/setup.d.ts +8 -0
  297. package/dist/setup.js +181 -0
  298. package/dist/shared/agent-core.d.ts +157 -0
  299. package/dist/shared/agent-core.js +534 -0
  300. package/dist/shared/anthropic-types.d.ts +105 -0
  301. package/dist/shared/anthropic-types.js +7 -0
  302. package/dist/shared/api-client.d.ts +90 -0
  303. package/dist/shared/api-client.js +379 -0
  304. package/dist/shared/constants.d.ts +33 -0
  305. package/dist/shared/constants.js +80 -0
  306. package/dist/shared/sse-parser.d.ts +26 -0
  307. package/dist/shared/sse-parser.js +259 -0
  308. package/dist/shared/tool-dispatch.d.ts +52 -0
  309. package/dist/shared/tool-dispatch.js +191 -0
  310. package/dist/shared/types.d.ts +72 -0
  311. package/dist/shared/types.js +7 -0
  312. package/dist/updater.d.ts +25 -0
  313. package/dist/updater.js +140 -0
  314. package/dist/webchat/widget.d.ts +0 -0
  315. package/dist/webchat/widget.js +397 -0
  316. package/package.json +95 -0
  317. package/src/cli/services/builtin-skills/commit.md +19 -0
  318. package/src/cli/services/builtin-skills/review-pr.md +21 -0
  319. package/src/cli/services/builtin-skills/review.md +18 -0
@@ -0,0 +1,756 @@
1
+ /**
2
+ * Markdown rendering — Apple-polished terminal output
3
+ *
4
+ * Syntax theme: purples, blues, pinks — no yellow.
5
+ * Financials: green for gains, red for losses/deductions.
6
+ * Uses marked + marked-terminal + cli-highlight.
7
+ */
8
+ import { Marked } from "marked";
9
+ import { markedTerminal } from "marked-terminal";
10
+ import chalk from "chalk";
11
+ import { createRequire } from "module";
12
+ import { colors } from "./Theme.js";
13
+ import { diffWords } from "diff";
14
+ // Note: chalk.level is auto-detected by supports-color.
15
+ // Apple_Terminal → level 2 (256-color), iTerm.app v3+ → level 3 (24-bit).
16
+ // Do NOT force level 3 — Terminal.app can't render 24-bit codes (shows gray).
17
+ const require = createRequire(import.meta.url);
18
+ const { highlight } = require("cli-highlight");
19
+ // ============================================================================
20
+ // Apple Dark palette — derived from Theme.tsx (respects ~/.swagmanager/theme.json)
21
+ // ============================================================================
22
+ const systemBlue = chalk.hex(colors.brand);
23
+ const systemCyan = chalk.hex(colors.info);
24
+ const systemPink = chalk.hex(colors.pink);
25
+ const systemPurple = chalk.hex(colors.purple);
26
+ const systemIndigo = chalk.hex(colors.indigo);
27
+ const systemGreen = chalk.hex(colors.success);
28
+ const systemMint = chalk.hex(colors.mint);
29
+ const systemRed = chalk.hex(colors.error);
30
+ const systemOrange = chalk.hex(colors.warning);
31
+ const text = chalk.hex(colors.text);
32
+ const secondary = chalk.hex(colors.secondary);
33
+ const tertiary = chalk.hex(colors.tertiary);
34
+ const separator = chalk.hex(colors.separator);
35
+ const lavender = chalk.hex(colors.lavender);
36
+ const roseGold = chalk.hex(colors.roseGold);
37
+ // ============================================================================
38
+ // Terminal width helpers — single source of truth for layout widths
39
+ // ============================================================================
40
+ function termWidth() {
41
+ return process.stdout.columns || 80;
42
+ }
43
+ /** Width for top-level assistant text (accounts for MessageList marginLeft=2) */
44
+ export function contentWidth() {
45
+ return termWidth() - 2;
46
+ }
47
+ /** Width for content nested inside tool results (MessageList=2 + ToolIndicator=2 + safety=2) */
48
+ export function toolContentWidth() {
49
+ return termWidth() - 6;
50
+ }
51
+ // ============================================================================
52
+ // console.warn suppression — safe in single-threaded Node.js
53
+ // ============================================================================
54
+ /** Suppress console.warn during a synchronous function call. */
55
+ function withSuppressedWarnings(fn) {
56
+ const orig = console.warn;
57
+ console.warn = () => { };
58
+ try {
59
+ return fn();
60
+ }
61
+ finally {
62
+ console.warn = orig;
63
+ }
64
+ }
65
+ // ============================================================================
66
+ // OSC 8 hyperlinks — only in terminals that support them
67
+ // ============================================================================
68
+ /** Detect if terminal supports OSC 8 clickable hyperlinks */
69
+ const supportsOsc8 = (() => {
70
+ const tp = process.env.TERM_PROGRAM || "";
71
+ if (/iterm|wezterm|kitty|hyper|warp|foot|alacritty/i.test(tp))
72
+ return true;
73
+ if (process.env.VTE_VERSION)
74
+ return true; // GNOME Terminal, Tilix
75
+ if (process.env.WT_SESSION)
76
+ return true; // Windows Terminal
77
+ if (process.env.KONSOLE_VERSION)
78
+ return true; // Konsole
79
+ // Apple Terminal.app does NOT support OSC 8
80
+ return false;
81
+ })();
82
+ function hyperlink(url, text) {
83
+ // mailto: → clean email address, no protocol prefix, no OSC 8
84
+ if (url.startsWith("mailto:")) {
85
+ const email = text || url.slice(7);
86
+ return systemCyan(email);
87
+ }
88
+ // tel: → clean phone number
89
+ if (url.startsWith("tel:")) {
90
+ return systemCyan(text || url.slice(4));
91
+ }
92
+ const display = text || url;
93
+ // OSC 8 clickable links — only where terminal supports them
94
+ if (supportsOsc8) {
95
+ return `\x1B]8;;${url}\x07${systemCyan.underline(display)}\x1B]8;;\x07`;
96
+ }
97
+ // Fallback: colored underlined text (no escape sequences)
98
+ return systemCyan.underline(display);
99
+ }
100
+ // ============================================================================
101
+ // Path helpers
102
+ // ============================================================================
103
+ /** Shorten a file path for code block headers */
104
+ function shortenPathForHeader(fullPath, maxLen = 40) {
105
+ let p = fullPath;
106
+ const cwd = process.cwd();
107
+ const home = process.env.HOME || "";
108
+ if (p.startsWith(cwd + "/"))
109
+ p = p.slice(cwd.length + 1);
110
+ else if (p.startsWith(cwd))
111
+ p = p.slice(cwd.length);
112
+ else if (home && p.startsWith(home))
113
+ p = "~" + p.slice(home.length);
114
+ if (p.length <= maxLen)
115
+ return p;
116
+ const parts = p.split("/");
117
+ const file = parts.pop();
118
+ if (file.length >= maxLen - 4)
119
+ return "…/" + file.slice(-(maxLen - 4));
120
+ const parent = parts.pop();
121
+ return parent ? "…/" + parent + "/" + file : "…/" + file;
122
+ }
123
+ // ============================================================================
124
+ // Syntax highlighting — purples / blues / pinks
125
+ // ============================================================================
126
+ const appleTheme = {
127
+ // Keywords — pink bold
128
+ keyword: systemPink.bold,
129
+ built_in: systemPurple,
130
+ type: systemCyan,
131
+ literal: systemIndigo,
132
+ number: systemMint,
133
+ regexp: systemPink,
134
+ // Strings — lavender
135
+ string: lavender,
136
+ subst: systemCyan,
137
+ symbol: systemPurple,
138
+ // Functions & classes — blue
139
+ class: systemCyan.bold,
140
+ function: systemBlue,
141
+ title: systemBlue.bold,
142
+ params: roseGold,
143
+ // Comments — tertiary italic
144
+ comment: tertiary.italic,
145
+ doctag: secondary.italic,
146
+ meta: systemIndigo,
147
+ "meta-keyword": systemPink,
148
+ "meta-string": lavender,
149
+ // Tags (HTML/JSX)
150
+ tag: systemPink,
151
+ name: systemCyan,
152
+ attr: systemPurple,
153
+ attribute: systemPurple,
154
+ // Variables & properties
155
+ variable: systemCyan,
156
+ property: systemBlue,
157
+ // Diff
158
+ addition: systemGreen,
159
+ deletion: systemRed,
160
+ // Lists & markup
161
+ bullet: systemPurple,
162
+ code: systemPink,
163
+ emphasis: chalk.italic,
164
+ strong: chalk.bold,
165
+ link: systemCyan.underline,
166
+ quote: secondary.italic,
167
+ // Selectors (CSS)
168
+ "selector-tag": systemPink,
169
+ "selector-id": systemBlue,
170
+ "selector-class": systemPurple,
171
+ "selector-pseudo": systemCyan,
172
+ "selector-attr": lavender,
173
+ // Template
174
+ "template-tag": systemPink,
175
+ "template-variable": systemCyan,
176
+ // JSON property names — purple
177
+ section: systemPurple,
178
+ // Fallback
179
+ default: (s) => s,
180
+ };
181
+ // ============================================================================
182
+ // Financial coloring
183
+ // ============================================================================
184
+ function colorizeFinancials(str) {
185
+ return str
186
+ // NOTE: URL hyperlinking is handled by marked's GFM autolink + link/href handlers.
187
+ // Do NOT add URL patterns here — it causes links to render 3-5x.
188
+ // Negative dollar amounts → red (-$1,234.56) — require digit after $
189
+ .replace(/(-\$\d[\d,]*\.?\d*)/g, (m) => systemRed(m))
190
+ // Positive dollar amounts → green ($1,234.56) — require digit after $
191
+ .replace(/((?:^|[^-])\$\d[\d,]*\.?\d*)/g, (m) => systemGreen(m))
192
+ // Negative percentages → red
193
+ .replace(/(-\d+\.?\d*%)/g, (m) => systemRed(m))
194
+ // Positive percentages → cyan
195
+ .replace(/((?:^|[^-])\d+\.?\d*%)/g, (m) => systemCyan(m))
196
+ // Explicit positive → green
197
+ .replace(/(\+\$?[\d,]+\.?\d*)/g, (m) => systemGreen(m))
198
+ // Financial-specific words → green (no generic words like running/ready/started)
199
+ .replace(/\b(profit|revenue|gain|in stock|available)\b/gi, (m) => systemGreen(m))
200
+ // Financial/status negatives → red
201
+ .replace(/\b(loss|deduction|deficit|expense|out of stock|low stock|overdue|expired|cancelled|failed|error|crashed|EADDRINUSE|ENOENT)\b/gi, (m) => systemRed(m));
202
+ }
203
+ // ============================================================================
204
+ // Markdown renderer
205
+ // ============================================================================
206
+ // ── Isolated marked instance (no global side effects) ──
207
+ const md = new Marked();
208
+ md.use(markedTerminal({
209
+ // Headings — bold blue
210
+ firstHeading: systemBlue.bold,
211
+ heading: systemBlue.bold,
212
+ // Inline
213
+ codespan: systemPink,
214
+ strong: text.bold,
215
+ em: lavender.italic,
216
+ // Blocks
217
+ blockquote: secondary.italic,
218
+ paragraph: (body) => colorizeFinancials(body),
219
+ hr: () => separator("─".repeat(50)),
220
+ // Links — OSC 8 clickable (single source of truth for URL rendering)
221
+ // NOTE: Only use `link` handler, NOT `href` — having both causes double-hyperlinking
222
+ link: (href, _title, text) => hyperlink(href, text !== href ? text : undefined),
223
+ // Lists — purple bullets, financial-aware
224
+ list: (body, ordered) => {
225
+ if (ordered) {
226
+ let n = 0;
227
+ return body.replace(/^\* /gm, () => {
228
+ n++;
229
+ return `${systemIndigo(String(n) + ".")} `;
230
+ });
231
+ }
232
+ return body.replace(/^\* /gm, `${systemPurple("●")} `);
233
+ },
234
+ listitem: (itemText) => {
235
+ return colorizeFinancials(itemText);
236
+ },
237
+ // Layout — adapt to terminal width
238
+ reflowText: false,
239
+ showSectionPrefix: false,
240
+ width: Math.min(120, contentWidth()),
241
+ tab: 2,
242
+ }, {
243
+ // cli-highlight — purple/blue/pink syntax theme
244
+ theme: appleTheme,
245
+ ignoreIllegals: true,
246
+ }));
247
+ // ============================================================================
248
+ // Diff renderer — background colors + word-level diff (Claude Code parity)
249
+ // ============================================================================
250
+ // Background colors for diff lines — derived from Theme (256-color safe cube values)
251
+ const diffAddedBg = chalk.bgHex(colors.diffAddedBg).white;
252
+ const diffRemovedBg = chalk.bgHex(colors.diffRemovedBg).white;
253
+ const diffWordAdded = chalk.bgHex(colors.diffWordAdded).whiteBright.bold;
254
+ const diffWordRemoved = chalk.bgHex(colors.diffWordRemoved).whiteBright.bold;
255
+ /** Compute word-level diff between two lines using the `diff` library. */
256
+ function wordDiff(oldLine, newLine) {
257
+ const changes = diffWords(oldLine, newLine);
258
+ const oldSegs = [];
259
+ const newSegs = [];
260
+ for (const change of changes) {
261
+ if (change.added) {
262
+ newSegs.push({ text: change.value, changed: true });
263
+ }
264
+ else if (change.removed) {
265
+ oldSegs.push({ text: change.value, changed: true });
266
+ }
267
+ else {
268
+ oldSegs.push({ text: change.value, changed: false });
269
+ newSegs.push({ text: change.value, changed: false });
270
+ }
271
+ }
272
+ // If >60% of either line changed, fall back to full-line highlight
273
+ const oldChanged = oldSegs.filter(s => s.changed).reduce((n, s) => n + s.text.length, 0);
274
+ const newChanged = newSegs.filter(s => s.changed).reduce((n, s) => n + s.text.length, 0);
275
+ if ((oldLine.length > 0 && oldChanged / oldLine.length > 0.6) ||
276
+ (newLine.length > 0 && newChanged / newLine.length > 0.6)) {
277
+ return { old: [{ text: oldLine, changed: true }], new: [{ text: newLine, changed: true }] };
278
+ }
279
+ return { old: oldSegs, new: newSegs };
280
+ }
281
+ /** Render segments with word-level highlighting */
282
+ function renderSegments(segs, wordStyle, lineStyle) {
283
+ return segs.map(s => s.changed ? wordStyle(s.text) : lineStyle(s.text)).join("");
284
+ }
285
+ function renderDiff(code) {
286
+ const lines = code.split("\n");
287
+ const tw = toolContentWidth();
288
+ const segments = [];
289
+ let oldLineNo = 1, newLineNo = 1;
290
+ let seenDiff = false;
291
+ for (const line of lines) {
292
+ // Skip file headers (--- a/file, +++ b/file)
293
+ if (line.startsWith("---") || line.startsWith("+++"))
294
+ continue;
295
+ // Hunk header — extract line numbers
296
+ const hunkMatch = line.match(/^@@\s*-(\d+)(?:,\d+)?\s*\+(\d+)(?:,\d+)?\s*@@/);
297
+ if (hunkMatch) {
298
+ oldLineNo = parseInt(hunkMatch[1]);
299
+ newLineNo = parseInt(hunkMatch[2]);
300
+ seenDiff = true;
301
+ continue;
302
+ }
303
+ // Skip non-diff header lines (e.g., "File edited:", "Applied N edits")
304
+ if (!seenDiff && !line.startsWith("-") && !line.startsWith("+") && !line.startsWith(" ")) {
305
+ continue;
306
+ }
307
+ seenDiff = true;
308
+ if (line.startsWith("-")) {
309
+ segments.push({ type: "remove", content: line.slice(1), lineNo: oldLineNo });
310
+ oldLineNo++;
311
+ }
312
+ else if (line.startsWith("+")) {
313
+ segments.push({ type: "add", content: line.slice(1), lineNo: newLineNo });
314
+ newLineNo++;
315
+ }
316
+ else {
317
+ const content = line.startsWith(" ") ? line.slice(1) : line;
318
+ segments.push({ type: "context", content, lineNo: newLineNo });
319
+ oldLineNo++;
320
+ newLineNo++;
321
+ }
322
+ }
323
+ if (segments.length === 0)
324
+ return code; // fallback
325
+ // Gutter width from max line number
326
+ const maxLineNo = segments.reduce((max, s) => Math.max(max, s.lineNo), 0);
327
+ const gutterW = Math.max(3, String(maxLineNo).length);
328
+ const out = [];
329
+ let i = 0;
330
+ while (i < segments.length) {
331
+ const seg = segments[i];
332
+ if (seg.type === "remove") {
333
+ // Collect consecutive removes
334
+ const removes = [];
335
+ while (i < segments.length && segments[i].type === "remove") {
336
+ removes.push(segments[i]);
337
+ i++;
338
+ }
339
+ // Collect consecutive adds
340
+ const adds = [];
341
+ while (i < segments.length && segments[i].type === "add") {
342
+ adds.push(segments[i]);
343
+ i++;
344
+ }
345
+ // Pair for word-level diff
346
+ const pairCount = Math.min(removes.length, adds.length);
347
+ for (let j = 0; j < removes.length; j++) {
348
+ const r = removes[j];
349
+ const rPrefix = `${String(r.lineNo).padStart(gutterW)} - `;
350
+ if (j < pairCount) {
351
+ const a = adds[j];
352
+ const aPrefix = `${String(a.lineNo).padStart(gutterW)} + `;
353
+ const wd = wordDiff(r.content, a.content);
354
+ // Removed line with word highlights
355
+ const rPad = Math.max(0, tw - rPrefix.length - r.content.length);
356
+ out.push(diffRemovedBg(rPrefix) + renderSegments(wd.old, diffWordRemoved, diffRemovedBg) + diffRemovedBg(" ".repeat(rPad)));
357
+ // Added line with word highlights
358
+ const aPad = Math.max(0, tw - aPrefix.length - a.content.length);
359
+ out.push(diffAddedBg(aPrefix) + renderSegments(wd.new, diffWordAdded, diffAddedBg) + diffAddedBg(" ".repeat(aPad)));
360
+ }
361
+ else {
362
+ // Unpaired remove
363
+ const raw = rPrefix + r.content;
364
+ const pad = Math.max(0, tw - raw.length);
365
+ out.push(diffRemovedBg(raw + " ".repeat(pad)));
366
+ }
367
+ }
368
+ // Unpaired adds
369
+ for (let j = pairCount; j < adds.length; j++) {
370
+ const a = adds[j];
371
+ const prefix = `${String(a.lineNo).padStart(gutterW)} + `;
372
+ const raw = prefix + a.content;
373
+ const pad = Math.max(0, tw - raw.length);
374
+ out.push(diffAddedBg(raw + " ".repeat(pad)));
375
+ }
376
+ continue;
377
+ }
378
+ if (seg.type === "add") {
379
+ // Standalone add (no preceding remove)
380
+ const prefix = `${String(seg.lineNo).padStart(gutterW)} + `;
381
+ const raw = prefix + seg.content;
382
+ const pad = Math.max(0, tw - raw.length);
383
+ out.push(diffAddedBg(raw + " ".repeat(pad)));
384
+ i++;
385
+ continue;
386
+ }
387
+ // Context line — dim line number, plain content, no background
388
+ const prefix = tertiary(`${String(seg.lineNo).padStart(gutterW)} `);
389
+ out.push(prefix + seg.content);
390
+ i++;
391
+ }
392
+ return out.join("\n") + "\n";
393
+ }
394
+ // ============================================================================
395
+ // Bar chart renderer — ```chart code blocks
396
+ // ============================================================================
397
+ const barGradient = [
398
+ chalk.hex("#BF5AF2"), // purple
399
+ chalk.hex("#5E5CE6"), // indigo
400
+ chalk.hex("#0A84FF"), // blue
401
+ chalk.hex("#64D2FF"), // cyan
402
+ chalk.hex("#6AC4DC"), // teal
403
+ chalk.hex("#30D158"), // green
404
+ chalk.hex("#FF9F0A"), // orange
405
+ chalk.hex("#FF375F"), // pink
406
+ ];
407
+ function renderBarChart(code) {
408
+ const lines = code.trim().split("\n").filter(l => l.trim());
409
+ // Optional title — first line without "label: number" pattern
410
+ let title = "";
411
+ let dataLines = lines;
412
+ if (lines.length > 1 && !/:\s*[$\-+]?[\d,]+/.test(lines[0])) {
413
+ title = lines[0].trim();
414
+ dataLines = lines.slice(1);
415
+ }
416
+ // Parse "Label: $1,234.56" or "Label: 42%" or "Label: 1000"
417
+ const entries = [];
418
+ for (const line of dataLines) {
419
+ const m = line.match(/^(.+?):\s*([+\-]?\$?[\d,]+\.?\d*%?)\s*$/);
420
+ if (!m)
421
+ continue;
422
+ const label = m[1].trim();
423
+ const raw = m[2].trim();
424
+ const value = Math.abs(parseFloat(raw.replace(/[$,%]/g, "")));
425
+ if (!isNaN(value))
426
+ entries.push({ label, value, raw });
427
+ }
428
+ if (entries.length === 0)
429
+ return code;
430
+ const maxVal = Math.max(...entries.map(e => e.value));
431
+ const maxLabel = Math.max(...entries.map(e => e.label.length));
432
+ const maxRaw = Math.max(...entries.map(e => e.raw.length));
433
+ const cw = contentWidth();
434
+ const barWidth = Math.min(36, Math.max(12, cw - 8 - maxLabel - maxRaw));
435
+ const out = [];
436
+ if (title) {
437
+ out.push(` ${systemBlue.bold(title)}`);
438
+ out.push("");
439
+ }
440
+ for (let i = 0; i < entries.length; i++) {
441
+ const e = entries[i];
442
+ const ratio = maxVal > 0 ? e.value / maxVal : 0;
443
+ const filled = Math.round(ratio * barWidth);
444
+ const color = barGradient[i % barGradient.length];
445
+ const label = secondary(e.label.padStart(maxLabel));
446
+ const bar = color("█".repeat(filled)) + chalk.hex("#2C2C2E")("░".repeat(barWidth - filled));
447
+ const val = e.raw.includes("$")
448
+ ? systemGreen(e.raw.padStart(maxRaw))
449
+ : e.raw.includes("%")
450
+ ? systemCyan(e.raw.padStart(maxRaw))
451
+ : systemMint(e.raw.padStart(maxRaw));
452
+ out.push(` ${label} ${bar} ${val}`);
453
+ }
454
+ return "\n" + out.join("\n") + "\n";
455
+ }
456
+ function getRowTone(cells) {
457
+ for (const cell of cells) {
458
+ const t = cell.trim();
459
+ // Negative financial → red row
460
+ if (/^-\$[\d,]+/.test(t) || /^-\d+\.?\d*%/.test(t))
461
+ return "negative";
462
+ // Explicit positive delta → green row
463
+ if (/^\+\d/.test(t) || /^\+\$/.test(t))
464
+ return "positive";
465
+ // Status badges
466
+ if (/^`?[✕✗]/.test(t) || /cancelled|failed|rejected|error|out of stock|low stock/i.test(t))
467
+ return "negative";
468
+ if (/^`?[✓●]/.test(t) || /completed|received|approved|active|success|paid|published/i.test(t))
469
+ return "positive";
470
+ }
471
+ return "neutral";
472
+ }
473
+ // Background tints for row-level coloring
474
+ const rowBgPositive = chalk.bgHex("#0d1f14"); // subtle green tint
475
+ const rowBgNegative = chalk.bgHex("#1f0d10"); // subtle red tint
476
+ function colorizeCell(val, isHeader, rowTone = "neutral") {
477
+ const trimmed = val.trim();
478
+ if (!trimmed)
479
+ return text("");
480
+ if (isHeader)
481
+ return systemIndigo.bold(trimmed);
482
+ // Badge format: `✓ status` or `◆ status` or `○ status` or `✕ status`
483
+ const badgeMatch = trimmed.match(/^`([✓●◆○✕◦])\s+(.+)`$/);
484
+ if (badgeMatch) {
485
+ const [, icon, label] = badgeMatch;
486
+ if (icon === "✓" || icon === "●")
487
+ return systemGreen(`${icon} ${label}`);
488
+ if (icon === "◆")
489
+ return systemCyan(`${icon} ${label}`);
490
+ if (icon === "○")
491
+ return systemOrange(`${icon} ${label}`);
492
+ if (icon === "✕")
493
+ return systemRed(`${icon} ${label}`);
494
+ return secondary(`${icon} ${label}`);
495
+ }
496
+ // Inline code (UUID, SKU, transfer number) — subtle style
497
+ if (trimmed.startsWith("`") && trimmed.endsWith("`")) {
498
+ return systemPurple(trimmed.slice(1, -1));
499
+ }
500
+ // Bold text
501
+ if (trimmed.startsWith("**") && trimmed.endsWith("**")) {
502
+ return text.bold(trimmed.slice(2, -2));
503
+ }
504
+ // Negative values → red
505
+ if (/^-\$?[\d,]+\.?\d*$/.test(trimmed) || /^-\d+\.?\d*%$/.test(trimmed)) {
506
+ return systemRed(trimmed);
507
+ }
508
+ // Positive financial → green
509
+ if (/^\+?\$[\d,]+\.?\d*$/.test(trimmed) || /^\$[\d,]+\.?\d*$/.test(trimmed)) {
510
+ return systemGreen(trimmed);
511
+ }
512
+ // Percentages → cyan
513
+ if (/^\d+\.?\d*%$/.test(trimmed)) {
514
+ return systemCyan(trimmed);
515
+ }
516
+ // Plain numbers → mint
517
+ if (/^[\d,]+\.?\d*$/.test(trimmed)) {
518
+ return systemMint(trimmed);
519
+ }
520
+ // Status words
521
+ if (/^(active|success|complete|approved|in stock|available)/i.test(trimmed)) {
522
+ return systemGreen(trimmed);
523
+ }
524
+ if (/^(inactive|error|failed|cancelled|out of stock|low|overdue|expired)/i.test(trimmed)) {
525
+ return systemRed(trimmed);
526
+ }
527
+ if (/^(pending|draft|processing)/i.test(trimmed)) {
528
+ return systemOrange(trimmed);
529
+ }
530
+ // Apply row tone tint to text cells
531
+ if (rowTone === "positive")
532
+ return rowBgPositive(text(trimmed));
533
+ if (rowTone === "negative")
534
+ return rowBgNegative(text(trimmed));
535
+ return text(trimmed);
536
+ }
537
+ function renderTable(token) {
538
+ // Extract cell text from token — handles both inline tokens and plain text
539
+ function getCellText(cell) {
540
+ if (!cell)
541
+ return "";
542
+ if (typeof cell === "string")
543
+ return cell;
544
+ if (cell.text !== undefined)
545
+ return String(cell.text);
546
+ if (cell.tokens) {
547
+ return cell.tokens.map((t) => t.raw || t.text || "").join("");
548
+ }
549
+ return String(cell);
550
+ }
551
+ const headers = (token.header || []).map((h) => getCellText(h));
552
+ const rows = (token.rows || []).map((row) => row.map((cell) => getCellText(cell)));
553
+ if (headers.length === 0)
554
+ return "";
555
+ // Responsive column widths based on terminal width
556
+ const cw = contentWidth();
557
+ const N = headers.length;
558
+ // Overhead: " ╭" (3) + N+1 border chars + N*2 cell padding + "╮"
559
+ const overhead = 3 + (N + 1) + (N * 2);
560
+ const availableForContent = cw - overhead;
561
+ // Scale minimum column width down for narrow terminals
562
+ const minCol = cw < 70 ? 3 : cw < 90 ? 4 : 6;
563
+ const maxPerCol = Math.max(minCol, Math.floor(availableForContent / N));
564
+ const colWidths = headers.map((h, i) => {
565
+ const dataMax = rows.reduce((max, row) => Math.max(max, (row[i] || "").length), 0);
566
+ return Math.min(maxPerCol, Math.max(minCol, h.length, dataMax) + 2);
567
+ });
568
+ const border = chalk.hex("#48484A");
569
+ const out = [];
570
+ // Top border: ╭──────┬──────╮
571
+ out.push(border(" ╭" + colWidths.map((w) => "─".repeat(w + 2)).join("┬") + "╮"));
572
+ // Header row (truncate headers to fit)
573
+ const hdrLine = headers.map((h, i) => {
574
+ const display = h.length > colWidths[i] ? h.slice(0, colWidths[i] - 1) + "…" : h;
575
+ return " " + systemIndigo.bold(display.padEnd(colWidths[i])) + " ";
576
+ }).join(border("│"));
577
+ out.push(border(" │") + hdrLine + border("│"));
578
+ // Header/body divider: ├──────┼──────┤
579
+ out.push(border(" ├" + colWidths.map((w) => "─".repeat(w + 2)).join("┼") + "┤"));
580
+ // Data rows (truncate values to fit, with row-level background tinting)
581
+ for (const row of rows) {
582
+ const tone = getRowTone(row);
583
+ const cells = headers.map((_, i) => {
584
+ const raw = row[i] || "";
585
+ const display = raw.length > colWidths[i] ? raw.slice(0, colWidths[i] - 1) + "…" : raw;
586
+ const colored = colorizeCell(display, false, tone);
587
+ const extraPad = Math.max(0, colWidths[i] - display.length);
588
+ const cellContent = " " + colored + " ".repeat(extraPad) + " ";
589
+ // Apply subtle background tint to the entire cell for positive/negative rows
590
+ if (tone === "positive")
591
+ return rowBgPositive(cellContent);
592
+ if (tone === "negative")
593
+ return rowBgNegative(cellContent);
594
+ return cellContent;
595
+ }).join(border("│"));
596
+ out.push(border(" │") + cells + border("│"));
597
+ }
598
+ // Bottom border: ╰──────┴──────╯
599
+ out.push(border(" ╰" + colWidths.map((w) => "─".repeat(w + 2)).join("┴") + "╯"));
600
+ return "\n" + out.join("\n") + "\n";
601
+ }
602
+ // Register chart + table extensions — intercepts before markedTerminal
603
+ md.use({
604
+ renderer: {
605
+ code(token) {
606
+ const rawLang = token.lang || "";
607
+ const code = token.text;
608
+ // Parse lang:subtitle (e.g. "typescript:src/foo.ts")
609
+ const colonIdx = rawLang.indexOf(":");
610
+ const lang = colonIdx > 0 ? rawLang.slice(0, colonIdx) : rawLang;
611
+ const subtitle = colonIdx > 0 ? rawLang.slice(colonIdx + 1) : "";
612
+ if (lang === "chart" || lang === "bar") {
613
+ return renderBarChart(code);
614
+ }
615
+ if (lang === "diff") {
616
+ return renderDiff(code);
617
+ }
618
+ {
619
+ // Command output mode: bash without subtitle = run_command output
620
+ // → no line numbers, wider content area, clean indent
621
+ const isCommandOutput = (lang === "bash" || lang === "terminal") && !subtitle;
622
+ const highlightLang = lang === "terminal" ? "bash" : lang;
623
+ // Build header: ── lang ── subtitle ──────
624
+ const cw = contentWidth();
625
+ const headerWidth = Math.max(20, cw - 6);
626
+ const displayLang = isCommandOutput ? "bash" : lang;
627
+ let header;
628
+ if (displayLang && subtitle) {
629
+ const shortSub = shortenPathForHeader(subtitle, headerWidth - displayLang.length - 10);
630
+ const pad = Math.max(2, headerWidth - displayLang.length - shortSub.length - 6);
631
+ header = separator(" ── ") + tertiary(displayLang) + separator(" ── ") + secondary(shortSub) + separator(` ${"─".repeat(pad)}`);
632
+ }
633
+ else if (displayLang) {
634
+ const pad = Math.max(2, headerWidth - displayLang.length - 3);
635
+ header = separator(" ── ") + tertiary(displayLang) + separator(` ${"─".repeat(pad)}`);
636
+ }
637
+ else {
638
+ header = separator(" ──" + "─".repeat(headerWidth - 2));
639
+ }
640
+ // Calculate max line width to prevent wrapping
641
+ const lineCount = code.split("\n").length;
642
+ const gutterW = isCommandOutput ? 0 : String(lineCount).length;
643
+ const gutterOverhead = isCommandOutput ? 4 : (2 + gutterW + 3); // " " or " 123 │ "
644
+ const maxLineWidth = Math.max(20, cw - gutterOverhead - 2);
645
+ // Pre-truncate lines BEFORE highlighting (avoids cutting ANSI codes)
646
+ const truncatedCode = code.split("\n").map((line) => {
647
+ if (line.length > maxLineWidth) {
648
+ return line.slice(0, maxLineWidth - 1) + "…";
649
+ }
650
+ return line;
651
+ }).join("\n");
652
+ let highlighted;
653
+ if (highlightLang) {
654
+ try {
655
+ highlighted = withSuppressedWarnings(() => highlight(truncatedCode, { language: highlightLang, ignoreIllegals: true, theme: appleTheme }));
656
+ }
657
+ catch {
658
+ highlighted = truncatedCode;
659
+ }
660
+ }
661
+ else {
662
+ highlighted = truncatedCode;
663
+ }
664
+ const hLines = highlighted.split("\n");
665
+ if (isCommandOutput) {
666
+ // Command output: no line numbers, 4-space indent
667
+ const body = hLines.map(l => " " + l).join("\n");
668
+ return "\n" + header + "\n" + body + "\n";
669
+ }
670
+ else {
671
+ // Code with line numbers + gutter
672
+ const numbered = hLines.map((l, i) => {
673
+ const num = tertiary(String(i + 1).padStart(gutterW));
674
+ return " " + num + separator(" │ ") + l;
675
+ }).join("\n");
676
+ return "\n" + header + "\n" + numbered + "\n";
677
+ }
678
+ }
679
+ },
680
+ table(token) {
681
+ return renderTable(token);
682
+ },
683
+ },
684
+ });
685
+ // ============================================================================
686
+ // Streaming fence closure — state-tracking approach
687
+ // ============================================================================
688
+ /** Extract text outside fenced code blocks */
689
+ function getNonFencedText(input) {
690
+ const lines = input.split("\n");
691
+ const parts = [];
692
+ let inFence = false;
693
+ for (const line of lines) {
694
+ if (line.trimStart().startsWith("```")) {
695
+ inFence = !inFence;
696
+ continue;
697
+ }
698
+ if (!inFence)
699
+ parts.push(line);
700
+ }
701
+ return parts.join("\n");
702
+ }
703
+ /** Strip complete inline code spans from text */
704
+ function getNonCodeText(t) {
705
+ return t.replace(/``[^`]*``/g, "").replace(/`[^`\n]*`/g, "");
706
+ }
707
+ /**
708
+ * Close incomplete markdown fences for safe streaming rendering.
709
+ * State-tracking approach: handles nested fences, escaped markers, double-backtick spans.
710
+ */
711
+ export function closeIncompleteFences(input) {
712
+ let result = input;
713
+ // 1. Code fences — walk lines, track open/close state
714
+ let inFence = false;
715
+ for (const line of result.split("\n")) {
716
+ const trimmed = line.trimStart();
717
+ if (trimmed.startsWith("```")) {
718
+ inFence = !inFence;
719
+ }
720
+ }
721
+ if (inFence) {
722
+ result += "\n```";
723
+ }
724
+ // 2. For inline markers, only examine text OUTSIDE fenced code blocks
725
+ const outside = getNonFencedText(result);
726
+ // 3. Inline backticks — remove escaped, remove complete spans, count remainder
727
+ let forBacktick = outside.replace(/\\`/g, "");
728
+ forBacktick = forBacktick.replace(/``[^`]*``/g, ""); // double-backtick spans
729
+ forBacktick = forBacktick.replace(/`[^`\n]*`/g, ""); // single-backtick spans
730
+ const unmatched = (forBacktick.match(/`/g) || []).length;
731
+ if (unmatched % 2 !== 0) {
732
+ result += "`";
733
+ }
734
+ // 4. Bold — count ** in non-code text
735
+ const forBold = getNonCodeText(outside);
736
+ const boldCount = (forBold.match(/\*\*/g) || []).length;
737
+ if (boldCount % 2 !== 0) {
738
+ result += "**";
739
+ }
740
+ // 5. Italic — count standalone * (not part of **) with escape awareness
741
+ const forItalic = forBold.replace(/\*\*/g, "");
742
+ const italicCount = (forItalic.match(/(?<!\\)\*/g) || []).length;
743
+ if (italicCount % 2 !== 0) {
744
+ result += "*";
745
+ }
746
+ return result;
747
+ }
748
+ /**
749
+ * Render markdown to ANSI-styled terminal string.
750
+ * Optionally applies streaming-safe fence closing.
751
+ */
752
+ export function renderMarkdown(input, streaming = false) {
753
+ const safe = streaming ? closeIncompleteFences(input) : input;
754
+ const result = md.parse(safe);
755
+ return result.replace(/\n+$/, "");
756
+ }