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,440 @@
|
|
|
1
|
+
"""
|
|
2
|
+
calc_terminal/hardware_analyzer.py
|
|
3
|
+
==================================
|
|
4
|
+
CAT Hardware Analyzer — Comprehensive cross-platform device capability detection.
|
|
5
|
+
|
|
6
|
+
Part of CAT (Created by Kazi Zillani).
|
|
7
|
+
Detects:
|
|
8
|
+
- CPU: Model, physical cores, logical threads, architecture.
|
|
9
|
+
- System RAM: Total, available, used, percent.
|
|
10
|
+
- GPU: Model name, total VRAM, free VRAM, vendor, compute platform (CUDA, ROCm, Metal, DirectML, CPU).
|
|
11
|
+
- Storage: Available disk capacity on the user's primary/models storage drive.
|
|
12
|
+
- Platform / OS / Python environment.
|
|
13
|
+
- Local AI Capability Rating: EXCELLENT / GOOD / MODERATE / LIMITED.
|
|
14
|
+
|
|
15
|
+
Designed to be lightweight, fail-safe (never raises unhandled exceptions), and
|
|
16
|
+
runs with standard library fallbacks if third-party modules are unavailable.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
import platform
|
|
23
|
+
import shutil
|
|
24
|
+
import subprocess
|
|
25
|
+
import sys
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from typing import Any, Dict, Optional, Tuple
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class CPUInfo:
|
|
32
|
+
model: str = "Unknown CPU"
|
|
33
|
+
physical_cores: int = 1
|
|
34
|
+
logical_cores: int = 1
|
|
35
|
+
arch: str = ""
|
|
36
|
+
features: list[str] = field(default_factory=list)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class RAMInfo:
|
|
41
|
+
total_gb: float = 8.0
|
|
42
|
+
available_gb: float = 4.0
|
|
43
|
+
used_gb: float = 4.0
|
|
44
|
+
percent_used: float = 50.0
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@dataclass
|
|
48
|
+
class GPUInfo:
|
|
49
|
+
detected: bool = False
|
|
50
|
+
name: str = "Integrated / CPU Only"
|
|
51
|
+
vendor: str = "Unknown" # NVIDIA, AMD, Apple, Intel, Unknown
|
|
52
|
+
vram_total_gb: float = 0.0
|
|
53
|
+
vram_free_gb: float = 0.0
|
|
54
|
+
backend: str = "CPU" # CUDA, ROCm, Metal, DirectML, CPU
|
|
55
|
+
driver_version: str = ""
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass
|
|
59
|
+
class StorageInfo:
|
|
60
|
+
total_gb: float = 0.0
|
|
61
|
+
free_gb: float = 0.0
|
|
62
|
+
used_gb: float = 0.0
|
|
63
|
+
path: str = ""
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class HardwareProfile:
|
|
68
|
+
cpu: CPUInfo = field(default_factory=CPUInfo)
|
|
69
|
+
ram: RAMInfo = field(default_factory=RAMInfo)
|
|
70
|
+
gpu: GPUInfo = field(default_factory=GPUInfo)
|
|
71
|
+
storage: StorageInfo = field(default_factory=StorageInfo)
|
|
72
|
+
os_name: str = ""
|
|
73
|
+
os_version: str = ""
|
|
74
|
+
python_version: str = ""
|
|
75
|
+
ai_capability: str = "MODERATE" # EXCELLENT, GOOD, MODERATE, LIMITED
|
|
76
|
+
ai_capability_notes: list[str] = field(default_factory=list)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def cpu_model(self) -> str:
|
|
80
|
+
return self.cpu.model
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def cpu_threads(self) -> int:
|
|
84
|
+
return self.cpu.logical_cores
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def ram_total_gb(self) -> float:
|
|
88
|
+
return self.ram.total_gb
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def ram_available_gb(self) -> float:
|
|
92
|
+
return self.ram.available_gb
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def has_gpu(self) -> bool:
|
|
96
|
+
return self.gpu.detected
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def gpu_name(self) -> str:
|
|
100
|
+
return self.gpu.name
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def vram_gb(self) -> float:
|
|
104
|
+
return self.gpu.vram_total_gb
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def gpu_backend(self) -> str:
|
|
108
|
+
return self.gpu.backend
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def local_ai_tier(self):
|
|
112
|
+
class _Tier:
|
|
113
|
+
def __init__(self, val):
|
|
114
|
+
self.value = val
|
|
115
|
+
def __str__(self):
|
|
116
|
+
return self.value
|
|
117
|
+
return _Tier(self.ai_capability)
|
|
118
|
+
|
|
119
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
120
|
+
return {
|
|
121
|
+
"cpu": {
|
|
122
|
+
"model": self.cpu.model,
|
|
123
|
+
"physical_cores": self.cpu.physical_cores,
|
|
124
|
+
"logical_cores": self.cpu.logical_cores,
|
|
125
|
+
"arch": self.cpu.arch,
|
|
126
|
+
},
|
|
127
|
+
"ram": {
|
|
128
|
+
"total_gb": self.ram.total_gb,
|
|
129
|
+
"available_gb": self.ram.available_gb,
|
|
130
|
+
"used_gb": self.ram.used_gb,
|
|
131
|
+
"percent_used": self.ram.percent_used,
|
|
132
|
+
},
|
|
133
|
+
"gpu": {
|
|
134
|
+
"detected": self.gpu.detected,
|
|
135
|
+
"name": self.gpu.name,
|
|
136
|
+
"vendor": self.gpu.vendor,
|
|
137
|
+
"vram_total_gb": self.gpu.vram_total_gb,
|
|
138
|
+
"vram_free_gb": self.gpu.vram_free_gb,
|
|
139
|
+
"backend": self.gpu.backend,
|
|
140
|
+
"driver_version": self.gpu.driver_version,
|
|
141
|
+
},
|
|
142
|
+
"storage": {
|
|
143
|
+
"total_gb": self.storage.total_gb,
|
|
144
|
+
"free_gb": self.storage.free_gb,
|
|
145
|
+
"used_gb": self.storage.used_gb,
|
|
146
|
+
"path": self.storage.path,
|
|
147
|
+
},
|
|
148
|
+
"os_name": self.os_name,
|
|
149
|
+
"os_version": self.os_version,
|
|
150
|
+
"python_version": self.python_version,
|
|
151
|
+
"ai_capability": self.ai_capability,
|
|
152
|
+
"ai_capability_notes": self.ai_capability_notes,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class HardwareAnalyzer:
|
|
157
|
+
"""Analyzes the local environment and grades local inference capability."""
|
|
158
|
+
|
|
159
|
+
_cached_profile: Optional[HardwareProfile] = None
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def analyze(cls, force_refresh: bool = False) -> HardwareProfile:
|
|
163
|
+
"""Run full hardware analysis, returning a structured HardwareProfile."""
|
|
164
|
+
if cls._cached_profile is not None and not force_refresh:
|
|
165
|
+
return cls._cached_profile
|
|
166
|
+
|
|
167
|
+
cpu_info = cls._detect_cpu()
|
|
168
|
+
ram_info = cls._detect_ram()
|
|
169
|
+
gpu_info = cls._detect_gpu()
|
|
170
|
+
storage_info = cls._detect_storage()
|
|
171
|
+
os_name = platform.system()
|
|
172
|
+
os_version = platform.platform()
|
|
173
|
+
python_ver = platform.python_version()
|
|
174
|
+
|
|
175
|
+
capability, notes = cls._evaluate_capability(ram_info, gpu_info, cpu_info)
|
|
176
|
+
|
|
177
|
+
profile = HardwareProfile(
|
|
178
|
+
cpu=cpu_info,
|
|
179
|
+
ram=ram_info,
|
|
180
|
+
gpu=gpu_info,
|
|
181
|
+
storage=storage_info,
|
|
182
|
+
os_name=os_name,
|
|
183
|
+
os_version=os_version,
|
|
184
|
+
python_version=python_ver,
|
|
185
|
+
ai_capability=capability,
|
|
186
|
+
ai_capability_notes=notes,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
cls._cached_profile = profile
|
|
190
|
+
return profile
|
|
191
|
+
|
|
192
|
+
@classmethod
|
|
193
|
+
def detect(cls, force_refresh: bool = False) -> HardwareProfile:
|
|
194
|
+
return cls.analyze(force_refresh=force_refresh)
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
def _detect_cpu(cls) -> CPUInfo:
|
|
198
|
+
model = "Unknown CPU"
|
|
199
|
+
logical = os.cpu_count() or 4
|
|
200
|
+
physical = logical
|
|
201
|
+
arch = platform.machine() or "x86_64"
|
|
202
|
+
|
|
203
|
+
# Try psutil for physical cores
|
|
204
|
+
try:
|
|
205
|
+
import psutil
|
|
206
|
+
p = psutil.cpu_count(logical=False)
|
|
207
|
+
if p:
|
|
208
|
+
physical = p
|
|
209
|
+
l = psutil.cpu_count(logical=True)
|
|
210
|
+
if l:
|
|
211
|
+
logical = l
|
|
212
|
+
except Exception:
|
|
213
|
+
pass
|
|
214
|
+
|
|
215
|
+
# Try OS specific name lookups
|
|
216
|
+
try:
|
|
217
|
+
if sys.platform == "win32":
|
|
218
|
+
import winreg
|
|
219
|
+
key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"HARDWARE\DESCRIPTION\System\CentralProcessor\0")
|
|
220
|
+
val, _ = winreg.QueryValueEx(key, "ProcessorNameString")
|
|
221
|
+
if val and str(val).strip():
|
|
222
|
+
model = str(val).strip()
|
|
223
|
+
elif sys.platform == "darwin":
|
|
224
|
+
out = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"], text=True, timeout=2)
|
|
225
|
+
if out.strip():
|
|
226
|
+
model = out.strip()
|
|
227
|
+
elif sys.platform.startswith("linux"):
|
|
228
|
+
with open("/proc/cpuinfo", "r", encoding="utf-8", errors="ignore") as f:
|
|
229
|
+
for line in f:
|
|
230
|
+
if "model name" in line:
|
|
231
|
+
model = line.split(":", 1)[1].strip()
|
|
232
|
+
break
|
|
233
|
+
except Exception:
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
if model == "Unknown CPU":
|
|
237
|
+
model = f"{arch} Processor ({logical} threads)"
|
|
238
|
+
|
|
239
|
+
return CPUInfo(model=model, physical_cores=physical, logical_cores=logical, arch=arch)
|
|
240
|
+
|
|
241
|
+
@classmethod
|
|
242
|
+
def _detect_ram(cls) -> RAMInfo:
|
|
243
|
+
total_gb = 8.0
|
|
244
|
+
avail_gb = 4.0
|
|
245
|
+
used_gb = 4.0
|
|
246
|
+
percent = 50.0
|
|
247
|
+
|
|
248
|
+
try:
|
|
249
|
+
import psutil
|
|
250
|
+
vm = psutil.virtual_memory()
|
|
251
|
+
total_gb = round(vm.total / (1024**3), 1)
|
|
252
|
+
avail_gb = round(vm.available / (1024**3), 1)
|
|
253
|
+
used_gb = round(vm.used / (1024**3), 1)
|
|
254
|
+
percent = round(vm.percent, 1)
|
|
255
|
+
return RAMInfo(total_gb=total_gb, available_gb=avail_gb, used_gb=used_gb, percent_used=percent)
|
|
256
|
+
except Exception:
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
# Windows ctypes fallback
|
|
260
|
+
if sys.platform == "win32":
|
|
261
|
+
try:
|
|
262
|
+
import ctypes
|
|
263
|
+
class MEMORYSTATUSEX(ctypes.Structure):
|
|
264
|
+
_fields_ = [
|
|
265
|
+
("dwLength", ctypes.c_ulong),
|
|
266
|
+
("dwMemoryLoad", ctypes.c_ulong),
|
|
267
|
+
("ullTotalPhys", ctypes.c_ulonglong),
|
|
268
|
+
("ullAvailPhys", ctypes.c_ulonglong),
|
|
269
|
+
("ullTotalPageFile", ctypes.c_ulonglong),
|
|
270
|
+
("ullAvailPageFile", ctypes.c_ulonglong),
|
|
271
|
+
("ullTotalVirtual", ctypes.c_ulonglong),
|
|
272
|
+
("ullAvailVirtual", ctypes.c_ulonglong),
|
|
273
|
+
("ullAvailExtendedVirtual", ctypes.c_ulonglong),
|
|
274
|
+
]
|
|
275
|
+
stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX))
|
|
276
|
+
if ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat)):
|
|
277
|
+
total_gb = round(stat.ullTotalPhys / (1024**3), 1)
|
|
278
|
+
avail_gb = round(stat.ullAvailPhys / (1024**3), 1)
|
|
279
|
+
used_gb = round((stat.ullTotalPhys - stat.ullAvailPhys) / (1024**3), 1)
|
|
280
|
+
percent = float(stat.dwMemoryLoad)
|
|
281
|
+
except Exception:
|
|
282
|
+
pass
|
|
283
|
+
|
|
284
|
+
return RAMInfo(total_gb=total_gb, available_gb=avail_gb, used_gb=used_gb, percent_used=percent)
|
|
285
|
+
|
|
286
|
+
@classmethod
|
|
287
|
+
def _detect_gpu(cls) -> GPUInfo:
|
|
288
|
+
# 1. Check NVIDIA via nvidia-smi
|
|
289
|
+
if shutil.which("nvidia-smi"):
|
|
290
|
+
try:
|
|
291
|
+
cmd = ["nvidia-smi", "--query-gpu=name,memory.total,memory.free,driver_version", "--format=csv,noheader"]
|
|
292
|
+
out = subprocess.check_output(cmd, timeout=3, text=True)
|
|
293
|
+
lines = [l.strip() for l in out.splitlines() if l.strip()]
|
|
294
|
+
if lines:
|
|
295
|
+
parts = [p.strip() for p in lines[0].split(",")]
|
|
296
|
+
name = parts[0]
|
|
297
|
+
vram_total = 0.0
|
|
298
|
+
vram_free = 0.0
|
|
299
|
+
driver = ""
|
|
300
|
+
if len(parts) > 1:
|
|
301
|
+
try:
|
|
302
|
+
vram_total = round(float(parts[1].replace("MiB", "").strip()) / 1024, 1)
|
|
303
|
+
except Exception:
|
|
304
|
+
pass
|
|
305
|
+
if len(parts) > 2:
|
|
306
|
+
try:
|
|
307
|
+
vram_free = round(float(parts[2].replace("MiB", "").strip()) / 1024, 1)
|
|
308
|
+
except Exception:
|
|
309
|
+
pass
|
|
310
|
+
if len(parts) > 3:
|
|
311
|
+
driver = parts[3]
|
|
312
|
+
return GPUInfo(
|
|
313
|
+
detected=True,
|
|
314
|
+
name=name,
|
|
315
|
+
vendor="NVIDIA",
|
|
316
|
+
vram_total_gb=vram_total,
|
|
317
|
+
vram_free_gb=vram_free,
|
|
318
|
+
backend="CUDA",
|
|
319
|
+
driver_version=driver,
|
|
320
|
+
)
|
|
321
|
+
except Exception:
|
|
322
|
+
pass
|
|
323
|
+
|
|
324
|
+
# 2. Check Apple Silicon via sysctl
|
|
325
|
+
if sys.platform == "darwin":
|
|
326
|
+
try:
|
|
327
|
+
out = subprocess.check_output(["sysctl", "-n", "machdep.cpu.brand_string"], text=True, timeout=2)
|
|
328
|
+
if "Apple" in out:
|
|
329
|
+
# Apple unified memory shares system RAM
|
|
330
|
+
import psutil
|
|
331
|
+
total_ram = round(psutil.virtual_memory().total / (1024**3), 1)
|
|
332
|
+
return GPUInfo(
|
|
333
|
+
detected=True,
|
|
334
|
+
name=out.strip() + " GPU (Unified)",
|
|
335
|
+
vendor="Apple",
|
|
336
|
+
vram_total_gb=total_ram,
|
|
337
|
+
vram_free_gb=round(total_ram * 0.7, 1),
|
|
338
|
+
backend="Metal",
|
|
339
|
+
driver_version="Metal 3",
|
|
340
|
+
)
|
|
341
|
+
except Exception:
|
|
342
|
+
pass
|
|
343
|
+
|
|
344
|
+
# 3. Check AMD ROCm / Linux
|
|
345
|
+
if shutil.which("rocm-smi"):
|
|
346
|
+
try:
|
|
347
|
+
out = subprocess.check_output(["rocm-smi", "--showmeminfo", "vram"], text=True, timeout=3)
|
|
348
|
+
return GPUInfo(detected=True, name="AMD Radeon (ROCm)", vendor="AMD", backend="ROCm")
|
|
349
|
+
except Exception:
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
# 4. Windows WMIC / DirectX VideoController fallback
|
|
353
|
+
if sys.platform == "win32":
|
|
354
|
+
try:
|
|
355
|
+
out = subprocess.check_output("wmic path win32_VideoController get name,adapterram", shell=True, timeout=3, text=True)
|
|
356
|
+
lines = [l.strip() for l in out.splitlines() if l.strip() and "Name" not in l and "AdapterRAM" not in l]
|
|
357
|
+
for line in lines:
|
|
358
|
+
parts = line.split()
|
|
359
|
+
if parts:
|
|
360
|
+
name = " ".join(parts[:-1]) if len(parts) > 1 else parts[0]
|
|
361
|
+
vram_bytes = parts[-1] if len(parts) > 1 and parts[-1].isdigit() else "0"
|
|
362
|
+
vram_gb = round(int(vram_bytes) / (1024**3), 1) if vram_bytes.isdigit() else 0.0
|
|
363
|
+
vendor = "NVIDIA" if "NVIDIA" in name.upper() else ("AMD" if "AMD" in name.upper() or "RADEON" in name.upper() else ("Intel" if "INTEL" in name.upper() else "Unknown"))
|
|
364
|
+
backend = "CUDA" if vendor == "NVIDIA" else "DirectML"
|
|
365
|
+
if vram_gb > 0.5 or "GeForce" in name or "Radeon" in name or "RTX" in name:
|
|
366
|
+
return GPUInfo(
|
|
367
|
+
detected=True,
|
|
368
|
+
name=name[:40],
|
|
369
|
+
vendor=vendor,
|
|
370
|
+
vram_total_gb=vram_gb,
|
|
371
|
+
vram_free_gb=vram_gb,
|
|
372
|
+
backend=backend,
|
|
373
|
+
)
|
|
374
|
+
except Exception:
|
|
375
|
+
pass
|
|
376
|
+
|
|
377
|
+
return GPUInfo()
|
|
378
|
+
|
|
379
|
+
@classmethod
|
|
380
|
+
def _detect_storage(cls) -> StorageInfo:
|
|
381
|
+
path = os.path.expanduser("~")
|
|
382
|
+
total_gb = 0.0
|
|
383
|
+
free_gb = 0.0
|
|
384
|
+
used_gb = 0.0
|
|
385
|
+
try:
|
|
386
|
+
du = shutil.disk_usage(path)
|
|
387
|
+
total_gb = round(du.total / (1024**3), 1)
|
|
388
|
+
free_gb = round(du.free / (1024**3), 1)
|
|
389
|
+
used_gb = round(du.used / (1024**3), 1)
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
return StorageInfo(total_gb=total_gb, free_gb=free_gb, used_gb=used_gb, path=path)
|
|
393
|
+
|
|
394
|
+
@classmethod
|
|
395
|
+
def _evaluate_capability(cls, ram: RAMInfo, gpu: GPUInfo, cpu: CPUInfo) -> Tuple[str, list[str]]:
|
|
396
|
+
notes = []
|
|
397
|
+
effective_vram = gpu.vram_total_gb if gpu.detected else 0.0
|
|
398
|
+
total_ram = ram.total_gb
|
|
399
|
+
|
|
400
|
+
# High-end GPU or high unified memory
|
|
401
|
+
if (gpu.detected and effective_vram >= 12.0) or (gpu.backend == "Metal" and total_ram >= 32.0):
|
|
402
|
+
capability = "EXCELLENT"
|
|
403
|
+
notes.append(f"High-speed {gpu.backend} acceleration with {effective_vram}GB memory.")
|
|
404
|
+
notes.append("Can comfortably run 8B-14B models with zero offloading, and 32B-70B with partial quantization.")
|
|
405
|
+
elif (gpu.detected and effective_vram >= 4.0) or total_ram >= 24.0:
|
|
406
|
+
capability = "GOOD"
|
|
407
|
+
notes.append(f"Solid local AI foundation: {gpu.name} ({effective_vram}GB VRAM) + {total_ram}GB RAM.")
|
|
408
|
+
notes.append("Ideal for 3B-8B parameter models with near-instant interactive token generation.")
|
|
409
|
+
if total_ram >= 32.0 and effective_vram < 8.0:
|
|
410
|
+
notes.append("Ample System RAM enables running 14B-32B models with hybrid CPU/GPU offloading.")
|
|
411
|
+
elif total_ram >= 12.0:
|
|
412
|
+
capability = "MODERATE"
|
|
413
|
+
notes.append(f"Capable of running compact 1B-4B models locally via CPU/RAM.")
|
|
414
|
+
notes.append("Cloud models recommended for multi-file coding or complex reasoning tasks.")
|
|
415
|
+
else:
|
|
416
|
+
capability = "LIMITED"
|
|
417
|
+
notes.append("Hardware is resource-constrained for large local inference.")
|
|
418
|
+
notes.append("Recommended to use lightweight 1B models or cloud AI providers.")
|
|
419
|
+
|
|
420
|
+
return capability, notes
|
|
421
|
+
|
|
422
|
+
@classmethod
|
|
423
|
+
def summary_text(cls) -> str:
|
|
424
|
+
"""Returns a concise, beautifully formatted human-readable hardware card."""
|
|
425
|
+
p = cls.analyze()
|
|
426
|
+
gpu_str = f"{p.gpu.name} ({p.gpu.vram_total_gb} GB {p.gpu.backend})" if p.gpu.detected else "None (CPU inference)"
|
|
427
|
+
lines = [
|
|
428
|
+
"CAT HARDWARE & ACCELERATION ANALYSIS",
|
|
429
|
+
"----------------------------------------------",
|
|
430
|
+
f"CPU : {p.cpu.model} ({p.cpu.logical_cores} threads)",
|
|
431
|
+
f"System RAM : {p.ram.total_gb} GB ({p.ram.available_gb} GB available)",
|
|
432
|
+
f"GPU : {gpu_str}",
|
|
433
|
+
f"Model Storage : {p.storage.free_gb} GB free on {p.storage.path}",
|
|
434
|
+
f"Operating Sys : {p.os_name} ({p.cpu.arch})",
|
|
435
|
+
"----------------------------------------------",
|
|
436
|
+
f"Local AI Tier : {p.ai_capability}",
|
|
437
|
+
]
|
|
438
|
+
for note in p.ai_capability_notes:
|
|
439
|
+
lines.append(f" * {note}")
|
|
440
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Browser subsystem — embedded QWebEngineView renderer owned by CAT.
|
|
3
|
+
|
|
4
|
+
When CAT needs to render a real webpage (HTML/CSS/JavaScript), it uses
|
|
5
|
+
this subsystem to create a browser window with QWebEngineView (Chromium).
|
|
6
|
+
|
|
7
|
+
The browser is NOT a separate application. It is created by CAT on demand
|
|
8
|
+
and destroyed when closed.
|
|
9
|
+
|
|
10
|
+
Usage:
|
|
11
|
+
from calc_terminal.host import launch_cat_host, can_launch_host
|
|
12
|
+
if can_launch_host():
|
|
13
|
+
launch_cat_host(start_browser_url="http://localhost:3000/connector.html")
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
def launch_cat_host(*args, **kwargs):
|
|
19
|
+
from .launcher import launch_cat_host as _impl
|
|
20
|
+
return _impl(*args, **kwargs)
|
|
21
|
+
|
|
22
|
+
def can_launch_host(*args, **kwargs):
|
|
23
|
+
from .launcher import can_launch_host as _impl
|
|
24
|
+
return _impl(*args, **kwargs)
|
|
25
|
+
|
|
26
|
+
def get_host_status(*args, **kwargs):
|
|
27
|
+
from .launcher import get_host_status as _impl
|
|
28
|
+
return _impl(*args, **kwargs)
|
|
29
|
+
|
|
30
|
+
__all__ = ["launch_cat_host", "can_launch_host", "get_host_status"]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT BrowserManager — internal browser controller owned by CAT.
|
|
3
|
+
|
|
4
|
+
Lifecycle belongs to CAT (no browser.exe, no separate app):
|
|
5
|
+
|
|
6
|
+
CAT (calc_terminal/host/desktop.py CATDesktopWindow)
|
|
7
|
+
└── BrowserManager
|
|
8
|
+
├── EmbeddedBrowserPane (QWebEngineView child)
|
|
9
|
+
├── BrowserState (tabs/history/bookmarks persistence)
|
|
10
|
+
└── QWebEngineProfile (cookies/storage/WebAuthn)
|
|
11
|
+
|
|
12
|
+
API:
|
|
13
|
+
|
|
14
|
+
createTab(), closeTab(), navigate(url), goBack(), goForward(),
|
|
15
|
+
reload(), executeScript(js), setViewport(w,h), destroy()
|
|
16
|
+
|
|
17
|
+
The browser renderer (QWebEngineView) is the working Chromium-based
|
|
18
|
+
engine that renders real HTML/CSS/JavaScript. It is integrated directly
|
|
19
|
+
into CAT's browser window — not launched as a separate application.
|
|
20
|
+
|
|
21
|
+
All rendering is LIVE (Chromium via Qt WebEngine / WebView2), not
|
|
22
|
+
screenshots, not text extraction.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from typing import Optional
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from PySide6.QtCore import QSize, QUrl
|
|
31
|
+
from PySide6.QtWebEngineCore import QWebEngineProfile
|
|
32
|
+
_QT = True
|
|
33
|
+
except ImportError:
|
|
34
|
+
try:
|
|
35
|
+
from PyQt6.QtCore import QSize, QUrl # type: ignore
|
|
36
|
+
from PyQt6.QtWebEngineCore import QWebEngineProfile # type: ignore
|
|
37
|
+
_QT = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
_QT = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BrowserManager:
|
|
43
|
+
"""
|
|
44
|
+
CAT-owned browser controller. One instance per CAT process/host window.
|
|
45
|
+
|
|
46
|
+
Wraps an EmbeddedBrowserPane (the graphical QWebEngineView surface) and
|
|
47
|
+
exposes a stable API for CAT CLI, AI Agent, and Fomoji auth to drive.
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
mgr = BrowserManager(browser_pane)
|
|
51
|
+
mgr.navigate("http://localhost:3000/connector.html")
|
|
52
|
+
mgr.goBack()
|
|
53
|
+
mgr.executeScript("document.title")
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, browser_pane):
|
|
57
|
+
"""
|
|
58
|
+
browser_pane: EmbeddedBrowserPane instance from desktop.py
|
|
59
|
+
"""
|
|
60
|
+
self.pane = browser_pane
|
|
61
|
+
|
|
62
|
+
# ------------------------------------------------------------------ tabs --
|
|
63
|
+
|
|
64
|
+
def createTab(self, url: str = "about:home", switch: bool = True):
|
|
65
|
+
"""Create a new tab. Returns tab index."""
|
|
66
|
+
return self.pane.new_tab(url)
|
|
67
|
+
|
|
68
|
+
def closeTab(self, index: Optional[int] = None):
|
|
69
|
+
"""Close tab at index, or current tab if None."""
|
|
70
|
+
if index is None:
|
|
71
|
+
index = self.pane.tab_bar.currentIndex()
|
|
72
|
+
self.pane._close_tab(index)
|
|
73
|
+
|
|
74
|
+
def switchTab(self, index: int):
|
|
75
|
+
self.pane.tab_bar.setCurrentIndex(index)
|
|
76
|
+
self.pane.viewport_stack.setCurrentIndex(index)
|
|
77
|
+
self.pane._sync_chrome()
|
|
78
|
+
|
|
79
|
+
def tabCount(self) -> int:
|
|
80
|
+
return self.pane.tab_bar.count()
|
|
81
|
+
|
|
82
|
+
# --------------------------------------------------------------- navigation --
|
|
83
|
+
|
|
84
|
+
def navigate(self, url: str):
|
|
85
|
+
"""Navigate current tab to url (real HTML/CSS/JS, not text)."""
|
|
86
|
+
self.pane.navigate(url)
|
|
87
|
+
|
|
88
|
+
def goBack(self):
|
|
89
|
+
self.pane.go_back()
|
|
90
|
+
|
|
91
|
+
def goForward(self):
|
|
92
|
+
self.pane.go_forward()
|
|
93
|
+
|
|
94
|
+
def reload(self):
|
|
95
|
+
self.pane.reload()
|
|
96
|
+
|
|
97
|
+
def home(self):
|
|
98
|
+
self.pane.navigate("about:home")
|
|
99
|
+
|
|
100
|
+
def currentUrl(self) -> str:
|
|
101
|
+
try:
|
|
102
|
+
idx = self.pane.tab_bar.currentIndex()
|
|
103
|
+
w = self.pane._views[idx]
|
|
104
|
+
if self.pane._is_newtab(w):
|
|
105
|
+
return "about:home"
|
|
106
|
+
return w.url().toString() # type: ignore
|
|
107
|
+
except Exception:
|
|
108
|
+
return ""
|
|
109
|
+
|
|
110
|
+
def currentTitle(self) -> str:
|
|
111
|
+
try:
|
|
112
|
+
idx = self.pane.tab_bar.currentIndex()
|
|
113
|
+
w = self.pane._views[idx]
|
|
114
|
+
if self.pane._is_newtab(w):
|
|
115
|
+
return "New Tab"
|
|
116
|
+
return w.title() # type: ignore
|
|
117
|
+
except Exception:
|
|
118
|
+
return ""
|
|
119
|
+
|
|
120
|
+
# -------------------------------------------------------------- scripting --
|
|
121
|
+
|
|
122
|
+
def executeScript(self, js: str, callback=None):
|
|
123
|
+
"""
|
|
124
|
+
Execute JavaScript in the current page's context (live DOM).
|
|
125
|
+
callback receives the result (if any).
|
|
126
|
+
"""
|
|
127
|
+
try:
|
|
128
|
+
idx = self.pane.tab_bar.currentIndex()
|
|
129
|
+
w = self.pane._views[idx]
|
|
130
|
+
if not self.pane._is_newtab(w):
|
|
131
|
+
page = w.page() # type: ignore
|
|
132
|
+
if callback:
|
|
133
|
+
page.runJavaScript(js, callback)
|
|
134
|
+
else:
|
|
135
|
+
page.runJavaScript(js)
|
|
136
|
+
return True
|
|
137
|
+
except Exception:
|
|
138
|
+
pass
|
|
139
|
+
return False
|
|
140
|
+
|
|
141
|
+
def setViewport(self, width: int, height: int):
|
|
142
|
+
"""Resize the web viewport — CSS sees real dimensions (vw/vh/media queries)."""
|
|
143
|
+
try:
|
|
144
|
+
idx = self.pane.tab_bar.currentIndex()
|
|
145
|
+
w = self.pane._views[idx]
|
|
146
|
+
if not self.pane._is_newtab(w):
|
|
147
|
+
w.page().setViewportSize(QSize(int(width), int(height))) # type: ignore
|
|
148
|
+
return True
|
|
149
|
+
except Exception:
|
|
150
|
+
pass
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
# -------------------------------------------------------------- lifecycle --
|
|
154
|
+
|
|
155
|
+
def destroy(self):
|
|
156
|
+
"""Tear down browser with CAT (no orphan process)."""
|
|
157
|
+
try:
|
|
158
|
+
# Close all WebEngineViews; profile will be destroyed with parent
|
|
159
|
+
for w in list(getattr(self.pane, "_views", [])):
|
|
160
|
+
try:
|
|
161
|
+
w.deleteLater()
|
|
162
|
+
except Exception:
|
|
163
|
+
pass
|
|
164
|
+
self.pane._views.clear() # type: ignore
|
|
165
|
+
except Exception:
|
|
166
|
+
pass
|
|
167
|
+
|
|
168
|
+
# -------------------------------------------------------------- devtools / AI hooks --
|
|
169
|
+
|
|
170
|
+
def getConsoleLogs(self):
|
|
171
|
+
"""Return recent console logs for AI Agent inspection (future)."""
|
|
172
|
+
# EmbeddedBrowserPane could expose page console via QWebEnginePage.javaScriptConsoleMessage
|
|
173
|
+
# For now return empty; hook point for AI integration.
|
|
174
|
+
return []
|
|
175
|
+
|
|
176
|
+
def screenshot(self, path: str) -> bool:
|
|
177
|
+
"""For AI analysis — capture viewport to file (not used for rendering)."""
|
|
178
|
+
try:
|
|
179
|
+
idx = self.pane.tab_bar.currentIndex()
|
|
180
|
+
w = self.pane._views[idx]
|
|
181
|
+
if not self.pane._is_newtab(w):
|
|
182
|
+
# QWebEngineView grab
|
|
183
|
+
pixmap = w.grab() # type: ignore
|
|
184
|
+
return pixmap.save(path)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
return False
|