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
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT v0.7.10 — project detection + external dev-server unit tests
|
|
3
|
+
(hermetic: pure filesystem + pure regex; NO npm/node required).
|
|
4
|
+
|
|
5
|
+
Covers spec sections 19-20:
|
|
6
|
+
|
|
7
|
+
detector static · vite (config) · vite (dependency) · next · CRA ·
|
|
8
|
+
node-without-dev-script · package.json-without-scripts ·
|
|
9
|
+
missing folder · dependencies_installed()
|
|
10
|
+
url parse Vite/Next/CRA output lines → local URL, 0.0.0.0/[::1]
|
|
11
|
+
normalization, punctuation trimming, non-URL lines
|
|
12
|
+
controller framework detection routes through DevServerProcess and
|
|
13
|
+
never starts CAT's static LiveServer in that mode (a fake
|
|
14
|
+
DevServerProcess proves the wiring without spawning npm)
|
|
15
|
+
|
|
16
|
+
Run: python -m pytest calc_terminal/test_project_detect.py -q
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
import tempfile
|
|
22
|
+
import unittest
|
|
23
|
+
|
|
24
|
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
25
|
+
|
|
26
|
+
from calc_terminal.browser.project_detector import (
|
|
27
|
+
detect_project, dependencies_installed,
|
|
28
|
+
STATIC, VITE, NEXT, CRA, NODE)
|
|
29
|
+
from calc_terminal.browser.devserver import DevServerProcess, extract_local_url
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _write(root, name, content=""):
|
|
33
|
+
path = os.path.join(root, name)
|
|
34
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
35
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
36
|
+
f.write(content)
|
|
37
|
+
return path
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class TestProjectDetection(unittest.TestCase):
|
|
41
|
+
def setUp(self):
|
|
42
|
+
self.root = tempfile.mkdtemp(prefix="cat_detect_")
|
|
43
|
+
|
|
44
|
+
def test_static_by_default(self):
|
|
45
|
+
_write(self.root, "index.html", "<h1>hi</h1>")
|
|
46
|
+
d = detect_project(self.root)
|
|
47
|
+
self.assertEqual(d.kind, STATIC)
|
|
48
|
+
self.assertFalse(d.is_framework)
|
|
49
|
+
self.assertTrue(dependencies_installed(d))
|
|
50
|
+
|
|
51
|
+
def test_vite_config_file_wins(self):
|
|
52
|
+
_write(self.root, "index.html")
|
|
53
|
+
_write(self.root, "package.json",
|
|
54
|
+
'{"scripts": {"dev": "vite"}}')
|
|
55
|
+
_write(self.root, "vite.config.ts", "export default {}")
|
|
56
|
+
d = detect_project(self.root)
|
|
57
|
+
self.assertEqual(d.kind, VITE)
|
|
58
|
+
self.assertEqual(d.command, ("npm", "run", "dev"))
|
|
59
|
+
self.assertIn("vite.config.ts", d.evidence)
|
|
60
|
+
|
|
61
|
+
def test_vite_dependency_only(self):
|
|
62
|
+
_write(self.root, "package.json",
|
|
63
|
+
'{"scripts": {"dev": "vite"},'
|
|
64
|
+
' "devDependencies": {"vite": "^5.0.0"}}')
|
|
65
|
+
d = detect_project(self.root)
|
|
66
|
+
self.assertEqual(d.kind, VITE)
|
|
67
|
+
|
|
68
|
+
def test_nextjs(self):
|
|
69
|
+
_write(self.root, "package.json",
|
|
70
|
+
'{"scripts": {"dev": "next dev"}}')
|
|
71
|
+
_write(self.root, "next.config.js", "module.exports = {}")
|
|
72
|
+
d = detect_project(self.root)
|
|
73
|
+
self.assertEqual(d.kind, NEXT)
|
|
74
|
+
|
|
75
|
+
def test_cra(self):
|
|
76
|
+
_write(self.root, "package.json",
|
|
77
|
+
'{"scripts": {"start": "react-scripts start"}}')
|
|
78
|
+
d = detect_project(self.root)
|
|
79
|
+
self.assertEqual(d.kind, CRA)
|
|
80
|
+
self.assertEqual(d.command, ("npm", "start"))
|
|
81
|
+
|
|
82
|
+
def test_node_without_framework(self):
|
|
83
|
+
_write(self.root, "package.json",
|
|
84
|
+
'{"scripts": {"dev": "node server.js"}}')
|
|
85
|
+
d = detect_project(self.root)
|
|
86
|
+
self.assertEqual(d.kind, NODE)
|
|
87
|
+
|
|
88
|
+
def test_package_without_scripts_falls_back_static(self):
|
|
89
|
+
_write(self.root, "package.json", '{"name": "x"}')
|
|
90
|
+
_write(self.root, "index.html")
|
|
91
|
+
d = detect_project(self.root)
|
|
92
|
+
self.assertEqual(d.kind, STATIC)
|
|
93
|
+
|
|
94
|
+
def test_missing_folder_is_safe(self):
|
|
95
|
+
d = detect_project(os.path.join(self.root, "nope"))
|
|
96
|
+
self.assertEqual(d.kind, STATIC)
|
|
97
|
+
|
|
98
|
+
def test_dependencies_installed_check(self):
|
|
99
|
+
_write(self.root, "package.json", '{"scripts": {"dev": "vite"}}')
|
|
100
|
+
_write(self.root, "vite.config.js")
|
|
101
|
+
d = detect_project(self.root)
|
|
102
|
+
self.assertTrue(d.is_framework)
|
|
103
|
+
self.assertFalse(dependencies_installed(d))
|
|
104
|
+
os.makedirs(os.path.join(self.root, "node_modules"))
|
|
105
|
+
self.assertTrue(dependencies_installed(d))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class TestExtractLocalUrl(unittest.TestCase):
|
|
109
|
+
def test_vite_line(self):
|
|
110
|
+
self.assertEqual(
|
|
111
|
+
extract_local_url(" ➜ Local: http://localhost:5173/"),
|
|
112
|
+
"http://localhost:5173/")
|
|
113
|
+
|
|
114
|
+
def test_next_line(self):
|
|
115
|
+
self.assertEqual(
|
|
116
|
+
extract_local_url("- Local: http://localhost:3000"),
|
|
117
|
+
"http://localhost:3000/")
|
|
118
|
+
|
|
119
|
+
def test_loopback_ip(self):
|
|
120
|
+
self.assertEqual(
|
|
121
|
+
extract_local_url("[1] Web server available at "
|
|
122
|
+
"http://127.0.0.1:8080/main/ ok"),
|
|
123
|
+
"http://127.0.0.1:8080/main/")
|
|
124
|
+
|
|
125
|
+
def test_bind_address_normalized(self):
|
|
126
|
+
url = extract_local_url("listening on http://0.0.0.0:4000/")
|
|
127
|
+
self.assertEqual(url, "http://127.0.0.1:4000/")
|
|
128
|
+
|
|
129
|
+
def test_ipv6_normalized(self):
|
|
130
|
+
url = extract_local_url("ready on http://[::1]:4321/")
|
|
131
|
+
self.assertEqual(url, "http://127.0.0.1:4321/")
|
|
132
|
+
|
|
133
|
+
def test_trailing_punctuation_trimmed(self):
|
|
134
|
+
self.assertEqual(extract_local_url("at http://localhost:5000."),
|
|
135
|
+
"http://localhost:5000/")
|
|
136
|
+
|
|
137
|
+
def test_non_url_lines_rejected(self):
|
|
138
|
+
self.assertIsNone(extract_local_url(""))
|
|
139
|
+
self.assertIsNone(extract_local_url("VITE v5.0.0 ready in 320 ms"))
|
|
140
|
+
self.assertIsNone(extract_local_url("https://example.com/remote-only"))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class TestControllerFrameworkRouting(unittest.TestCase):
|
|
144
|
+
"""The controller must reuse the project's own dev server instead
|
|
145
|
+
of stacking CAT's static LiveServer on top of it (spec §19)."""
|
|
146
|
+
|
|
147
|
+
def setUp(self):
|
|
148
|
+
self.root = tempfile.mkdtemp(prefix="cat_fw_")
|
|
149
|
+
_write(self.root, "index.html")
|
|
150
|
+
_write(self.root, "package.json", '{"scripts": {"dev": "vite"}}')
|
|
151
|
+
_write(self.root, "vite.config.js")
|
|
152
|
+
os.makedirs(os.path.join(self.root, "node_modules"), exist_ok=True)
|
|
153
|
+
|
|
154
|
+
def test_framework_routes_through_devserver(self):
|
|
155
|
+
from calc_terminal.browser.preview import PreviewController
|
|
156
|
+
from calc_terminal.browser.state import PreviewSnapshot
|
|
157
|
+
|
|
158
|
+
class FakeDevServer:
|
|
159
|
+
def __init__(self, root, command, on_activity=None):
|
|
160
|
+
self.root, self.command = root, tuple(command)
|
|
161
|
+
self.url = ""
|
|
162
|
+
self.stopped = False
|
|
163
|
+
|
|
164
|
+
def start(self, timeout=45.0):
|
|
165
|
+
self.url = "http://localhost:5173/"
|
|
166
|
+
return True
|
|
167
|
+
|
|
168
|
+
@property
|
|
169
|
+
def running(self):
|
|
170
|
+
return bool(self.url) and not self.stopped
|
|
171
|
+
|
|
172
|
+
def stop(self):
|
|
173
|
+
self.stopped = True
|
|
174
|
+
|
|
175
|
+
created = []
|
|
176
|
+
|
|
177
|
+
def factory(root, command, on_activity=None):
|
|
178
|
+
ds = FakeDevServer(root, command, on_activity)
|
|
179
|
+
created.append(ds)
|
|
180
|
+
return ds
|
|
181
|
+
|
|
182
|
+
class FakeEngine:
|
|
183
|
+
available = True
|
|
184
|
+
started = False
|
|
185
|
+
navigate_calls = []
|
|
186
|
+
|
|
187
|
+
def start(self):
|
|
188
|
+
self.started = True
|
|
189
|
+
|
|
190
|
+
def navigate(self, url, timeout=35.0):
|
|
191
|
+
self.navigate_calls.append(url)
|
|
192
|
+
return PreviewSnapshot(url=url, ok=True, elements=1)
|
|
193
|
+
|
|
194
|
+
def close(self, timeout=6.0):
|
|
195
|
+
pass
|
|
196
|
+
|
|
197
|
+
ctrl = PreviewController(self.root, engine=FakeEngine(),
|
|
198
|
+
on_event=lambda *a, **k: None,
|
|
199
|
+
devserver_factory=factory)
|
|
200
|
+
|
|
201
|
+
ok = ctrl.start_for_file(os.path.join(self.root, "index.html"))
|
|
202
|
+
|
|
203
|
+
self.assertTrue(ok)
|
|
204
|
+
self.assertEqual(ctrl.detection.kind, VITE)
|
|
205
|
+
self.assertEqual(len(created), 1)
|
|
206
|
+
# The engine was pointed at the DEV SERVER URL, not a static one.
|
|
207
|
+
self.assertEqual(FakeEngine.navigate_calls,
|
|
208
|
+
["http://localhost:5173/"])
|
|
209
|
+
# And CAT's own static LiveServer stayed idle.
|
|
210
|
+
self.assertFalse(ctrl.server.running)
|
|
211
|
+
self.assertTrue(ctrl.running)
|
|
212
|
+
# stop_preview must tear the dev-server down too.
|
|
213
|
+
ctrl.stop_preview()
|
|
214
|
+
self.assertTrue(created[0].stopped)
|
|
215
|
+
|
|
216
|
+
def test_framework_without_deps_uses_static_path(self):
|
|
217
|
+
"""node_modules missing: no fake success — the honest fallback
|
|
218
|
+
is CAT's static server for the plain index.html at the root."""
|
|
219
|
+
import shutil
|
|
220
|
+
from calc_terminal.browser.preview import PreviewController
|
|
221
|
+
from calc_terminal.browser.state import PreviewSnapshot
|
|
222
|
+
|
|
223
|
+
shutil.rmtree(os.path.join(self.root, "node_modules"),
|
|
224
|
+
ignore_errors=True)
|
|
225
|
+
|
|
226
|
+
created = []
|
|
227
|
+
|
|
228
|
+
def factory(root, command, on_activity=None):
|
|
229
|
+
created.append(1)
|
|
230
|
+
raise AssertionError("devserver must NOT be started without "
|
|
231
|
+
"node_modules")
|
|
232
|
+
|
|
233
|
+
class NoStartEngine:
|
|
234
|
+
available = False
|
|
235
|
+
|
|
236
|
+
def close(self, timeout=6.0):
|
|
237
|
+
pass
|
|
238
|
+
|
|
239
|
+
ctrl = PreviewController(self.root, engine=NoStartEngine(),
|
|
240
|
+
on_event=lambda *a, **k: None,
|
|
241
|
+
devserver_factory=factory)
|
|
242
|
+
self.assertTrue(ctrl.detection.is_framework)
|
|
243
|
+
self.assertFalse(dependencies_installed(ctrl.detection))
|
|
244
|
+
# Engine unavailable → start fails honestly AFTER choosing the
|
|
245
|
+
# static path; crucially the dev-server factory was never used.
|
|
246
|
+
try:
|
|
247
|
+
ctrl.start_for_file(os.path.join(self.root, "index.html"))
|
|
248
|
+
except Exception:
|
|
249
|
+
pass
|
|
250
|
+
self.assertEqual(created, [])
|
|
251
|
+
ctrl.stop_preview() # must not raise
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
if __name__ == "__main__":
|
|
255
|
+
unittest.main()
|