cct-cli 0.7.9.0__py3-none-any.whl

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 (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
calc_terminal/anim.py ADDED
@@ -0,0 +1,367 @@
1
+ """
2
+ Reusable animation primitives for Chemistry Calc Terminal — the "creativity
3
+ on interface with animation" layer.
4
+
5
+ Each function is self-contained, TTY-aware (no-op + clean fallback when output
6
+ isn't a real terminal, e.g. piped or under tests), and built entirely on
7
+ `theme`'s 24-bit ANSI helpers. Used by boot, the `/art` browser, easter eggs,
8
+ and the new chat surface.
9
+
10
+ Animation policy: every function here checks `engine.ANIMATE` (the existing
11
+ global flag) and, if off, degrades to an instant static render. So `engine
12
+ --no-animation` users get the same final visual, just without motion.
13
+ """
14
+
15
+ if __name__ == '__main__':
16
+ print("This is a library file and is not meant to be run directly.")
17
+ print("Please run 'python main.py' or 'python model.py' from the project root directory.")
18
+ import sys
19
+ sys.exit(1)
20
+
21
+ import random
22
+ import shutil
23
+ import sys
24
+ import time
25
+
26
+ from . import theme
27
+
28
+ try:
29
+ from . import engine as _engine
30
+ except Exception:
31
+ _engine = None
32
+
33
+
34
+ def _animate_enabled():
35
+ """Honour the global ANIMATE flag when present; default to True."""
36
+ if _engine is not None:
37
+ return getattr(_engine, "ANIMATE", True)
38
+ return True
39
+
40
+
41
+ def _is_tty():
42
+ return sys.stdout.isatty()
43
+
44
+
45
+ # OpenCode's exact braille spinner frames, in order, at 80ms — the same set
46
+ # OpenCode's generic Spinner uses (`spinner.tsx`).
47
+ BRAILLE_FRAMES = ["\u280b", "\u2819", "\u2839", "\u2838", "\u283c",
48
+ "\u2834", "\u2826", "\u2827", "\u2807", "\u280f"]
49
+
50
+
51
+ def dots_spinner(label="thinking", cycles=10, color=None, interval=0.08):
52
+ """OpenCode-style inline braille spinner with a label, self-erasing.
53
+
54
+ Renders `{frame} {label}` on one line, cycling the braille frames, then
55
+ erases the whole line when done (cursor stays on a clean line for whatever
56
+ prints next). No-op when not a TTY or animations are off.
57
+ """
58
+ if not _is_tty() or not _animate_enabled():
59
+ return
60
+ color = color or theme.OC_PRIMARY
61
+ last_w = 0
62
+ for i in range(max(1, cycles)):
63
+ frame = BRAILLE_FRAMES[i % len(BRAILLE_FRAMES)]
64
+ cell = theme.fg(frame, color, bold=True) + " " + theme.fg(label, theme.OC_MUTED)
65
+ w = theme.vlen(cell) + 2
66
+ pad = " " * max(0, last_w - w)
67
+ sys.stdout.write("\r" + cell + pad)
68
+ sys.stdout.flush()
69
+ last_w = w
70
+ time.sleep(interval)
71
+ # erase the line we drew
72
+ sys.stdout.write("\r" + " " * (last_w + 1) + "\r")
73
+ sys.stdout.flush()
74
+
75
+
76
+ def text_shimmer(s, colors=None, passes=1, step=0.02, bold=True):
77
+ """Animate a moving gradient 'shimmer' across `s`, then leave the final
78
+ gradient render printed in place.
79
+
80
+ Each pass slides the color phase across the characters, so a CYAN→PURPLE
81
+ gradient appears to flow left→right. Finishes with the static gradient so
82
+ the text stays readable. No-op (prints the static gradient only) when not a
83
+ TTY or animations are off.
84
+ """
85
+ if colors is None:
86
+ colors = (theme.CYAN, theme.PURPLE, theme.CYAN)
87
+ live = _is_tty() and _animate_enabled()
88
+ n = max(1, len(s) - 1)
89
+ b = theme.BOLD if bold else ""
90
+ phases = range(0, n, max(1, n // 10)) if live else [0]
91
+
92
+ for p in range(max(1, passes)):
93
+ for off in phases:
94
+ out = []
95
+ for i, ch in enumerate(s):
96
+ if ch == " ":
97
+ out.append(" ")
98
+ continue
99
+ t = ((i / n) + (off / max(1, n))) % 1.0
100
+ c = theme._stops(t, list(colors))
101
+ out.append(f"{b}{theme.rgb(*c)}{ch}")
102
+ out.append(theme.RESET)
103
+ if live:
104
+ sys.stdout.write("\r" + "".join(out))
105
+ sys.stdout.flush()
106
+ time.sleep(step)
107
+ if live:
108
+ sys.stdout.write("\r")
109
+ # final static render
110
+ print(theme.gradient(s, *colors, bold=bold))
111
+
112
+
113
+ def bars_load(label="warming up", duration=0.7, width=34, color=None):
114
+ """An OpenCode-style filling blocks bar: `▀▄` pairs sweep in left→right
115
+ with a label and a percentage, ending at 100%. A richer sibling of
116
+ `theme.progress_bar`. Falls back to a single static line when not animated.
117
+ """
118
+ color = color or theme.OC_PRIMARY
119
+ steps = 22
120
+ label_cell = theme.fg(label, theme.OC_MUTED)
121
+ live = _is_tty() and _animate_enabled()
122
+ for i in range(steps + 1):
123
+ filled = int(width * i / steps)
124
+ # alternate half-blocks for a woven look
125
+ bar_chars = []
126
+ for j in range(width):
127
+ if j < filled:
128
+ bar_chars.append(theme.HALF_UP if j % 2 == 0 else theme.HALF_DN)
129
+ else:
130
+ bar_chars.append(theme.fg("\u2591", theme.OC_BG_ELEMENT))
131
+ bar = theme.fg("".join(bar_chars[:filled]), color) + "".join(bar_chars[filled:])
132
+ pct = int(100 * i / steps)
133
+ sys.stdout.write(f"\r {label_cell} {bar} {theme.fg(str(pct) + '%', theme.TEXT, bold=True)}")
134
+ sys.stdout.flush()
135
+ if live:
136
+ time.sleep(duration / steps)
137
+ sys.stdout.write("\n")
138
+
139
+
140
+ def scanline_clear(width=None, duration=0.25, color=None):
141
+ """A single `▞` sweep line that travels top→bottom across `height` rows
142
+ then clears, used as a premium transition between major screens. Leaves the
143
+ screen empty afterward (caller draws the next view fresh). No-op when not a
144
+ TTY or animations are off.
145
+
146
+ Implemented as a vertical sweep of a full-width dim line so it reads as a
147
+ scan-wipe without needing to know the full screen content.
148
+ """
149
+ if not _is_tty() or not _animate_enabled():
150
+ return
151
+ color = color or theme.OC_BORDER
152
+ try:
153
+ cols, rows = shutil.get_terminal_size(fallback=(78, 24))
154
+ except Exception:
155
+ cols, rows = 78, 24
156
+ if width is None:
157
+ width = cols
158
+ line = theme.fg(theme.SWEEP * width, color)
159
+ theme.hide_cursor()
160
+ try:
161
+ steps = max(3, min(rows - 2, 10))
162
+ for i in range(steps):
163
+ # move down `i+1` rows from the current line, draw, then it'll be
164
+ # overwritten by the next step — simplest robust sweep that doesn't
165
+ # depend on absolute cursor coordinates.
166
+ if i > 0:
167
+ sys.stdout.write(f"\033[1A\r") # step back up one row
168
+ sys.stdout.write("\r" + line)
169
+ sys.stdout.flush()
170
+ time.sleep(duration / steps)
171
+ sys.stdout.write("\r" + " " * width + "\r")
172
+ sys.stdout.flush()
173
+ sys.stdout.write("\n")
174
+ finally:
175
+ theme.show_cursor()
176
+
177
+
178
+ def bouncy_reveal(lines, delay=0.04, color=None, colors=None, bold=True,
179
+ align="center", width=None):
180
+ """Reveal a block of lines (typically ASCII art) one at a time with a
181
+ gentle vertical 'bounce': each line is printed, briefly re-drawn one row up,
182
+ then settled — giving art a playful entrance. Lines may be plain or already
183
+ ANSI-colored; plain lines get a gradient via `theme.gradient`.
184
+
185
+ `colors` (a tuple) drives the gradient. `color` (single) is a fallback if
186
+ `colors` is None. Falls back to a flat one-line-at-a-time reveal (same as
187
+ `theme.reveal_lines`) when not animated.
188
+ """
189
+ if colors is None:
190
+ colors = (theme.CYAN, theme.PURPLE) if color is None else (color, color)
191
+ live = _is_tty() and _animate_enabled()
192
+ for idx, line in enumerate(lines):
193
+ if line.strip() == "":
194
+ print()
195
+ continue
196
+ # If the line already contains ANSI color codes, render as-is;
197
+ # otherwise apply the gradient.
198
+ already_colored = "\033[" in line
199
+ rendered = line if already_colored else theme.gradient(line, *colors, bold=bold)
200
+ if align == "center" and width:
201
+ rendered = theme.vpad(rendered, width, "center")
202
+ if live:
203
+ # print, nudge up, reprint — the 'bounce'
204
+ print(rendered)
205
+ sys.stdout.write("\033[1A\r") # up one row, col 0
206
+ time.sleep(delay * 0.4)
207
+ sys.stdout.write(rendered + "\n")
208
+ sys.stdout.flush()
209
+ time.sleep(delay * 0.6)
210
+ else:
211
+ print(rendered)
212
+ time.sleep(delay)
213
+
214
+
215
+ def confetti_burst(width=None, count=20, colors=None, duration=0.5):
216
+ """A quick celebratory scatter of `* ✦ · +` glyphs in assorted colors
217
+ across the top of the current area, used by easter eggs and `/art` reveals.
218
+ Self-erasing. No-op when not a TTY or animations are off.
219
+ """
220
+ if not _is_tty() or not _animate_enabled():
221
+ return
222
+ if colors is None:
223
+ colors = [theme.CYAN, theme.PURPLE, theme.ORANGE, theme.GREEN, theme.OC_ACCENT]
224
+ if width is None:
225
+ try:
226
+ width = shutil.get_terminal_size(fallback=(78, 24)).columns
227
+ except Exception:
228
+ width = 78
229
+ glyphs = ["*", "\u2726", "\u00b7", "+", "\u2727"] # * ✦ · + ✧
230
+ rng = random.Random()
231
+ rows = 3
232
+ theme.hide_cursor()
233
+ try:
234
+ frames = 8
235
+ for f in range(frames):
236
+ grid = [[" "] * width for _ in range(rows)]
237
+ for _ in range(count):
238
+ x = rng.randrange(width)
239
+ y = rng.randrange(rows)
240
+ grid[y][x] = theme.fg(rng.choice(glyphs), rng.choice(colors), bold=True)
241
+ buf = "\n".join("".join(r) for r in grid)
242
+ if f > 0:
243
+ sys.stdout.write(f"\033[{rows}A")
244
+ sys.stdout.write("\r" + buf + "\n")
245
+ sys.stdout.flush()
246
+ time.sleep(duration / frames)
247
+ # erase
248
+ sys.stdout.write(f"\033[{rows}A")
249
+ for _ in range(rows):
250
+ sys.stdout.write("\r" + " " * width + "\n")
251
+ sys.stdout.write(f"\033[{rows}A\r")
252
+ sys.stdout.flush()
253
+ finally:
254
+ theme.show_cursor()
255
+
256
+
257
+ # =================================================================== v0.5.6 ==
258
+ # Additional "creativity on interface with animation" primitives — the extra
259
+ # motion layer for the polished OpenCode-style chat surface. Each is TTY-aware
260
+ # and honours the global ANIMATE flag, degrading to a static render otherwise.
261
+
262
+ def rail_grow(width, color=None, char=None, delay=0.006):
263
+ """Animate a horizontal bar (default the OpenCode `▀` half-block cap)
264
+ growing left→right in place, then leave the full bar printed.
265
+
266
+ Used as the closing cap of the OpenCode input box so the box doesn't just
267
+ *appear* — it *draws shut*, the way a real TUI slides its elements in.
268
+ Returns nothing; prints one line. No-op animation when not a TTY/anim off
269
+ (still prints the finished bar so the box stays visually closed)."""
270
+ color = color or theme.OC_PRIMARY
271
+ ch = char or theme.HALF_UP
272
+ live = _is_tty() and _animate_enabled()
273
+ width = max(1, width)
274
+ if live:
275
+ for n in range(1, width + 1):
276
+ # ease-out-ish: step a little faster as it nears completion
277
+ sys.stdout.write("\r" + theme.fg(ch * n, color))
278
+ sys.stdout.flush()
279
+ time.sleep(delay)
280
+ sys.stdout.write("\n")
281
+ else:
282
+ print(theme.fg(ch * width, color))
283
+
284
+
285
+ def wave_pulse(width, color_a=None, color_b=None, cycles=2, delay=0.05):
286
+ """An animated sine-wave divider using block-height glyphs
287
+ (`▁▂▃▄▅▆▇█▇▆▅▄▃▂▁`) that scrolls left→right, then settles on a static
288
+ gradient rule. A premium section break between the chat history and the
289
+ next input box. Degrades to a static gradient rule when not animated."""
290
+ color_a = color_a or theme.OC_PRIMARY
291
+ color_b = color_b or theme.OC_SECONDARY
292
+ blocks = "\u2581\u2582\u2583\u2584\u2585\u2586\u2587\u2588\u2587\u2586\u2585\u2584\u2583\u2582"
293
+ live = _is_tty() and _animate_enabled()
294
+ width = max(8, width)
295
+ if not live:
296
+ print(theme.gradient(blocks[:width] if width < len(blocks) else blocks * (width // len(blocks) + 1),
297
+ color_a, color_b))
298
+ return
299
+ n = len(blocks)
300
+ phase = 0
301
+ total = cycles * n
302
+ last_w = width
303
+ theme.hide_cursor()
304
+ try:
305
+ for _ in range(total):
306
+ row = "".join(blocks[(phase + j) % n] for j in range(width))
307
+ # two-tone by position so it reads as a flowing wave
308
+ out = "".join(
309
+ theme.fg(row[j], color_a if (j % n) < n // 2 else color_b)
310
+ for j in range(width))
311
+ sys.stdout.write("\r" + out + " " * max(0, last_w - width))
312
+ sys.stdout.flush()
313
+ last_w = width
314
+ phase += 1
315
+ time.sleep(delay)
316
+ # settle to a clean static gradient rule
317
+ sys.stdout.write("\r" + theme.gradient("\u2500" * width, color_a, color_b) + "\n")
318
+ sys.stdout.flush()
319
+ finally:
320
+ theme.show_cursor()
321
+
322
+
323
+ def bouncing_dots(label="thinking", count=3, color=None, cycles=12, delay=0.12):
324
+ """Three dots that bounce up and down in sequence beside a label — a
325
+ playful alternate to the braille spinner. Self-erasing. No-op when not a
326
+ TTY or animations are off."""
327
+ if not _is_tty() or not _animate_enabled():
328
+ return
329
+ color = color or theme.OC_PRIMARY
330
+ heights = [" ", "\u2584", "\u2580"] # below / dot / above baseline
331
+ for _ in range(max(1, cycles)):
332
+ for offset in range(len(heights)):
333
+ cells = []
334
+ for d in range(count):
335
+ # each dot is phase-shifted so they ripple
336
+ h = (offset + d) % len(heights)
337
+ cells.append(theme.fg(heights[h], color, bold=True) if heights[h] != " " else " ")
338
+ row = theme.fg(label, theme.OC_MUTED) + " " + " ".join(cells)
339
+ sys.stdout.write("\r" + row + " ")
340
+ sys.stdout.flush()
341
+ time.sleep(delay)
342
+ # erase
343
+ sys.stdout.write("\r" + " " * (theme.vlen(theme.fg(label, theme.OC_MUTED)) + 2 + count * 2 + 3) + "\r")
344
+ sys.stdout.flush()
345
+
346
+
347
+ def glow_pulse(text, color=None, cycles=4, delay=0.12, bold=True):
348
+ """Pulse `text` between dim and vivid brightness a few times, then leave
349
+ it printed vivid — a soft "alive" highlight for role labels / status
350
+ words. No-op animation (static vivid print) when not a TTY/anim off."""
351
+ color = color or theme.OC_PRIMARY
352
+ vivid = color
353
+ dim = tuple(int(c * 0.45) for c in color) # darken ~55% for the dim beat
354
+ live = _is_tty() and _animate_enabled()
355
+ if not live:
356
+ print(theme.fg(text, vivid, bold=bold))
357
+ return
358
+ n = max(2, len(text))
359
+ for i in range(max(1, cycles) * 2):
360
+ beat = vivid if i % 2 == 0 else dim
361
+ # gradient across the text for a little shimmer while it pulses
362
+ rendered = theme.gradient(text, beat, vivid, bold=bold)
363
+ sys.stdout.write("\r" + rendered + " " * 2)
364
+ sys.stdout.flush()
365
+ time.sleep(delay)
366
+ sys.stdout.write("\r")
367
+ print(theme.gradient(text, vivid, color_b=theme.OC_SECONDARY, bold=bold))