@calliopelabs/cli 0.8.20 → 2.0.2

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 (402) hide show
  1. package/dist/agents/agent-config-loader.d.ts +60 -0
  2. package/dist/agents/agent-config-loader.d.ts.map +1 -0
  3. package/dist/agents/agent-config-loader.js +402 -0
  4. package/dist/agents/agent-config-loader.js.map +1 -0
  5. package/dist/agents/agent-config-presets.d.ts +10 -0
  6. package/dist/agents/agent-config-presets.d.ts.map +1 -0
  7. package/dist/agents/agent-config-presets.js +940 -0
  8. package/dist/agents/agent-config-presets.js.map +1 -0
  9. package/dist/agents/agent-config-types.d.ts +145 -0
  10. package/dist/agents/agent-config-types.d.ts.map +1 -0
  11. package/dist/agents/agent-config-types.js +12 -0
  12. package/dist/agents/agent-config-types.js.map +1 -0
  13. package/dist/{agterm → agents}/agent-detection.d.ts +1 -1
  14. package/dist/{agterm → agents}/agent-detection.d.ts.map +1 -1
  15. package/dist/{agterm → agents}/agent-detection.js +21 -5
  16. package/dist/agents/agent-detection.js.map +1 -0
  17. package/dist/agents/aggregator.d.ts +19 -0
  18. package/dist/agents/aggregator.d.ts.map +1 -0
  19. package/dist/agents/aggregator.js +141 -0
  20. package/dist/agents/aggregator.js.map +1 -0
  21. package/dist/{agterm → agents}/cli-backend.d.ts +1 -1
  22. package/dist/{agterm → agents}/cli-backend.d.ts.map +1 -1
  23. package/dist/{agterm → agents}/cli-backend.js +90 -12
  24. package/dist/agents/cli-backend.js.map +1 -0
  25. package/dist/agents/council-types.d.ts +113 -0
  26. package/dist/agents/council-types.d.ts.map +1 -0
  27. package/dist/agents/council-types.js +81 -0
  28. package/dist/agents/council-types.js.map +1 -0
  29. package/dist/agents/council.d.ts +107 -0
  30. package/dist/agents/council.d.ts.map +1 -0
  31. package/dist/agents/council.js +586 -0
  32. package/dist/agents/council.js.map +1 -0
  33. package/dist/agents/decomposer.d.ts +33 -0
  34. package/dist/agents/decomposer.d.ts.map +1 -0
  35. package/dist/agents/decomposer.js +138 -0
  36. package/dist/agents/decomposer.js.map +1 -0
  37. package/dist/agents/dynamic-tools.d.ts +52 -0
  38. package/dist/agents/dynamic-tools.d.ts.map +1 -0
  39. package/dist/agents/dynamic-tools.js +395 -0
  40. package/dist/agents/dynamic-tools.js.map +1 -0
  41. package/dist/agents/index.d.ts +29 -0
  42. package/dist/agents/index.d.ts.map +1 -0
  43. package/dist/agents/index.js +29 -0
  44. package/dist/agents/index.js.map +1 -0
  45. package/dist/agents/installer.d.ts +39 -0
  46. package/dist/agents/installer.d.ts.map +1 -0
  47. package/dist/agents/installer.js +205 -0
  48. package/dist/agents/installer.js.map +1 -0
  49. package/dist/{agterm → agents}/orchestrator.d.ts +7 -2
  50. package/dist/agents/orchestrator.d.ts.map +1 -0
  51. package/dist/{agterm → agents}/orchestrator.js +22 -2
  52. package/dist/agents/orchestrator.js.map +1 -0
  53. package/dist/agents/sdk-backend.d.ts +63 -0
  54. package/dist/agents/sdk-backend.d.ts.map +1 -0
  55. package/dist/agents/sdk-backend.js +489 -0
  56. package/dist/agents/sdk-backend.js.map +1 -0
  57. package/dist/agents/swarm-types.d.ts +83 -0
  58. package/dist/agents/swarm-types.d.ts.map +1 -0
  59. package/dist/agents/swarm-types.js +20 -0
  60. package/dist/agents/swarm-types.js.map +1 -0
  61. package/dist/agents/swarm.d.ts +74 -0
  62. package/dist/agents/swarm.d.ts.map +1 -0
  63. package/dist/agents/swarm.js +307 -0
  64. package/dist/agents/swarm.js.map +1 -0
  65. package/dist/{agterm → agents}/tools.d.ts +7 -5
  66. package/dist/agents/tools.d.ts.map +1 -0
  67. package/dist/agents/tools.js +776 -0
  68. package/dist/agents/tools.js.map +1 -0
  69. package/dist/{agterm → agents}/types.d.ts +14 -2
  70. package/dist/agents/types.d.ts.map +1 -0
  71. package/dist/{agterm → agents}/types.js +2 -2
  72. package/dist/agents/types.js.map +1 -0
  73. package/dist/api-server.d.ts +26 -0
  74. package/dist/api-server.d.ts.map +1 -0
  75. package/dist/api-server.js +230 -0
  76. package/dist/api-server.js.map +1 -0
  77. package/dist/auto-checkpoint.d.ts +35 -0
  78. package/dist/auto-checkpoint.d.ts.map +1 -0
  79. package/dist/auto-checkpoint.js +143 -0
  80. package/dist/auto-checkpoint.js.map +1 -0
  81. package/dist/auto-compressor.d.ts +44 -0
  82. package/dist/auto-compressor.d.ts.map +1 -0
  83. package/dist/auto-compressor.js +145 -0
  84. package/dist/auto-compressor.js.map +1 -0
  85. package/dist/background-jobs.d.ts +45 -0
  86. package/dist/background-jobs.d.ts.map +1 -0
  87. package/dist/background-jobs.js +122 -0
  88. package/dist/background-jobs.js.map +1 -0
  89. package/dist/bin.d.ts +6 -2
  90. package/dist/bin.d.ts.map +1 -1
  91. package/dist/bin.js +127 -24
  92. package/dist/bin.js.map +1 -1
  93. package/dist/checkpoint.d.ts +49 -0
  94. package/dist/checkpoint.d.ts.map +1 -0
  95. package/dist/checkpoint.js +219 -0
  96. package/dist/checkpoint.js.map +1 -0
  97. package/dist/circuit-breaker/breaker.d.ts +80 -0
  98. package/dist/circuit-breaker/breaker.d.ts.map +1 -0
  99. package/dist/circuit-breaker/breaker.js +408 -0
  100. package/dist/circuit-breaker/breaker.js.map +1 -0
  101. package/dist/circuit-breaker/defaults.d.ts +8 -0
  102. package/dist/circuit-breaker/defaults.d.ts.map +1 -0
  103. package/dist/circuit-breaker/defaults.js +35 -0
  104. package/dist/circuit-breaker/defaults.js.map +1 -0
  105. package/dist/circuit-breaker/index.d.ts +9 -0
  106. package/dist/circuit-breaker/index.d.ts.map +1 -0
  107. package/dist/circuit-breaker/index.js +8 -0
  108. package/dist/circuit-breaker/index.js.map +1 -0
  109. package/dist/circuit-breaker/types.d.ts +77 -0
  110. package/dist/circuit-breaker/types.d.ts.map +1 -0
  111. package/dist/circuit-breaker/types.js +8 -0
  112. package/dist/circuit-breaker/types.js.map +1 -0
  113. package/dist/circuit-breaker.d.ts +8 -0
  114. package/dist/circuit-breaker.d.ts.map +1 -0
  115. package/dist/circuit-breaker.js +7 -0
  116. package/dist/circuit-breaker.js.map +1 -0
  117. package/dist/cli/agent.d.ts +9 -0
  118. package/dist/cli/agent.d.ts.map +1 -0
  119. package/dist/cli/agent.js +262 -0
  120. package/dist/cli/agent.js.map +1 -0
  121. package/dist/cli/commands.d.ts +12 -0
  122. package/dist/cli/commands.d.ts.map +1 -0
  123. package/dist/{cli.js → cli/commands.js} +285 -422
  124. package/dist/cli/commands.js.map +1 -0
  125. package/dist/cli/index.d.ts +8 -0
  126. package/dist/cli/index.d.ts.map +1 -0
  127. package/dist/cli/index.js +222 -0
  128. package/dist/cli/index.js.map +1 -0
  129. package/dist/cli/types.d.ts +30 -0
  130. package/dist/cli/types.d.ts.map +1 -0
  131. package/dist/cli/types.js +20 -0
  132. package/dist/cli/types.js.map +1 -0
  133. package/dist/companions.d.ts +54 -0
  134. package/dist/companions.d.ts.map +1 -0
  135. package/dist/companions.js +440 -0
  136. package/dist/companions.js.map +1 -0
  137. package/dist/config.d.ts +23 -1
  138. package/dist/config.d.ts.map +1 -1
  139. package/dist/config.js +95 -22
  140. package/dist/config.js.map +1 -1
  141. package/dist/diff.d.ts +27 -0
  142. package/dist/diff.d.ts.map +1 -1
  143. package/dist/diff.js +415 -10
  144. package/dist/diff.js.map +1 -1
  145. package/dist/errors.d.ts.map +1 -1
  146. package/dist/errors.js +20 -11
  147. package/dist/errors.js.map +1 -1
  148. package/dist/git-status.d.ts +23 -0
  149. package/dist/git-status.d.ts.map +1 -0
  150. package/dist/git-status.js +92 -0
  151. package/dist/git-status.js.map +1 -0
  152. package/dist/headless.d.ts +25 -0
  153. package/dist/headless.d.ts.map +1 -0
  154. package/dist/headless.js +182 -0
  155. package/dist/headless.js.map +1 -0
  156. package/dist/hud/api.d.ts +35 -0
  157. package/dist/hud/api.d.ts.map +1 -0
  158. package/dist/hud/api.js +448 -0
  159. package/dist/hud/api.js.map +1 -0
  160. package/dist/hud/palettes.d.ts +9 -0
  161. package/dist/hud/palettes.d.ts.map +1 -0
  162. package/dist/hud/palettes.js +280 -0
  163. package/dist/hud/palettes.js.map +1 -0
  164. package/dist/hud/skins.d.ts +12 -0
  165. package/dist/hud/skins.d.ts.map +1 -0
  166. package/dist/hud/skins.js +365 -0
  167. package/dist/hud/skins.js.map +1 -0
  168. package/dist/hud/theme-packs/api.d.ts +51 -0
  169. package/dist/hud/theme-packs/api.d.ts.map +1 -0
  170. package/dist/hud/theme-packs/api.js +145 -0
  171. package/dist/hud/theme-packs/api.js.map +1 -0
  172. package/dist/hud/theme-packs/index.d.ts +18 -0
  173. package/dist/hud/theme-packs/index.d.ts.map +1 -0
  174. package/dist/hud/theme-packs/index.js +38 -0
  175. package/dist/hud/theme-packs/index.js.map +1 -0
  176. package/dist/hud/theme-packs/types.d.ts +29 -0
  177. package/dist/hud/theme-packs/types.d.ts.map +1 -0
  178. package/dist/hud/theme-packs/types.js +9 -0
  179. package/dist/hud/theme-packs/types.js.map +1 -0
  180. package/dist/hud/types.d.ts +182 -0
  181. package/dist/hud/types.d.ts.map +1 -0
  182. package/dist/hud/types.js +7 -0
  183. package/dist/hud/types.js.map +1 -0
  184. package/dist/idle-eviction.d.ts +34 -0
  185. package/dist/idle-eviction.d.ts.map +1 -0
  186. package/dist/idle-eviction.js +78 -0
  187. package/dist/idle-eviction.js.map +1 -0
  188. package/dist/index.d.ts +9 -3
  189. package/dist/index.d.ts.map +1 -1
  190. package/dist/index.js +9 -3
  191. package/dist/index.js.map +1 -1
  192. package/dist/iteration-ledger.d.ts +105 -0
  193. package/dist/iteration-ledger.d.ts.map +1 -0
  194. package/dist/iteration-ledger.js +237 -0
  195. package/dist/iteration-ledger.js.map +1 -0
  196. package/dist/markdown.d.ts.map +1 -1
  197. package/dist/markdown.js +1 -27
  198. package/dist/markdown.js.map +1 -1
  199. package/dist/mcp.d.ts +35 -0
  200. package/dist/mcp.d.ts.map +1 -1
  201. package/dist/mcp.js +291 -7
  202. package/dist/mcp.js.map +1 -1
  203. package/dist/memory.d.ts.map +1 -1
  204. package/dist/memory.js +12 -2
  205. package/dist/memory.js.map +1 -1
  206. package/dist/model-detection.d.ts +5 -0
  207. package/dist/model-detection.d.ts.map +1 -1
  208. package/dist/model-detection.js +278 -10
  209. package/dist/model-detection.js.map +1 -1
  210. package/dist/model-router.d.ts.map +1 -1
  211. package/dist/model-router.js +33 -11
  212. package/dist/model-router.js.map +1 -1
  213. package/dist/plugins.d.ts +8 -0
  214. package/dist/plugins.d.ts.map +1 -1
  215. package/dist/plugins.js +97 -6
  216. package/dist/plugins.js.map +1 -1
  217. package/dist/providers/anthropic.d.ts +10 -0
  218. package/dist/providers/anthropic.d.ts.map +1 -0
  219. package/dist/providers/anthropic.js +221 -0
  220. package/dist/providers/anthropic.js.map +1 -0
  221. package/dist/providers/bedrock.d.ts +17 -0
  222. package/dist/providers/bedrock.d.ts.map +1 -0
  223. package/dist/providers/bedrock.js +574 -0
  224. package/dist/providers/bedrock.js.map +1 -0
  225. package/dist/providers/compat.d.ts +13 -0
  226. package/dist/providers/compat.d.ts.map +1 -0
  227. package/dist/providers/compat.js +202 -0
  228. package/dist/providers/compat.js.map +1 -0
  229. package/dist/providers/google.d.ts +10 -0
  230. package/dist/providers/google.d.ts.map +1 -0
  231. package/dist/providers/google.js +203 -0
  232. package/dist/providers/google.js.map +1 -0
  233. package/dist/providers/index.d.ts +23 -0
  234. package/dist/providers/index.d.ts.map +1 -0
  235. package/dist/providers/index.js +145 -0
  236. package/dist/providers/index.js.map +1 -0
  237. package/dist/providers/ollama.d.ts +17 -0
  238. package/dist/providers/ollama.d.ts.map +1 -0
  239. package/dist/providers/ollama.js +289 -0
  240. package/dist/providers/ollama.js.map +1 -0
  241. package/dist/providers/openai.d.ts +121 -0
  242. package/dist/providers/openai.d.ts.map +1 -0
  243. package/dist/providers/openai.js +485 -0
  244. package/dist/providers/openai.js.map +1 -0
  245. package/dist/providers/types.d.ts +63 -0
  246. package/dist/providers/types.d.ts.map +1 -0
  247. package/dist/providers/types.js +164 -0
  248. package/dist/providers/types.js.map +1 -0
  249. package/dist/sandbox-native.d.ts +59 -0
  250. package/dist/sandbox-native.d.ts.map +1 -0
  251. package/dist/sandbox-native.js +292 -0
  252. package/dist/sandbox-native.js.map +1 -0
  253. package/dist/sandbox.d.ts +2 -2
  254. package/dist/sandbox.d.ts.map +1 -1
  255. package/dist/sandbox.js +59 -13
  256. package/dist/sandbox.js.map +1 -1
  257. package/dist/scope.d.ts +3 -1
  258. package/dist/scope.d.ts.map +1 -1
  259. package/dist/scope.js +13 -1
  260. package/dist/scope.js.map +1 -1
  261. package/dist/session-timeout.d.ts +31 -0
  262. package/dist/session-timeout.d.ts.map +1 -0
  263. package/dist/session-timeout.js +100 -0
  264. package/dist/session-timeout.js.map +1 -0
  265. package/dist/setup.d.ts.map +1 -1
  266. package/dist/setup.js +29 -17
  267. package/dist/setup.js.map +1 -1
  268. package/dist/smart-router.d.ts +73 -0
  269. package/dist/smart-router.d.ts.map +1 -0
  270. package/dist/smart-router.js +332 -0
  271. package/dist/smart-router.js.map +1 -0
  272. package/dist/storage.d.ts +19 -0
  273. package/dist/storage.d.ts.map +1 -1
  274. package/dist/storage.js +164 -1
  275. package/dist/storage.js.map +1 -1
  276. package/dist/streaming.d.ts +4 -0
  277. package/dist/streaming.d.ts.map +1 -1
  278. package/dist/streaming.js +12 -0
  279. package/dist/streaming.js.map +1 -1
  280. package/dist/styles.d.ts +32 -0
  281. package/dist/styles.d.ts.map +1 -1
  282. package/dist/styles.js +91 -0
  283. package/dist/styles.js.map +1 -1
  284. package/dist/summarization.d.ts +1 -1
  285. package/dist/summarization.js +4 -4
  286. package/dist/summarization.js.map +1 -1
  287. package/dist/terminal-image.d.ts +115 -0
  288. package/dist/terminal-image.d.ts.map +1 -0
  289. package/dist/terminal-image.js +766 -0
  290. package/dist/terminal-image.js.map +1 -0
  291. package/dist/terminal-recording.d.ts +55 -0
  292. package/dist/terminal-recording.d.ts.map +1 -0
  293. package/dist/terminal-recording.js +182 -0
  294. package/dist/terminal-recording.js.map +1 -0
  295. package/dist/themes.d.ts +19 -35
  296. package/dist/themes.d.ts.map +1 -1
  297. package/dist/themes.js +101 -210
  298. package/dist/themes.js.map +1 -1
  299. package/dist/tmux.d.ts +35 -0
  300. package/dist/tmux.d.ts.map +1 -0
  301. package/dist/tmux.js +106 -0
  302. package/dist/tmux.js.map +1 -0
  303. package/dist/tools.d.ts +3 -3
  304. package/dist/tools.d.ts.map +1 -1
  305. package/dist/tools.js +587 -45
  306. package/dist/tools.js.map +1 -1
  307. package/dist/trust.d.ts +53 -0
  308. package/dist/trust.d.ts.map +1 -0
  309. package/dist/trust.js +154 -0
  310. package/dist/trust.js.map +1 -0
  311. package/dist/types.d.ts +7 -3
  312. package/dist/types.d.ts.map +1 -1
  313. package/dist/types.js +70 -32
  314. package/dist/types.js.map +1 -1
  315. package/dist/ui/agent.d.ts +61 -0
  316. package/dist/ui/agent.d.ts.map +1 -0
  317. package/dist/ui/agent.js +768 -0
  318. package/dist/ui/agent.js.map +1 -0
  319. package/dist/ui/chat-input.d.ts +32 -0
  320. package/dist/ui/chat-input.d.ts.map +1 -0
  321. package/dist/ui/chat-input.js +355 -0
  322. package/dist/ui/chat-input.js.map +1 -0
  323. package/dist/ui/commands.d.ts +92 -0
  324. package/dist/ui/commands.d.ts.map +1 -0
  325. package/dist/ui/commands.js +3006 -0
  326. package/dist/ui/commands.js.map +1 -0
  327. package/dist/ui/completions.d.ts +22 -0
  328. package/dist/ui/completions.d.ts.map +1 -0
  329. package/dist/ui/completions.js +215 -0
  330. package/dist/ui/completions.js.map +1 -0
  331. package/dist/ui/components.d.ts +38 -0
  332. package/dist/ui/components.d.ts.map +1 -0
  333. package/dist/ui/components.js +422 -0
  334. package/dist/ui/components.js.map +1 -0
  335. package/dist/ui/context.d.ts +12 -0
  336. package/dist/ui/context.d.ts.map +1 -0
  337. package/dist/ui/context.js +102 -0
  338. package/dist/ui/context.js.map +1 -0
  339. package/dist/ui/error-boundary.d.ts +33 -0
  340. package/dist/ui/error-boundary.d.ts.map +1 -0
  341. package/dist/ui/error-boundary.js +94 -0
  342. package/dist/ui/error-boundary.js.map +1 -0
  343. package/dist/ui/frame.d.ts +13 -0
  344. package/dist/ui/frame.d.ts.map +1 -0
  345. package/dist/ui/frame.js +89 -0
  346. package/dist/ui/frame.js.map +1 -0
  347. package/dist/ui/index.d.ts +12 -0
  348. package/dist/ui/index.d.ts.map +1 -0
  349. package/dist/ui/index.js +928 -0
  350. package/dist/ui/index.js.map +1 -0
  351. package/dist/ui/messages.d.ts +19 -0
  352. package/dist/ui/messages.d.ts.map +1 -0
  353. package/dist/ui/messages.js +181 -0
  354. package/dist/ui/messages.js.map +1 -0
  355. package/dist/ui/modals.d.ts +52 -0
  356. package/dist/ui/modals.d.ts.map +1 -0
  357. package/dist/ui/modals.js +204 -0
  358. package/dist/ui/modals.js.map +1 -0
  359. package/dist/ui/pack-picker.d.ts +12 -0
  360. package/dist/ui/pack-picker.d.ts.map +1 -0
  361. package/dist/ui/pack-picker.js +101 -0
  362. package/dist/ui/pack-picker.js.map +1 -0
  363. package/dist/ui/status-bar.d.ts +20 -0
  364. package/dist/ui/status-bar.d.ts.map +1 -0
  365. package/dist/ui/status-bar.js +41 -0
  366. package/dist/ui/status-bar.js.map +1 -0
  367. package/dist/ui/theme-picker.d.ts +24 -0
  368. package/dist/ui/theme-picker.d.ts.map +1 -0
  369. package/dist/ui/theme-picker.js +190 -0
  370. package/dist/ui/theme-picker.js.map +1 -0
  371. package/dist/ui/types.d.ts +62 -0
  372. package/dist/ui/types.d.ts.map +1 -0
  373. package/dist/ui/types.js +7 -0
  374. package/dist/ui/types.js.map +1 -0
  375. package/dist/version-check.d.ts.map +1 -1
  376. package/dist/version-check.js +1 -9
  377. package/dist/version-check.js.map +1 -1
  378. package/package.json +8 -3
  379. package/dist/agterm/agent-detection.js.map +0 -1
  380. package/dist/agterm/cli-backend.js.map +0 -1
  381. package/dist/agterm/index.d.ts +0 -12
  382. package/dist/agterm/index.d.ts.map +0 -1
  383. package/dist/agterm/index.js +0 -15
  384. package/dist/agterm/index.js.map +0 -1
  385. package/dist/agterm/orchestrator.d.ts.map +0 -1
  386. package/dist/agterm/orchestrator.js.map +0 -1
  387. package/dist/agterm/tools.d.ts.map +0 -1
  388. package/dist/agterm/tools.js +0 -278
  389. package/dist/agterm/tools.js.map +0 -1
  390. package/dist/agterm/types.d.ts.map +0 -1
  391. package/dist/agterm/types.js.map +0 -1
  392. package/dist/cli.d.ts +0 -14
  393. package/dist/cli.d.ts.map +0 -1
  394. package/dist/cli.js.map +0 -1
  395. package/dist/providers.d.ts +0 -51
  396. package/dist/providers.d.ts.map +0 -1
  397. package/dist/providers.js +0 -1146
  398. package/dist/providers.js.map +0 -1
  399. package/dist/ui-cli.d.ts +0 -17
  400. package/dist/ui-cli.d.ts.map +0 -1
  401. package/dist/ui-cli.js +0 -3730
  402. package/dist/ui-cli.js.map +0 -1
