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,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT AI Personalization (v0.7.8 BONUS 2).
|
|
3
|
+
|
|
4
|
+
A professional, honest personalization layer on top of the existing
|
|
5
|
+
prompt pipeline. The user keeps a set of named "profiles"; the active
|
|
6
|
+
profile adjusts every AI answer's style WITHOUT replacing any of CCT's
|
|
7
|
+
norman system prompts, tools, or memory:
|
|
8
|
+
|
|
9
|
+
- tone -> a short directive appended to the system prompt
|
|
10
|
+
(balanced / concise / detailed / playful / formal)
|
|
11
|
+
- additions -> free-form rules the user writes ("always use SI
|
|
12
|
+
units", "never mention the JSON protocol", ...)
|
|
13
|
+
- temperature -> a sampling override (0.0 - 2.0), applied where the
|
|
14
|
+
request config supports it — see request_config()
|
|
15
|
+
- model -> a preferred model label; advisory today (kept as
|
|
16
|
+
metadata the panel shows, because forcing a model
|
|
17
|
+
string across every provider/api_style the way a
|
|
18
|
+
real pin would is exactly the kind of deep rewrite
|
|
19
|
+
this passes on). It IS surfaced in the dashboard.
|
|
20
|
+
- description, top_p, creativity, reasoning, response_length,
|
|
21
|
+
memory_pref -> advisory metadata (v0.7.8.1 Personalize redesign):
|
|
22
|
+
stored on the profile and surfaced in the dashboard; top_p IS
|
|
23
|
+
forwarded to the request config (and payloads) where supported,
|
|
24
|
+
the rest describe the profile for future wiring and are honest
|
|
25
|
+
metadata, never fake settings.
|
|
26
|
+
|
|
27
|
+
Persistence: ~/.cct_profiles.json
|
|
28
|
+
{"active": "<name>", "profiles": [ {name, description, tone,
|
|
29
|
+
temperature, top_p, additions, model, creativity, reasoning,
|
|
30
|
+
response_length, memory_pref, created_at, updated_at}, ... ]}
|
|
31
|
+
|
|
32
|
+
Integration points (both single-line, no behavior rewrite):
|
|
33
|
+
- ai_modes.system_prompt_for — every non-agent UI mode prompt
|
|
34
|
+
- agent.run_agent — agent + ai personas (tools loop)
|
|
35
|
+
|
|
36
|
+
A profile never affects anything when none is active; callers that
|
|
37
|
+
want the raw prompt keep pinning their own system_prompt and nothing
|
|
38
|
+
changes.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
from __future__ import annotations
|
|
42
|
+
|
|
43
|
+
import json
|
|
44
|
+
import os
|
|
45
|
+
import time
|
|
46
|
+
|
|
47
|
+
PROFILE_FILE = os.path.join(os.path.expanduser("~"), ".cct_profiles.json")
|
|
48
|
+
|
|
49
|
+
TONES = {
|
|
50
|
+
"balanced": "Keep a measured, even tone: clear and direct, neither terse nor padded.",
|
|
51
|
+
"concise": "Be concise. Answer in the fewest words that stay complete; prefer short sentences and no filler.",
|
|
52
|
+
"detailed": "Be thorough. Give full context, step-by-step reasoning, and complete derivations where relevant.",
|
|
53
|
+
"playful": "Keep a friendly, light tone. Stay accurate, but feel free to be a little playful and warm.",
|
|
54
|
+
"formal": "Be formal and precise. Use professional, carefully-phrased language without casualisms.",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
DEFAULT_PROFILE = {
|
|
58
|
+
"name": "Balanced",
|
|
59
|
+
"description": "",
|
|
60
|
+
"tone": "balanced",
|
|
61
|
+
"temperature": None,
|
|
62
|
+
"top_p": None,
|
|
63
|
+
"additions": "",
|
|
64
|
+
"model": "",
|
|
65
|
+
"creativity": None,
|
|
66
|
+
"reasoning": "",
|
|
67
|
+
"response_length": "",
|
|
68
|
+
"memory_pref": "",
|
|
69
|
+
"created_at": 0,
|
|
70
|
+
"updated_at": 0,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def load_profiles() -> dict:
|
|
75
|
+
"""Return {'active': name, 'profiles': [...]}. Never raises."""
|
|
76
|
+
try:
|
|
77
|
+
if os.path.exists(PROFILE_FILE):
|
|
78
|
+
with open(PROFILE_FILE, "r", encoding="utf-8") as f:
|
|
79
|
+
data = json.load(f)
|
|
80
|
+
if isinstance(data, dict):
|
|
81
|
+
profiles = data.get("profiles")
|
|
82
|
+
if isinstance(profiles, list):
|
|
83
|
+
cleaned = []
|
|
84
|
+
for p in profiles:
|
|
85
|
+
if not isinstance(p, dict):
|
|
86
|
+
continue
|
|
87
|
+
base = dict(DEFAULT_PROFILE)
|
|
88
|
+
base.update({k: p.get(k, v) for k, v in DEFAULT_PROFILE.items()})
|
|
89
|
+
cleaned.append(base)
|
|
90
|
+
return {"active": str(data.get("active") or ""), "profiles": cleaned}
|
|
91
|
+
except Exception:
|
|
92
|
+
pass
|
|
93
|
+
return {"active": "", "profiles": []}
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def save_profiles(data: dict) -> bool:
|
|
97
|
+
try:
|
|
98
|
+
with open(PROFILE_FILE, "w", encoding="utf-8") as f:
|
|
99
|
+
json.dump({
|
|
100
|
+
"active": str(data.get("active") or ""),
|
|
101
|
+
"profiles": [dict(DEFAULT_PROFILE, **p) for p in (data.get("profiles") or [])],
|
|
102
|
+
}, f, indent=2)
|
|
103
|
+
return True
|
|
104
|
+
except Exception:
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def profiles() -> list[dict]:
|
|
109
|
+
return load_profiles().get("profiles", [])
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def profile_names() -> list[str]:
|
|
113
|
+
return [p.get("name", "?") for p in profiles()]
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def active_profile() -> dict | None:
|
|
117
|
+
"""The active profile as a dict, or None when none is active or
|
|
118
|
+
the active name no longer exists."""
|
|
119
|
+
data = load_profiles()
|
|
120
|
+
name = data.get("active") or ""
|
|
121
|
+
if not name:
|
|
122
|
+
return None
|
|
123
|
+
for p in data.get("profiles", []):
|
|
124
|
+
if p.get("name") == name:
|
|
125
|
+
return p
|
|
126
|
+
return None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def set_active(name: str):
|
|
130
|
+
data = load_profiles()
|
|
131
|
+
data["active"] = name
|
|
132
|
+
save_profiles(data)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def add_profile(name: str, tone: str = "balanced", additions: str = "",
|
|
136
|
+
temperature=None, model: str = "", **extras) -> dict:
|
|
137
|
+
"""Create or replace a profile by name (name is the key). Returns it.
|
|
138
|
+
|
|
139
|
+
`extras` are stored as-is (used by the Personalize panel for the
|
|
140
|
+
advisory fields: description, top_p, creativity, reasoning,
|
|
141
|
+
response_length, memory_pref)."""
|
|
142
|
+
now = int(time.time())
|
|
143
|
+
prof = {
|
|
144
|
+
"name": name,
|
|
145
|
+
"tone": tone if tone in TONES else "balanced",
|
|
146
|
+
"temperature": temperature,
|
|
147
|
+
"additions": additions,
|
|
148
|
+
"model": model,
|
|
149
|
+
"created_at": now,
|
|
150
|
+
"updated_at": now,
|
|
151
|
+
}
|
|
152
|
+
for key in ("description", "top_p", "creativity", "reasoning",
|
|
153
|
+
"response_length", "memory_pref"):
|
|
154
|
+
if extras.get(key) is not None:
|
|
155
|
+
prof[key] = extras[key]
|
|
156
|
+
data = load_profiles()
|
|
157
|
+
data["profiles"] = [p for p in data["profiles"] if p.get("name") != name]
|
|
158
|
+
data["profiles"].append(prof)
|
|
159
|
+
save_profiles(data)
|
|
160
|
+
return prof
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def update_profile(name: str, **fields) -> dict | None:
|
|
164
|
+
prof = add_profile(name,
|
|
165
|
+
tone=fields.get("tone", "balanced"),
|
|
166
|
+
additions=fields.get("additions", ""),
|
|
167
|
+
temperature=fields.get("temperature"),
|
|
168
|
+
model=fields.get("model", ""),
|
|
169
|
+
description=fields.get("description"),
|
|
170
|
+
top_p=fields.get("top_p"),
|
|
171
|
+
creativity=fields.get("creativity"),
|
|
172
|
+
reasoning=fields.get("reasoning"),
|
|
173
|
+
response_length=fields.get("response_length"),
|
|
174
|
+
memory_pref=fields.get("memory_pref"))
|
|
175
|
+
return prof
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def delete_profile(name: str):
|
|
179
|
+
data = load_profiles()
|
|
180
|
+
data["profiles"] = [p for p in data["profiles"] if p.get("name") != name]
|
|
181
|
+
if data.get("active") == name:
|
|
182
|
+
data["active"] = ""
|
|
183
|
+
save_profiles(data)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# ---------------------------------------------------------------------------
|
|
187
|
+
# Integration helpers
|
|
188
|
+
|
|
189
|
+
ABOUT_ME_FILE = os.path.join(os.path.expanduser("~"), ".cct_about_me.json")
|
|
190
|
+
|
|
191
|
+
def load_about_me() -> dict:
|
|
192
|
+
try:
|
|
193
|
+
if os.path.exists(ABOUT_ME_FILE):
|
|
194
|
+
with open(ABOUT_ME_FILE, "r", encoding="utf-8") as f:
|
|
195
|
+
data = json.load(f)
|
|
196
|
+
if isinstance(data, dict):
|
|
197
|
+
return data
|
|
198
|
+
except Exception:
|
|
199
|
+
pass
|
|
200
|
+
return {}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def about_me_block() -> str:
|
|
204
|
+
data = load_about_me()
|
|
205
|
+
if not data or not any(v.strip() for v in data.values() if isinstance(v, str)):
|
|
206
|
+
return ""
|
|
207
|
+
lines = []
|
|
208
|
+
for k, v in data.items():
|
|
209
|
+
if isinstance(v, str) and v.strip():
|
|
210
|
+
label = k.replace("_", " ").title()
|
|
211
|
+
lines.append(f"- {label}: {v.strip()}")
|
|
212
|
+
if not lines:
|
|
213
|
+
return ""
|
|
214
|
+
return "About the user:\n" + "\n".join(lines)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def is_memory_enabled() -> bool:
|
|
218
|
+
prof = active_profile()
|
|
219
|
+
if not prof:
|
|
220
|
+
return True
|
|
221
|
+
pref = (prof.get("memory_pref") or "auto").strip().lower()
|
|
222
|
+
if pref == "off":
|
|
223
|
+
return False
|
|
224
|
+
return True
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def tone_directive(profile: dict | None) -> str:
|
|
228
|
+
if not profile:
|
|
229
|
+
return ""
|
|
230
|
+
tone = profile.get("tone") or "balanced"
|
|
231
|
+
base = TONES.get(tone, TONES["balanced"])
|
|
232
|
+
additions = (profile.get("additions") or "").strip()
|
|
233
|
+
if not additions:
|
|
234
|
+
return base
|
|
235
|
+
return base + "\nUser style rules (always follow these, they override defaults):\n" + additions
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def personalize_system_prompt(base_prompt: str) -> str:
|
|
239
|
+
"""Append the active profile's style directive + About Me to a system prompt."""
|
|
240
|
+
if os.environ.get("CCT_DISABLE_PERSONALIZATION"):
|
|
241
|
+
return base_prompt
|
|
242
|
+
prof = active_profile()
|
|
243
|
+
directive = tone_directive(prof)
|
|
244
|
+
about = about_me_block()
|
|
245
|
+
extra = ""
|
|
246
|
+
if directive:
|
|
247
|
+
extra += "\n\n<Personal style>\n" + directive + "\n</Personal style>"
|
|
248
|
+
if about:
|
|
249
|
+
extra += "\n\n<About Me>\n" + about + "\n</About Me>"
|
|
250
|
+
if not extra:
|
|
251
|
+
return base_prompt
|
|
252
|
+
if base_prompt.endswith(extra.strip()):
|
|
253
|
+
return base_prompt
|
|
254
|
+
return base_prompt.rstrip() + extra
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def request_config() -> dict | None:
|
|
258
|
+
"""Request-config overrides for the active profile: a dict with
|
|
259
|
+
'temperature' and/or 'top_p' keys when the profile pins them, else
|
|
260
|
+
None. Pass as `config` to aicore.query_ai/stream_ai to merge over
|
|
261
|
+
the primary provider config (aicore merges {'temperature': ...} /
|
|
262
|
+
{'top_p': ...} into the OpenAI/Anthropic/Gemini payloads)."""
|
|
263
|
+
prof = active_profile()
|
|
264
|
+
if not prof:
|
|
265
|
+
return None
|
|
266
|
+
out = {}
|
|
267
|
+
temp = prof.get("temperature")
|
|
268
|
+
if temp is not None:
|
|
269
|
+
try:
|
|
270
|
+
out["temperature"] = float(temp)
|
|
271
|
+
except (TypeError, ValueError):
|
|
272
|
+
pass
|
|
273
|
+
top_p = prof.get("top_p")
|
|
274
|
+
if top_p is not None:
|
|
275
|
+
try:
|
|
276
|
+
out["top_p"] = float(top_p)
|
|
277
|
+
except (TypeError, ValueError):
|
|
278
|
+
pass
|
|
279
|
+
return out if out else None
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def active_model_label() -> str:
|
|
283
|
+
prof = active_profile()
|
|
284
|
+
if not prof:
|
|
285
|
+
return ""
|
|
286
|
+
return prof.get("model") or ""
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT — ai_preview_feedback.py: AI + Web Preview Feedback Loop (v0.7.10 spec #35).
|
|
3
|
+
|
|
4
|
+
Enables the AI to:
|
|
5
|
+
1. See the current preview state (DOM snapshot, console errors)
|
|
6
|
+
2. Take screenshots of the preview
|
|
7
|
+
3. Interact with the preview (click, type, navigate)
|
|
8
|
+
4. Get feedback on changes made
|
|
9
|
+
|
|
10
|
+
Architecture:
|
|
11
|
+
AI Agent -> FeedbackController -> PreviewController -> BrowserEngine
|
|
12
|
+
<- FeedbackController <- PreviewState
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
print("This is a library file and is not meant to be run directly.")
|
|
17
|
+
import sys
|
|
18
|
+
sys.exit(1)
|
|
19
|
+
|
|
20
|
+
import os
|
|
21
|
+
import threading
|
|
22
|
+
import time
|
|
23
|
+
from typing import Optional, Dict, Any, List
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PreviewFeedback:
|
|
29
|
+
"""Feedback from the preview about the current state."""
|
|
30
|
+
url: str = ""
|
|
31
|
+
title: str = ""
|
|
32
|
+
dom_snapshot: str = ""
|
|
33
|
+
console_errors: List[str] = field(default_factory=list)
|
|
34
|
+
console_logs: List[str] = field(default_factory=list)
|
|
35
|
+
screenshot_path: str = ""
|
|
36
|
+
is_loading: bool = False
|
|
37
|
+
load_error: str = ""
|
|
38
|
+
timestamp: float = field(default_factory=time.time)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class AIAction:
|
|
43
|
+
"""An action the AI wants to perform on the preview."""
|
|
44
|
+
action_type: str # "navigate", "click", "type", "screenshot", "evaluate"
|
|
45
|
+
target: str = "" # CSS selector, URL, or JS code
|
|
46
|
+
value: str = "" # Text to type, URL to navigate, etc.
|
|
47
|
+
callback: Optional[callable] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AIPreviewFeedbackController:
|
|
51
|
+
"""Controls the AI + Web Preview feedback loop.
|
|
52
|
+
|
|
53
|
+
This controller:
|
|
54
|
+
- Monitors preview state and provides feedback to the AI
|
|
55
|
+
- Executes AI actions on the preview
|
|
56
|
+
- Manages the feedback cycle
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, preview_controller=None):
|
|
60
|
+
self.preview_controller = preview_controller
|
|
61
|
+
self._action_queue = []
|
|
62
|
+
self._feedback_history = []
|
|
63
|
+
self._lock = threading.Lock()
|
|
64
|
+
self._max_history = 50
|
|
65
|
+
|
|
66
|
+
def get_preview_state(self) -> PreviewFeedback:
|
|
67
|
+
"""Get the current state of the preview for AI consumption."""
|
|
68
|
+
feedback = PreviewFeedback()
|
|
69
|
+
|
|
70
|
+
if not self.preview_controller:
|
|
71
|
+
feedback.load_error = "No preview controller available"
|
|
72
|
+
return feedback
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
# Get URL and title
|
|
76
|
+
feedback.url = self.preview_controller.url or ""
|
|
77
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
78
|
+
engine = self.preview_controller.engine
|
|
79
|
+
if hasattr(engine, 'current_title'):
|
|
80
|
+
feedback.title = engine.current_title or ""
|
|
81
|
+
|
|
82
|
+
# Get DOM snapshot
|
|
83
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
84
|
+
engine = self.preview_controller.engine
|
|
85
|
+
if hasattr(engine, 'get_snapshot'):
|
|
86
|
+
snapshot = engine.get_snapshot()
|
|
87
|
+
if snapshot:
|
|
88
|
+
feedback.dom_snapshot = getattr(snapshot, 'text', '')
|
|
89
|
+
|
|
90
|
+
# Get console errors
|
|
91
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
92
|
+
engine = self.preview_controller.engine
|
|
93
|
+
if hasattr(engine, 'console_errors'):
|
|
94
|
+
feedback.console_errors = list(engine.console_errors[-10:])
|
|
95
|
+
|
|
96
|
+
# Check loading state
|
|
97
|
+
if hasattr(self.preview_controller, 'preview_state'):
|
|
98
|
+
from .browser.state import PreviewState
|
|
99
|
+
feedback.is_loading = (
|
|
100
|
+
self.preview_controller.preview_state == PreviewState.STARTING
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
feedback.load_error = str(e)
|
|
105
|
+
|
|
106
|
+
return feedback
|
|
107
|
+
|
|
108
|
+
def execute_action(self, action: AIAction) -> bool:
|
|
109
|
+
"""Execute an AI action on the preview."""
|
|
110
|
+
if not self.preview_controller:
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
if action.action_type == "navigate":
|
|
115
|
+
return self._navigate(action.target)
|
|
116
|
+
elif action.action_type == "click":
|
|
117
|
+
return self._click(action.target)
|
|
118
|
+
elif action.action_type == "type":
|
|
119
|
+
return self._type(action.target, action.value)
|
|
120
|
+
elif action.action_type == "screenshot":
|
|
121
|
+
return self._screenshot(action.target)
|
|
122
|
+
elif action.action_type == "evaluate":
|
|
123
|
+
return self._evaluate(action.target)
|
|
124
|
+
else:
|
|
125
|
+
return False
|
|
126
|
+
except Exception:
|
|
127
|
+
return False
|
|
128
|
+
|
|
129
|
+
def _navigate(self, url: str) -> bool:
|
|
130
|
+
"""Navigate the preview to a URL."""
|
|
131
|
+
if not url.startswith("http"):
|
|
132
|
+
# Relative URL - make absolute
|
|
133
|
+
base = self.preview_controller.base_url or ""
|
|
134
|
+
url = base.rstrip("/") + "/" + url.lstrip("/")
|
|
135
|
+
self.preview_controller.navigate(url)
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
def _click(self, selector: str) -> bool:
|
|
139
|
+
"""Click an element in the preview."""
|
|
140
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
141
|
+
engine = self.preview_controller.engine
|
|
142
|
+
if hasattr(engine, 'click'):
|
|
143
|
+
engine.click(selector)
|
|
144
|
+
return True
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
def _type(self, selector: str, text: str) -> bool:
|
|
148
|
+
"""Type text into an element in the preview."""
|
|
149
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
150
|
+
engine = self.preview_controller.engine
|
|
151
|
+
if hasattr(engine, 'type'):
|
|
152
|
+
engine.type(selector, text)
|
|
153
|
+
return True
|
|
154
|
+
return False
|
|
155
|
+
|
|
156
|
+
def _screenshot(self, path: str) -> bool:
|
|
157
|
+
"""Take a screenshot of the preview."""
|
|
158
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
159
|
+
engine = self.preview_controller.engine
|
|
160
|
+
if hasattr(engine, 'screenshot'):
|
|
161
|
+
engine.screenshot(path)
|
|
162
|
+
return True
|
|
163
|
+
return False
|
|
164
|
+
|
|
165
|
+
def _evaluate(self, js_code: str) -> bool:
|
|
166
|
+
"""Evaluate JavaScript in the preview."""
|
|
167
|
+
if hasattr(self.preview_controller, 'engine'):
|
|
168
|
+
engine = self.preview_controller.engine
|
|
169
|
+
if hasattr(engine, 'evaluate'):
|
|
170
|
+
engine.evaluate(js_code)
|
|
171
|
+
return True
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
def record_feedback(self, feedback: PreviewFeedback):
|
|
175
|
+
"""Record feedback for history."""
|
|
176
|
+
with self._lock:
|
|
177
|
+
self._feedback_history.append(feedback)
|
|
178
|
+
if len(self._feedback_history) > self._max_history:
|
|
179
|
+
self._feedback_history = self._feedback_history[-self._max_history:]
|
|
180
|
+
|
|
181
|
+
def get_feedback_summary(self) -> str:
|
|
182
|
+
"""Get a summary of recent feedback for AI consumption."""
|
|
183
|
+
with self._lock:
|
|
184
|
+
if not self._feedback_history:
|
|
185
|
+
return "No preview feedback available."
|
|
186
|
+
|
|
187
|
+
recent = self._feedback_history[-5:] # Last 5 feedback items
|
|
188
|
+
summary = []
|
|
189
|
+
for fb in recent:
|
|
190
|
+
if fb.url:
|
|
191
|
+
summary.append(f"URL: {fb.url}")
|
|
192
|
+
if fb.title:
|
|
193
|
+
summary.append(f"Title: {fb.title}")
|
|
194
|
+
if fb.console_errors:
|
|
195
|
+
summary.append(f"Console errors: {len(fb.console_errors)}")
|
|
196
|
+
if fb.load_error:
|
|
197
|
+
summary.append(f"Error: {fb.load_error}")
|
|
198
|
+
|
|
199
|
+
return "\n".join(summary) if summary else "Preview state unknown."
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
# Global instance
|
|
203
|
+
_ai_feedback_controller = None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def get_ai_feedback_controller(preview_controller=None) -> AIPreviewFeedbackController:
|
|
207
|
+
"""Get or create the global AI feedback controller."""
|
|
208
|
+
global _ai_feedback_controller
|
|
209
|
+
if _ai_feedback_controller is None:
|
|
210
|
+
_ai_feedback_controller = AIPreviewFeedbackController(preview_controller)
|
|
211
|
+
elif preview_controller and _ai_feedback_controller.preview_controller is None:
|
|
212
|
+
_ai_feedback_controller.preview_controller = preview_controller
|
|
213
|
+
return _ai_feedback_controller
|