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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- 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))
|