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,350 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Compute Fabric per §25, §26, §87-§90:
|
|
3
|
+
- Heterogeneous compute target abstraction (Local CPU, Local GPU, Ollama, Docker, WSL, SSH, Slurm, Cloud)
|
|
4
|
+
- Honest environment probe and status determination (zero mocking)
|
|
5
|
+
- Intelligent compute routing based on resource requirements (CPU, RAM, GPU, container, cluster)
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import enum
|
|
11
|
+
import json
|
|
12
|
+
import os
|
|
13
|
+
import platform
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import time
|
|
17
|
+
from dataclasses import dataclass, field
|
|
18
|
+
from typing import Any, Dict, List, Optional, Tuple
|
|
19
|
+
|
|
20
|
+
from calc_terminal.hardware_analyzer import HardwareAnalyzer
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ComputeTargetType(str, enum.Enum):
|
|
24
|
+
LOCAL_CPU = "local_cpu"
|
|
25
|
+
LOCAL_GPU = "local_gpu"
|
|
26
|
+
OLLAMA = "ollama"
|
|
27
|
+
DOCKER = "docker"
|
|
28
|
+
WSL = "wsl"
|
|
29
|
+
SSH = "ssh"
|
|
30
|
+
SLURM = "slurm"
|
|
31
|
+
CLOUD = "cloud"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ComputeTargetStatus(str, enum.Enum):
|
|
35
|
+
AVAILABLE = "available"
|
|
36
|
+
UNAVAILABLE = "unavailable"
|
|
37
|
+
AUTHENTICATION_REQUIRED = "auth_required"
|
|
38
|
+
CONFIG_REQUIRED = "config_required"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ComputeTarget:
|
|
43
|
+
target_id: str
|
|
44
|
+
target_type: ComputeTargetType
|
|
45
|
+
name: str
|
|
46
|
+
status: ComputeTargetStatus = ComputeTargetStatus.UNAVAILABLE
|
|
47
|
+
hardware_details: Dict[str, Any] = field(default_factory=dict)
|
|
48
|
+
metadata: Dict[str, Any] = field(default_factory=dict)
|
|
49
|
+
|
|
50
|
+
def probe(self) -> ComputeTargetStatus:
|
|
51
|
+
"""Real honest check of target availability without mocking."""
|
|
52
|
+
return self.status
|
|
53
|
+
|
|
54
|
+
def execute(
|
|
55
|
+
self,
|
|
56
|
+
command: str,
|
|
57
|
+
working_dir: Optional[str] = None,
|
|
58
|
+
env: Optional[Dict[str, str]] = None,
|
|
59
|
+
timeout: int = 120,
|
|
60
|
+
) -> Dict[str, Any]:
|
|
61
|
+
"""Execute command on this compute target."""
|
|
62
|
+
if self.status != ComputeTargetStatus.AVAILABLE:
|
|
63
|
+
return {
|
|
64
|
+
"success": False,
|
|
65
|
+
"error": f"Target {self.target_id} is not available (Status: {self.status.value})",
|
|
66
|
+
"return_code": -1,
|
|
67
|
+
"stdout": "",
|
|
68
|
+
"stderr": f"Target {self.name} is unavailable.",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
cwd = working_dir or os.getcwd()
|
|
72
|
+
full_env = dict(os.environ)
|
|
73
|
+
if env:
|
|
74
|
+
full_env.update(env)
|
|
75
|
+
|
|
76
|
+
start_time = time.time()
|
|
77
|
+
try:
|
|
78
|
+
res = subprocess.run(
|
|
79
|
+
command,
|
|
80
|
+
shell=True,
|
|
81
|
+
cwd=cwd,
|
|
82
|
+
env=full_env,
|
|
83
|
+
capture_output=True,
|
|
84
|
+
text=True,
|
|
85
|
+
timeout=timeout,
|
|
86
|
+
)
|
|
87
|
+
return {
|
|
88
|
+
"success": res.returncode == 0,
|
|
89
|
+
"return_code": res.returncode,
|
|
90
|
+
"stdout": res.stdout,
|
|
91
|
+
"stderr": res.stderr,
|
|
92
|
+
"duration_seconds": round(time.time() - start_time, 3),
|
|
93
|
+
}
|
|
94
|
+
except subprocess.TimeoutExpired:
|
|
95
|
+
return {
|
|
96
|
+
"success": False,
|
|
97
|
+
"error": f"Command timed out after {timeout}s",
|
|
98
|
+
"return_code": -1,
|
|
99
|
+
"stdout": "",
|
|
100
|
+
"stderr": f"Execution timed out on {self.target_id}",
|
|
101
|
+
"duration_seconds": round(time.time() - start_time, 3),
|
|
102
|
+
}
|
|
103
|
+
except Exception as ex:
|
|
104
|
+
return {
|
|
105
|
+
"success": False,
|
|
106
|
+
"error": str(ex),
|
|
107
|
+
"return_code": -1,
|
|
108
|
+
"stdout": "",
|
|
109
|
+
"stderr": str(ex),
|
|
110
|
+
"duration_seconds": round(time.time() - start_time, 3),
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
114
|
+
return {
|
|
115
|
+
"target_id": self.target_id,
|
|
116
|
+
"target_type": self.target_type.value,
|
|
117
|
+
"name": self.name,
|
|
118
|
+
"status": self.status.value,
|
|
119
|
+
"hardware_details": self.hardware_details,
|
|
120
|
+
"metadata": self.metadata,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class LocalCPUTarget(ComputeTarget):
|
|
125
|
+
def __init__(self):
|
|
126
|
+
super().__init__(
|
|
127
|
+
target_id="local_cpu",
|
|
128
|
+
target_type=ComputeTargetType.LOCAL_CPU,
|
|
129
|
+
name="Local CPU Host",
|
|
130
|
+
)
|
|
131
|
+
self.probe()
|
|
132
|
+
|
|
133
|
+
def probe(self) -> ComputeTargetStatus:
|
|
134
|
+
try:
|
|
135
|
+
profile = HardwareAnalyzer.analyze()
|
|
136
|
+
self.hardware_details = {
|
|
137
|
+
"cpu_model": profile.cpu_model,
|
|
138
|
+
"logical_cores": profile.cpu_threads,
|
|
139
|
+
"ram_total_gb": profile.ram_total_gb,
|
|
140
|
+
"ram_available_gb": profile.ram_available_gb,
|
|
141
|
+
"os": f"{profile.os_name} {profile.os_version}",
|
|
142
|
+
}
|
|
143
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
144
|
+
except Exception:
|
|
145
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
146
|
+
self.hardware_details = {
|
|
147
|
+
"logical_cores": os.cpu_count() or 1,
|
|
148
|
+
"os": platform.platform(),
|
|
149
|
+
}
|
|
150
|
+
return self.status
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class LocalGPUTarget(ComputeTarget):
|
|
154
|
+
def __init__(self):
|
|
155
|
+
super().__init__(
|
|
156
|
+
target_id="local_gpu",
|
|
157
|
+
target_type=ComputeTargetType.LOCAL_GPU,
|
|
158
|
+
name="Local GPU Accelerator",
|
|
159
|
+
)
|
|
160
|
+
self.probe()
|
|
161
|
+
|
|
162
|
+
def probe(self) -> ComputeTargetStatus:
|
|
163
|
+
try:
|
|
164
|
+
profile = HardwareAnalyzer.analyze()
|
|
165
|
+
if profile.has_gpu:
|
|
166
|
+
self.hardware_details = {
|
|
167
|
+
"gpu_name": profile.gpu_name,
|
|
168
|
+
"backend": profile.gpu_backend,
|
|
169
|
+
"vram_gb": profile.vram_gb,
|
|
170
|
+
}
|
|
171
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
172
|
+
else:
|
|
173
|
+
self.hardware_details = {"gpu_name": "None", "backend": "None", "vram_gb": 0.0}
|
|
174
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
175
|
+
except Exception:
|
|
176
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
177
|
+
return self.status
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class OllamaTarget(ComputeTarget):
|
|
181
|
+
def __init__(self):
|
|
182
|
+
super().__init__(
|
|
183
|
+
target_id="ollama",
|
|
184
|
+
target_type=ComputeTargetType.OLLAMA,
|
|
185
|
+
name="Ollama Local Inference",
|
|
186
|
+
)
|
|
187
|
+
self.probe()
|
|
188
|
+
|
|
189
|
+
def probe(self) -> ComputeTargetStatus:
|
|
190
|
+
# Check CLI presence first
|
|
191
|
+
ollama_bin = shutil.which("ollama")
|
|
192
|
+
if not ollama_bin:
|
|
193
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
194
|
+
return self.status
|
|
195
|
+
|
|
196
|
+
# Try fast ping to local endpoint
|
|
197
|
+
try:
|
|
198
|
+
import urllib.request
|
|
199
|
+
req = urllib.request.Request("http://127.0.0.1:11434/api/tags", headers={"User-Agent": "CAT"})
|
|
200
|
+
with urllib.request.urlopen(req, timeout=1.0) as resp:
|
|
201
|
+
if resp.status == 200:
|
|
202
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
203
|
+
data = json.loads(resp.read().decode("utf-8"))
|
|
204
|
+
models = [m.get("name") for m in data.get("models", [])]
|
|
205
|
+
self.metadata = {"models": models}
|
|
206
|
+
return self.status
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
self.status = ComputeTargetStatus.CONFIG_REQUIRED
|
|
211
|
+
self.metadata = {"notes": "Ollama installed but daemon is not running on port 11434"}
|
|
212
|
+
return self.status
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class DockerTarget(ComputeTarget):
|
|
216
|
+
def __init__(self):
|
|
217
|
+
super().__init__(
|
|
218
|
+
target_id="docker",
|
|
219
|
+
target_type=ComputeTargetType.DOCKER,
|
|
220
|
+
name="Docker Container Runtime",
|
|
221
|
+
)
|
|
222
|
+
self.probe()
|
|
223
|
+
|
|
224
|
+
def probe(self) -> ComputeTargetStatus:
|
|
225
|
+
docker_bin = shutil.which("docker")
|
|
226
|
+
if not docker_bin:
|
|
227
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
228
|
+
return self.status
|
|
229
|
+
|
|
230
|
+
try:
|
|
231
|
+
res = subprocess.run(
|
|
232
|
+
["docker", "info"],
|
|
233
|
+
capture_output=True,
|
|
234
|
+
text=True,
|
|
235
|
+
timeout=2.0,
|
|
236
|
+
)
|
|
237
|
+
if res.returncode == 0:
|
|
238
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
239
|
+
else:
|
|
240
|
+
self.status = ComputeTargetStatus.CONFIG_REQUIRED
|
|
241
|
+
self.metadata = {"notes": "Docker installed but daemon is not responding"}
|
|
242
|
+
except Exception:
|
|
243
|
+
self.status = ComputeTargetStatus.CONFIG_REQUIRED
|
|
244
|
+
return self.status
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class WSLTarget(ComputeTarget):
|
|
248
|
+
def __init__(self):
|
|
249
|
+
super().__init__(
|
|
250
|
+
target_id="wsl",
|
|
251
|
+
target_type=ComputeTargetType.WSL,
|
|
252
|
+
name="Windows Subsystem for Linux (WSL2)",
|
|
253
|
+
)
|
|
254
|
+
self.probe()
|
|
255
|
+
|
|
256
|
+
def probe(self) -> ComputeTargetStatus:
|
|
257
|
+
if platform.system().lower() != "windows":
|
|
258
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
259
|
+
return self.status
|
|
260
|
+
|
|
261
|
+
wsl_bin = shutil.which("wsl")
|
|
262
|
+
if not wsl_bin:
|
|
263
|
+
self.status = ComputeTargetStatus.UNAVAILABLE
|
|
264
|
+
return self.status
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
res = subprocess.run(
|
|
268
|
+
["wsl", "--status"],
|
|
269
|
+
capture_output=True,
|
|
270
|
+
text=True,
|
|
271
|
+
timeout=2.0,
|
|
272
|
+
)
|
|
273
|
+
if res.returncode == 0:
|
|
274
|
+
self.status = ComputeTargetStatus.AVAILABLE
|
|
275
|
+
else:
|
|
276
|
+
self.status = ComputeTargetStatus.CONFIG_REQUIRED
|
|
277
|
+
except Exception:
|
|
278
|
+
self.status = ComputeTargetStatus.CONFIG_REQUIRED
|
|
279
|
+
return self.status
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class ComputeFabric:
|
|
283
|
+
"""Unified Compute Fabric coordinating targets and job routing per §25, §26."""
|
|
284
|
+
|
|
285
|
+
def __init__(self):
|
|
286
|
+
self._targets: Dict[str, ComputeTarget] = {}
|
|
287
|
+
self.discover_targets()
|
|
288
|
+
|
|
289
|
+
def register_target(self, target: ComputeTarget):
|
|
290
|
+
self._targets[target.target_id] = target
|
|
291
|
+
|
|
292
|
+
def discover_targets(self):
|
|
293
|
+
"""Discover and initialize standard compute targets."""
|
|
294
|
+
self.register_target(LocalCPUTarget())
|
|
295
|
+
self.register_target(LocalGPUTarget())
|
|
296
|
+
self.register_target(OllamaTarget())
|
|
297
|
+
self.register_target(DockerTarget())
|
|
298
|
+
self.register_target(WSLTarget())
|
|
299
|
+
|
|
300
|
+
def get_target(self, target_id: str) -> Optional[ComputeTarget]:
|
|
301
|
+
return self._targets.get(target_id)
|
|
302
|
+
|
|
303
|
+
def list_targets(self) -> List[ComputeTarget]:
|
|
304
|
+
return list(self._targets.values())
|
|
305
|
+
|
|
306
|
+
def get_available_targets(self) -> List[ComputeTarget]:
|
|
307
|
+
return [t for t in self._targets.values() if t.status == ComputeTargetStatus.AVAILABLE]
|
|
308
|
+
|
|
309
|
+
def route_task(self, requirements: Dict[str, Any]) -> Tuple[ComputeTarget, Optional[str]]:
|
|
310
|
+
"""
|
|
311
|
+
Intelligently route task to best compute target per §87.
|
|
312
|
+
Returns (target, warning_or_note).
|
|
313
|
+
"""
|
|
314
|
+
# 1. Check GPU requirement
|
|
315
|
+
if requirements.get("requires_gpu"):
|
|
316
|
+
gpu = self.get_target("local_gpu")
|
|
317
|
+
if gpu and gpu.status == ComputeTargetStatus.AVAILABLE:
|
|
318
|
+
return gpu, None
|
|
319
|
+
# Fall back to CPU with clear warning
|
|
320
|
+
cpu = self.get_target("local_cpu") or LocalCPUTarget()
|
|
321
|
+
return cpu, "Task requires GPU, but local GPU is unavailable. Routing to Local CPU."
|
|
322
|
+
|
|
323
|
+
# 2. Check Docker requirement
|
|
324
|
+
if requirements.get("container") or requirements.get("docker_image"):
|
|
325
|
+
docker = self.get_target("docker")
|
|
326
|
+
if docker and docker.status == ComputeTargetStatus.AVAILABLE:
|
|
327
|
+
return docker, None
|
|
328
|
+
cpu = self.get_target("local_cpu") or LocalCPUTarget()
|
|
329
|
+
return cpu, "Task requested container environment, but Docker is unavailable. Falling back to Host CPU."
|
|
330
|
+
|
|
331
|
+
# 3. Check WSL requirement
|
|
332
|
+
if requirements.get("wsl"):
|
|
333
|
+
wsl = self.get_target("wsl")
|
|
334
|
+
if wsl and wsl.status == ComputeTargetStatus.AVAILABLE:
|
|
335
|
+
return wsl, None
|
|
336
|
+
cpu = self.get_target("local_cpu") or LocalCPUTarget()
|
|
337
|
+
return cpu, "WSL requested but unavailable. Falling back to Host CPU."
|
|
338
|
+
|
|
339
|
+
# 4. Check Ollama requirement
|
|
340
|
+
if requirements.get("ollama"):
|
|
341
|
+
ollama = self.get_target("ollama")
|
|
342
|
+
if ollama and ollama.status == ComputeTargetStatus.AVAILABLE:
|
|
343
|
+
return ollama, None
|
|
344
|
+
|
|
345
|
+
# Default to CPU
|
|
346
|
+
cpu = self.get_target("local_cpu") or LocalCPUTarget()
|
|
347
|
+
return cpu, None
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
compute_fabric = ComputeFabric()
|
calc_terminal/config.py
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT centralized configuration (spec section 13).
|
|
3
|
+
|
|
4
|
+
Before this module, settings were scattered: aicore.py wrote its own
|
|
5
|
+
~/.cct_ai_config.json for provider/model, theme.py wrote its own
|
|
6
|
+
~/.cct_theme.json for the color theme, memory.py had its own store for
|
|
7
|
+
facts/activity, and things like animation speed, history limits, or a
|
|
8
|
+
feedback-recipient address weren't stored anywhere — they were literal
|
|
9
|
+
constants inline in whichever file used them.
|
|
10
|
+
|
|
11
|
+
This module is the single source of truth going forward: one
|
|
12
|
+
dataclass (`CCTConfig`), one file (`~/.cct_config.json`), one
|
|
13
|
+
get/set/save API. It does NOT yet rip out aicore.py's or theme.py's
|
|
14
|
+
own persistence — that's real surgery on files that currently work,
|
|
15
|
+
and doing it carelessly under time pressure is how you break a
|
|
16
|
+
working AI connection. What it DOES do, today:
|
|
17
|
+
|
|
18
|
+
- defines the full settings schema from the spec (provider, mode,
|
|
19
|
+
theme, accent colors, animation speed, terminal appearance,
|
|
20
|
+
startup page, model, ollama/openrouter endpoints, workspace dir,
|
|
21
|
+
history limits, memory limits, simulation settings, feedback email)
|
|
22
|
+
- persists it centrally and exposes get_config()/save_config()
|
|
23
|
+
- is the fallback default source: theme.py's loader now asks this
|
|
24
|
+
module for the configured default *before* falling back to its
|
|
25
|
+
own hardcoded "dark" (see theme.load_saved_theme) — a real,
|
|
26
|
+
working integration point, not parallel dead code
|
|
27
|
+
- is ready for the Settings UI (cmd_settings) to read/write against
|
|
28
|
+
instead of ad hoc prompts
|
|
29
|
+
|
|
30
|
+
Migrating aicore.py's provider/model storage and theme.py's per-theme
|
|
31
|
+
file fully onto this module is the next increment, tracked in
|
|
32
|
+
CHANGELOG_v0.6.3_architecture.md — not done silently, not skipped
|
|
33
|
+
silently.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import json
|
|
37
|
+
import os
|
|
38
|
+
from dataclasses import dataclass, field, asdict, fields
|
|
39
|
+
from typing import Optional
|
|
40
|
+
|
|
41
|
+
CONFIG_PATH = os.path.join(os.path.expanduser("~"), ".cct_config.json")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class SimulationSettings:
|
|
46
|
+
frame_delay: float = 0.06
|
|
47
|
+
default_max_frames: int = 600
|
|
48
|
+
default_orbital_points: int = 2200
|
|
49
|
+
monte_carlo_batch: int = 6
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class CCTConfig:
|
|
54
|
+
# --- feedback ---
|
|
55
|
+
feedback_email: Optional[str] = None # recipient; None = unset
|
|
56
|
+
feedback_backend: str = "none" # "none" | "smtp" | "api"
|
|
57
|
+
|
|
58
|
+
# --- AI ---
|
|
59
|
+
default_ai_provider: str = "openai" # openai|claude|gemini|groq|openrouter|ollama|custom
|
|
60
|
+
default_ai_mode: str = "notebook" # notebook|research|plan|build|debugger|agent
|
|
61
|
+
default_model: str = "" # provider-specific model id; "" = provider default
|
|
62
|
+
privacy_policy: str = "local_first" # local_first | cloud_first | best_available | never_cloud
|
|
63
|
+
enable_model_fallback: bool = True # auto failover across local/cloud model chain
|
|
64
|
+
ollama_endpoint: str = "http://localhost:11434"
|
|
65
|
+
openrouter_endpoint: str = "https://openrouter.ai/api/v1"
|
|
66
|
+
model_cache_ttl_hours: float = 24.0 # how long cache/models/ lists stay fresh before auto-refresh
|
|
67
|
+
|
|
68
|
+
# --- AI request lifecycle (v0.7.9.5) --------------------------------
|
|
69
|
+
# Total-request deadlines by prompt class, in seconds. A streaming
|
|
70
|
+
# response is NEVER cut off mid-tokens just because it's long: the
|
|
71
|
+
# total deadline only fires when nothing useful has been produced,
|
|
72
|
+
# while model_idle_timeout bounds silence BETWEEN tokens.
|
|
73
|
+
timeout_simple: int = 30 # trivial/fast-path prompts
|
|
74
|
+
timeout_normal: int = 60 # regular chat turns
|
|
75
|
+
timeout_large: int = 300 # agent / build / pipeline work
|
|
76
|
+
model_idle_timeout: int = 45 # max silence between stream chunks
|
|
77
|
+
model_max_retries_per_provider: int = 1 # extra attempts per provider
|
|
78
|
+
model_provider_cooldown: float = 20.0 # seconds a failing provider sits out
|
|
79
|
+
model_retry_backoff_base: float = 0.6 # exponential backoff base (0.6s, 1.2s, ...)
|
|
80
|
+
|
|
81
|
+
# --- appearance ---
|
|
82
|
+
default_theme: str = "dark"
|
|
83
|
+
accent_colors: dict = field(default_factory=lambda: {
|
|
84
|
+
"notebook": "#7aa2f7", # blue (ai_modes MODE_META notebook)
|
|
85
|
+
"agent": "#bb9af7", # purple (agent)
|
|
86
|
+
"build": "#e0af68", # orange (build)
|
|
87
|
+
"plan": "#9ece6a", # green (plan)
|
|
88
|
+
"research": "#ff69b4", # hot pink (research)
|
|
89
|
+
"debugger": "#dc143c", # crimson (debugger)
|
|
90
|
+
})
|
|
91
|
+
accent_gradients: dict = field(default_factory=dict) # optional explicit gradients {mode: [start_hex, end_hex]}
|
|
92
|
+
# --- full mode customization (v0.7.9.8) ---
|
|
93
|
+
# When non-empty, these completely replace ai_modes.MODE_META / MODE_ORDER
|
|
94
|
+
# (snapshot of current modes). Empty = use built-in defaults + accent_colors overrides.
|
|
95
|
+
user_modes: dict = field(default_factory=dict) # {key: {label, icon, accent, gradient:[s,e], purpose, system_prompt?}}
|
|
96
|
+
user_mode_order: list = field(default_factory=list) # ordered keys matching user_modes
|
|
97
|
+
# --- extensions (v0.7.9.10) ---
|
|
98
|
+
# Gestures / Vision / Personalize are installable extensions: enabled => show in main menu,
|
|
99
|
+
# disabled/uninstalled => hidden until re-enabled via Extensions panel.
|
|
100
|
+
# Empty dict = all enabled (backward compat). Explicit False = disabled/uninstalled.
|
|
101
|
+
extensions: dict = field(default_factory=dict) # e.g. {"gestures": true, "vision": false, "personalize": true}
|
|
102
|
+
animation_speed: float = 1.0 # multiplier; 0.5 = slower, 2.0 = faster
|
|
103
|
+
terminal_appearance: str = "auto" # "auto" | "ansi256" | "truecolor"
|
|
104
|
+
startup_page: str = "welcome" # "welcome" | "last_session" | "notebook"
|
|
105
|
+
sidebar_collapsed: bool = False # Explorer sidebar open/closed (persisted toggle state)
|
|
106
|
+
|
|
107
|
+
# --- permissions ---
|
|
108
|
+
default_permission_mode: str = "ask" # ask|restricted|full
|
|
109
|
+
|
|
110
|
+
# --- workspace / data ---
|
|
111
|
+
workspace_directory: str = field(
|
|
112
|
+
default_factory=lambda: os.path.join(os.path.expanduser("~"), "cct_workspace"))
|
|
113
|
+
history_limit: int = 500
|
|
114
|
+
memory_limit: int = 2000
|
|
115
|
+
|
|
116
|
+
simulation: SimulationSettings = field(default_factory=SimulationSettings)
|
|
117
|
+
|
|
118
|
+
# ---------------------------------------------------------- helpers --
|
|
119
|
+
def to_dict(self):
|
|
120
|
+
d = asdict(self)
|
|
121
|
+
return d
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_dict(cls, d):
|
|
125
|
+
d = dict(d or {})
|
|
126
|
+
sim = d.pop("simulation", None) or {}
|
|
127
|
+
known = {f.name for f in fields(cls)}
|
|
128
|
+
d = {k: v for k, v in d.items() if k in known}
|
|
129
|
+
cfg = cls(**d)
|
|
130
|
+
if sim:
|
|
131
|
+
for k, v in sim.items():
|
|
132
|
+
if hasattr(cfg.simulation, k):
|
|
133
|
+
setattr(cfg.simulation, k, v)
|
|
134
|
+
return cfg
|
|
135
|
+
|
|
136
|
+
def get(self, dotted_key, default=None):
|
|
137
|
+
"""e.g. get('accent_colors.build') or get('simulation.frame_delay')"""
|
|
138
|
+
obj = self
|
|
139
|
+
for part in dotted_key.split("."):
|
|
140
|
+
if isinstance(obj, dict):
|
|
141
|
+
if part not in obj:
|
|
142
|
+
return default
|
|
143
|
+
obj = obj[part]
|
|
144
|
+
elif hasattr(obj, part):
|
|
145
|
+
obj = getattr(obj, part)
|
|
146
|
+
else:
|
|
147
|
+
return default
|
|
148
|
+
return obj
|
|
149
|
+
|
|
150
|
+
def set(self, dotted_key, value):
|
|
151
|
+
parts = dotted_key.split(".")
|
|
152
|
+
obj = self
|
|
153
|
+
for part in parts[:-1]:
|
|
154
|
+
obj = obj[part] if isinstance(obj, dict) else getattr(obj, part)
|
|
155
|
+
last = parts[-1]
|
|
156
|
+
if isinstance(obj, dict):
|
|
157
|
+
obj[last] = value
|
|
158
|
+
else:
|
|
159
|
+
setattr(obj, last, value)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
_cached = None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _seed_from_aicore(cfg):
|
|
166
|
+
"""If the user already configured a provider via /ai (aicore.py's
|
|
167
|
+
own ~/.cct_ai_config.json) before this central config existed, use
|
|
168
|
+
its non-secret fields (provider/model/ollama_url) as the initial
|
|
169
|
+
defaults here instead of showing the generic hardcoded ones. Reads
|
|
170
|
+
the file directly rather than importing aicore, so config.py stays
|
|
171
|
+
the lighter-weight module with no dependency on it."""
|
|
172
|
+
aicore_file = os.path.join(os.path.expanduser("~"), ".cct_ai_config.json")
|
|
173
|
+
if not os.path.exists(aicore_file):
|
|
174
|
+
return cfg
|
|
175
|
+
try:
|
|
176
|
+
with open(aicore_file, "r", encoding="utf-8") as f:
|
|
177
|
+
ai = json.load(f)
|
|
178
|
+
except (json.JSONDecodeError, OSError):
|
|
179
|
+
return cfg
|
|
180
|
+
if ai.get("provider"):
|
|
181
|
+
cfg.default_ai_provider = ai["provider"]
|
|
182
|
+
if ai.get("model"):
|
|
183
|
+
cfg.default_model = ai["model"]
|
|
184
|
+
if ai.get("ollama_url"):
|
|
185
|
+
cfg.ollama_endpoint = ai["ollama_url"]
|
|
186
|
+
return cfg
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def load_config():
|
|
190
|
+
"""Read ~/.cct_config.json, filling in defaults for anything missing
|
|
191
|
+
or malformed. Never raises — a corrupt config file degrades to
|
|
192
|
+
defaults rather than crashing the app."""
|
|
193
|
+
global _cached
|
|
194
|
+
if _cached is not None:
|
|
195
|
+
return _cached
|
|
196
|
+
cfg = CCTConfig()
|
|
197
|
+
if os.path.exists(CONFIG_PATH):
|
|
198
|
+
try:
|
|
199
|
+
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
|
200
|
+
raw = json.load(f)
|
|
201
|
+
cfg = CCTConfig.from_dict(raw)
|
|
202
|
+
except (json.JSONDecodeError, OSError, TypeError, ValueError):
|
|
203
|
+
pass
|
|
204
|
+
else:
|
|
205
|
+
# first run ever — no point re-asking for a provider the user
|
|
206
|
+
# already set up through /ai before this module existed
|
|
207
|
+
cfg = _seed_from_aicore(cfg)
|
|
208
|
+
_cached = cfg
|
|
209
|
+
return cfg
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def save_config(cfg=None):
|
|
213
|
+
"""Persist the given (or currently cached) config to disk. Returns
|
|
214
|
+
True on success, False if the write failed (e.g. read-only home)."""
|
|
215
|
+
global _cached
|
|
216
|
+
cfg = cfg or _cached or CCTConfig()
|
|
217
|
+
_cached = cfg
|
|
218
|
+
try:
|
|
219
|
+
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
|
|
220
|
+
json.dump(cfg.to_dict(), f, indent=2)
|
|
221
|
+
return True
|
|
222
|
+
except OSError:
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def get_config():
|
|
227
|
+
return load_config()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CAT Core Package — re-exports core architectural modules and singletons.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from calc_terminal.core.mode_registry import ModeRegistry, mode_registry
|
|
8
|
+
from calc_terminal.core.task_graph import TaskGraph, TaskNode, TaskStatus
|
|
9
|
+
from calc_terminal.core.verification import RealityEngine, reality_engine
|
|
10
|
+
from calc_terminal.core.unified_runtime import UnifiedAgentRuntime, unified_runtime
|
|
11
|
+
from calc_terminal.core.project_graph import ProjectGraph, project_graph, SymbolInfo
|
|
12
|
+
from calc_terminal.core.checkpoint import CheckpointManager, checkpoint_manager, Checkpoint
|
|
13
|
+
from calc_terminal.core.recovery import RecoveryEngine, recovery_engine, RecoverySnapshot
|
|
14
|
+
from calc_terminal.core.security_layer import SecurityLayer, security_layer, PermissionTier
|
|
15
|
+
|
|
16
|
+
from .. import input
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"input",
|
|
20
|
+
"ModeRegistry",
|
|
21
|
+
"mode_registry",
|
|
22
|
+
"TaskGraph",
|
|
23
|
+
"TaskNode",
|
|
24
|
+
"TaskStatus",
|
|
25
|
+
"RealityEngine",
|
|
26
|
+
"reality_engine",
|
|
27
|
+
"UnifiedAgentRuntime",
|
|
28
|
+
"unified_runtime",
|
|
29
|
+
"ProjectGraph",
|
|
30
|
+
"project_graph",
|
|
31
|
+
"SymbolInfo",
|
|
32
|
+
"CheckpointManager",
|
|
33
|
+
"checkpoint_manager",
|
|
34
|
+
"Checkpoint",
|
|
35
|
+
"RecoveryEngine",
|
|
36
|
+
"recovery_engine",
|
|
37
|
+
"RecoverySnapshot",
|
|
38
|
+
"SecurityLayer",
|
|
39
|
+
"security_layer",
|
|
40
|
+
"PermissionTier",
|
|
41
|
+
]
|