@@ -0,0 +1,766 @@
1
+ /**
2
+ * Terminal Image & Banner Rendering
3
+ *
4
+ * Provides terminal capability detection and text-based banner rendering
5
+ * with ANSI colors. No external image processing dependencies required.
6
+ *
7
+ * Extracted from scripts/image-poc.mjs detection logic, adapted for
8
+ * integration with the HUD skin system.
9
+ *
10
+ * @see scripts/image-poc.mjs - Full image rendering POC (requires sharp)
11
+ */
12
+ // ============================================================================
13
+ // Terminal Capability Detection
14
+ // ============================================================================
15
+ /**
16
+ * Detect the best image rendering mode for the current terminal.
17
+ *
18
+ * Detection rules (in priority order):
19
+ * 1. ITERM_SESSION_ID / LC_TERMINAL=iTerm2 / TERM_PROGRAM=iTerm.app|WezTerm -> iterm2
20
+ * 2. KITTY_PID / TERM=xterm-kitty / GHOSTTY_RESOURCES_DIR -> kitty
21
+ * 3. COLORTERM=truecolor|24bit -> halfblock
22
+ * 4. Otherwise -> ascii
23
+ */
24
+ export function detectBestMode() {
25
+ const env = process.env;
26
+ // iTerm2 protocol support
27
+ if (env.ITERM_SESSION_ID ||
28
+ env.LC_TERMINAL === 'iTerm2' ||
29
+ env.TERM_PROGRAM === 'iTerm.app' ||
30
+ env.TERM_PROGRAM === 'WezTerm') {
31
+ return 'iterm2';
32
+ }
33
+ // Kitty graphics protocol
34
+ if (env.KITTY_PID ||
35
+ env.TERM === 'xterm-kitty' ||
36
+ env.GHOSTTY_RESOURCES_DIR) {
37
+ return 'kitty';
38
+ }
39
+ // Truecolor support -> half-block rendering
40
+ if (env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit') {
41
+ return 'halfblock';
42
+ }
43
+ // Fallback to ASCII
44
+ return 'ascii';
45
+ }
46
+ /**
47
+ * Returns terminal image capabilities summary.
48
+ */
49
+ export function getTerminalImageInfo() {
50
+ const env = process.env;
51
+ return {
52
+ mode: detectBestMode(),
53
+ truecolor: env.COLORTERM === 'truecolor' || env.COLORTERM === '24bit',
54
+ width: parseInt(env.COLUMNS || '', 10) || process.stdout.columns || 80,
55
+ };
56
+ }
57
+ // ============================================================================
58
+ // ANSI Color Helpers
59
+ // ============================================================================
60
+ const ESC = '\x1b[';
61
+ const RESET = `${ESC}0m`;
62
+ /** Apply 256-color or truecolor foreground based on hex string (#RRGGBB) */
63
+ export function colorFg(text, hex) {
64
+ const rgb = hexToRgb(hex);
65
+ if (!rgb)
66
+ return text;
67
+ return `${ESC}38;2;${rgb.r};${rgb.g};${rgb.b}m${text}${RESET}`;
68
+ }
69
+ /** Apply truecolor background based on hex string (#RRGGBB) */
70
+ export function colorBg(text, hex) {
71
+ const rgb = hexToRgb(hex);
72
+ if (!rgb)
73
+ return text;
74
+ return `${ESC}48;2;${rgb.r};${rgb.g};${rgb.b}m${text}${RESET}`;
75
+ }
76
+ /** Bold text */
77
+ export function bold(text) {
78
+ return `${ESC}1m${text}${RESET}`;
79
+ }
80
+ /** Dim text */
81
+ export function dim(text) {
82
+ return `${ESC}2m${text}${RESET}`;
83
+ }
84
+ function hexToRgb(hex) {
85
+ const match = hex.match(/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/);
86
+ if (!match)
87
+ return null;
88
+ return {
89
+ r: parseInt(match[1], 16),
90
+ g: parseInt(match[2], 16),
91
+ b: parseInt(match[3], 16),
92
+ };
93
+ }
94
+ // ============================================================================
95
+ // Banner Rendering
96
+ // ============================================================================
97
+ /**
98
+ * Render a text-based decorative banner using box-drawing characters.
99
+ *
100
+ * Uses the detected terminal mode to choose appropriate decoration level:
101
+ * - iterm2/kitty/halfblock: Full Unicode box drawing with color
102
+ * - ascii: Simple ASCII borders
103
+ * - none: Just the text
104
+ */
105
+ export function renderBanner(text, mode) {
106
+ const m = mode ?? detectBestMode();
107
+ const lines = [];
108
+ if (m === 'none') {
109
+ lines.push(text);
110
+ return lines.join('\n');
111
+ }
112
+ const isUnicode = m !== 'ascii';
113
+ const h = isUnicode ? '\u2500' : '-';
114
+ const v = isUnicode ? '\u2502' : '|';
115
+ const tl = isUnicode ? '\u256D' : '+';
116
+ const tr = isUnicode ? '\u256E' : '+';
117
+ const bl = isUnicode ? '\u2570' : '+';
118
+ const br = isUnicode ? '\u256F' : '+';
119
+ const dh = isUnicode ? '\u2550' : '=';
120
+ const width = Math.max(text.length + 4, 40);
121
+ const padding = width - text.length - 2;
122
+ const padLeft = Math.floor(padding / 2);
123
+ const padRight = padding - padLeft;
124
+ // Top border with decorative double line
125
+ lines.push(` ${tl}${dh}${h.repeat(width - 2)}${dh}${tr}`);
126
+ // Content line
127
+ lines.push(` ${v}${' '.repeat(padLeft)} ${text} ${' '.repeat(padRight)}${v}`);
128
+ // Bottom border
129
+ lines.push(` ${bl}${dh}${h.repeat(width - 2)}${dh}${br}`);
130
+ return lines.join('\n');
131
+ }
132
+ /**
133
+ * Render pre-formatted ASCII art lines with optional per-line color function.
134
+ *
135
+ * @param art - Array of pre-formatted ASCII art lines
136
+ * @param colorFn - Optional function to apply ANSI color to each line
137
+ * @returns Rendered string with newlines
138
+ */
139
+ export function renderAsciiArt(art, colorFn) {
140
+ if (!art.length)
141
+ return '';
142
+ const lines = art.map((line, i) => colorFn ? colorFn(line, i) : line);
143
+ return lines.join('\n');
144
+ }
145
+ /**
146
+ * Render a skin's banner art with full decorative frame and ANSI colors.
147
+ *
148
+ * This is the main function used by the /banner command. It takes banner art
149
+ * lines, wraps them in a frame appropriate for the terminal mode, and applies
150
+ * the given palette color.
151
+ *
152
+ * @param art - Banner art lines from skin.banner.art
153
+ * @param color - Hex color for the banner frame (from palette)
154
+ * @param tagline - Optional tagline to display below the art
155
+ * @param mode - Override image mode detection
156
+ */
157
+ export function renderSkinBanner(art, color, tagline, mode) {
158
+ const m = mode ?? detectBestMode();
159
+ const lines = [];
160
+ const isUnicode = m !== 'ascii' && m !== 'none';
161
+ // Determine max width from art
162
+ const artWidths = art.map(line => stripAnsi(line).length);
163
+ const maxArtWidth = Math.max(...artWidths, 30);
164
+ const frameWidth = maxArtWidth + 4;
165
+ // Box chars
166
+ const h = isUnicode ? '\u2500' : '-';
167
+ const v = isUnicode ? '\u2502' : '|';
168
+ const tl = isUnicode ? '\u256D' : '+';
169
+ const tr = isUnicode ? '\u256E' : '+';
170
+ const bl = isUnicode ? '\u2570' : '+';
171
+ const br = isUnicode ? '\u256F' : '+';
172
+ const applyColor = (text) => color ? colorFg(text, color) : text;
173
+ // Top frame
174
+ lines.push(applyColor(`${tl}${h.repeat(frameWidth)}${tr}`));
175
+ // Empty line for spacing
176
+ lines.push(applyColor(`${v}${' '.repeat(frameWidth)}${v}`));
177
+ // Art lines (centered in frame)
178
+ for (const artLine of art) {
179
+ const visLen = stripAnsi(artLine).length;
180
+ const totalPad = frameWidth - visLen;
181
+ const padL = Math.floor(totalPad / 2);
182
+ const padR = totalPad - padL;
183
+ lines.push(applyColor(v) + ' '.repeat(padL) + artLine + ' '.repeat(padR) + applyColor(v));
184
+ }
185
+ // Empty line for spacing
186
+ lines.push(applyColor(`${v}${' '.repeat(frameWidth)}${v}`));
187
+ // Tagline if present
188
+ if (tagline) {
189
+ const tagVisLen = tagline.length;
190
+ const totalPad = frameWidth - tagVisLen;
191
+ const padL = Math.floor(totalPad / 2);
192
+ const padR = totalPad - padL;
193
+ lines.push(applyColor(v) + ' '.repeat(padL) + dim(tagline) + ' '.repeat(padR) + applyColor(v));
194
+ lines.push(applyColor(`${v}${' '.repeat(frameWidth)}${v}`));
195
+ }
196
+ // Bottom frame
197
+ lines.push(applyColor(`${bl}${h.repeat(frameWidth)}${br}`));
198
+ return lines.join('\n');
199
+ }
200
+ /**
201
+ * Render a skin's banner using coloredArt from splash config.
202
+ * Each line gets its own hex color. Falls back to single-color rendering.
203
+ *
204
+ * @param coloredArt - Array of {text, color} objects
205
+ * @param tagline - Optional tagline
206
+ * @param mode - Override image mode detection
207
+ */
208
+ export function renderColoredBanner(coloredArt, tagline, mode) {
209
+ const m = mode ?? detectBestMode();
210
+ const lines = [];
211
+ const isUnicode = m !== 'ascii' && m !== 'none';
212
+ // Determine max width from art
213
+ const artWidths = coloredArt.map(line => stripAnsi(line.text).length);
214
+ const maxArtWidth = Math.max(...artWidths, 30);
215
+ const frameWidth = maxArtWidth + 4;
216
+ const h = isUnicode ? '\u2500' : '-';
217
+ const v = isUnicode ? '\u2502' : '|';
218
+ const tl = isUnicode ? '\u256D' : '+';
219
+ const tr = isUnicode ? '\u256E' : '+';
220
+ const bl = isUnicode ? '\u2570' : '+';
221
+ const br = isUnicode ? '\u256F' : '+';
222
+ // Use the first line's color for the frame, or white
223
+ const frameColor = coloredArt[0]?.color;
224
+ const applyFrame = (text) => frameColor ? colorFg(text, frameColor) : text;
225
+ // Top frame
226
+ lines.push(applyFrame(`${tl}${h.repeat(frameWidth)}${tr}`));
227
+ lines.push(applyFrame(`${v}${' '.repeat(frameWidth)}${v}`));
228
+ // Art lines with per-line color
229
+ for (const { text, color } of coloredArt) {
230
+ const visLen = stripAnsi(text).length;
231
+ const totalPad = frameWidth - visLen;
232
+ const padL = Math.floor(totalPad / 2);
233
+ const padR = totalPad - padL;
234
+ lines.push(applyFrame(v) + ' '.repeat(padL) + colorFg(text, color) + ' '.repeat(padR) + applyFrame(v));
235
+ }
236
+ lines.push(applyFrame(`${v}${' '.repeat(frameWidth)}${v}`));
237
+ if (tagline) {
238
+ const tagVisLen = tagline.length;
239
+ const totalPad = frameWidth - tagVisLen;
240
+ const padL = Math.floor(totalPad / 2);
241
+ const padR = totalPad - padL;
242
+ lines.push(applyFrame(v) + ' '.repeat(padL) + dim(tagline) + ' '.repeat(padR) + applyFrame(v));
243
+ lines.push(applyFrame(`${v}${' '.repeat(frameWidth)}${v}`));
244
+ }
245
+ lines.push(applyFrame(`${bl}${h.repeat(frameWidth)}${br}`));
246
+ return lines.join('\n');
247
+ }
248
+ /**
249
+ * Render a splash animation to stdout (pre-Ink, raw terminal output).
250
+ * Returns a promise that resolves when animation completes.
251
+ * Skippable by any keypress if process.stdin is available.
252
+ */
253
+ export async function renderSplashAnimation(art, animation, speed = 50, color) {
254
+ const applyColor = (text) => color ? colorFg(text, color) : text;
255
+ switch (animation) {
256
+ case 'scan-lines': {
257
+ // Reveal line by line from top
258
+ for (const line of art) {
259
+ console.log(applyColor(line));
260
+ await delay(speed);
261
+ }
262
+ break;
263
+ }
264
+ case 'typewriter': {
265
+ // Type each line character by character
266
+ for (const line of art) {
267
+ const colored = applyColor(line);
268
+ process.stdout.write(colored);
269
+ process.stdout.write('\n');
270
+ await delay(speed);
271
+ }
272
+ break;
273
+ }
274
+ case 'fade-in': {
275
+ // Show dim first, then bright
276
+ const dimCode = '\x1b[2m';
277
+ const resetCode = '\x1b[0m';
278
+ // First pass: dim
279
+ const dimLines = [];
280
+ for (const line of art) {
281
+ const dimLine = `${dimCode}${line}${resetCode}`;
282
+ dimLines.push(dimLine);
283
+ console.log(dimLine);
284
+ }
285
+ await delay(speed * 3);
286
+ // Move cursor up and overwrite with bright
287
+ process.stdout.write(`\x1b[${art.length}A`);
288
+ for (const line of art) {
289
+ console.log(applyColor(line));
290
+ }
291
+ break;
292
+ }
293
+ case 'drop-in': {
294
+ // Lines appear one at a time with a slight bounce effect
295
+ for (const line of art) {
296
+ console.log(applyColor(line));
297
+ await delay(speed);
298
+ }
299
+ break;
300
+ }
301
+ }
302
+ }
303
+ function delay(ms) {
304
+ return new Promise(resolve => setTimeout(resolve, ms));
305
+ }
306
+ /**
307
+ * Run a full-screen theme transition effect.
308
+ * Fills the terminal with a brief animation, then clears for the new theme.
309
+ * All effects are pre-Ink (raw stdout) and self-cleaning.
310
+ */
311
+ export async function renderTransition(config) {
312
+ if (!process.stdout.isTTY || config.effect === 'none')
313
+ return;
314
+ const cols = process.stdout.columns || 80;
315
+ const rows = process.stdout.rows || 24;
316
+ const duration = config.duration ?? 1500;
317
+ const color = config.color ?? '#00FF00';
318
+ const colorSec = config.colorSecondary ?? '#003300';
319
+ // Hide cursor during animation
320
+ process.stdout.write('\x1b[?25l');
321
+ try {
322
+ switch (config.effect) {
323
+ case 'matrix-rain':
324
+ await matrixRain(cols, rows, duration, color, colorSec, config.chars);
325
+ break;
326
+ case 'warp-speed':
327
+ await warpSpeed(cols, rows, duration, color);
328
+ break;
329
+ case 'glitch':
330
+ await glitchEffect(cols, rows, duration, color, colorSec);
331
+ break;
332
+ case 'terminal-boot':
333
+ await terminalBoot(cols, rows, duration, color);
334
+ break;
335
+ case 'pixel-dissolve':
336
+ await pixelDissolve(cols, rows, duration, color);
337
+ break;
338
+ case 'sparkle':
339
+ await sparkleEffect(cols, rows, duration, color, colorSec);
340
+ break;
341
+ case 'rainbow-wave':
342
+ await rainbowWave(cols, rows, duration);
343
+ break;
344
+ case 'static-noise':
345
+ await staticNoise(cols, rows, duration);
346
+ break;
347
+ case 'fade':
348
+ case 'fade-in':
349
+ await fadeEffect(cols, rows, duration, color);
350
+ break;
351
+ case 'scan-lines':
352
+ await scanLinesEffect(cols, rows, duration, color, colorSec);
353
+ break;
354
+ case 'digital-rain':
355
+ await matrixRain(cols, rows, duration, color, colorSec, config.chars);
356
+ break;
357
+ }
358
+ }
359
+ finally {
360
+ // Show cursor, clear screen
361
+ process.stdout.write('\x1b[?25h');
362
+ process.stdout.write('\x1b[2J\x1b[H');
363
+ }
364
+ }
365
+ // --- Matrix Digital Rain ---
366
+ async function matrixRain(cols, rows, duration, color, colorDim, chars) {
367
+ const defaultChars = '\u30A2\u30A4\u30A6\u30A8\u30AA\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD\u30BF\u30C1\u30C4\u30C6\u30C80123456789';
368
+ const charArr = Array.isArray(chars) ? chars : [...(chars || defaultChars)];
369
+ const randChar = () => charArr[Math.floor(Math.random() * charArr.length)];
370
+ // Column state: each column has a "drop" position that falls
371
+ const drops = new Array(cols).fill(0).map(() => Math.floor(Math.random() * -rows));
372
+ const speeds = new Array(cols).fill(0).map(() => 0.5 + Math.random() * 1.5);
373
+ const frameTime = 50;
374
+ const frames = Math.floor(duration / frameTime);
375
+ // Clear screen
376
+ process.stdout.write('\x1b[2J\x1b[H');
377
+ for (let f = 0; f < frames; f++) {
378
+ // Build frame buffer
379
+ let output = '\x1b[H'; // Move to top-left
380
+ for (let y = 0; y < Math.min(rows - 1, 30); y++) {
381
+ let line = '';
382
+ for (let x = 0; x < Math.min(cols, 120); x += 2) { // Skip every other col for performance
383
+ const dropY = Math.floor(drops[x]);
384
+ const dist = y - dropY;
385
+ if (dist === 0) {
386
+ // Leading character — bright
387
+ line += colorFg(randChar(), '#FFFFFF');
388
+ }
389
+ else if (dist > 0 && dist < 8) {
390
+ // Trail — primary color, fading
391
+ const fade = 1 - (dist / 8);
392
+ const r = Math.floor(parseInt(color.slice(1, 3), 16) * fade);
393
+ const g = Math.floor(parseInt(color.slice(3, 5), 16) * fade);
394
+ const b = Math.floor(parseInt(color.slice(5, 7), 16) * fade);
395
+ const hex = `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`;
396
+ line += colorFg(randChar(), hex);
397
+ }
398
+ else if (dist > 0 && dist < 15 && Math.random() > 0.7) {
399
+ // Sparse background rain
400
+ line += colorFg(randChar(), colorDim);
401
+ }
402
+ else {
403
+ line += ' ';
404
+ }
405
+ }
406
+ output += line + '\n';
407
+ }
408
+ process.stdout.write(output);
409
+ // Advance drops
410
+ for (let x = 0; x < cols; x += 2) {
411
+ drops[x] += speeds[x];
412
+ if (drops[x] > rows + 10) {
413
+ drops[x] = Math.floor(Math.random() * -8);
414
+ speeds[x] = 0.5 + Math.random() * 1.5;
415
+ }
416
+ }
417
+ await delay(frameTime);
418
+ }
419
+ }
420
+ // --- Warp Speed (Star Trek style) ---
421
+ async function warpSpeed(cols, rows, duration, color) {
422
+ const frameTime = 60;
423
+ const frames = Math.floor(duration / frameTime);
424
+ const centerX = Math.floor(cols / 2);
425
+ const centerY = Math.floor(Math.min(rows - 1, 25) / 2);
426
+ const stars = [];
427
+ const starChars = ['.', '*', '+', '\u2022', '\u2219', '\u00B7'];
428
+ process.stdout.write('\x1b[2J\x1b[H');
429
+ for (let f = 0; f < frames; f++) {
430
+ // Spawn new stars from center
431
+ const spawnCount = Math.min(3, Math.floor(f / 3) + 1);
432
+ for (let i = 0; i < spawnCount; i++) {
433
+ const angle = Math.random() * Math.PI * 2;
434
+ const speed = 0.5 + Math.random() * 2;
435
+ stars.push({
436
+ x: centerX, y: centerY,
437
+ vx: Math.cos(angle) * speed,
438
+ vy: Math.sin(angle) * speed * 0.5, // Squish vertically for terminal aspect
439
+ char: starChars[Math.floor(Math.random() * starChars.length)],
440
+ });
441
+ }
442
+ // Build frame
443
+ const grid = Array.from({ length: Math.min(rows - 1, 25) }, () => new Array(Math.min(cols, 120)).fill(' '));
444
+ // Update and render stars
445
+ for (let i = stars.length - 1; i >= 0; i--) {
446
+ const s = stars[i];
447
+ s.x += s.vx;
448
+ s.y += s.vy;
449
+ // Accelerate as they move outward (warp stretch)
450
+ s.vx *= 1.08;
451
+ s.vy *= 1.08;
452
+ const sx = Math.floor(s.x);
453
+ const sy = Math.floor(s.y);
454
+ if (sx < 0 || sx >= Math.min(cols, 120) || sy < 0 || sy >= Math.min(rows - 1, 25)) {
455
+ stars.splice(i, 1);
456
+ continue;
457
+ }
458
+ // Streak effect — draw a line from current to previous position
459
+ const dist = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
460
+ const streakChar = dist > 3 ? '\u2500' : dist > 1.5 ? '\u2022' : s.char;
461
+ grid[sy][sx] = streakChar;
462
+ }
463
+ let output = '\x1b[H';
464
+ for (const row of grid) {
465
+ output += colorFg(row.join(''), color) + '\n';
466
+ }
467
+ process.stdout.write(output);
468
+ // Keep star count manageable
469
+ while (stars.length > 200)
470
+ stars.shift();
471
+ await delay(frameTime);
472
+ }
473
+ }
474
+ // --- Glitch Effect (Cyberpunk) ---
475
+ async function glitchEffect(cols, rows, duration, color, colorSec) {
476
+ const frameTime = 80;
477
+ const frames = Math.floor(duration / frameTime);
478
+ const glitchChars = '\u2588\u2593\u2592\u2591\u2580\u2584\u258C\u2590/\\|#@$%&';
479
+ const glitchArr = [...glitchChars];
480
+ const w = Math.min(cols, 120);
481
+ const h = Math.min(rows - 1, 25);
482
+ process.stdout.write('\x1b[2J\x1b[H');
483
+ for (let f = 0; f < frames; f++) {
484
+ let output = '\x1b[H';
485
+ const glitchIntensity = Math.sin((f / frames) * Math.PI); // Peaks in middle
486
+ for (let y = 0; y < h; y++) {
487
+ let line = '';
488
+ if (Math.random() < glitchIntensity * 0.4) {
489
+ // Glitch line — random block characters
490
+ const offset = Math.floor(Math.random() * 10) - 5;
491
+ const spaces = ' '.repeat(Math.max(0, offset));
492
+ for (let x = 0; x < w - Math.abs(offset); x++) {
493
+ if (Math.random() < glitchIntensity * 0.6) {
494
+ const c = Math.random() > 0.5 ? color : colorSec;
495
+ line += colorFg(glitchArr[Math.floor(Math.random() * glitchArr.length)], c);
496
+ }
497
+ else {
498
+ line += ' ';
499
+ }
500
+ }
501
+ line = spaces + line;
502
+ }
503
+ else {
504
+ // Mostly empty with sparse glitch
505
+ for (let x = 0; x < w; x++) {
506
+ if (Math.random() < glitchIntensity * 0.05) {
507
+ line += colorFg(glitchArr[Math.floor(Math.random() * glitchArr.length)], color);
508
+ }
509
+ else {
510
+ line += ' ';
511
+ }
512
+ }
513
+ }
514
+ output += line.slice(0, w) + '\n';
515
+ }
516
+ process.stdout.write(output);
517
+ await delay(frameTime);
518
+ }
519
+ }
520
+ // --- Terminal Boot (WarGames / Retro) ---
521
+ async function terminalBoot(cols, rows, duration, color) {
522
+ const bootLines = [
523
+ 'INITIALIZING SYSTEM...',
524
+ 'LOADING KERNEL... OK',
525
+ 'MEMORY CHECK... 640K OK',
526
+ 'LOADING DRIVERS...',
527
+ ' [OK] TERMINAL',
528
+ ' [OK] NETWORK',
529
+ ' [OK] AI SUBSYSTEM',
530
+ '',
531
+ 'SYSTEM READY.',
532
+ '',
533
+ 'WELCOME TO CALLIOPE',
534
+ '',
535
+ ];
536
+ process.stdout.write('\x1b[2J\x1b[H');
537
+ const timePerLine = Math.floor(duration / bootLines.length);
538
+ for (const line of bootLines) {
539
+ if (line === '') {
540
+ console.log();
541
+ await delay(timePerLine / 2);
542
+ continue;
543
+ }
544
+ // Typewriter effect per character
545
+ const colored = colorFg(line, color);
546
+ process.stdout.write(colored);
547
+ process.stdout.write('\n');
548
+ await delay(timePerLine);
549
+ }
550
+ }
551
+ // --- Pixel Dissolve ---
552
+ async function pixelDissolve(cols, rows, duration, color) {
553
+ const frameTime = 60;
554
+ const frames = Math.floor(duration / frameTime);
555
+ const w = Math.min(cols, 120);
556
+ const h = Math.min(rows - 1, 25);
557
+ const blockChars = ['\u2588', '\u2593', '\u2592', '\u2591', ' '];
558
+ process.stdout.write('\x1b[2J\x1b[H');
559
+ for (let f = 0; f < frames; f++) {
560
+ const progress = f / frames; // 0 → 1
561
+ let output = '\x1b[H';
562
+ for (let y = 0; y < h; y++) {
563
+ let line = '';
564
+ for (let x = 0; x < w; x++) {
565
+ // Dissolve from full → empty, with some randomness
566
+ const threshold = progress + (Math.random() * 0.3 - 0.15);
567
+ if (threshold < 0.3) {
568
+ line += colorFg('\u2588', color);
569
+ }
570
+ else if (threshold < 0.5) {
571
+ line += colorFg('\u2593', color);
572
+ }
573
+ else if (threshold < 0.7) {
574
+ line += colorFg('\u2592', color);
575
+ }
576
+ else if (threshold < 0.85) {
577
+ line += colorFg('\u2591', color);
578
+ }
579
+ else {
580
+ line += ' ';
581
+ }
582
+ }
583
+ output += line + '\n';
584
+ }
585
+ process.stdout.write(output);
586
+ await delay(frameTime);
587
+ }
588
+ }
589
+ // --- Sparkle (Zelda / Fantasy) ---
590
+ async function sparkleEffect(cols, rows, duration, color, colorSec) {
591
+ const frameTime = 80;
592
+ const frames = Math.floor(duration / frameTime);
593
+ const sparkleChars = ['\u2728', '\u2727', '\u2726', '\u2735', '\u2734', '\u2733', '\u00B7', ' '];
594
+ const w = Math.min(cols, 120);
595
+ const h = Math.min(rows - 1, 25);
596
+ process.stdout.write('\x1b[2J\x1b[H');
597
+ for (let f = 0; f < frames; f++) {
598
+ const intensity = Math.sin((f / frames) * Math.PI); // Peak in middle
599
+ let output = '\x1b[H';
600
+ for (let y = 0; y < h; y++) {
601
+ let line = '';
602
+ for (let x = 0; x < w; x++) {
603
+ if (Math.random() < intensity * 0.15) {
604
+ const c = Math.random() > 0.5 ? color : colorSec;
605
+ line += colorFg(sparkleChars[Math.floor(Math.random() * 4)], c);
606
+ }
607
+ else if (Math.random() < intensity * 0.05) {
608
+ line += colorFg('\u00B7', color);
609
+ }
610
+ else {
611
+ line += ' ';
612
+ }
613
+ }
614
+ output += line + '\n';
615
+ }
616
+ process.stdout.write(output);
617
+ await delay(frameTime);
618
+ }
619
+ }
620
+ // --- Rainbow Wave (Saggitaria) ---
621
+ async function rainbowWave(cols, rows, duration) {
622
+ const frameTime = 60;
623
+ const frames = Math.floor(duration / frameTime);
624
+ const w = Math.min(cols, 120);
625
+ const h = Math.min(rows - 1, 25);
626
+ const waveChars = ['\u2588', '\u2593', '\u2592', '\u2591'];
627
+ const colors = ['#FF6B6B', '#FFE66D', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD', '#FF6B6B'];
628
+ process.stdout.write('\x1b[2J\x1b[H');
629
+ for (let f = 0; f < frames; f++) {
630
+ let output = '\x1b[H';
631
+ const phase = (f / frames) * Math.PI * 4;
632
+ for (let y = 0; y < h; y++) {
633
+ let line = '';
634
+ for (let x = 0; x < w; x++) {
635
+ const wave = Math.sin(phase + x * 0.1 + y * 0.2);
636
+ const colorIdx = Math.floor(((wave + 1) / 2) * (colors.length - 1));
637
+ const charIdx = Math.floor(((wave + 1) / 2) * (waveChars.length - 1));
638
+ if (Math.abs(wave) > 0.3) {
639
+ line += colorFg(waveChars[charIdx], colors[colorIdx]);
640
+ }
641
+ else {
642
+ line += ' ';
643
+ }
644
+ }
645
+ output += line + '\n';
646
+ }
647
+ process.stdout.write(output);
648
+ await delay(frameTime);
649
+ }
650
+ }
651
+ // --- Static Noise ---
652
+ async function staticNoise(cols, rows, duration) {
653
+ const frameTime = 50;
654
+ const frames = Math.floor(duration / frameTime);
655
+ const noiseChars = '\u2588\u2593\u2592\u2591 ';
656
+ const noiseArr = [...noiseChars];
657
+ const w = Math.min(cols, 120);
658
+ const h = Math.min(rows - 1, 25);
659
+ process.stdout.write('\x1b[2J\x1b[H');
660
+ for (let f = 0; f < frames; f++) {
661
+ const fadeOut = 1 - (f / frames); // Fade to black
662
+ let output = '\x1b[H';
663
+ for (let y = 0; y < h; y++) {
664
+ let line = '';
665
+ for (let x = 0; x < w; x++) {
666
+ if (Math.random() < fadeOut * 0.5) {
667
+ const gray = Math.floor(Math.random() * 200 * fadeOut);
668
+ const hex = `#${gray.toString(16).padStart(2, '0')}${gray.toString(16).padStart(2, '0')}${gray.toString(16).padStart(2, '0')}`;
669
+ line += colorFg(noiseArr[Math.floor(Math.random() * noiseArr.length)], hex);
670
+ }
671
+ else {
672
+ line += ' ';
673
+ }
674
+ }
675
+ output += line + '\n';
676
+ }
677
+ process.stdout.write(output);
678
+ await delay(frameTime);
679
+ }
680
+ }
681
+ // --- Fade Effect ---
682
+ async function fadeEffect(cols, rows, duration, color) {
683
+ const frameTime = 60;
684
+ const frames = Math.floor(duration / frameTime);
685
+ const w = Math.min(cols, 120);
686
+ const h = Math.min(rows - 1, 25);
687
+ const blockChars = [' ', '\u2591', '\u2592', '\u2593', '\u2588', '\u2593', '\u2592', '\u2591', ' '];
688
+ process.stdout.write('\x1b[2J\x1b[H');
689
+ for (let f = 0; f < frames; f++) {
690
+ const progress = f / frames;
691
+ // Fade in then out: peak at 0.5
692
+ const intensity = Math.sin(progress * Math.PI);
693
+ const charIdx = Math.floor(intensity * (blockChars.length - 1));
694
+ const ch = blockChars[charIdx];
695
+ // Parse base color and scale brightness by intensity
696
+ const r = parseInt(color.slice(1, 3), 16);
697
+ const g = parseInt(color.slice(3, 5), 16);
698
+ const b = parseInt(color.slice(5, 7), 16);
699
+ const cr = Math.floor(r * intensity);
700
+ const cg = Math.floor(g * intensity);
701
+ const cb = Math.floor(b * intensity);
702
+ const hex = `#${cr.toString(16).padStart(2, '0')}${cg.toString(16).padStart(2, '0')}${cb.toString(16).padStart(2, '0')}`;
703
+ let output = '\x1b[H';
704
+ const line = colorFg(ch.repeat(w), hex);
705
+ for (let y = 0; y < h; y++) {
706
+ output += line + '\n';
707
+ }
708
+ process.stdout.write(output);
709
+ await delay(frameTime);
710
+ }
711
+ }
712
+ // --- Scan Lines Effect ---
713
+ async function scanLinesEffect(cols, rows, duration, color, colorSec) {
714
+ const frameTime = 50;
715
+ const frames = Math.floor(duration / frameTime);
716
+ const w = Math.min(cols, 120);
717
+ const h = Math.min(rows - 1, 25);
718
+ process.stdout.write('\x1b[2J\x1b[H');
719
+ for (let f = 0; f < frames; f++) {
720
+ const scanY = Math.floor((f / frames) * h * 2) % h;
721
+ let output = '\x1b[H';
722
+ for (let y = 0; y < h; y++) {
723
+ const dist = Math.abs(y - scanY);
724
+ if (dist === 0) {
725
+ // Bright scan line
726
+ output += colorFg('\u2588'.repeat(w), '#FFFFFF') + '\n';
727
+ }
728
+ else if (dist <= 2) {
729
+ // Near glow
730
+ output += colorFg('\u2593'.repeat(w), color) + '\n';
731
+ }
732
+ else if (dist <= 4) {
733
+ // Dim trail
734
+ output += colorFg('\u2591'.repeat(w), colorSec) + '\n';
735
+ }
736
+ else {
737
+ output += ' '.repeat(w) + '\n';
738
+ }
739
+ }
740
+ process.stdout.write(output);
741
+ await delay(frameTime);
742
+ }
743
+ }
744
+ /**
745
+ * Get a human-readable label for an image mode.
746
+ */
747
+ export function getImageModeLabel(mode) {
748
+ const labels = {
749
+ iterm2: 'iTerm2 Inline Image',
750
+ kitty: 'Kitty Graphics Protocol',
751
+ halfblock: 'Unicode Half-Block',
752
+ braille: 'Braille Dots',
753
+ ascii: 'ASCII',
754
+ none: 'None',
755
+ };
756
+ return labels[mode];
757
+ }
758
+ // ============================================================================
759
+ // Utility
760
+ // ============================================================================
761
+ /** Strip ANSI escape sequences from a string for measuring visible width */
762
+ function stripAnsi(str) {
763
+ // eslint-disable-next-line no-control-regex
764
+ return str.replace(/\x1b\[[0-9;]*m/g, '').replace(/\x1b[\\_\]][^\x07\x1b]*[\x07\x1b\\]?/g, '');
765
+ }
766
+ //# sourceMappingURL=terminal-image.js.map