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
@@ -0,0 +1,216 @@
1
+ """
2
+ calc_terminal/identity.py
3
+ ==========================
4
+ Single source of truth for CCT's application identity: what CCT is, who
5
+ made it, and what it's actually built with.
6
+
7
+ Before this module existed, "who made you" style questions were answered
8
+ in three different, drifting places: the AI's default system prompt (no
9
+ creator info at all), the AGENT/AI system prompts in agent.py (also
10
+ nothing), and a hidden easter egg in easter_eggs.py (vibes, no name).
11
+ That's why the app used to give inconsistent or flatly wrong answers.
12
+
13
+ Every one of those places now reads from here instead of hardcoding its
14
+ own copy:
15
+ - aicore.py's default system prompts embed IDENTITY_BLOCK
16
+ - agent.py's AGENT_SYSTEM_PROMPT / AI_SYSTEM_PROMPT embed IDENTITY_BLOCK
17
+ - easter_eggs.py's "who made this" panel renders answer_creator()
18
+ - the Textual UI (ui/app.py) and the fallback CLI (app.py) both run a
19
+ deterministic fast-path intercept (is_identity_question / answer_for)
20
+ *before* ever reaching the AI, so the answer is 100% consistent no
21
+ matter which AI provider is active, whether it's offline, or how a
22
+ particular model happens to phrase things.
23
+
24
+ tech_stack_sentence() never invents technology — it only ever reports the
25
+ Python/Textual/engine core that's always true plus whatever AI provider
26
+ is actually configured right now (from aicore's own config), so it can't
27
+ drift out of sync with reality either.
28
+ """
29
+
30
+ import re
31
+
32
+ # --------------------------------------------------------------- facts --
33
+ APP_NAME = "Coding Agent Terminal"
34
+ SHORT_NAME = "CAT"
35
+ APP_TAGLINE = "a terminal-based AI-powered coding and scientific workspace"
36
+ APP_VERSION = "0.7.9.0"
37
+
38
+ CLI_TITLE = "CAT CLI"
39
+ CLI_EMOJI = "🐱"
40
+ FULL_CLI_TITLE = f"{CLI_EMOJI} {CLI_TITLE}"
41
+
42
+ CREATOR_NAME = "Kazi Zillani"
43
+ CREATOR_ROLE = "a student and independent developer"
44
+ DEVELOPMENT_NOTE = "designed and developed by a single developer"
45
+
46
+ # Technology that's always part of CAT regardless of which AI backend
47
+ # (if any) is currently configured.
48
+ _CORE_TECH = [
49
+ "Python",
50
+ "the Textual terminal UI framework",
51
+ "a custom chemistry calculation engine",
52
+ ]
53
+
54
+ _PROVIDER_LABELS = {
55
+ "openai": "OpenAI",
56
+ "anthropic": "Anthropic",
57
+ "gemini": "Google Gemini",
58
+ "openrouter": "OpenRouter",
59
+ "groq": "Groq",
60
+ "ollama": "a local Ollama model",
61
+ }
62
+
63
+
64
+ def _provider_phrase(config):
65
+ """Describe the currently configured AI backend truthfully, or return
66
+ None if nothing is configured — never guess a provider that isn't
67
+ actually active."""
68
+ if not config:
69
+ return None
70
+ provider = (config.get("provider") or "").strip().lower()
71
+ if not provider:
72
+ return None
73
+ model = (config.get("model") or "").strip()
74
+ label = _PROVIDER_LABELS.get(provider, provider)
75
+ if provider == "ollama":
76
+ return f"{label} (model: {model})" if model else label
77
+ return f"{label} (model: {model})" if model else label
78
+
79
+
80
+ def tech_stack_parts(config=None):
81
+ """List of technology strings actually in play right now. `config`
82
+ should be aicore.load_config()'s return value, if available."""
83
+ parts = list(_CORE_TECH)
84
+ provider_phrase = _provider_phrase(config)
85
+ if provider_phrase:
86
+ parts.append(provider_phrase)
87
+ return parts
88
+
89
+
90
+ def tech_stack_sentence(config=None):
91
+ parts = tech_stack_parts(config)
92
+ if len(parts) == 1:
93
+ return parts[0]
94
+ return ", ".join(parts[:-1]) + ", and " + parts[-1]
95
+
96
+
97
+ # ------------------------------------------------------- system prompt --
98
+ # Folded into every AI system prompt so the model itself stays accurate
99
+ # even for identity phrasings the fast-path intercept below doesn't
100
+ # happen to catch.
101
+ IDENTITY_BLOCK = f"""[CAT IDENTITY \u2014 factual, do not deviate from this]
102
+ You are running inside {APP_NAME} ({SHORT_NAME}), {APP_TAGLINE}.
103
+ {SHORT_NAME} the application was created by {CREATOR_NAME}, {CREATOR_ROLE} \u2014 {DEVELOPMENT_NOTE}.
104
+ You (the AI answering right now) are a separate thing from {SHORT_NAME} itself: {SHORT_NAME} is the app/terminal the person is typing into; the AI is whichever model provider is currently configured. Never claim {CREATOR_NAME} built or trained the underlying AI model \u2014 only the {SHORT_NAME} application around it.
105
+ If asked who made/built/created/developed {SHORT_NAME}, or who owns this project: the answer is {CREATOR_NAME}, {CREATOR_ROLE}.
106
+ If asked what AI model or provider is answering: name the actual configured provider/model, never {CREATOR_NAME}.
107
+ Never invent capabilities, companies, or technologies that aren't actually part of {SHORT_NAME}."""
108
+
109
+
110
+ # ---------------------------------------------------- deterministic Q&A --
111
+ # A fast-path for the most common phrasings, answered directly from this
112
+ # module with zero AI involved, so the answer can never depend on model
113
+ # mood, provider, or connectivity. Broader than an exact-match easter
114
+ # egg on purpose: "who built cat", "who owns this project", "who
115
+ # developed you" etc. should all land here, not just one exact string.
116
+ _CREATOR_PATTERNS = [
117
+ r"\bwho\b[^.?!]{0,40}\b(made|built|created|developed|designed|coded|wrote|owns)\b[^.?!]{0,25}\b(you|this|it|cat|cct|the app|the application|the project|coding agent terminal)\b",
118
+ r"\bwho(?:'s| is)\b[^.?!]{0,25}\b(creator|developer|maker|author|owner)\b",
119
+ r"\bwho\s+(?:made|built|created|developed|owns)\s+(?:cat|cct)\b",
120
+ ]
121
+ _WHAT_ARE_YOU_PATTERNS = [
122
+ r"^\s*what\s+are\s+you\s*\??\s*$",
123
+ r"^\s*who\s+are\s+you\s*\??\s*$",
124
+ r"^\s*whoareyou\s*\??\s*$",
125
+ r"^\s*whatareyou\s*\??\s*$",
126
+ r"\bwhat\s+is\s+cat\b",
127
+ r"\bwhat\s+is\s+cct\b",
128
+ r"\bwhat\s+is\s+coding\s+agent\s+terminal\b",
129
+ # handle concatenated like Hi,howcanIhelpyou?User:whoareyou?
130
+ r"who\s*are\s*you",
131
+ r"what\s*are\s*you",
132
+ ]
133
+ _MODEL_PATTERNS = [
134
+ r"\bwhat\s+(?:ai\s+)?model\b[^.?!]{0,25}\b(are you|is this|using|running|powered by)\b",
135
+ r"\bwhich\s+(?:ai\s+)?model\b",
136
+ r"\bwhat\s+llm\b",
137
+ ]
138
+
139
+ _CREATOR_RE = re.compile("|".join(_CREATOR_PATTERNS), re.IGNORECASE)
140
+ _WHAT_ARE_YOU_RE = re.compile("|".join(_WHAT_ARE_YOU_PATTERNS), re.IGNORECASE)
141
+ _MODEL_RE = re.compile("|".join(_MODEL_PATTERNS), re.IGNORECASE)
142
+
143
+
144
+ def classify_identity_question(text):
145
+ """Return 'model', 'creator', 'what', or None. Checked in this order
146
+ so 'what AI model are you using' (a model question) doesn't get
147
+ swallowed by the broader 'what are you' pattern."""
148
+ t = (text or "").strip()
149
+ if not t:
150
+ return None
151
+ if _MODEL_RE.search(t):
152
+ return "model"
153
+ if _CREATOR_RE.search(t):
154
+ return "creator"
155
+ if _WHAT_ARE_YOU_RE.search(t):
156
+ return "what"
157
+ return None
158
+
159
+
160
+ def is_identity_question(text):
161
+ return classify_identity_question(text) is not None
162
+
163
+
164
+ def answer_creator(config=None):
165
+ return (
166
+ f"{SHORT_NAME} ({APP_NAME}) was created by {CREATOR_NAME}, {CREATOR_ROLE} \u2014 "
167
+ f"{DEVELOPMENT_NOTE}.\n"
168
+ f"Under the hood it's built with {tech_stack_sentence(config)}."
169
+ )
170
+
171
+
172
+ def answer_what(config=None):
173
+ return (
174
+ f"{SHORT_NAME} is {APP_TAGLINE}. It's built with {tech_stack_sentence(config)}, "
175
+ f"created by {CREATOR_NAME}, {CREATOR_ROLE}."
176
+ )
177
+
178
+
179
+ def answer_model(config=None):
180
+ provider_phrase = _provider_phrase(config)
181
+ if not provider_phrase:
182
+ return (
183
+ f"No AI provider is configured for {SHORT_NAME} right now \u2014 run /model to set "
184
+ f"one up. That's separate from {SHORT_NAME} itself, which was created by "
185
+ f"{CREATOR_NAME}, {CREATOR_ROLE}."
186
+ )
187
+ return (
188
+ f"Right now {SHORT_NAME} is talking to you through {provider_phrase}. That's the "
189
+ f"underlying AI provider, not {SHORT_NAME} itself \u2014 the {SHORT_NAME} application "
190
+ f"around it was created by {CREATOR_NAME}."
191
+ )
192
+
193
+
194
+ def answer_for(text, config=None):
195
+ """Return the canonical answer for an identity question, or None if
196
+ `text` isn't one."""
197
+ kind = classify_identity_question(text)
198
+ if kind == "creator":
199
+ return answer_creator(config)
200
+ if kind == "model":
201
+ return answer_model(config)
202
+ if kind == "what":
203
+ return answer_what(config)
204
+ return None
205
+
206
+
207
+ # Re-export terminal identity functions for convenience
208
+ from .terminal_identity import (
209
+ set_terminal_title,
210
+ restore_terminal_title,
211
+ init_terminal_identity,
212
+ get_icon_path,
213
+ configure_windows_terminal_profile,
214
+ start_title_guard,
215
+ stop_title_guard,
216
+ )
@@ -0,0 +1,54 @@
1
+ """
2
+ CAT Input Architecture Module
3
+ Centralized input abstraction for touchscreens, 2-in-1s, mice, and keyboards.
4
+ """
5
+
6
+ from .capabilities import (
7
+ DeviceType,
8
+ InputCapabilities,
9
+ detect_capabilities,
10
+ get_input_capabilities,
11
+ set_touch_mode,
12
+ toggle_touch_mode,
13
+ register_listener,
14
+ unregister_listener,
15
+ )
16
+ from .touch import (
17
+ TouchTapRecognizer,
18
+ TouchScrollHandler,
19
+ TAP_MAX_DISTANCE,
20
+ TAP_MAX_DURATION,
21
+ DOUBLE_TAP_MAX_DELAY,
22
+ LONG_PRESS_DURATION,
23
+ )
24
+ from .pointer import (
25
+ TouchHitZone,
26
+ RESIZER_TOUCH_PADDING,
27
+ )
28
+ from .focus import (
29
+ TouchFocusManager,
30
+ )
31
+ from .gestures import (
32
+ GestureBridge,
33
+ )
34
+
35
+ __all__ = [
36
+ "DeviceType",
37
+ "InputCapabilities",
38
+ "detect_capabilities",
39
+ "get_input_capabilities",
40
+ "set_touch_mode",
41
+ "toggle_touch_mode",
42
+ "register_listener",
43
+ "unregister_listener",
44
+ "TouchTapRecognizer",
45
+ "TouchScrollHandler",
46
+ "TAP_MAX_DISTANCE",
47
+ "TAP_MAX_DURATION",
48
+ "DOUBLE_TAP_MAX_DELAY",
49
+ "LONG_PRESS_DURATION",
50
+ "TouchHitZone",
51
+ "RESIZER_TOUCH_PADDING",
52
+ "TouchFocusManager",
53
+ "GestureBridge",
54
+ ]
@@ -0,0 +1,258 @@
1
+ """
2
+ CAT Input Architecture — capabilities.py
3
+ Runtime input device detection and capabilities abstraction.
4
+
5
+ Detects:
6
+ - Touchscreen presence and maximum concurrent touch points (Win32 SM_MAXIMUMTOUCHES, SM_DIGITIZER)
7
+ - Mouse availability (SM_MOUSEPRESENT)
8
+ - Convertible / 2-in-1 slate mode (SM_CONVERTIBLESLATEMODE)
9
+ - Tablet PC capabilities (SM_TABLETPC)
10
+ - Display scaling / DPI awareness
11
+
12
+ Classifies runtime environment into:
13
+ - Normal Desktop
14
+ - Touchscreen Laptop
15
+ - 2-in-1 / Convertible
16
+ - Tablet-style Windows Device
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import sys
23
+ import json
24
+ from dataclasses import dataclass
25
+ from enum import Enum
26
+ from pathlib import Path
27
+ from typing import Optional, Callable, List
28
+
29
+ CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".cct_config.json")
30
+
31
+
32
+ class DeviceType(str, Enum):
33
+ DESKTOP = "desktop"
34
+ TOUCHSCREEN_LAPTOP = "touchscreen_laptop"
35
+ CONVERTIBLE_2IN1 = "convertible_2in1"
36
+ TABLET_WINDOWS = "tablet_windows"
37
+
38
+ @property
39
+ def display_name(self) -> str:
40
+ if self == DeviceType.TOUCHSCREEN_LAPTOP:
41
+ return "Touchscreen Laptop"
42
+ if self == DeviceType.CONVERTIBLE_2IN1:
43
+ return "2-in-1 / Convertible"
44
+ if self == DeviceType.TABLET_WINDOWS:
45
+ return "Tablet-style Windows Device"
46
+ return "Normal Desktop"
47
+
48
+
49
+ @dataclass
50
+ class InputCapabilities:
51
+ """Centralized input capability descriptor reusable across CAT UI."""
52
+ touchscreen_available: bool = False
53
+ mouse_available: bool = True
54
+ keyboard_available: bool = True
55
+ touch_primary: bool = False
56
+ touch_mode_enabled: bool = False
57
+ max_touch_points: int = 0
58
+ device_type: DeviceType = DeviceType.DESKTOP
59
+ slate_mode: bool = False
60
+ scaling_factor: float = 1.0
61
+
62
+ def to_dict(self) -> dict:
63
+ return {
64
+ "touchscreen_available": self.touchscreen_available,
65
+ "mouse_available": self.mouse_available,
66
+ "keyboard_available": self.keyboard_available,
67
+ "touch_primary": self.touch_primary,
68
+ "touch_mode_enabled": self.touch_mode_enabled,
69
+ "max_touch_points": self.max_touch_points,
70
+ "device_type": self.device_type.value,
71
+ "device_name": self.device_type.display_name,
72
+ "slate_mode": self.slate_mode,
73
+ "scaling_factor": self.scaling_factor,
74
+ }
75
+
76
+
77
+ # Listeners for capability changes (e.g. tablet flip, touch mode toggle)
78
+ _LISTENERS: List[Callable[[InputCapabilities], None]] = []
79
+ _CURRENT_CAPABILITIES: Optional[InputCapabilities] = None
80
+
81
+
82
+ def _read_config_touch_mode() -> Optional[bool]:
83
+ """Read user-configured touch mode preference from ~/.cct_config.json if set."""
84
+ try:
85
+ if os.path.exists(CONFIG_PATH):
86
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
87
+ data = json.load(f)
88
+ if isinstance(data, dict) and "touch_mode" in data:
89
+ val = data["touch_mode"]
90
+ if isinstance(val, bool):
91
+ return val
92
+ except Exception:
93
+ pass
94
+ return None
95
+
96
+
97
+ def _save_config_touch_mode(enabled: bool) -> None:
98
+ """Save user touch mode toggle to ~/.cct_config.json."""
99
+ try:
100
+ data = {}
101
+ if os.path.exists(CONFIG_PATH):
102
+ try:
103
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
104
+ data = json.load(f)
105
+ except Exception:
106
+ data = {}
107
+ data["touch_mode"] = bool(enabled)
108
+ with open(CONFIG_PATH, "w", encoding="utf-8") as f:
109
+ json.dump(data, f, indent=2)
110
+ except Exception:
111
+ pass
112
+
113
+
114
+ def detect_capabilities() -> InputCapabilities:
115
+ """Perform real runtime capability detection on Windows and cross-platform."""
116
+ touchscreen_available = False
117
+ mouse_available = True
118
+ keyboard_available = True
119
+ max_touch_points = 0
120
+ slate_mode = False
121
+ is_tablet_pc = False
122
+ slate_mode_val = None
123
+ device_type = DeviceType.DESKTOP
124
+ scaling_factor = 1.0
125
+
126
+ # Win32 detection
127
+ if sys.platform == "win32":
128
+ try:
129
+ import ctypes
130
+ user32 = ctypes.windll.user32
131
+
132
+ # SM_DIGITIZER (94)
133
+ # 0x01 = NID_INTEGRATED_TOUCH, 0x02 = NID_EXTERNAL_TOUCH, 0x40 = NID_MULTI_INPUT, 0x80 = NID_READY
134
+ digitizer_val = user32.GetSystemMetrics(94)
135
+ # SM_MAXIMUMTOUCHES (95) - returns number of touches or 0
136
+ max_touches = user32.GetSystemMetrics(95)
137
+ max_touch_points = max(0, int(max_touches))
138
+
139
+ # SM_MOUSEPRESENT (19)
140
+ mouse_present = user32.GetSystemMetrics(19)
141
+ mouse_available = bool(mouse_present)
142
+
143
+ # SM_TABLETPC (86)
144
+ tablet_pc_val = user32.GetSystemMetrics(86)
145
+ is_tablet_pc = bool(tablet_pc_val)
146
+
147
+ # SM_CONVERTIBLESLATEMODE (0x2003 = 8195)
148
+ # 0 = laptop/clamshell mode, 1 = slate/tablet mode
149
+ slate_mode_val = user32.GetSystemMetrics(0x2003)
150
+ slate_mode = bool(slate_mode_val == 1)
151
+
152
+ # If max touches > 0 or digitizer has touch bits
153
+ if max_touch_points > 0 or (digitizer_val & 0x03):
154
+ touchscreen_available = True
155
+
156
+ # DPI detection
157
+ try:
158
+ dpi = user32.GetDpiForSystem()
159
+ if dpi > 0:
160
+ scaling_factor = round(dpi / 96.0, 2)
161
+ except (AttributeError, Exception):
162
+ scaling_factor = 1.0
163
+
164
+ except Exception:
165
+ # Fallback if ctypes or user32 fails
166
+ touchscreen_available = False
167
+ mouse_available = True
168
+
169
+ # Device Classification
170
+ if touchscreen_available:
171
+ if slate_mode or (is_tablet_pc and not mouse_available):
172
+ device_type = DeviceType.TABLET_WINDOWS
173
+ elif is_tablet_pc or slate_mode_val == 0:
174
+ # Device has 2-in-1 sensor or convertible capability
175
+ device_type = DeviceType.CONVERTIBLE_2IN1
176
+ else:
177
+ device_type = DeviceType.TOUCHSCREEN_LAPTOP
178
+ else:
179
+ device_type = DeviceType.DESKTOP
180
+
181
+ # Touch Primary determination
182
+ touch_primary = False
183
+ if device_type == DeviceType.TABLET_WINDOWS or slate_mode:
184
+ touch_primary = True
185
+
186
+ # Touch Mode Enabled determination:
187
+ # 1. Environment variable override: CCT_TOUCH_MODE=1 / 0
188
+ # 2. Config file preference: ~/.cct_config.json -> touch_mode
189
+ # 3. Default: True if touchscreen is detected, False otherwise
190
+ env_override = os.environ.get("CCT_TOUCH_MODE", "").strip().lower()
191
+ config_pref = _read_config_touch_mode()
192
+
193
+ if env_override in ("1", "true", "yes", "on"):
194
+ touch_mode_enabled = True
195
+ elif env_override in ("0", "false", "no", "off"):
196
+ touch_mode_enabled = False
197
+ elif config_pref is not None:
198
+ touch_mode_enabled = config_pref
199
+ else:
200
+ touch_mode_enabled = bool(touchscreen_available)
201
+
202
+ return InputCapabilities(
203
+ touchscreen_available=touchscreen_available,
204
+ mouse_available=mouse_available,
205
+ keyboard_available=keyboard_available,
206
+ touch_primary=touch_primary,
207
+ touch_mode_enabled=touch_mode_enabled,
208
+ max_touch_points=max_touch_points,
209
+ device_type=device_type,
210
+ slate_mode=slate_mode,
211
+ scaling_factor=scaling_factor,
212
+ )
213
+
214
+
215
+ def get_input_capabilities(force_refresh: bool = False) -> InputCapabilities:
216
+ """Get or initialize the cached input capabilities."""
217
+ global _CURRENT_CAPABILITIES
218
+ if _CURRENT_CAPABILITIES is None or force_refresh:
219
+ _CURRENT_CAPABILITIES = detect_capabilities()
220
+ return _CURRENT_CAPABILITIES
221
+
222
+
223
+ def set_touch_mode(enabled: bool, persist: bool = True) -> InputCapabilities:
224
+ """Explicitly enable or disable touch-friendly mode."""
225
+ caps = get_input_capabilities()
226
+ caps.touch_mode_enabled = bool(enabled)
227
+ if persist:
228
+ _save_config_touch_mode(caps.touch_mode_enabled)
229
+ _notify_listeners(caps)
230
+ return caps
231
+
232
+
233
+ def toggle_touch_mode(persist: bool = True) -> bool:
234
+ """Toggle touch-friendly mode on or off."""
235
+ caps = get_input_capabilities()
236
+ new_state = not caps.touch_mode_enabled
237
+ set_touch_mode(new_state, persist=persist)
238
+ return new_state
239
+
240
+
241
+ def register_listener(callback: Callable[[InputCapabilities], None]) -> None:
242
+ """Register a listener invoked when input capabilities or touch mode changes."""
243
+ if callback not in _LISTENERS:
244
+ _LISTENERS.append(callback)
245
+
246
+
247
+ def unregister_listener(callback: Callable[[InputCapabilities], None]) -> None:
248
+ """Unregister a capability listener."""
249
+ if callback in _LISTENERS:
250
+ _LISTENERS.remove(callback)
251
+
252
+
253
+ def _notify_listeners(caps: InputCapabilities) -> None:
254
+ for cb in list(_LISTENERS):
255
+ try:
256
+ cb(caps)
257
+ except Exception:
258
+ pass
@@ -0,0 +1,87 @@
1
+ """
2
+ CAT Input Architecture — focus.py
3
+ Touch-friendly focus management and virtual/on-screen keyboard adaptation.
4
+
5
+ Prevents focus jumping, preserves text input focus when Windows touch keyboard appears,
6
+ and manages focus restoration on modal/popup dismissals.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Optional
12
+ from textual.widget import Widget
13
+ from textual.widgets import Input, TextArea
14
+
15
+
16
+ class TouchFocusManager:
17
+ """Manages focus state, input activation, and focus restoration for touchscreen interactions."""
18
+
19
+ def __init__(self):
20
+ self._last_focused: Optional[Widget] = None
21
+ self._previous_screen_focus: Optional[Widget] = None
22
+
23
+ def on_touch_tap_widget(self, target: Optional[Widget]) -> None:
24
+ """Ensure tapped text inputs receive immediate and stable focus."""
25
+ if target is None:
26
+ return
27
+
28
+ # Check if the target is an editable input or inside one
29
+ cur = target
30
+ input_widget = None
31
+ while cur is not None:
32
+ if isinstance(cur, (Input, TextArea)):
33
+ input_widget = cur
34
+ break
35
+ cur = getattr(cur, "parent", None)
36
+
37
+ if input_widget is not None and getattr(input_widget, "can_focus", True):
38
+ try:
39
+ if not input_widget.has_focus:
40
+ input_widget.focus()
41
+ self._last_focused = input_widget
42
+ except Exception:
43
+ pass
44
+ return
45
+
46
+ # Check if target is inside composer card (prompt glyph, prompt row, card margin)
47
+ cur = target
48
+ inside_composer = False
49
+ is_button = False
50
+ from textual.widgets import Button
51
+ while cur is not None:
52
+ if getattr(cur, "id", None) == "cct-composer":
53
+ inside_composer = True
54
+ if isinstance(cur, (Button,)):
55
+ is_button = True
56
+ cur = getattr(cur, "parent", None)
57
+ if inside_composer and not is_button:
58
+ try:
59
+ app = getattr(target, "app", None)
60
+ if app is not None:
61
+ composer_input = app.query_one("#cct-input", TextArea)
62
+ if composer_input is not None and not composer_input.has_focus:
63
+ composer_input.focus()
64
+ self._last_focused = composer_input
65
+ return
66
+ except Exception:
67
+ pass
68
+
69
+ if getattr(target, "can_focus", False):
70
+ try:
71
+ self._last_focused = target
72
+ except Exception:
73
+ pass
74
+
75
+ def save_focus(self, widget: Optional[Widget]) -> None:
76
+ """Save active focus before opening a modal or popup."""
77
+ self._previous_screen_focus = widget or self._last_focused
78
+
79
+ def restore_focus(self) -> None:
80
+ """Restore saved focus after modal or popup dismisses."""
81
+ if self._previous_screen_focus is not None:
82
+ try:
83
+ if self._previous_screen_focus.is_attached and getattr(self._previous_screen_focus, "can_focus", False):
84
+ self._previous_screen_focus.focus()
85
+ except Exception:
86
+ pass
87
+ self._previous_screen_focus = None
@@ -0,0 +1,64 @@
1
+ """
2
+ CAT Input Architecture — gestures.py
3
+ Touchscreen gesture integration with CAT Gestures Extension.
4
+
5
+ Connects touch interactions (tap, double-tap, long-press, swipe) to the
6
+ existing Gestures extension without creating duplicate backends.
7
+ Works safely even when the Gestures extension is disabled.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from typing import Optional, Dict, Any
13
+ from textual.widget import Widget
14
+ from textual.app import App
15
+
16
+
17
+ class GestureBridge:
18
+ """Bridges touch gestures into the CAT Gestures subsystem."""
19
+
20
+ @classmethod
21
+ def dispatch_touch_gesture(
22
+ cls,
23
+ trigger: str,
24
+ target: str,
25
+ app: Optional[App] = None,
26
+ context: Optional[Dict[str, Any]] = None,
27
+ ) -> bool:
28
+ """
29
+ Dispatch a gesture to calc_terminal.gestures.manager.
30
+ Returns True if an enabled action was triggered, False otherwise.
31
+ """
32
+ try:
33
+ from ..gestures.manager import handle_gesture
34
+ # Map double_tap to double_click for gesture registry compatibility
35
+ mapped_trigger = "double_click" if trigger == "double_tap" else trigger
36
+ return bool(handle_gesture(mapped_trigger, target, app=app, context=context or {}))
37
+ except Exception:
38
+ return False
39
+
40
+ @classmethod
41
+ def resolve_target_surface(cls, widget: Optional[Widget]) -> str:
42
+ """Map a Textual widget to a Gestures target surface (editor, chat, file, diff, preview, explorer)."""
43
+ if widget is None:
44
+ return "global"
45
+
46
+ cur = widget
47
+ while cur is not None:
48
+ wid = getattr(cur, "id", "") or ""
49
+ cls_name = cur.__class__.__name__
50
+
51
+ if wid == "cct-editor" or "EditorPane" in cls_name or "TextArea" in cls_name:
52
+ return "editor"
53
+ if wid == "cct-conversation" or "Conversation" in cls_name or "MessageRow" in cls_name:
54
+ return "chat"
55
+ if wid in ("cct-sidebar", "cct-sidebar-body") or "DirectoryTree" in cls_name or "Explorer" in cls_name:
56
+ return "file"
57
+ if wid == "cct-preview" or "Preview" in cls_name:
58
+ return "preview"
59
+ if "diff" in wid.lower() or "Diff" in cls_name:
60
+ return "diff"
61
+
62
+ cur = getattr(cur, "parent", None)
63
+
64
+ return "global"