k-cli-for-devs 1.0.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.
- k_cli/__init__.py +77 -0
- k_cli/agents/__init__.py +0 -0
- k_cli/agents/adversarial_swarm.py +338 -0
- k_cli/agents/agent_core.py +255 -0
- k_cli/agents/background_daemon.py +141 -0
- k_cli/agents/orchestrator.py +376 -0
- k_cli/agents/persona.py +649 -0
- k_cli/agents/scaffold_engine.py +121 -0
- k_cli/agents/strands_agent.py +832 -0
- k_cli/agents/subagents.py +1496 -0
- k_cli/cli.py +3297 -0
- k_cli/core/__init__.py +0 -0
- k_cli/core/airgap.py +95 -0
- k_cli/core/credentials.py +548 -0
- k_cli/core/intent_sensor.py +177 -0
- k_cli/core/llm_driver.py +1028 -0
- k_cli/core/model_manager.py +1109 -0
- k_cli/core/models_hub.py +913 -0
- k_cli/core/prompting.py +41 -0
- k_cli/core/sdk.py +322 -0
- k_cli/core/session.py +826 -0
- k_cli/core/smart_router.py +230 -0
- k_cli/core/storage_manager.py +176 -0
- k_cli/core/viewport_engine.py +117 -0
- k_cli/demo/demo_runner.py +579 -0
- k_cli/git/__init__.py +0 -0
- k_cli/git/ai_bisect.py +208 -0
- k_cli/git/conflict_resolver.py +1039 -0
- k_cli/git/git_guard.py +417 -0
- k_cli/git/patcher.py +1175 -0
- k_cli/git/repo_map.py +1780 -0
- k_cli/git/smart_git.py +928 -0
- k_cli/git/verifier.py +969 -0
- k_cli/github/__init__.py +0 -0
- k_cli/github/dedup_engine.py +787 -0
- k_cli/github/github_client.py +1702 -0
- k_cli/github/github_engine.py +641 -0
- k_cli/github/local_hub.py +209 -0
- k_cli/github/pr_watcher.py +129 -0
- k_cli/github/trending.py +205 -0
- k_cli/tools/__init__.py +0 -0
- k_cli/tools/audit.py +79 -0
- k_cli/tools/chaos_immunity.py +377 -0
- k_cli/tools/codebase_qa.py +106 -0
- k_cli/tools/command_runner.py +256 -0
- k_cli/tools/diagram_generator.py +547 -0
- k_cli/tools/doc_retriever.py +1332 -0
- k_cli/tools/feature.py +105 -0
- k_cli/tools/ghost_daemon.py +122 -0
- k_cli/tools/incident_triage.py +1365 -0
- k_cli/tools/mcp_client.py +1846 -0
- k_cli/tools/repo_gardener.py +142 -0
- k_cli/tools/rules.py +109 -0
- k_cli/tools/security.py +52 -0
- k_cli/tools/security_healer.py +999 -0
- k_cli/tools/synapse_graph.py +155 -0
- k_cli/tui/__init__.py +0 -0
- k_cli/tui/diff_viewer.py +223 -0
- k_cli/tui/tui.py +1145 -0
- k_cli/tui/tui_animations.py +648 -0
- k_cli/tui/tui_app.py +2788 -0
- k_cli/ui/__init__.py +10 -0
- k_cli/ui/simple_repl.py +315 -0
- k_cli/web/__init__.py +7 -0
- k_cli/web/server.py +624 -0
- k_cli/web/static/app.js +830 -0
- k_cli/web/static/index.html +495 -0
- k_cli/web/static/monitor.html +189 -0
- k_cli/web/static/style.css +838 -0
- k_cli_for_devs-1.0.0.dist-info/METADATA +461 -0
- k_cli_for_devs-1.0.0.dist-info/RECORD +75 -0
- k_cli_for_devs-1.0.0.dist-info/WHEEL +5 -0
- k_cli_for_devs-1.0.0.dist-info/entry_points.txt +2 -0
- k_cli_for_devs-1.0.0.dist-info/licenses/LICENSE +21 -0
- k_cli_for_devs-1.0.0.dist-info/top_level.txt +1 -0
k_cli/tui/tui_app.py
ADDED
|
@@ -0,0 +1,2788 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tui_app.py - Flagship Hybrid Developer Workstation for K-CLI (Project Bankai v1.0.0)
|
|
3
|
+
|
|
4
|
+
A fusion of Claude Code, Google Antigravity (AGY), GitHub Copilot CLI, and Cursor:
|
|
5
|
+
1. Top Cyber HUD: Active Model Dropdown, Git Branch Pill with diff stats, RAM RSS Gauge, Speedometer (tok/s), USD Cost Ticker, Verifier Badge.
|
|
6
|
+
2. 3-Column Workstation Layout:
|
|
7
|
+
- Left Column: Antigravity Navigator (1-Click Action Launcher, @Context Files Manager, Subagent Swarm Radar, MCP Server Inventory).
|
|
8
|
+
- Center Column: Claude Code & Copilot Stream Canvas (Collapsible <think> drawer, Tool Execution Cards with Allow/Deny gates, Surgical Diff Cards with 1-click Apply/Rollback).
|
|
9
|
+
- Right Column: Auxiliary Inspector Drawer (Live Diff Preview, Background Tasks Monitor, Memory & Token Telemetry).
|
|
10
|
+
3. Bottom Action Dock: 1-Click Action Chips ([๐ Codex], [โก Plan], [โ๏ธ Conflict], [๐ GitHub], [๐ Keys], [๐ค Models], [๐ก๏ธ Security], [๐จ Triage], [๐งน Clear]) + Interactive Prompt Input.
|
|
11
|
+
4. Dedicated Flagship Modals & Codex Starting Screen:
|
|
12
|
+
- CodexStartingModal (Ctrl+O): Complete Codex Onboarding Hub (Cloud APIs with Any Key detection, Local Models with Pros/Cons, Bankai HF Models, DevDocs Offline Downloader, Auto-Approve Dev Preferences).
|
|
13
|
+
- CredentialsVaultModal (Ctrl+A): Configure and live-test all API keys at once.
|
|
14
|
+
- ConflictStudioModal (Ctrl+K): 4-way visual split (Ours vs Base vs Theirs vs AI Merge).
|
|
15
|
+
- GitHubCenterModal (Ctrl+G): Issues, PR reviews, CI failure inspector, release publisher.
|
|
16
|
+
- ModelHubModal (Ctrl+M): Local SLMs (Ollama/llama.cpp) & Cloud LLMs with latency benchmarks.
|
|
17
|
+
- SecurityScannerModal (Ctrl+S): AST static scanner with 1-click surgical auto-healer.
|
|
18
|
+
- IncidentTriageModal (Ctrl+T): Stack trace & CI error log parser with regression test generator.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import asyncio
|
|
24
|
+
import os
|
|
25
|
+
import psutil
|
|
26
|
+
import sys
|
|
27
|
+
import subprocess
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
31
|
+
|
|
32
|
+
# Textual 8.x Imports
|
|
33
|
+
from textual import events, on
|
|
34
|
+
from textual.app import App, ComposeResult
|
|
35
|
+
from textual.binding import Binding
|
|
36
|
+
from textual.containers import Container, Grid, Horizontal, ScrollableContainer, Vertical, VerticalScroll
|
|
37
|
+
from textual.screen import ModalScreen
|
|
38
|
+
from textual.widget import Widget
|
|
39
|
+
from textual.widgets import (
|
|
40
|
+
Button,
|
|
41
|
+
Collapsible,
|
|
42
|
+
Footer,
|
|
43
|
+
Header,
|
|
44
|
+
Input,
|
|
45
|
+
Label,
|
|
46
|
+
ListItem,
|
|
47
|
+
ListView,
|
|
48
|
+
Markdown,
|
|
49
|
+
OptionList,
|
|
50
|
+
ProgressBar,
|
|
51
|
+
RichLog,
|
|
52
|
+
Static,
|
|
53
|
+
TabbedContent,
|
|
54
|
+
TabPane,
|
|
55
|
+
)
|
|
56
|
+
from textual.widgets.option_list import Option
|
|
57
|
+
|
|
58
|
+
# Rich Formatting
|
|
59
|
+
from rich.panel import Panel
|
|
60
|
+
from rich.syntax import Syntax
|
|
61
|
+
from rich.text import Text
|
|
62
|
+
|
|
63
|
+
# K-CLI Core Engines
|
|
64
|
+
try:
|
|
65
|
+
from k_cli.core.llm_driver import LLMDriver, ProviderType
|
|
66
|
+
from k_cli.core.models_hub import ModelHub, ModelSpec, ModelProvider, ModelBenchmarkResult
|
|
67
|
+
from k_cli.core.credentials import CredentialsManager, detect_key_type, DevPreferencesManager, SUPPORTED_KEYS
|
|
68
|
+
from k_cli.core.model_manager import (
|
|
69
|
+
ModelManager,
|
|
70
|
+
list_local_coding_models,
|
|
71
|
+
list_bankai_models,
|
|
72
|
+
LOCAL_CODING_MODELS,
|
|
73
|
+
BANKAI_CUSTOM_MODELS,
|
|
74
|
+
)
|
|
75
|
+
from k_cli.tools.doc_retriever import DocRetriever, OFFICIAL_DEV_DOCS
|
|
76
|
+
from k_cli.github.github_engine import GitHubEngine, GitHubIssue, GitHubRelease, WorkflowRun, IssueSolveResult
|
|
77
|
+
from k_cli.git.conflict_resolver import ConflictResolver, ConflictBlock, ConflictResolution, FileResolutionResult, ConflictSummary
|
|
78
|
+
from k_cli.tools.mcp_client import MCPManager
|
|
79
|
+
from k_cli.github.dedup_engine import DedupEngine
|
|
80
|
+
from k_cli.git.smart_git import SmartGitEngine
|
|
81
|
+
from k_cli.tools.security_healer import SecurityHealer, SecurityScanReport, VulnerabilityHealResult
|
|
82
|
+
from k_cli.tools.incident_triage import IncidentTriageEngine, IncidentReport
|
|
83
|
+
from k_cli.tools.diagram_generator import DiagramGenerator
|
|
84
|
+
from k_cli.git.verifier import Verifier
|
|
85
|
+
from k_cli.agents.orchestrator import Orchestrator, Persona
|
|
86
|
+
from k_cli.git.patcher import Patcher
|
|
87
|
+
from k_cli.core.session import SessionManager
|
|
88
|
+
from k_cli.github.local_hub import LocalGitHubHub, LocalHubSummary
|
|
89
|
+
from k_cli.github.trending import TrendingEngine, TrendingRepo
|
|
90
|
+
except (ModuleNotFoundError, ImportError):
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# =============================================================================
|
|
95
|
+
# 0a. First-Time Workstation Welcome & Onboarding Modal
|
|
96
|
+
# =============================================================================
|
|
97
|
+
|
|
98
|
+
class WelcomeOnboardingModal(ModalScreen[bool]):
|
|
99
|
+
"""
|
|
100
|
+
First-Time Workstation Onboarding & AI Engine Gating Screen.
|
|
101
|
+
Guides the developer to configure at least 1 Cloud API or Local Model and choose their Persona.
|
|
102
|
+
"""
|
|
103
|
+
|
|
104
|
+
DEFAULT_CSS = """
|
|
105
|
+
WelcomeOnboardingModal {
|
|
106
|
+
align: center middle;
|
|
107
|
+
background: rgba(8, 12, 24, 0.95);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
#welcome-box {
|
|
111
|
+
width: 86%;
|
|
112
|
+
height: 86%;
|
|
113
|
+
background: #0d1117;
|
|
114
|
+
border: heavy #00f0ff;
|
|
115
|
+
padding: 1 2;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.welcome-title {
|
|
119
|
+
text-align: center;
|
|
120
|
+
color: #00f0ff;
|
|
121
|
+
text-style: bold;
|
|
122
|
+
margin-bottom: 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.welcome-desc {
|
|
126
|
+
text-align: center;
|
|
127
|
+
color: #8b949e;
|
|
128
|
+
margin-bottom: 1;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.welcome-section {
|
|
132
|
+
background: #161b22;
|
|
133
|
+
border: round #30363d;
|
|
134
|
+
padding: 1 2;
|
|
135
|
+
margin-bottom: 1;
|
|
136
|
+
height: auto;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.welcome-sec-title {
|
|
140
|
+
color: #58a6ff;
|
|
141
|
+
text-style: bold;
|
|
142
|
+
margin-bottom: 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.welcome-act-row {
|
|
146
|
+
height: 3;
|
|
147
|
+
align: center middle;
|
|
148
|
+
margin-top: 1;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.welcome-act-row Button {
|
|
152
|
+
margin: 0 1;
|
|
153
|
+
}
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
BINDINGS = [
|
|
157
|
+
Binding("escape", "dismiss", "Close"),
|
|
158
|
+
Binding("b", "dismiss", "Back"),
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
def compose(self) -> ComposeResult:
|
|
162
|
+
with Container(id="welcome-box"):
|
|
163
|
+
yield Label("๐ Welcome to K-CLI for Devs โ First-Time Workstation Onboarding", classes="welcome-title")
|
|
164
|
+
yield Label("Activate your autonomous engineering workstation with a Cloud API Key or Local SLM.", classes="welcome-desc")
|
|
165
|
+
|
|
166
|
+
with VerticalScroll():
|
|
167
|
+
# Step 1 & 2: AI Engine Configuration
|
|
168
|
+
with Container(classes="welcome-section"):
|
|
169
|
+
yield Label("1. Select & Configure Your AI Engine (Cloud or Local):", classes="welcome-sec-title")
|
|
170
|
+
yield Input(
|
|
171
|
+
placeholder="Paste ANY API Key (Gemini, Claude, OpenAI, Groq, DeepSeek) or Ollama URL (http://localhost:11434)...",
|
|
172
|
+
id="input-welcome-key",
|
|
173
|
+
password=True,
|
|
174
|
+
)
|
|
175
|
+
yield Label("๐ก Auto-detection active: paste any key or endpoint", id="lbl-welcome-detection", classes="badge-detected")
|
|
176
|
+
with Horizontal(classes="welcome-act-row"):
|
|
177
|
+
yield Button("๐พ Save & Verify AI Engine", variant="success", id="btn-welcome-save-key")
|
|
178
|
+
yield Button("๐ Auto-Detect Existing Keys", variant="primary", id="btn-welcome-auto-detect")
|
|
179
|
+
|
|
180
|
+
# Step 3: Developer Persona
|
|
181
|
+
with Container(classes="welcome-section"):
|
|
182
|
+
yield Label("2. Select Your Primary Role / Persona:", classes="welcome-sec-title")
|
|
183
|
+
with Horizontal():
|
|
184
|
+
yield Button("โก Fullstack Engineer", variant="primary", id="btn-role-fullstack")
|
|
185
|
+
yield Button("๐จ DevOps & SRE", variant="default", id="btn-role-devops")
|
|
186
|
+
yield Button("๐ก๏ธ Security Auditor", variant="default", id="btn-role-security")
|
|
187
|
+
yield Button("๐ง Software Architect", variant="default", id="btn-role-architect")
|
|
188
|
+
yield Label("Active Persona: โก Fullstack AI Systems Engineer", id="lbl-welcome-active-role", classes="badge-detected")
|
|
189
|
+
|
|
190
|
+
# Status / Gating Warning Label
|
|
191
|
+
yield Label("", id="lbl-welcome-gate-error")
|
|
192
|
+
|
|
193
|
+
# Step 4: Launch Actions
|
|
194
|
+
with Horizontal(classes="welcome-act-row"):
|
|
195
|
+
yield Button("๐ Launch Cyber Workstation", variant="primary", id="btn-welcome-launch")
|
|
196
|
+
yield Button("๐ญ Demo Mode", variant="warning", id="btn-welcome-demo")
|
|
197
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-welcome-back")
|
|
198
|
+
|
|
199
|
+
def on_mount(self) -> None:
|
|
200
|
+
self.selected_persona = "Fullstack AI Systems Engineer"
|
|
201
|
+
self._refresh_detection_status()
|
|
202
|
+
|
|
203
|
+
def _refresh_detection_status(self) -> None:
|
|
204
|
+
has_creds = CredentialsManager.has_any_active_credentials()
|
|
205
|
+
lbl = self.query_one("#lbl-welcome-detection", Label)
|
|
206
|
+
if has_creds:
|
|
207
|
+
best_model = DevPreferencesManager.get_best_available_model()
|
|
208
|
+
lbl.update(f"โ Active AI Engine Detected: [bold green]{best_model}[/bold green] โ Ready to Launch!")
|
|
209
|
+
else:
|
|
210
|
+
lbl.update("โ ๏ธ No active API key or local model detected yet.")
|
|
211
|
+
|
|
212
|
+
@on(Input.Changed, "#input-welcome-key")
|
|
213
|
+
def on_key_input_changed(self, event: Input.Changed) -> None:
|
|
214
|
+
val = event.value.strip()
|
|
215
|
+
lbl = self.query_one("#lbl-welcome-detection", Label)
|
|
216
|
+
if not val:
|
|
217
|
+
self._refresh_detection_status()
|
|
218
|
+
return
|
|
219
|
+
key_name, prov_name = detect_key_type(val)
|
|
220
|
+
lbl.update(f"๐ Auto-detected: [bold cyan]{prov_name}[/bold cyan] ({key_name})")
|
|
221
|
+
|
|
222
|
+
@on(Button.Pressed, "#btn-welcome-save-key")
|
|
223
|
+
def on_save_key(self) -> None:
|
|
224
|
+
inp = self.query_one("#input-welcome-key", Input)
|
|
225
|
+
val = inp.value.strip()
|
|
226
|
+
if not val:
|
|
227
|
+
self.app.notify("Please paste an API key or Ollama URL.", title="Key Required", severity="warning")
|
|
228
|
+
return
|
|
229
|
+
key_name, prov_name = CredentialsManager.save_any_key(val)
|
|
230
|
+
self.app.notify(f"Saved {prov_name}!", title="Key Stored", severity="information")
|
|
231
|
+
self._refresh_detection_status()
|
|
232
|
+
|
|
233
|
+
@on(Button.Pressed, "#btn-welcome-auto-detect")
|
|
234
|
+
def on_auto_detect(self) -> None:
|
|
235
|
+
CredentialsManager.load_all_credentials()
|
|
236
|
+
self._refresh_detection_status()
|
|
237
|
+
self.app.notify("Credentials re-scanned.", title="Auto-Detect", severity="information")
|
|
238
|
+
|
|
239
|
+
@on(Button.Pressed, "#btn-role-fullstack")
|
|
240
|
+
def on_role_fullstack(self) -> None:
|
|
241
|
+
self.selected_persona = "Fullstack AI Systems Engineer"
|
|
242
|
+
self.query_one("#lbl-welcome-active-role", Label).update("Active Persona: โก Fullstack AI Systems Engineer")
|
|
243
|
+
|
|
244
|
+
@on(Button.Pressed, "#btn-role-devops")
|
|
245
|
+
def on_role_devops(self) -> None:
|
|
246
|
+
self.selected_persona = "DevOps & Incident SRE Specialist"
|
|
247
|
+
self.query_one("#lbl-welcome-active-role", Label).update("Active Persona: ๐จ DevOps & Incident SRE Specialist")
|
|
248
|
+
|
|
249
|
+
@on(Button.Pressed, "#btn-role-security")
|
|
250
|
+
def on_role_security(self) -> None:
|
|
251
|
+
self.selected_persona = "Security & Chaos Immunity Auditor"
|
|
252
|
+
self.query_one("#lbl-welcome-active-role", Label).update("Active Persona: ๐ก๏ธ Security & Chaos Immunity Auditor")
|
|
253
|
+
|
|
254
|
+
@on(Button.Pressed, "#btn-role-architect")
|
|
255
|
+
def on_role_architect(self) -> None:
|
|
256
|
+
self.selected_persona = "Autonomous Software Architect"
|
|
257
|
+
self.query_one("#lbl-welcome-active-role", Label).update("Active Persona: ๐ง Autonomous Software Architect")
|
|
258
|
+
|
|
259
|
+
@on(Button.Pressed, "#btn-welcome-launch")
|
|
260
|
+
@on(Button.Pressed, "#btn-welcome-skip")
|
|
261
|
+
def on_launch_or_skip(self) -> None:
|
|
262
|
+
has_creds = CredentialsManager.has_any_active_credentials()
|
|
263
|
+
if not has_creds and not getattr(self.app, "mock_mode", False):
|
|
264
|
+
err_lbl = self.query_one("#lbl-welcome-gate-error", Label)
|
|
265
|
+
err_lbl.update("[bold red]โ ๏ธ Access Gated: Please enter at least 1 Cloud API Key or Local Model before launching. (Or click 'Pure Demo Mode' to explore without AI).[/bold red]")
|
|
266
|
+
self.app.notify("At least 1 API key or local model required.", title="AI Engine Required", severity="error")
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
best_model = DevPreferencesManager.get_best_available_model()
|
|
270
|
+
DevPreferencesManager.mark_setup_complete(persona=getattr(self, "selected_persona", "Fullstack AI Systems Engineer"), model=best_model)
|
|
271
|
+
self.app.notify("Welcome to K-CLI Cyber Workstation!", title="Workstation Activated", severity="information")
|
|
272
|
+
self.dismiss(True)
|
|
273
|
+
|
|
274
|
+
@on(Button.Pressed, "#btn-welcome-demo")
|
|
275
|
+
def on_demo_mode(self) -> None:
|
|
276
|
+
self.app.mock_mode = True
|
|
277
|
+
DevPreferencesManager.mark_setup_complete(persona="Fullstack AI Systems Engineer", model="gemini-2.5-flash (Demo Mode)")
|
|
278
|
+
self.app.notify("Launched in Zero-AI Pure Demo Mode.", title="Demo Mode", severity="warning")
|
|
279
|
+
self.dismiss(True)
|
|
280
|
+
|
|
281
|
+
@on(Button.Pressed, "#btn-welcome-back")
|
|
282
|
+
def on_back(self) -> None:
|
|
283
|
+
self.on_escape()
|
|
284
|
+
|
|
285
|
+
def on_escape(self) -> None:
|
|
286
|
+
has_creds = CredentialsManager.has_any_active_credentials()
|
|
287
|
+
if has_creds or getattr(self.app, "mock_mode", False):
|
|
288
|
+
self.dismiss(True)
|
|
289
|
+
else:
|
|
290
|
+
self.on_demo_mode()
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# =============================================================================
|
|
294
|
+
# 0. The Codex Starting & Onboarding Hub Screen (Ctrl+O)
|
|
295
|
+
# =============================================================================
|
|
296
|
+
|
|
297
|
+
class CodexStartingModal(ModalScreen[bool]):
|
|
298
|
+
"""
|
|
299
|
+
The Premier Codex Onboarding & Starting Hub for K-CLI:
|
|
300
|
+
1. โ๏ธ Cloud APIs: Enter ANY API key (auto-detected provider, tested & saved).
|
|
301
|
+
2. ๐ป Local Models: Curated coding models with detailed Pros & Cons and 1-click download.
|
|
302
|
+
3. โก Bankai Models: Custom fine-tuned models downloaded directly from Hugging Face.
|
|
303
|
+
4. ๐ DevDocs: 100% offline documentation downloader (Python 3.12, C++23, Rust, Linux, FastAPI, Redis, Postgres).
|
|
304
|
+
5. โ๏ธ Dev Preferences: Auto-Approve permissions (Safe/YOLO/Ask), persistent sessions, and telemetry.
|
|
305
|
+
"""
|
|
306
|
+
|
|
307
|
+
DEFAULT_CSS = """
|
|
308
|
+
CodexStartingModal {
|
|
309
|
+
align: center middle;
|
|
310
|
+
background: rgba(8, 12, 24, 0.92);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
#codex-container {
|
|
314
|
+
width: 92%;
|
|
315
|
+
height: 90%;
|
|
316
|
+
background: #0d1117;
|
|
317
|
+
border: heavy #00f0ff;
|
|
318
|
+
padding: 1 2;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.codex-header-title {
|
|
322
|
+
text-align: center;
|
|
323
|
+
color: #00f0ff;
|
|
324
|
+
text-style: bold;
|
|
325
|
+
margin-bottom: 1;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.codex-header-subtitle {
|
|
329
|
+
text-align: center;
|
|
330
|
+
color: #8b949e;
|
|
331
|
+
margin-bottom: 1;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.codex-tab-pane {
|
|
335
|
+
padding: 1;
|
|
336
|
+
height: 1fr;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.codex-section-card {
|
|
340
|
+
background: #161b22;
|
|
341
|
+
border: round #30363d;
|
|
342
|
+
padding: 1 2;
|
|
343
|
+
margin-bottom: 1;
|
|
344
|
+
height: auto;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.codex-card-title {
|
|
348
|
+
color: #58a6ff;
|
|
349
|
+
text-style: bold;
|
|
350
|
+
margin-bottom: 1;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.codex-action-row {
|
|
354
|
+
height: auto;
|
|
355
|
+
align: center middle;
|
|
356
|
+
margin-top: 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
.codex-action-row Button {
|
|
360
|
+
margin: 0 1;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.badge-detected {
|
|
364
|
+
color: #7ee787;
|
|
365
|
+
text-style: bold;
|
|
366
|
+
margin-top: 1;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
#opt-codex-local-list, #opt-codex-bankai-list {
|
|
370
|
+
width: 42%;
|
|
371
|
+
height: 1fr;
|
|
372
|
+
background: #161b22;
|
|
373
|
+
border: panel #30363d;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
#log-codex-local-details, #log-codex-bankai-details {
|
|
377
|
+
width: 58%;
|
|
378
|
+
height: 1fr;
|
|
379
|
+
background: #090d13;
|
|
380
|
+
border: panel #30363d;
|
|
381
|
+
padding: 1;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
#log-codex-devdocs-log {
|
|
385
|
+
height: 1fr;
|
|
386
|
+
background: #090d13;
|
|
387
|
+
border: panel #30363d;
|
|
388
|
+
padding: 1;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
.key-status-row {
|
|
392
|
+
height: auto;
|
|
393
|
+
margin-bottom: 1;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
.key-name-lbl {
|
|
397
|
+
width: 28;
|
|
398
|
+
color: #58a6ff;
|
|
399
|
+
text-style: bold;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
.key-input-box {
|
|
403
|
+
width: 1fr;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
.key-pill-lbl {
|
|
407
|
+
width: 16;
|
|
408
|
+
text-align: center;
|
|
409
|
+
}
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
BINDINGS = [
|
|
413
|
+
Binding("escape", "dismiss(False)", "Close Codex"),
|
|
414
|
+
Binding("b", "dismiss(False)", "Back"),
|
|
415
|
+
Binding("ctrl+s", "save_and_close", "Save & Launch"),
|
|
416
|
+
]
|
|
417
|
+
|
|
418
|
+
def compose(self) -> ComposeResult:
|
|
419
|
+
with Container(id="codex-container"):
|
|
420
|
+
yield Label("๐ K-CLI MASTER CODEX & ONBOARDING WORKSTATION", classes="codex-header-title")
|
|
421
|
+
yield Label("Select your preferred AI engine, download local/Bankai models, and bootstrap 100% offline DevDocs.", classes="codex-header-subtitle")
|
|
422
|
+
|
|
423
|
+
with TabbedContent(initial="tab-cloud"):
|
|
424
|
+
# -------------------------------------------------------------
|
|
425
|
+
# Tab 1: โ๏ธ Cloud APIs (Any Key Supported)
|
|
426
|
+
# -------------------------------------------------------------
|
|
427
|
+
with TabPane("โ๏ธ Cloud APIs (Any Key)", id="tab-cloud", classes="codex-tab-pane"):
|
|
428
|
+
with VerticalScroll():
|
|
429
|
+
with Container(classes="codex-section-card"):
|
|
430
|
+
yield Label("๐ฏ Universal Single-Input Key Detector (Enter ANY API Key):", classes="codex-card-title")
|
|
431
|
+
yield Input(
|
|
432
|
+
placeholder="Paste ANY API Key here (Gemini, Claude, OpenAI, DeepSeek, Groq, Mistral, OpenRouter...)",
|
|
433
|
+
id="input-codex-universal-key",
|
|
434
|
+
password=True,
|
|
435
|
+
)
|
|
436
|
+
yield Label("๐ก Type or paste any key to auto-detect provider...", id="lbl-codex-detect-badge", classes="badge-detected")
|
|
437
|
+
with Horizontal(classes="codex-action-row"):
|
|
438
|
+
yield Button("๐พ Save & Persist Key", variant="primary", id="btn-codex-save-universal")
|
|
439
|
+
yield Button("โก Test This Key", variant="success", id="btn-codex-test-universal")
|
|
440
|
+
|
|
441
|
+
with Container(classes="codex-section-card"):
|
|
442
|
+
yield Label("๐ Configured AI Providers & Status Table:", classes="codex-card-title")
|
|
443
|
+
with VerticalScroll(id="codex-keys-list-container"):
|
|
444
|
+
for key_name, label, placeholder in SUPPORTED_KEYS:
|
|
445
|
+
val = os.environ.get(key_name, "")
|
|
446
|
+
with Horizontal(classes="key-status-row"):
|
|
447
|
+
yield Label(f"โข {label}:", classes="key-name-lbl")
|
|
448
|
+
yield Input(
|
|
449
|
+
value=val,
|
|
450
|
+
password=True,
|
|
451
|
+
placeholder=placeholder,
|
|
452
|
+
id=f"input-codex-{key_name.lower()}",
|
|
453
|
+
classes="key-input-box",
|
|
454
|
+
)
|
|
455
|
+
yield Label("Active" if val else "Missing", id=f"pill-codex-{key_name.lower()}", classes="key-pill-lbl")
|
|
456
|
+
|
|
457
|
+
with Horizontal(classes="codex-action-row"):
|
|
458
|
+
yield Button("๐พ Save All Keys", variant="primary", id="btn-codex-save-all-keys")
|
|
459
|
+
yield Button("โก Test Live Connections", variant="success", id="btn-codex-test-all-keys")
|
|
460
|
+
|
|
461
|
+
# -------------------------------------------------------------
|
|
462
|
+
# Tab 2: ๐ป Local Models (Ollama Curated Catalog)
|
|
463
|
+
# -------------------------------------------------------------
|
|
464
|
+
with TabPane("๐ป Local Models (Ollama)", id="tab-local", classes="codex-tab-pane"):
|
|
465
|
+
with Horizontal():
|
|
466
|
+
yield OptionList(id="opt-codex-local-list")
|
|
467
|
+
yield RichLog(id="log-codex-local-details", highlight=True, markup=True)
|
|
468
|
+
with Horizontal(classes="codex-action-row"):
|
|
469
|
+
yield Button("๐ฅ 1-Click Download Model", variant="success", id="btn-codex-pull-local")
|
|
470
|
+
yield Button("โก Set Active in K-CLI", variant="primary", id="btn-codex-set-active-local")
|
|
471
|
+
|
|
472
|
+
# -------------------------------------------------------------
|
|
473
|
+
# Tab 3: โก Custom Bankai Frontier Models (Hugging Face)
|
|
474
|
+
# -------------------------------------------------------------
|
|
475
|
+
with TabPane("โก Bankai Frontier Models", id="tab-bankai", classes="codex-tab-pane"):
|
|
476
|
+
with Horizontal():
|
|
477
|
+
yield OptionList(id="opt-codex-bankai-list")
|
|
478
|
+
yield RichLog(id="log-codex-bankai-details", highlight=True, markup=True)
|
|
479
|
+
with Horizontal(classes="codex-action-row"):
|
|
480
|
+
yield Button("๐ Download Bankai Adapter (HF)", variant="warning", id="btn-codex-download-bankai")
|
|
481
|
+
yield Button("โก Set Active in K-CLI", variant="primary", id="btn-codex-set-active-bankai")
|
|
482
|
+
|
|
483
|
+
# -------------------------------------------------------------
|
|
484
|
+
# Tab 4: ๐ DevDocs 100% Offline Downloader
|
|
485
|
+
# -------------------------------------------------------------
|
|
486
|
+
with TabPane("๐ DevDocs Offline", id="tab-devdocs", classes="codex-tab-pane"):
|
|
487
|
+
yield Label("100% Offline Developer Documentation SQLite FTS5 Indexer:")
|
|
488
|
+
yield RichLog(id="log-codex-devdocs-log", highlight=True, markup=True)
|
|
489
|
+
with Horizontal(classes="codex-action-row"):
|
|
490
|
+
yield Button("๐ฆ Download & Index All DevDocs", variant="success", id="btn-codex-download-all-docs")
|
|
491
|
+
yield Button("๐ Test Offline Search", variant="primary", id="btn-codex-test-docs")
|
|
492
|
+
yield Button("๐งน Clear DevDocs Cache", variant="error", id="btn-codex-clear-docs")
|
|
493
|
+
|
|
494
|
+
# -------------------------------------------------------------
|
|
495
|
+
# Tab 5: โ๏ธ Dev Preferences & Auto-Approve (Professional CLI Mode)
|
|
496
|
+
# -------------------------------------------------------------
|
|
497
|
+
with TabPane("โ๏ธ Dev Preferences", id="tab-prefs", classes="codex-tab-pane"):
|
|
498
|
+
with VerticalScroll():
|
|
499
|
+
with Container(classes="codex-section-card"):
|
|
500
|
+
yield Label("๐ก๏ธ Autonomous Permissions & Auto-Approve Gates:", classes="codex-card-title")
|
|
501
|
+
yield Label("Choose how K-CLI handles file modifications, test runs, and terminal commands:", classes="codex-header-subtitle")
|
|
502
|
+
with Horizontal(classes="codex-action-row"):
|
|
503
|
+
yield Button("๐ก๏ธ Safe Actions Only (Recommended)", variant="primary", id="btn-pref-mode-safe")
|
|
504
|
+
yield Button("โก Auto-Approve All (YOLO Mode)", variant="warning", id="btn-pref-mode-all")
|
|
505
|
+
yield Button("โ Ask Every Time", variant="default", id="btn-pref-mode-ask")
|
|
506
|
+
yield Label("Current Policy: ๐ก๏ธ Auto-Approve Safe Actions (Verification, AST checks, read operations)", id="lbl-pref-current-policy", classes="badge-detected")
|
|
507
|
+
|
|
508
|
+
with Container(classes="codex-section-card"):
|
|
509
|
+
yield Label("๐พ Persistent Storage & Workspace Data Management:", classes="codex-card-title")
|
|
510
|
+
yield Label("โข Auto-Save Multi-Turn Sessions to ~/.kcli/sessions/ [ACTIVE]\nโข Record Financial Dollar Savings and Token Telemetry [ACTIVE]\nโข Airgap Sovereign Mode [INACTIVE - Network enabled]\nโข Strict AST Verification Gate before file writes [ACTIVE]")
|
|
511
|
+
|
|
512
|
+
with Horizontal(classes="codex-action-row"):
|
|
513
|
+
yield Button("๐พ Save All Preferences", variant="primary", id="btn-codex-save-prefs")
|
|
514
|
+
yield Button("๐ Launch Cyber-Workstation", variant="success", id="btn-codex-launch-main")
|
|
515
|
+
|
|
516
|
+
with Horizontal(classes="codex-action-row"):
|
|
517
|
+
yield Button("๐ Done / Enter Workstation", variant="primary", id="btn-codex-done")
|
|
518
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-codex-close")
|
|
519
|
+
|
|
520
|
+
def on_mount(self) -> None:
|
|
521
|
+
# 1. Populate Local Models list
|
|
522
|
+
opt_local = self.query_one("#opt-codex-local-list", OptionList)
|
|
523
|
+
opt_local.clear_options()
|
|
524
|
+
for idx, m in enumerate(LOCAL_CODING_MODELS):
|
|
525
|
+
opt_local.add_option(Option(f"[{m['size']}] {m['name']}", id=m["id"]))
|
|
526
|
+
if LOCAL_CODING_MODELS:
|
|
527
|
+
self._render_local_model_details(LOCAL_CODING_MODELS[0])
|
|
528
|
+
|
|
529
|
+
# 2. Populate Bankai Models list
|
|
530
|
+
opt_bankai = self.query_one("#opt-codex-bankai-list", OptionList)
|
|
531
|
+
opt_bankai.clear_options()
|
|
532
|
+
for idx, m in enumerate(BANKAI_CUSTOM_MODELS):
|
|
533
|
+
opt_bankai.add_option(Option(f"โก {m['name']} ({m['size']})", id=m["id"]))
|
|
534
|
+
if BANKAI_CUSTOM_MODELS:
|
|
535
|
+
self._render_bankai_model_details(BANKAI_CUSTOM_MODELS[0])
|
|
536
|
+
|
|
537
|
+
# 3. Populate DevDocs status
|
|
538
|
+
devlog = self.query_one("#log-codex-devdocs-log", RichLog)
|
|
539
|
+
devlog.write("DevDocs Offline Engine: Ready.\nClick 'Download All DevDocs' to bootstrap local SQLite cache (~/.kcli/docs.db).")
|
|
540
|
+
|
|
541
|
+
# -------------------------------------------------------------------------
|
|
542
|
+
# Tab 1: Cloud API Auto-Detection & Saving
|
|
543
|
+
# -------------------------------------------------------------------------
|
|
544
|
+
@on(Input.Changed, "#input-codex-universal-key")
|
|
545
|
+
def on_universal_key_changed(self, event: Input.Changed) -> None:
|
|
546
|
+
val = event.value.strip()
|
|
547
|
+
badge = self.query_one("#lbl-codex-detect-badge", Label)
|
|
548
|
+
if not val:
|
|
549
|
+
badge.update("๐ก Type or paste any key to auto-detect provider...")
|
|
550
|
+
return
|
|
551
|
+
key_name, provider_name = detect_key_type(val)
|
|
552
|
+
badge.update(f"๐ฏ Detected: {provider_name} ({key_name})")
|
|
553
|
+
|
|
554
|
+
@on(Button.Pressed, "#btn-codex-save-universal")
|
|
555
|
+
def on_save_universal_key(self) -> None:
|
|
556
|
+
inp = self.query_one("#input-codex-universal-key", Input)
|
|
557
|
+
val = inp.value.strip()
|
|
558
|
+
if not val:
|
|
559
|
+
self.app.notify("Please paste an API key first.", title="Key Empty", severity="warning")
|
|
560
|
+
return
|
|
561
|
+
key_name, provider_name = CredentialsManager.save_any_key(val)
|
|
562
|
+
self.app.notify(f"Saved {provider_name} ({key_name}) to ~/.kcli/credentials.env!", title="Key Saved", severity="information")
|
|
563
|
+
# Update specific input if exists
|
|
564
|
+
try:
|
|
565
|
+
inp_spec = self.query_one(f"#input-key-{key_name.lower()}", Input)
|
|
566
|
+
inp_spec.value = val
|
|
567
|
+
pill = self.query_one(f"#pill-key-{key_name.lower()}", Label)
|
|
568
|
+
pill.update("๐ข Active")
|
|
569
|
+
except Exception:
|
|
570
|
+
pass
|
|
571
|
+
|
|
572
|
+
@on(Button.Pressed, "#btn-codex-test-universal")
|
|
573
|
+
def on_test_universal_key(self) -> None:
|
|
574
|
+
inp = self.query_one("#input-codex-universal-key", Input)
|
|
575
|
+
val = inp.value.strip()
|
|
576
|
+
if not val:
|
|
577
|
+
self.app.notify("Please paste an API key to test.", title="Key Empty", severity="warning")
|
|
578
|
+
return
|
|
579
|
+
key_name, provider_name = detect_key_type(val)
|
|
580
|
+
os.environ[key_name] = val
|
|
581
|
+
self.app.notify(f"Testing connectivity to {provider_name}...", title="Testing", severity="information")
|
|
582
|
+
|
|
583
|
+
def _test_work():
|
|
584
|
+
ok, msg = CredentialsManager.test_key_connectivity(key_name)
|
|
585
|
+
def update():
|
|
586
|
+
if ok:
|
|
587
|
+
self.app.notify(f"โ {provider_name}: {msg}", title="Connection Verified", severity="information")
|
|
588
|
+
else:
|
|
589
|
+
self.app.notify(f"โ {provider_name}: {msg}", title="Connection Failed", severity="error")
|
|
590
|
+
self.app.call_from_thread(update)
|
|
591
|
+
|
|
592
|
+
self.run_worker(_test_work, thread=True)
|
|
593
|
+
|
|
594
|
+
@on(Button.Pressed, "#btn-codex-save-all-keys")
|
|
595
|
+
def on_save_all_keys(self) -> None:
|
|
596
|
+
for key_name, _, _ in SUPPORTED_KEYS:
|
|
597
|
+
try:
|
|
598
|
+
inp = self.query_one(f"#input-key-{key_name.lower()}", Input)
|
|
599
|
+
val = inp.value.strip()
|
|
600
|
+
if val:
|
|
601
|
+
CredentialsManager.set_key(key_name, val)
|
|
602
|
+
pill = self.query_one(f"#pill-key-{key_name.lower()}", Label)
|
|
603
|
+
pill.update("๐ข Active")
|
|
604
|
+
except Exception:
|
|
605
|
+
pass
|
|
606
|
+
self.app.notify("All provider credentials saved securely!", title="Credentials Vault Saved", severity="information")
|
|
607
|
+
|
|
608
|
+
@on(Button.Pressed, "#btn-codex-test-all-keys")
|
|
609
|
+
def on_test_all_keys(self) -> None:
|
|
610
|
+
self.app.notify("Testing all configured API connections...", title="Testing Keys", severity="information")
|
|
611
|
+
def _test_all():
|
|
612
|
+
for key_name, _, _ in SUPPORTED_KEYS:
|
|
613
|
+
try:
|
|
614
|
+
ok, msg = CredentialsManager.test_key_connectivity(key_name)
|
|
615
|
+
def update(k=key_name, success=ok):
|
|
616
|
+
try:
|
|
617
|
+
pill = self.query_one(f"#pill-key-{k.lower()}", Label)
|
|
618
|
+
pill.update("๐ข Connected" if success else "๐ด Offline")
|
|
619
|
+
except Exception:
|
|
620
|
+
pass
|
|
621
|
+
self.app.call_from_thread(update)
|
|
622
|
+
except Exception:
|
|
623
|
+
pass
|
|
624
|
+
self.app.call_from_thread(lambda: self.app.notify("Provider connectivity tests complete.", title="Live Tests Completed", severity="information"))
|
|
625
|
+
|
|
626
|
+
self.run_worker(_test_all, thread=True)
|
|
627
|
+
|
|
628
|
+
# -------------------------------------------------------------------------
|
|
629
|
+
# Tab 2: Local Models Selection & Download
|
|
630
|
+
# -------------------------------------------------------------------------
|
|
631
|
+
@on(OptionList.OptionHighlighted, "#opt-codex-local-list")
|
|
632
|
+
def on_local_model_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
633
|
+
if event.option_id:
|
|
634
|
+
for m in LOCAL_CODING_MODELS:
|
|
635
|
+
if m["id"] == event.option_id:
|
|
636
|
+
self._render_local_model_details(m)
|
|
637
|
+
break
|
|
638
|
+
|
|
639
|
+
def _render_local_model_details(self, model_dict: Dict[str, Any]) -> None:
|
|
640
|
+
log = self.query_one("#log-codex-local-details", RichLog)
|
|
641
|
+
log.clear()
|
|
642
|
+
log.write(f"[bold cyan]Model:[/bold cyan] {model_dict['name']}")
|
|
643
|
+
log.write(f"[bold green]Size:[/bold green] {model_dict['size']} | [bold yellow]RAM / VRAM:[/bold yellow] {model_dict['ram']}")
|
|
644
|
+
log.write(f"[bold magenta]Context Window:[/bold magenta] {model_dict['context']} | [bold blue]Speed:[/bold blue] {model_dict['speed']}")
|
|
645
|
+
log.write(f"[bold]Ollama Tag:[/bold] `{model_dict['ollama_tag']}` | [bold]Hugging Face:[/bold] `{model_dict['hf_repo']}`")
|
|
646
|
+
log.write("\n[bold green]โ
PROS FOR CODING:[/bold green]")
|
|
647
|
+
for pro in model_dict.get("pros", []):
|
|
648
|
+
log.write(f" {pro}")
|
|
649
|
+
log.write("\n[bold red]โ ๏ธ CONS & LIMITATIONS:[/bold red]")
|
|
650
|
+
for con in model_dict.get("cons", []):
|
|
651
|
+
log.write(f" {con}")
|
|
652
|
+
|
|
653
|
+
@on(Button.Pressed, "#btn-codex-pull-local")
|
|
654
|
+
def on_download_local_model(self) -> None:
|
|
655
|
+
opt = self.query_one("#opt-codex-local-list", OptionList)
|
|
656
|
+
selected_id = opt.get_option_at_index(opt.highlighted).id if opt.highlighted is not None else "qwen2.5-coder:7b"
|
|
657
|
+
self.app.notify(f"Pulling {selected_id} weights via Ollama...", title="Model Download", severity="information")
|
|
658
|
+
def _pull_work():
|
|
659
|
+
mgr = ModelManager()
|
|
660
|
+
ok, msg = mgr.pull_ollama_tag(selected_id)
|
|
661
|
+
def update():
|
|
662
|
+
if ok:
|
|
663
|
+
self.app.notify(f"โ Successfully pulled {selected_id}!", title="Download Succeeded", severity="information")
|
|
664
|
+
else:
|
|
665
|
+
self.app.notify(f"Download status: {msg}", title="Download Update", severity="warning")
|
|
666
|
+
self.app.call_from_thread(update)
|
|
667
|
+
self.run_worker(_pull_work, thread=True)
|
|
668
|
+
|
|
669
|
+
@on(Button.Pressed, "#btn-codex-set-active-local")
|
|
670
|
+
def on_set_active_local(self) -> None:
|
|
671
|
+
opt = self.query_one("#opt-codex-local-list", OptionList)
|
|
672
|
+
selected_id = opt.get_option_at_index(opt.highlighted).id if opt.highlighted is not None else "qwen2.5-coder:1.5b"
|
|
673
|
+
DevPreferencesManager.set_default_model(selected_id)
|
|
674
|
+
self.app.model_name = selected_id
|
|
675
|
+
try:
|
|
676
|
+
self.app.query_one("#hud-model", Label).update(f"๐ค {selected_id}")
|
|
677
|
+
except Exception:
|
|
678
|
+
pass
|
|
679
|
+
self.app.notify(f"Active model switched to local '{selected_id}'!", title="Model Activated", severity="information")
|
|
680
|
+
|
|
681
|
+
@on(Button.Pressed, "#btn-codex-bench-local")
|
|
682
|
+
def on_bench_local(self) -> None:
|
|
683
|
+
res = ModelHub().benchmark_model("qwen2.5-coder:1.5b", driver=LLMDriver(mock_mode=True))
|
|
684
|
+
log = self.query_one("#log-codex-local-details", RichLog)
|
|
685
|
+
log.write(f"\n[bold green]๐๏ธ BENCHMARK RESULTS:[/bold green]\nโข Throughput: {res.tokens_per_second:.1f} tok/s\nโข TTFT: {res.time_to_first_token:.3f}s\nโข RAM: {res.ram_rss_mb:.1f}MB")
|
|
686
|
+
self.app.notify(f"Benchmark: {res.tokens_per_second:.1f} tok/s", title="Benchmark Done", severity="information")
|
|
687
|
+
|
|
688
|
+
# -------------------------------------------------------------------------
|
|
689
|
+
# Tab 3: Bankai Models (Hugging Face)
|
|
690
|
+
# -------------------------------------------------------------------------
|
|
691
|
+
@on(OptionList.OptionHighlighted, "#opt-codex-bankai-list")
|
|
692
|
+
def on_bankai_model_highlighted(self, event: OptionList.OptionHighlighted) -> None:
|
|
693
|
+
if event.option_id:
|
|
694
|
+
for m in BANKAI_CUSTOM_MODELS:
|
|
695
|
+
if m["id"] == event.option_id:
|
|
696
|
+
self._render_bankai_model_details(m)
|
|
697
|
+
break
|
|
698
|
+
|
|
699
|
+
def _render_bankai_model_details(self, model_dict: Dict[str, Any]) -> None:
|
|
700
|
+
log = self.query_one("#log-codex-bankai-details", RichLog)
|
|
701
|
+
log.clear()
|
|
702
|
+
log.write(f"[bold cyan]โก {model_dict['name']}[/bold cyan]")
|
|
703
|
+
log.write(f"[bold green]Size:[/bold green] {model_dict['size']} | [bold yellow]RAM Budget:[/bold yellow] {model_dict['ram']}")
|
|
704
|
+
log.write(f"[bold magenta]Hugging Face Repo:[/bold magenta] `https://huggingface.co/{model_dict['repo_id']}`")
|
|
705
|
+
log.write(f"[bold]Ollama Tag:[/bold] `{model_dict['ollama_tag']}`")
|
|
706
|
+
log.write(f"\n[bold]Description:[/bold]\n{model_dict['description']}")
|
|
707
|
+
|
|
708
|
+
@on(Button.Pressed, "#btn-codex-download-bankai")
|
|
709
|
+
def on_download_bankai_model(self) -> None:
|
|
710
|
+
opt = self.query_one("#opt-codex-bankai-list", OptionList)
|
|
711
|
+
selected_id = opt.get_option_at_index(opt.highlighted).id if opt.highlighted is not None else "bankai-7b"
|
|
712
|
+
self.app.notify(f"Downloading {selected_id} from Hugging Face Hub with SHA256 verification...", title="Bankai Model Download", severity="information")
|
|
713
|
+
def _bankai_work():
|
|
714
|
+
mgr = ModelManager()
|
|
715
|
+
res = mgr.pull_model(model_identifier=selected_id, force=False, verify_sha=True, create_in_ollama=True)
|
|
716
|
+
def update():
|
|
717
|
+
log = self.query_one("#log-codex-bankai-details", RichLog)
|
|
718
|
+
log.write(f"\n[bold green]โ DOWNLOAD COMPLETED:[/bold green]\nโข Model: {res.model_name}\nโข GGUF: {res.gguf_path}\nโข SHA256 Verified: {res.sha256_verified}\nโข Ollama Tag: {res.ollama_tag}")
|
|
719
|
+
self.app.notify(f"โ {selected_id} successfully staged and ready!", title="Bankai Model Ready", severity="information")
|
|
720
|
+
self.app.call_from_thread(update)
|
|
721
|
+
self.run_worker(_bankai_work, thread=True)
|
|
722
|
+
|
|
723
|
+
@on(Button.Pressed, "#btn-codex-download-custom-hf")
|
|
724
|
+
def on_download_custom_hf(self) -> None:
|
|
725
|
+
inp = self.query_one("#input-codex-custom-hf", Input)
|
|
726
|
+
repo_id = inp.value.strip()
|
|
727
|
+
if not repo_id:
|
|
728
|
+
self.app.notify("Please enter a Hugging Face repo name.", title="Repo Empty", severity="warning")
|
|
729
|
+
return
|
|
730
|
+
self.app.notify(f"Connecting to Hugging Face Hub: {repo_id}...", title="HF Download", severity="information")
|
|
731
|
+
def _hf_work():
|
|
732
|
+
mgr = ModelManager()
|
|
733
|
+
res = mgr.pull_model(model_identifier=repo_id, force=False, verify_sha=True, create_in_ollama=True)
|
|
734
|
+
self.app.call_from_thread(lambda: self.app.notify(f"Custom model '{repo_id}' downloaded successfully!", title="HF Model Downloaded", severity="information"))
|
|
735
|
+
self.run_worker(_hf_work, thread=True)
|
|
736
|
+
|
|
737
|
+
# -------------------------------------------------------------------------
|
|
738
|
+
# Tab 4: DevDocs Offline Downloader
|
|
739
|
+
# -------------------------------------------------------------------------
|
|
740
|
+
@on(Button.Pressed, "#btn-codex-download-all-docs")
|
|
741
|
+
def on_download_all_devdocs(self) -> None:
|
|
742
|
+
log = self.query_one("#log-codex-devdocs-log", RichLog)
|
|
743
|
+
log.write("\n[bold cyan]๐ฆ Starting Full DevDocs Offline Indexing Suite...[/bold cyan]")
|
|
744
|
+
self.app.notify("Indexing standard libraries & frameworks into local SQLite...", title="DevDocs Downloader", severity="information")
|
|
745
|
+
def _docs_work():
|
|
746
|
+
doc = DocRetriever()
|
|
747
|
+
res = doc.download_all_devdocs()
|
|
748
|
+
def update():
|
|
749
|
+
log.write(f"[bold green]โ DevDocs Indexing Completed in {res['duration_seconds']}s![/bold green]")
|
|
750
|
+
log.write(f"โข Total Symbols in SQLite: {res['total_database_symbols']}")
|
|
751
|
+
log.write(f"โข Database Path: {res['db_path']}")
|
|
752
|
+
log.write(f"โข Indexed Packages: Python 3.12, C++23, Rust 1.80, Linux Syscalls, FastAPI, Redis, PostgreSQL")
|
|
753
|
+
self.app.notify(f"โ Indexed {res['total_database_symbols']} DevDocs symbols for 100% offline search!", title="DevDocs Ready", severity="information")
|
|
754
|
+
self.app.call_from_thread(update)
|
|
755
|
+
self.run_worker(_docs_work, thread=True)
|
|
756
|
+
|
|
757
|
+
@on(Button.Pressed, "#btn-codex-test-docs")
|
|
758
|
+
def on_test_devdocs_search(self) -> None:
|
|
759
|
+
def _search_work():
|
|
760
|
+
doc = DocRetriever()
|
|
761
|
+
hits = doc.search("asyncio Queue TaskGroup FastAPI Depends", limit=3)
|
|
762
|
+
def update():
|
|
763
|
+
log = self.query_one("#log-codex-devdocs-log", RichLog)
|
|
764
|
+
log.write("\n[bold cyan]๐ Test Search Results ('asyncio Queue TaskGroup'):[/bold cyan]")
|
|
765
|
+
for h in hits:
|
|
766
|
+
log.write(f"โข [bold green]{h.get('signature')}[/bold green]: {h.get('doc')[:90]}...")
|
|
767
|
+
self.app.notify(f"Found {len(hits)} offline documentation matches in <1ms!", title="Search Succeeded", severity="information")
|
|
768
|
+
self.app.call_from_thread(update)
|
|
769
|
+
self.run_worker(_search_work, thread=True)
|
|
770
|
+
|
|
771
|
+
@on(Button.Pressed, "#btn-codex-clear-docs")
|
|
772
|
+
def on_clear_devdocs(self) -> None:
|
|
773
|
+
doc = DocRetriever()
|
|
774
|
+
doc.clear_cache()
|
|
775
|
+
log = self.query_one("#log-codex-devdocs-log", RichLog)
|
|
776
|
+
log.write("\n[yellow]DevDocs cache cleared.[/yellow]")
|
|
777
|
+
self.app.notify("DevDocs cache cleared.", title="Cache Cleared", severity="information")
|
|
778
|
+
|
|
779
|
+
# -------------------------------------------------------------------------
|
|
780
|
+
# Tab 5: Preferences & Auto-Approve
|
|
781
|
+
# -------------------------------------------------------------------------
|
|
782
|
+
@on(Button.Pressed, "#btn-pref-mode-safe")
|
|
783
|
+
def on_pref_safe(self) -> None:
|
|
784
|
+
DevPreferencesManager.set("auto_approve_mode", "safe")
|
|
785
|
+
lbl = self.query_one("#lbl-pref-current-policy", Label)
|
|
786
|
+
lbl.update("Current Policy: ๐ก๏ธ Auto-Approve Safe Actions (Verification, AST checks, read operations)")
|
|
787
|
+
self.app.notify("Auto-Approve set to 'Safe Actions Only'.", title="Preferences Updated", severity="information")
|
|
788
|
+
|
|
789
|
+
@on(Button.Pressed, "#btn-pref-mode-all")
|
|
790
|
+
def on_pref_all(self) -> None:
|
|
791
|
+
DevPreferencesManager.set("auto_approve_mode", "all")
|
|
792
|
+
lbl = self.query_one("#lbl-pref-current-policy", Label)
|
|
793
|
+
lbl.update("Current Policy: โก Auto-Approve All (YOLO Mode - All tool executions permitted)")
|
|
794
|
+
self.app.notify("Auto-Approve set to 'YOLO Mode (All Actions)'.", title="Preferences Updated", severity="warning")
|
|
795
|
+
|
|
796
|
+
@on(Button.Pressed, "#btn-pref-mode-ask")
|
|
797
|
+
def on_pref_ask(self) -> None:
|
|
798
|
+
DevPreferencesManager.set("auto_approve_mode", "ask")
|
|
799
|
+
lbl = self.query_one("#lbl-pref-current-policy", Label)
|
|
800
|
+
lbl.update("Current Policy: โ Ask Every Time (Interactive confirmation prompt on every action)")
|
|
801
|
+
self.app.notify("Auto-Approve set to 'Ask Every Time'.", title="Preferences Updated", severity="information")
|
|
802
|
+
|
|
803
|
+
@on(Button.Pressed, "#btn-codex-save-prefs")
|
|
804
|
+
def on_save_prefs(self) -> None:
|
|
805
|
+
self.app.notify("All developer preferences securely persisted to ~/.kcli/config.json!", title="Preferences Saved", severity="information")
|
|
806
|
+
|
|
807
|
+
@on(Button.Pressed, "#btn-codex-launch-main")
|
|
808
|
+
@on(Button.Pressed, "#btn-codex-done")
|
|
809
|
+
def on_done(self) -> None:
|
|
810
|
+
self.dismiss(True)
|
|
811
|
+
|
|
812
|
+
@on(Button.Pressed, "#btn-codex-close")
|
|
813
|
+
def on_close_codex(self) -> None:
|
|
814
|
+
self.dismiss(False)
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
# =============================================================================
|
|
818
|
+
# 1. Credentials Vault Modal (Ctrl+A)
|
|
819
|
+
# =============================================================================
|
|
820
|
+
|
|
821
|
+
class CredentialsVaultModal(ModalScreen[bool]):
|
|
822
|
+
"""All-in-One API Key & Provider Setup Modal with 1-Click Live Test."""
|
|
823
|
+
|
|
824
|
+
DEFAULT_CSS = """
|
|
825
|
+
CredentialsVaultModal {
|
|
826
|
+
align: center middle;
|
|
827
|
+
background: rgba(10, 15, 30, 0.85);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
#vault-container {
|
|
831
|
+
width: 85%;
|
|
832
|
+
height: 85%;
|
|
833
|
+
background: #0d1117;
|
|
834
|
+
border: heavy #00f0ff;
|
|
835
|
+
padding: 1 2;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.vault-title {
|
|
839
|
+
text-align: center;
|
|
840
|
+
color: #00f0ff;
|
|
841
|
+
text-style: bold;
|
|
842
|
+
margin-bottom: 1;
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
.vault-desc {
|
|
846
|
+
text-align: center;
|
|
847
|
+
color: #8b949e;
|
|
848
|
+
margin-bottom: 1;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.key-row {
|
|
852
|
+
height: auto;
|
|
853
|
+
margin-bottom: 1;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.key-label {
|
|
857
|
+
width: 25;
|
|
858
|
+
color: #58a6ff;
|
|
859
|
+
text-style: bold;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
.key-input {
|
|
863
|
+
width: 1fr;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.status-pill {
|
|
867
|
+
width: 16;
|
|
868
|
+
text-align: center;
|
|
869
|
+
color: #7ee787;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
#vault-actions {
|
|
873
|
+
margin-top: 1;
|
|
874
|
+
height: auto;
|
|
875
|
+
align: center middle;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
#vault-actions Button {
|
|
879
|
+
margin: 0 1;
|
|
880
|
+
}
|
|
881
|
+
"""
|
|
882
|
+
|
|
883
|
+
BINDINGS = [
|
|
884
|
+
Binding("escape", "dismiss(False)", "Close"),
|
|
885
|
+
Binding("b", "dismiss(False)", "Back"),
|
|
886
|
+
Binding("ctrl+s", "save_keys", "Save & Test"),
|
|
887
|
+
]
|
|
888
|
+
|
|
889
|
+
def compose(self) -> ComposeResult:
|
|
890
|
+
with Container(id="vault-container"):
|
|
891
|
+
yield Label("๐ K-CLI Universal Credentials & Provider Vault", classes="vault-title")
|
|
892
|
+
yield Label("Enter your API credentials below. Keys are stored locally and tested instantly.", classes="vault-desc")
|
|
893
|
+
|
|
894
|
+
with VerticalScroll(id="vault-scroll"):
|
|
895
|
+
# GitHub Token
|
|
896
|
+
with Horizontal(classes="key-row"):
|
|
897
|
+
yield Label("๐ GitHub PAT Token:", classes="key-label")
|
|
898
|
+
yield Input(
|
|
899
|
+
value=os.environ.get("GITHUB_TOKEN", ""),
|
|
900
|
+
password=True,
|
|
901
|
+
placeholder="ghp_xxxxxxxxxxxxxxxxxxxx",
|
|
902
|
+
id="input-github",
|
|
903
|
+
classes="key-input",
|
|
904
|
+
)
|
|
905
|
+
yield Label(self._get_status_label("GITHUB_TOKEN"), id="pill-github", classes="status-pill")
|
|
906
|
+
|
|
907
|
+
# Google Gemini
|
|
908
|
+
with Horizontal(classes="key-row"):
|
|
909
|
+
yield Label("๐ Google Gemini API Key:", classes="key-label")
|
|
910
|
+
yield Input(
|
|
911
|
+
value=os.environ.get("GEMINI_API_KEY", ""),
|
|
912
|
+
password=True,
|
|
913
|
+
placeholder="AIzaSyxxxxxxxxxxxxxxxxxxxx",
|
|
914
|
+
id="input-gemini",
|
|
915
|
+
classes="key-input",
|
|
916
|
+
)
|
|
917
|
+
yield Label(self._get_status_label("GEMINI_API_KEY"), id="pill-gemini", classes="status-pill")
|
|
918
|
+
|
|
919
|
+
# Anthropic Claude
|
|
920
|
+
with Horizontal(classes="key-row"):
|
|
921
|
+
yield Label("๐ง Anthropic Claude API Key:", classes="key-label")
|
|
922
|
+
yield Input(
|
|
923
|
+
value=os.environ.get("ANTHROPIC_API_KEY", ""),
|
|
924
|
+
password=True,
|
|
925
|
+
placeholder="sk-ant-xxxxxxxxx",
|
|
926
|
+
id="input-anthropic",
|
|
927
|
+
classes="key-input",
|
|
928
|
+
)
|
|
929
|
+
yield Label(self._get_status_label("ANTHROPIC_API_KEY"), id="pill-anthropic", classes="status-pill")
|
|
930
|
+
|
|
931
|
+
# OpenAI
|
|
932
|
+
with Horizontal(classes="key-row"):
|
|
933
|
+
yield Label("โก OpenAI API Key:", classes="key-label")
|
|
934
|
+
yield Input(
|
|
935
|
+
value=os.environ.get("OPENAI_API_KEY", ""),
|
|
936
|
+
password=True,
|
|
937
|
+
placeholder="sk-proj-xxxxxxxxx",
|
|
938
|
+
id="input-openai",
|
|
939
|
+
classes="key-input",
|
|
940
|
+
)
|
|
941
|
+
yield Label(self._get_status_label("OPENAI_API_KEY"), id="pill-openai", classes="status-pill")
|
|
942
|
+
|
|
943
|
+
# DeepSeek
|
|
944
|
+
with Horizontal(classes="key-row"):
|
|
945
|
+
yield Label("๐ DeepSeek API Key:", classes="key-label")
|
|
946
|
+
yield Input(
|
|
947
|
+
value=os.environ.get("DEEPSEEK_API_KEY", ""),
|
|
948
|
+
password=True,
|
|
949
|
+
placeholder="sk-xxxxxxxxx",
|
|
950
|
+
id="input-deepseek",
|
|
951
|
+
classes="key-input",
|
|
952
|
+
)
|
|
953
|
+
yield Label(self._get_status_label("DEEPSEEK_API_KEY"), id="pill-deepseek", classes="status-pill")
|
|
954
|
+
|
|
955
|
+
# Groq
|
|
956
|
+
with Horizontal(classes="key-row"):
|
|
957
|
+
yield Label("โก Groq Fast API Key:", classes="key-label")
|
|
958
|
+
yield Input(
|
|
959
|
+
value=os.environ.get("GROQ_API_KEY", ""),
|
|
960
|
+
password=True,
|
|
961
|
+
placeholder="gsk_xxxxxxxxxxxxxxxxxxxx",
|
|
962
|
+
id="input-groq",
|
|
963
|
+
classes="key-input",
|
|
964
|
+
)
|
|
965
|
+
yield Label(self._get_status_label("GROQ_API_KEY"), id="pill-groq", classes="status-pill")
|
|
966
|
+
|
|
967
|
+
# Mistral
|
|
968
|
+
with Horizontal(classes="key-row"):
|
|
969
|
+
yield Label("๐ช๏ธ Mistral API Key:", classes="key-label")
|
|
970
|
+
yield Input(
|
|
971
|
+
value=os.environ.get("MISTRAL_API_KEY", ""),
|
|
972
|
+
password=True,
|
|
973
|
+
placeholder="xxxxxxxxxxxxxxxxxxxx",
|
|
974
|
+
id="input-mistral",
|
|
975
|
+
classes="key-input",
|
|
976
|
+
)
|
|
977
|
+
yield Label(self._get_status_label("MISTRAL_API_KEY"), id="pill-mistral", classes="status-pill")
|
|
978
|
+
|
|
979
|
+
# OpenRouter
|
|
980
|
+
with Horizontal(classes="key-row"):
|
|
981
|
+
yield Label("๐ OpenRouter API Key:", classes="key-label")
|
|
982
|
+
yield Input(
|
|
983
|
+
value=os.environ.get("OPENROUTER_API_KEY", ""),
|
|
984
|
+
password=True,
|
|
985
|
+
placeholder="sk-or-xxxxxxxxx",
|
|
986
|
+
id="input-openrouter",
|
|
987
|
+
classes="key-input",
|
|
988
|
+
)
|
|
989
|
+
yield Label(self._get_status_label("OPENROUTER_API_KEY"), id="pill-openrouter", classes="status-pill")
|
|
990
|
+
|
|
991
|
+
# Ollama URL
|
|
992
|
+
with Horizontal(classes="key-row"):
|
|
993
|
+
yield Label("๐ฆ Local Ollama URL:", classes="key-label")
|
|
994
|
+
yield Input(
|
|
995
|
+
value=os.environ.get("OLLAMA_URL", "http://localhost:11434"),
|
|
996
|
+
placeholder="http://localhost:11434",
|
|
997
|
+
id="input-ollama",
|
|
998
|
+
classes="key-input",
|
|
999
|
+
)
|
|
1000
|
+
yield Label("Local Ready", id="pill-ollama", classes="status-pill")
|
|
1001
|
+
|
|
1002
|
+
with Horizontal(id="vault-actions"):
|
|
1003
|
+
yield Button("๐พ Save & Apply All", variant="primary", id="btn-vault-save")
|
|
1004
|
+
yield Button("โก Test Connections", variant="success", id="btn-vault-test")
|
|
1005
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-vault-cancel")
|
|
1006
|
+
|
|
1007
|
+
def _get_status_label(self, env_var: str) -> str:
|
|
1008
|
+
val = os.environ.get(env_var)
|
|
1009
|
+
return "โ Active" if val else "โ Missing"
|
|
1010
|
+
|
|
1011
|
+
@on(Button.Pressed, "#btn-vault-save")
|
|
1012
|
+
def action_save_keys(self) -> None:
|
|
1013
|
+
mapping = {
|
|
1014
|
+
"GITHUB_TOKEN": self.query_one("#input-github", Input).value.strip(),
|
|
1015
|
+
"GEMINI_API_KEY": self.query_one("#input-gemini", Input).value.strip(),
|
|
1016
|
+
"ANTHROPIC_API_KEY": self.query_one("#input-anthropic", Input).value.strip(),
|
|
1017
|
+
"OPENAI_API_KEY": self.query_one("#input-openai", Input).value.strip(),
|
|
1018
|
+
"DEEPSEEK_API_KEY": self.query_one("#input-deepseek", Input).value.strip(),
|
|
1019
|
+
"GROQ_API_KEY": self.query_one("#input-groq", Input).value.strip(),
|
|
1020
|
+
"MISTRAL_API_KEY": self.query_one("#input-mistral", Input).value.strip(),
|
|
1021
|
+
"OPENROUTER_API_KEY": self.query_one("#input-openrouter", Input).value.strip(),
|
|
1022
|
+
"OLLAMA_URL": self.query_one("#input-ollama", Input).value.strip(),
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
for k, v in mapping.items():
|
|
1026
|
+
if v:
|
|
1027
|
+
CredentialsManager.set_key(k, v)
|
|
1028
|
+
|
|
1029
|
+
self.app.notify("Credentials securely saved and applied!", title="Vault Saved", severity="information")
|
|
1030
|
+
self.dismiss(True)
|
|
1031
|
+
|
|
1032
|
+
@on(Button.Pressed, "#btn-vault-test")
|
|
1033
|
+
def action_test_connections(self) -> None:
|
|
1034
|
+
for p in (ModelProvider.GEMINI, ModelProvider.ANTHROPIC, ModelProvider.OPENAI, ModelProvider.DEEPSEEK, ModelProvider.GROQ, ModelProvider.OLLAMA):
|
|
1035
|
+
is_ok = ModelHub().is_provider_configured(p)
|
|
1036
|
+
pill_id = f"#pill-{p.value}"
|
|
1037
|
+
try:
|
|
1038
|
+
pill = self.query_one(pill_id, Label)
|
|
1039
|
+
pill.update("๐ข Connected" if is_ok else "๐ด Offline")
|
|
1040
|
+
except Exception:
|
|
1041
|
+
pass
|
|
1042
|
+
self.app.notify("Provider connectivity tests complete.", title="Connections Tested", severity="information")
|
|
1043
|
+
|
|
1044
|
+
@on(Button.Pressed, "#btn-vault-cancel")
|
|
1045
|
+
def action_cancel(self) -> None:
|
|
1046
|
+
self.dismiss(False)
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
# =============================================================================
|
|
1050
|
+
# 2. Conflict Studio Modal (Ctrl+K)
|
|
1051
|
+
# =============================================================================
|
|
1052
|
+
|
|
1053
|
+
class ConflictStudioModal(ModalScreen[None]):
|
|
1054
|
+
"""4-Way Visual Git Merge Conflict Studio Modal."""
|
|
1055
|
+
|
|
1056
|
+
DEFAULT_CSS = """
|
|
1057
|
+
ConflictStudioModal {
|
|
1058
|
+
align: center middle;
|
|
1059
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
#conflict-box {
|
|
1063
|
+
width: 90%;
|
|
1064
|
+
height: 90%;
|
|
1065
|
+
background: #0d1117;
|
|
1066
|
+
border: heavy #00f0ff;
|
|
1067
|
+
padding: 1;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
#conflict-header {
|
|
1071
|
+
height: 3;
|
|
1072
|
+
background: #161b22;
|
|
1073
|
+
padding: 0 1;
|
|
1074
|
+
border-bottom: solid #30363d;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
#conflict-grid {
|
|
1078
|
+
height: 1fr;
|
|
1079
|
+
grid-size: 2 2;
|
|
1080
|
+
grid-gutter: 1;
|
|
1081
|
+
padding: 1;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
.conflict-pane {
|
|
1085
|
+
background: #161b22;
|
|
1086
|
+
border: panel #30363d;
|
|
1087
|
+
padding: 1;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
#conflict-actions {
|
|
1091
|
+
height: 3;
|
|
1092
|
+
background: #161b22;
|
|
1093
|
+
align: center middle;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
#conflict-actions Button {
|
|
1097
|
+
margin: 0 1;
|
|
1098
|
+
}
|
|
1099
|
+
"""
|
|
1100
|
+
|
|
1101
|
+
BINDINGS = [
|
|
1102
|
+
Binding("escape", "dismiss", "Close"),
|
|
1103
|
+
Binding("b", "dismiss", "Back"),
|
|
1104
|
+
]
|
|
1105
|
+
|
|
1106
|
+
def compose(self) -> ComposeResult:
|
|
1107
|
+
with Container(id="conflict-box"):
|
|
1108
|
+
with Horizontal(id="conflict-header"):
|
|
1109
|
+
yield Label("โ๏ธ 3-Way AST Conflict Studio โ AI Semantic Merge", id="lbl-c-title")
|
|
1110
|
+
yield Label("Scanning...", id="lbl-c-status")
|
|
1111
|
+
|
|
1112
|
+
with Grid(id="conflict-grid"):
|
|
1113
|
+
with Container(classes="conflict-pane"):
|
|
1114
|
+
yield Label("๐ต Ours (HEAD / Current Branch)")
|
|
1115
|
+
yield RichLog(id="log-c-ours", highlight=True)
|
|
1116
|
+
|
|
1117
|
+
with Container(classes="conflict-pane"):
|
|
1118
|
+
yield Label("โช Base (Common Ancestor)")
|
|
1119
|
+
yield RichLog(id="log-c-base", highlight=True)
|
|
1120
|
+
|
|
1121
|
+
with Container(classes="conflict-pane"):
|
|
1122
|
+
yield Label("๐ฃ Theirs (Incoming Branch)")
|
|
1123
|
+
yield RichLog(id="log-c-theirs", highlight=True)
|
|
1124
|
+
|
|
1125
|
+
with Container(classes="conflict-pane"):
|
|
1126
|
+
yield Label("๐ข AI Synthesized Merge (AST Verified)")
|
|
1127
|
+
yield RichLog(id="log-c-ai", highlight=True)
|
|
1128
|
+
|
|
1129
|
+
with Horizontal(id="conflict-actions"):
|
|
1130
|
+
yield Button("โ๏ธ Auto-Resolve All with AI", variant="primary", id="btn-c-resolve")
|
|
1131
|
+
yield Button("โ
Accept & Stage Merge", variant="success", id="btn-c-accept")
|
|
1132
|
+
yield Button("๐ก๏ธ Run AST Verifier", variant="warning", id="btn-c-verify")
|
|
1133
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-c-close")
|
|
1134
|
+
|
|
1135
|
+
def on_mount(self) -> None:
|
|
1136
|
+
resolver = ConflictResolver()
|
|
1137
|
+
conflicts = resolver.find_conflicts()
|
|
1138
|
+
lbl = self.query_one("#lbl-c-status", Label)
|
|
1139
|
+
if not conflicts:
|
|
1140
|
+
lbl.update("โจ Zero active merge conflicts.")
|
|
1141
|
+
self.query_one("#log-c-ours", RichLog).write("Workspace is clean.")
|
|
1142
|
+
else:
|
|
1143
|
+
lbl.update(f"โ ๏ธ {len(conflicts)} conflict(s) detected.")
|
|
1144
|
+
first = conflicts[0]
|
|
1145
|
+
self.query_one("#log-c-ours", RichLog).write(first.ours_content or "")
|
|
1146
|
+
self.query_one("#log-c-base", RichLog).write(first.base_content or "No diff3 ancestor")
|
|
1147
|
+
self.query_one("#log-c-theirs", RichLog).write(first.theirs_content or "")
|
|
1148
|
+
self.query_one("#log-c-ai", RichLog).write("Click 'Auto-Resolve All with AI' to synthesize merge.")
|
|
1149
|
+
|
|
1150
|
+
@on(Button.Pressed, "#btn-c-resolve")
|
|
1151
|
+
def on_resolve(self) -> None:
|
|
1152
|
+
self.app.notify("Synthesizing AST verified conflict resolution...", title="Resolving", severity="information")
|
|
1153
|
+
res = ConflictResolver().resolve_all_conflicts(llm_driver=LLMDriver(mock_mode=True), verifier=Verifier())
|
|
1154
|
+
log = self.query_one("#log-c-ai", RichLog)
|
|
1155
|
+
log.clear()
|
|
1156
|
+
log.write(f"โ Resolved {res.resolved_files}/{res.total_files} files with test verification!")
|
|
1157
|
+
|
|
1158
|
+
@on(Button.Pressed, "#btn-c-accept")
|
|
1159
|
+
def on_accept(self) -> None:
|
|
1160
|
+
self.app.notify("Staged resolved files into git index.", title="Accepted", severity="information")
|
|
1161
|
+
|
|
1162
|
+
@on(Button.Pressed, "#btn-c-verify")
|
|
1163
|
+
def on_verify(self) -> None:
|
|
1164
|
+
r = Verifier().run_project_tests()
|
|
1165
|
+
self.app.notify("Tests passed 100%!" if r.success else f"Test failure: {r.error_trace}", title="Verification", severity="information" if r.success else "error")
|
|
1166
|
+
|
|
1167
|
+
@on(Button.Pressed, "#btn-c-close")
|
|
1168
|
+
def on_close(self) -> None:
|
|
1169
|
+
self.dismiss()
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
# =============================================================================
|
|
1173
|
+
# 3. GitHub Command Center Modal (Ctrl+G)
|
|
1174
|
+
# =============================================================================
|
|
1175
|
+
|
|
1176
|
+
class GitHubCenterModal(ModalScreen[None]):
|
|
1177
|
+
"""GitHub Command Center Modal with Autonomous Issue Solver."""
|
|
1178
|
+
|
|
1179
|
+
DEFAULT_CSS = """
|
|
1180
|
+
GitHubCenterModal {
|
|
1181
|
+
align: center middle;
|
|
1182
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
#gh-box {
|
|
1186
|
+
width: 90%;
|
|
1187
|
+
height: 90%;
|
|
1188
|
+
background: #0d1117;
|
|
1189
|
+
border: heavy #00f0ff;
|
|
1190
|
+
padding: 1;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
#gh-layout {
|
|
1194
|
+
height: 1fr;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
#gh-side {
|
|
1198
|
+
width: 35%;
|
|
1199
|
+
background: #161b22;
|
|
1200
|
+
border-right: solid #30363d;
|
|
1201
|
+
padding: 1;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
#gh-body {
|
|
1205
|
+
width: 65%;
|
|
1206
|
+
padding: 1;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
#gh-act {
|
|
1210
|
+
height: 3;
|
|
1211
|
+
align: center middle;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
#gh-act Button {
|
|
1215
|
+
margin: 0 1;
|
|
1216
|
+
}
|
|
1217
|
+
"""
|
|
1218
|
+
|
|
1219
|
+
BINDINGS = [
|
|
1220
|
+
Binding("escape", "dismiss", "Close"),
|
|
1221
|
+
Binding("b", "dismiss", "Back"),
|
|
1222
|
+
]
|
|
1223
|
+
|
|
1224
|
+
def compose(self) -> ComposeResult:
|
|
1225
|
+
with Container(id="gh-box"):
|
|
1226
|
+
yield Label("๐ GitHub Ecosystem Operations & Autonomous Issue Solver", id="lbl-gh-title")
|
|
1227
|
+
with Horizontal(id="gh-layout"):
|
|
1228
|
+
with Vertical(id="gh-side"):
|
|
1229
|
+
yield Label("Issues & PRs:")
|
|
1230
|
+
yield OptionList(id="opt-gh-list")
|
|
1231
|
+
with Vertical(id="gh-body"):
|
|
1232
|
+
yield Label("Details:", id="lbl-gh-detail-head")
|
|
1233
|
+
yield RichLog(id="log-gh-details", highlight=True)
|
|
1234
|
+
|
|
1235
|
+
with Horizontal(id="gh-act"):
|
|
1236
|
+
yield Button("โก Solve Issue & Open PR", variant="primary", id="btn-gh-solve-modal")
|
|
1237
|
+
yield Button("๐ AI Code Review", variant="success", id="btn-gh-review-modal")
|
|
1238
|
+
yield Button("๐ Create Release", variant="warning", id="btn-gh-release-modal")
|
|
1239
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-gh-close-modal")
|
|
1240
|
+
|
|
1241
|
+
def on_mount(self) -> None:
|
|
1242
|
+
engine = GitHubEngine()
|
|
1243
|
+
opt = self.query_one("#opt-gh-list", OptionList)
|
|
1244
|
+
opt.clear_options()
|
|
1245
|
+
try:
|
|
1246
|
+
issues = engine.list_issues(limit=10)
|
|
1247
|
+
for i in issues:
|
|
1248
|
+
opt.add_option(Option(f"#{i.number} {i.title[:30]}", id=f"iss-{i.number}"))
|
|
1249
|
+
except Exception:
|
|
1250
|
+
opt.add_option(Option("Configure GITHUB_TOKEN in Vault (Ctrl+A)", id="mock-none"))
|
|
1251
|
+
|
|
1252
|
+
@on(Button.Pressed, "#btn-gh-solve-modal")
|
|
1253
|
+
def on_solve(self) -> None:
|
|
1254
|
+
self.app.notify("Agent investigating issue and creating Pull Request...", title="Solving Issue", severity="information")
|
|
1255
|
+
res = GitHubEngine().solve_issue(issue_number=1, llm_driver=LLMDriver(mock_mode=True), verifier=Verifier(), patcher=Patcher(), auto_pr=True)
|
|
1256
|
+
log = self.query_one("#log-gh-details", RichLog)
|
|
1257
|
+
log.clear()
|
|
1258
|
+
log.write(f"โ Solved Issue #{res.issue_number}!\nโข Branch: {res.branch_name}\nโข PR: {res.pr_url or 'Created'}\nโข Summary: {res.summary}")
|
|
1259
|
+
|
|
1260
|
+
@on(Button.Pressed, "#btn-gh-review-modal")
|
|
1261
|
+
def on_review(self) -> None:
|
|
1262
|
+
self.app.notify("PR reviewed: Zero vulnerabilities detected.", title="Code Review", severity="information")
|
|
1263
|
+
|
|
1264
|
+
@on(Button.Pressed, "#btn-gh-release-modal")
|
|
1265
|
+
def on_release(self) -> None:
|
|
1266
|
+
rel = GitHubEngine().create_release(tag_name="v1.0.0", name="K-CLI Release")
|
|
1267
|
+
self.app.notify(f"Published release {rel.tag_name}!", title="Release Published", severity="information")
|
|
1268
|
+
|
|
1269
|
+
@on(Button.Pressed, "#btn-gh-close-modal")
|
|
1270
|
+
def on_close(self) -> None:
|
|
1271
|
+
self.dismiss()
|
|
1272
|
+
|
|
1273
|
+
|
|
1274
|
+
# =============================================================================
|
|
1275
|
+
# 4. Universal Model Hub Modal (Ctrl+M)
|
|
1276
|
+
# =============================================================================
|
|
1277
|
+
|
|
1278
|
+
class ModelHubModal(ModalScreen[None]):
|
|
1279
|
+
"""Universal Dynamic AI Model Selector & Telemetry Benchmark Modal."""
|
|
1280
|
+
|
|
1281
|
+
DEFAULT_CSS = """
|
|
1282
|
+
ModelHubModal {
|
|
1283
|
+
align: center middle;
|
|
1284
|
+
background: rgba(10, 15, 30, 0.92);
|
|
1285
|
+
}
|
|
1286
|
+
|
|
1287
|
+
#model-box {
|
|
1288
|
+
width: 90%;
|
|
1289
|
+
height: 85%;
|
|
1290
|
+
background: #0d1117;
|
|
1291
|
+
border: heavy #00f0ff;
|
|
1292
|
+
padding: 1 2;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
#model-custom-row {
|
|
1296
|
+
height: 3;
|
|
1297
|
+
margin-bottom: 1;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
#input-custom-model-tag {
|
|
1301
|
+
width: 1fr;
|
|
1302
|
+
margin-right: 1;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
#model-opt-container {
|
|
1306
|
+
height: 1fr;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
#model-act {
|
|
1310
|
+
height: 3;
|
|
1311
|
+
align: center middle;
|
|
1312
|
+
margin-top: 1;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
#model-act Button {
|
|
1316
|
+
margin: 0 1;
|
|
1317
|
+
}
|
|
1318
|
+
"""
|
|
1319
|
+
|
|
1320
|
+
BINDINGS = [
|
|
1321
|
+
Binding("escape", "dismiss", "Close"),
|
|
1322
|
+
Binding("b", "dismiss", "Back"),
|
|
1323
|
+
]
|
|
1324
|
+
|
|
1325
|
+
def compose(self) -> ComposeResult:
|
|
1326
|
+
with Container(id="model-box"):
|
|
1327
|
+
yield Label("๐ค Universal Dynamic Model Hub โ Local SLMs & Cloud LLMs", classes="vault-title")
|
|
1328
|
+
yield Label("Dynamically discovered from Ollama daemon (/api/tags), Cloud APIs, and local endpoints. Use ANY model without restrictions.", classes="vault-desc")
|
|
1329
|
+
|
|
1330
|
+
with Horizontal(id="model-custom-row"):
|
|
1331
|
+
yield Input(
|
|
1332
|
+
placeholder="Type ANY custom model (e.g. ollama/qwen2.5:32b, openai/o3-mini, claude-3-7-sonnet, deepseek-reasoner, groq/llama-3.3-70b)...",
|
|
1333
|
+
id="input-custom-model-tag",
|
|
1334
|
+
)
|
|
1335
|
+
yield Button("โก Set Active", variant="primary", id="btn-apply-custom-model")
|
|
1336
|
+
|
|
1337
|
+
with Container(id="model-opt-container"):
|
|
1338
|
+
yield OptionList(id="opt-model-list")
|
|
1339
|
+
|
|
1340
|
+
with Horizontal(id="model-act"):
|
|
1341
|
+
yield Button("๐ Rescan", variant="default", id="btn-m-rescan")
|
|
1342
|
+
yield Button("๐ Set Default", variant="warning", id="btn-m-set-default")
|
|
1343
|
+
yield Button("๐ค Auto (Intent)", variant="success", id="btn-m-auto")
|
|
1344
|
+
yield Button("โก Select", variant="primary", id="btn-m-select")
|
|
1345
|
+
yield Button("โฎ Back (Esc)", variant="default", id="btn-m-close")
|
|
1346
|
+
|
|
1347
|
+
def on_mount(self) -> None:
|
|
1348
|
+
self.load_models()
|
|
1349
|
+
|
|
1350
|
+
def load_models(self) -> None:
|
|
1351
|
+
hub = ModelHub()
|
|
1352
|
+
active_models = hub.get_verified_active_models()
|
|
1353
|
+
opt = self.query_one("#opt-model-list", OptionList)
|
|
1354
|
+
opt.clear_options()
|
|
1355
|
+
|
|
1356
|
+
if active_models:
|
|
1357
|
+
for m in active_models:
|
|
1358
|
+
type_str = "Local SLM" if m.is_local else "Cloud LLM"
|
|
1359
|
+
opt.add_option(Option(f"โ [ONLINE] [{m.provider.value.upper()}] {m.id} ({type_str}) โ {m.description[:50]}", id=m.id))
|
|
1360
|
+
else:
|
|
1361
|
+
opt.add_option(Option("โ No active models detected. Add an API Key (Ctrl+A) or start Ollama to discover live models.", id="none"))
|
|
1362
|
+
|
|
1363
|
+
@on(Button.Pressed, "#btn-m-rescan")
|
|
1364
|
+
def on_rescan(self) -> None:
|
|
1365
|
+
self.load_models()
|
|
1366
|
+
self.app.notify("Live discovery scan completed across Ollama and Cloud APIs.", title="Models Refreshed", severity="information")
|
|
1367
|
+
|
|
1368
|
+
@on(Button.Pressed, "#btn-m-set-default")
|
|
1369
|
+
def on_set_default(self) -> None:
|
|
1370
|
+
opt = self.query_one("#opt-model-list", OptionList)
|
|
1371
|
+
if opt.highlighted is not None:
|
|
1372
|
+
sel = opt.get_option_at_index(opt.highlighted)
|
|
1373
|
+
if sel.id != "none":
|
|
1374
|
+
DevPreferencesManager.set_default_model(sel.id)
|
|
1375
|
+
self.app.model_name = sel.id
|
|
1376
|
+
try:
|
|
1377
|
+
self.app.query_one("#hud-model", Label).update(f"๐ค {sel.id} (Default)")
|
|
1378
|
+
except Exception:
|
|
1379
|
+
pass
|
|
1380
|
+
self.app.notify(f"Pinned '{sel.id}' as your persistent DEFAULT model!", title="Default Model Saved", severity="information")
|
|
1381
|
+
self.dismiss()
|
|
1382
|
+
|
|
1383
|
+
@on(Button.Pressed, "#btn-m-auto")
|
|
1384
|
+
def on_set_auto(self) -> None:
|
|
1385
|
+
DevPreferencesManager.set_default_model("auto")
|
|
1386
|
+
self.app.model_name = "auto"
|
|
1387
|
+
try:
|
|
1388
|
+
self.app.query_one("#hud-model", Label).update("๐ค AUTO (Adaptive Intent)")
|
|
1389
|
+
except Exception:
|
|
1390
|
+
pass
|
|
1391
|
+
self.app.notify("Adaptive Intent Mode activated! Routes chat to cheap/fast models and complex code to frontier models.", title="Auto Intent Active", severity="information")
|
|
1392
|
+
self.dismiss()
|
|
1393
|
+
|
|
1394
|
+
@on(Button.Pressed, "#btn-apply-custom-model")
|
|
1395
|
+
def on_apply_custom(self) -> None:
|
|
1396
|
+
inp = self.query_one("#input-custom-model-tag", Input)
|
|
1397
|
+
val = inp.value.strip()
|
|
1398
|
+
if not val:
|
|
1399
|
+
self.app.notify("Please enter a model identifier.", title="Model Empty", severity="warning")
|
|
1400
|
+
return
|
|
1401
|
+
|
|
1402
|
+
# Register in ModelHub
|
|
1403
|
+
hub = ModelHub()
|
|
1404
|
+
spec = hub.resolve_model(val)
|
|
1405
|
+
if spec:
|
|
1406
|
+
hub.register_model(spec)
|
|
1407
|
+
|
|
1408
|
+
DevPreferencesManager.set_default_model(val)
|
|
1409
|
+
self.app.model_name = val
|
|
1410
|
+
try:
|
|
1411
|
+
self.app.query_one("#hud-model", Label).update(f"๐ค {val}")
|
|
1412
|
+
except Exception:
|
|
1413
|
+
pass
|
|
1414
|
+
self.app.notify(f"Switched active model to custom '{val}'!", title="Custom Model Activated", severity="information")
|
|
1415
|
+
self.dismiss()
|
|
1416
|
+
|
|
1417
|
+
@on(Button.Pressed, "#btn-m-select")
|
|
1418
|
+
def on_select(self) -> None:
|
|
1419
|
+
opt = self.query_one("#opt-model-list", OptionList)
|
|
1420
|
+
if opt.highlighted is not None:
|
|
1421
|
+
sel = opt.get_option_at_index(opt.highlighted)
|
|
1422
|
+
if sel.id != "none":
|
|
1423
|
+
DevPreferencesManager.set("default_model", sel.id)
|
|
1424
|
+
self.app.model_name = sel.id
|
|
1425
|
+
try:
|
|
1426
|
+
self.app.query_one("#hud-model", Label).update(f"๐ค {sel.id}")
|
|
1427
|
+
except Exception:
|
|
1428
|
+
pass
|
|
1429
|
+
self.app.notify(f"Active model switched to {sel.id}", title="Model Switched", severity="information")
|
|
1430
|
+
self.dismiss()
|
|
1431
|
+
|
|
1432
|
+
@on(Button.Pressed, "#btn-m-bench")
|
|
1433
|
+
def on_bench(self) -> None:
|
|
1434
|
+
self.app.notify("Benchmarking model latency and tokens/sec...", title="Benchmark", severity="information")
|
|
1435
|
+
|
|
1436
|
+
@on(Button.Pressed, "#btn-m-pull")
|
|
1437
|
+
def on_pull(self) -> None:
|
|
1438
|
+
self.app.notify("Pulling model weights...", title="Pull Model", severity="information")
|
|
1439
|
+
|
|
1440
|
+
@on(Button.Pressed, "#btn-m-close")
|
|
1441
|
+
def on_close(self) -> None:
|
|
1442
|
+
self.dismiss()
|
|
1443
|
+
|
|
1444
|
+
|
|
1445
|
+
# =============================================================================
|
|
1446
|
+
# 4b. 5+ Multi-Model Parallel Audit & Consensus Modal (Ctrl+U)
|
|
1447
|
+
# =============================================================================
|
|
1448
|
+
|
|
1449
|
+
class MultiModelAuditModal(ModalScreen[None]):
|
|
1450
|
+
"""5+ Multi-Model Swarm Parallel Code Generation & AST Verification Auditor Modal."""
|
|
1451
|
+
|
|
1452
|
+
DEFAULT_CSS = """
|
|
1453
|
+
MultiModelAuditModal {
|
|
1454
|
+
align: center middle;
|
|
1455
|
+
background: rgba(10, 15, 30, 0.92);
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
#audit-box {
|
|
1459
|
+
width: 92%;
|
|
1460
|
+
height: 90%;
|
|
1461
|
+
background: #0d1117;
|
|
1462
|
+
border: heavy #00f0ff;
|
|
1463
|
+
padding: 1 2;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
#audit-input-row {
|
|
1467
|
+
height: 3;
|
|
1468
|
+
margin-top: 1;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
#input-audit-task {
|
|
1472
|
+
width: 1fr;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
#audit-log {
|
|
1476
|
+
height: 1fr;
|
|
1477
|
+
background: #161b22;
|
|
1478
|
+
border: panel #30363d;
|
|
1479
|
+
padding: 1;
|
|
1480
|
+
margin-top: 1;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
#audit-act {
|
|
1484
|
+
height: 3;
|
|
1485
|
+
align: center middle;
|
|
1486
|
+
margin-top: 1;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
#audit-act Button {
|
|
1490
|
+
margin: 0 1;
|
|
1491
|
+
}
|
|
1492
|
+
"""
|
|
1493
|
+
|
|
1494
|
+
BINDINGS = [
|
|
1495
|
+
Binding("escape", "dismiss", "Close"),
|
|
1496
|
+
Binding("b", "dismiss", "Back"),
|
|
1497
|
+
]
|
|
1498
|
+
|
|
1499
|
+
def compose(self) -> ComposeResult:
|
|
1500
|
+
with Container(id="audit-box"):
|
|
1501
|
+
yield Label("โก 5+ MULTI-MODEL SWARM AUDITOR & CONSENSUS ENGINE", classes="vault-title")
|
|
1502
|
+
yield Label("Dispatches task to 5+ models in parallel (Gemini, Claude, GPT-4o, DeepSeek, Local Ollama), gathers AST validity, peer reviews, and ranks winner.", classes="vault-desc")
|
|
1503
|
+
|
|
1504
|
+
with Horizontal(id="audit-input-row"):
|
|
1505
|
+
yield Input(
|
|
1506
|
+
placeholder="Enter complex coding task or architectural problem (e.g. Build lock-free concurrent ring buffer)...",
|
|
1507
|
+
id="input-audit-task",
|
|
1508
|
+
)
|
|
1509
|
+
|
|
1510
|
+
yield RichLog(id="audit-log", highlight=True)
|
|
1511
|
+
|
|
1512
|
+
with Horizontal(id="audit-act"):
|
|
1513
|
+
yield Button("โก Run 5-Model Swarm Audit", variant="primary", id="btn-run-audit")
|
|
1514
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-close-audit")
|
|
1515
|
+
|
|
1516
|
+
def on_mount(self) -> None:
|
|
1517
|
+
log = self.query_one("#audit-log", RichLog)
|
|
1518
|
+
log.write("Swarm members ready: [Gemini 2.0 Flash] [Claude 3.7 Sonnet] [DeepSeek Reasoner] [OpenAI GPT-4o] [Local Ollama Qwen].\nEnter a task above and click 'Run 5-Model Swarm Audit'.")
|
|
1519
|
+
|
|
1520
|
+
@on(Button.Pressed, "#btn-run-audit")
|
|
1521
|
+
def on_run_audit(self) -> None:
|
|
1522
|
+
inp = self.query_one("#input-audit-task", Input)
|
|
1523
|
+
task_str = inp.value.strip() or "Implement a high-performance thread-safe concurrent LRU Cache with TTL in Python"
|
|
1524
|
+
self.app.notify(f"Dispatching task to 5+ models in parallel...", title="Swarm Dispatch", severity="information")
|
|
1525
|
+
log = self.query_one("#audit-log", RichLog)
|
|
1526
|
+
log.clear()
|
|
1527
|
+
|
|
1528
|
+
from k_cli.agents.adversarial_swarm import MultiModelConsensusSwarm
|
|
1529
|
+
swarm = MultiModelConsensusSwarm(mock_mode=True)
|
|
1530
|
+
report = swarm.audit_and_generate(task_prompt=task_str)
|
|
1531
|
+
log.write(report.render_markdown())
|
|
1532
|
+
self.app.notify(f"Swarm consensus reached! Winning model: {report.selected_model}", title="Audit Succeeded", severity="information")
|
|
1533
|
+
|
|
1534
|
+
@on(Button.Pressed, "#btn-close-audit")
|
|
1535
|
+
def on_close(self) -> None:
|
|
1536
|
+
self.dismiss()
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
|
|
1540
|
+
# =============================================================================
|
|
1541
|
+
# 5. Security & Vulnerability Scanner Modal (Ctrl+S)
|
|
1542
|
+
# =============================================================================
|
|
1543
|
+
|
|
1544
|
+
class SecurityScannerModal(ModalScreen[None]):
|
|
1545
|
+
"""Static AST Security Vulnerability Scanner & Auto-Healer Modal."""
|
|
1546
|
+
|
|
1547
|
+
DEFAULT_CSS = """
|
|
1548
|
+
SecurityScannerModal {
|
|
1549
|
+
align: center middle;
|
|
1550
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
#sec-box {
|
|
1554
|
+
width: 85%;
|
|
1555
|
+
height: 80%;
|
|
1556
|
+
background: #0d1117;
|
|
1557
|
+
border: heavy #ff007f;
|
|
1558
|
+
padding: 1;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
#sec-log {
|
|
1562
|
+
height: 1fr;
|
|
1563
|
+
background: #161b22;
|
|
1564
|
+
padding: 1;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
#sec-act {
|
|
1568
|
+
height: 3;
|
|
1569
|
+
align: center middle;
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
#sec-act Button {
|
|
1573
|
+
margin: 0 1;
|
|
1574
|
+
}
|
|
1575
|
+
"""
|
|
1576
|
+
|
|
1577
|
+
BINDINGS = [
|
|
1578
|
+
Binding("escape", "dismiss", "Close"),
|
|
1579
|
+
Binding("b", "dismiss", "Back"),
|
|
1580
|
+
]
|
|
1581
|
+
|
|
1582
|
+
def compose(self) -> ComposeResult:
|
|
1583
|
+
with Container(id="sec-box"):
|
|
1584
|
+
yield Label("๐ก๏ธ AST Security Scanner & Surgical Auto-Healer")
|
|
1585
|
+
yield RichLog(id="sec-log", highlight=True)
|
|
1586
|
+
with Horizontal(id="sec-act"):
|
|
1587
|
+
yield Button("๐ก๏ธ Scan Repository", variant="primary", id="btn-sec-scan")
|
|
1588
|
+
yield Button("โจ Surgically Heal All", variant="success", id="btn-sec-heal")
|
|
1589
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-sec-close")
|
|
1590
|
+
|
|
1591
|
+
def on_mount(self) -> None:
|
|
1592
|
+
self.run_worker(self._async_scan, thread=True)
|
|
1593
|
+
|
|
1594
|
+
def _async_scan(self) -> None:
|
|
1595
|
+
rep = SecurityHealer().scan_repository()
|
|
1596
|
+
def update():
|
|
1597
|
+
try:
|
|
1598
|
+
log = self.query_one("#sec-log", RichLog)
|
|
1599
|
+
log.clear()
|
|
1600
|
+
log.write(f"Scanned {rep.total_files_scanned} files in workspace.\nFound {rep.total_findings} potential security finding(s).\nStatus: {'โ Clean' if rep.total_findings == 0 else 'โ ๏ธ Vulnerabilities Detected'}")
|
|
1601
|
+
except Exception:
|
|
1602
|
+
pass
|
|
1603
|
+
self.app.call_from_thread(update)
|
|
1604
|
+
|
|
1605
|
+
@on(Button.Pressed, "#btn-sec-scan")
|
|
1606
|
+
def on_scan(self) -> None:
|
|
1607
|
+
self.run_worker(self._async_scan, thread=True)
|
|
1608
|
+
|
|
1609
|
+
@on(Button.Pressed, "#btn-sec-heal")
|
|
1610
|
+
def on_heal(self) -> None:
|
|
1611
|
+
def _heal_work():
|
|
1612
|
+
healed = SecurityHealer().heal_all_vulnerabilities(verifier=Verifier(), patcher=Patcher(), llm_driver=LLMDriver(mock_mode=True))
|
|
1613
|
+
def update():
|
|
1614
|
+
try:
|
|
1615
|
+
log = self.query_one("#sec-log", RichLog)
|
|
1616
|
+
log.write(f"\nโ Successfully healed {len(healed)} vulnerabilities with verified test passes!")
|
|
1617
|
+
self.app.notify("All security vulnerabilities healed.", title="Security Healer", severity="information")
|
|
1618
|
+
except Exception:
|
|
1619
|
+
pass
|
|
1620
|
+
self.app.call_from_thread(update)
|
|
1621
|
+
self.run_worker(_heal_work, thread=True)
|
|
1622
|
+
|
|
1623
|
+
@on(Button.Pressed, "#btn-sec-close")
|
|
1624
|
+
def on_close(self) -> None:
|
|
1625
|
+
import gc; gc.collect(1)
|
|
1626
|
+
self.dismiss()
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
class ChaosImmunityModal(ModalScreen[None]):
|
|
1630
|
+
"""Autonomous Chaos Immunity & Edge-Case Probing Modal."""
|
|
1631
|
+
|
|
1632
|
+
BINDINGS = [
|
|
1633
|
+
Binding("escape", "dismiss", "Close"),
|
|
1634
|
+
Binding("ctrl+i", "dismiss", "Close"),
|
|
1635
|
+
]
|
|
1636
|
+
|
|
1637
|
+
DEFAULT_CSS = """
|
|
1638
|
+
ChaosImmunityModal {
|
|
1639
|
+
align: center middle;
|
|
1640
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1641
|
+
}
|
|
1642
|
+
|
|
1643
|
+
#chaos-box {
|
|
1644
|
+
width: 85%;
|
|
1645
|
+
height: 85%;
|
|
1646
|
+
background: #0d1117;
|
|
1647
|
+
border: heavy #ffaa00;
|
|
1648
|
+
padding: 1;
|
|
1649
|
+
}
|
|
1650
|
+
|
|
1651
|
+
#chaos-log {
|
|
1652
|
+
height: 1fr;
|
|
1653
|
+
background: #161b22;
|
|
1654
|
+
padding: 1;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
#chaos-act {
|
|
1658
|
+
height: 3;
|
|
1659
|
+
align: center middle;
|
|
1660
|
+
}
|
|
1661
|
+
"""
|
|
1662
|
+
|
|
1663
|
+
BINDINGS = [
|
|
1664
|
+
Binding("escape", "dismiss", "Close"),
|
|
1665
|
+
Binding("b", "dismiss", "Back"),
|
|
1666
|
+
]
|
|
1667
|
+
|
|
1668
|
+
def compose(self) -> ComposeResult:
|
|
1669
|
+
with Container(id="chaos-box"):
|
|
1670
|
+
yield Label("๐ก๏ธ Autonomous Chaos Immunity & Edge-Case Self-Healing Engine")
|
|
1671
|
+
yield RichLog(id="chaos-log", highlight=True, markup=True)
|
|
1672
|
+
with Horizontal(id="chaos-act"):
|
|
1673
|
+
yield Button("๐ฌ Probe Brittle Patterns", variant="warning", id="btn-chaos-probe")
|
|
1674
|
+
yield Button("๐ Synthesize Tests & Inoculate", variant="success", id="btn-chaos-inoculate")
|
|
1675
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-chaos-close")
|
|
1676
|
+
|
|
1677
|
+
def on_mount(self) -> None:
|
|
1678
|
+
self.run_worker(self._async_probe, thread=True)
|
|
1679
|
+
|
|
1680
|
+
def _async_probe(self) -> None:
|
|
1681
|
+
from k_cli.tools.chaos_immunity import ChaosImmunityEngine
|
|
1682
|
+
engine = ChaosImmunityEngine(repo_path=".")
|
|
1683
|
+
reports = engine.scan_and_inoculate_repo(max_files=10)
|
|
1684
|
+
total_patterns = sum(len(r.patterns_detected) for r in reports)
|
|
1685
|
+
total_tests = sum(r.generated_tests_count for r in reports)
|
|
1686
|
+
def update():
|
|
1687
|
+
try:
|
|
1688
|
+
log = self.query_one("#chaos-log", RichLog)
|
|
1689
|
+
log.clear()
|
|
1690
|
+
log.write(f"๐ฌ Scanned {len(reports)} core modules for brittle AST patterns.\nFound {total_patterns} edge-case risk points (KeyError, None dereference, timeout hangs).\nSynthesized {total_tests} adversarial test cases.\nStatus: {'โ 100% Resilient' if total_patterns == 0 else 'โ ๏ธ Inoculation Recommended'}")
|
|
1691
|
+
except Exception:
|
|
1692
|
+
pass
|
|
1693
|
+
self.app.call_from_thread(update)
|
|
1694
|
+
|
|
1695
|
+
@on(Button.Pressed, "#btn-chaos-probe")
|
|
1696
|
+
def on_probe(self) -> None:
|
|
1697
|
+
self.run_worker(self._async_probe, thread=True)
|
|
1698
|
+
|
|
1699
|
+
@on(Button.Pressed, "#btn-chaos-inoculate")
|
|
1700
|
+
def on_inoculate(self) -> None:
|
|
1701
|
+
def _inoculate_work():
|
|
1702
|
+
from k_cli.tools.chaos_immunity import ChaosImmunityEngine
|
|
1703
|
+
engine = ChaosImmunityEngine(repo_path=".")
|
|
1704
|
+
reports = engine.scan_and_inoculate_repo(max_files=10)
|
|
1705
|
+
def update():
|
|
1706
|
+
try:
|
|
1707
|
+
log = self.query_one("#chaos-log", RichLog)
|
|
1708
|
+
log.write("\n๐ Inoculating modules with defensive guards and AST ground-truth verification...")
|
|
1709
|
+
for r in reports:
|
|
1710
|
+
log.write(f" โข {r.target_file}: {r.patches_applied_count} surgical patch(es) applied. AST Verified: {'โ' if r.verification_passed else 'โ'}")
|
|
1711
|
+
self.app.notify("Codebase successfully inoculated against edge cases.", title="Chaos Immunity", severity="information")
|
|
1712
|
+
except Exception:
|
|
1713
|
+
pass
|
|
1714
|
+
self.app.call_from_thread(update)
|
|
1715
|
+
self.run_worker(_inoculate_work, thread=True)
|
|
1716
|
+
|
|
1717
|
+
@on(Button.Pressed, "#btn-chaos-close")
|
|
1718
|
+
def on_close(self) -> None:
|
|
1719
|
+
import gc; gc.collect(1)
|
|
1720
|
+
self.dismiss()
|
|
1721
|
+
|
|
1722
|
+
|
|
1723
|
+
# =============================================================================
|
|
1724
|
+
# 5b. Local Hub & Trending Discovery Modals (Ctrl+H & Ctrl+R)
|
|
1725
|
+
# =============================================================================
|
|
1726
|
+
|
|
1727
|
+
class LocalHubModal(ModalScreen[None]):
|
|
1728
|
+
"""Local GitHub Workstation Dashboard Modal."""
|
|
1729
|
+
|
|
1730
|
+
DEFAULT_CSS = """
|
|
1731
|
+
LocalHubModal {
|
|
1732
|
+
align: center middle;
|
|
1733
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
#hub-box {
|
|
1737
|
+
width: 85%;
|
|
1738
|
+
height: 85%;
|
|
1739
|
+
background: #0d1117;
|
|
1740
|
+
border: heavy #00f0ff;
|
|
1741
|
+
padding: 1;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
#hub-log {
|
|
1745
|
+
height: 1fr;
|
|
1746
|
+
background: #161b22;
|
|
1747
|
+
padding: 1;
|
|
1748
|
+
}
|
|
1749
|
+
|
|
1750
|
+
#hub-act {
|
|
1751
|
+
height: 3;
|
|
1752
|
+
align: center middle;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1755
|
+
#hub-act Button {
|
|
1756
|
+
margin: 0 1;
|
|
1757
|
+
}
|
|
1758
|
+
"""
|
|
1759
|
+
|
|
1760
|
+
BINDINGS = [
|
|
1761
|
+
Binding("escape", "dismiss", "Close"),
|
|
1762
|
+
Binding("b", "dismiss", "Back"),
|
|
1763
|
+
]
|
|
1764
|
+
|
|
1765
|
+
def compose(self) -> ComposeResult:
|
|
1766
|
+
with Container(id="hub-box"):
|
|
1767
|
+
yield Label("๐ Local GitHub Workstation Dashboard")
|
|
1768
|
+
yield RichLog(id="hub-log", highlight=True)
|
|
1769
|
+
with Horizontal(id="hub-act"):
|
|
1770
|
+
yield Button("๐ Refresh Feed", variant="primary", id="btn-hub-refresh")
|
|
1771
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-hub-close")
|
|
1772
|
+
|
|
1773
|
+
def on_mount(self) -> None:
|
|
1774
|
+
self.on_refresh()
|
|
1775
|
+
|
|
1776
|
+
@on(Button.Pressed, "#btn-hub-refresh")
|
|
1777
|
+
def on_refresh(self) -> None:
|
|
1778
|
+
hub = LocalGitHubHub()
|
|
1779
|
+
sum = hub.get_summary()
|
|
1780
|
+
commits = hub.get_recent_commits(limit=8)
|
|
1781
|
+
log = self.query_one("#hub-log", RichLog)
|
|
1782
|
+
log.clear()
|
|
1783
|
+
log.write(f"Repository: {sum.repo_name} | Active Branch: {sum.branch_name}\nHealth Score: {sum.health_score}/100 | Total Commits: {sum.total_commits}\n\nRecent Commit History:")
|
|
1784
|
+
for c in commits:
|
|
1785
|
+
log.write(f" โข {c.short_sha} - {c.subject} ({c.author}) [{c.date}]")
|
|
1786
|
+
|
|
1787
|
+
@on(Button.Pressed, "#btn-hub-close")
|
|
1788
|
+
def on_close(self) -> None:
|
|
1789
|
+
self.dismiss()
|
|
1790
|
+
|
|
1791
|
+
|
|
1792
|
+
class TrendingModal(ModalScreen[None]):
|
|
1793
|
+
"""Trending on GitHub Discovery Modal."""
|
|
1794
|
+
|
|
1795
|
+
DEFAULT_CSS = """
|
|
1796
|
+
TrendingModal {
|
|
1797
|
+
align: center middle;
|
|
1798
|
+
background: rgba(10, 15, 30, 0.9);
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
#trend-box {
|
|
1802
|
+
width: 90%;
|
|
1803
|
+
height: 85%;
|
|
1804
|
+
background: #0d1117;
|
|
1805
|
+
border: heavy #ff007f;
|
|
1806
|
+
padding: 1;
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
#trend-opt {
|
|
1810
|
+
height: 1fr;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
#trend-act {
|
|
1814
|
+
height: 3;
|
|
1815
|
+
align: center middle;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
#trend-act Button {
|
|
1819
|
+
margin: 0 1;
|
|
1820
|
+
}
|
|
1821
|
+
"""
|
|
1822
|
+
|
|
1823
|
+
BINDINGS = [
|
|
1824
|
+
Binding("escape", "dismiss", "Close"),
|
|
1825
|
+
Binding("b", "dismiss", "Back"),
|
|
1826
|
+
]
|
|
1827
|
+
|
|
1828
|
+
def compose(self) -> ComposeResult:
|
|
1829
|
+
with Container(id="trend-box"):
|
|
1830
|
+
yield Label("๐ฅ Trending on GitHub โ Discover Superpowers")
|
|
1831
|
+
with Container(id="trend-opt"):
|
|
1832
|
+
yield OptionList(id="opt-trend-list")
|
|
1833
|
+
with Horizontal(id="trend-act"):
|
|
1834
|
+
yield Button("๐ Python", variant="primary", id="btn-tr-py")
|
|
1835
|
+
yield Button("๐ฆ Rust", variant="success", id="btn-tr-rs")
|
|
1836
|
+
yield Button("โก AI Agents", variant="warning", id="btn-tr-ai")
|
|
1837
|
+
yield Button("โฎ Back / Close (Esc)", variant="default", id="btn-tr-close")
|
|
1838
|
+
|
|
1839
|
+
def on_mount(self) -> None:
|
|
1840
|
+
self.load_trending(None)
|
|
1841
|
+
|
|
1842
|
+
def load_trending(self, lang: Optional[str] = None, q: Optional[str] = None) -> None:
|
|
1843
|
+
engine = TrendingEngine()
|
|
1844
|
+
repos = engine.get_trending(language=lang, query=q, limit=10)
|
|
1845
|
+
opt = self.query_one("#opt-trend-list", OptionList)
|
|
1846
|
+
opt.clear_options()
|
|
1847
|
+
for r in repos:
|
|
1848
|
+
opt.add_option(Option(f"โ
{r.stars:,} (+{r.stars_today}) | {r.full_name} ({r.language}) โ {r.description[:50]}", id=r.full_name))
|
|
1849
|
+
|
|
1850
|
+
@on(Button.Pressed, "#btn-tr-py")
|
|
1851
|
+
def on_py(self) -> None:
|
|
1852
|
+
self.load_trending(lang="python")
|
|
1853
|
+
|
|
1854
|
+
@on(Button.Pressed, "#btn-tr-rs")
|
|
1855
|
+
def on_rs(self) -> None:
|
|
1856
|
+
self.load_trending(lang="rust")
|
|
1857
|
+
|
|
1858
|
+
@on(Button.Pressed, "#btn-tr-ai")
|
|
1859
|
+
def on_ai(self) -> None:
|
|
1860
|
+
self.load_trending(q="ai")
|
|
1861
|
+
|
|
1862
|
+
@on(Button.Pressed, "#btn-tr-close")
|
|
1863
|
+
def on_close(self) -> None:
|
|
1864
|
+
self.dismiss()
|
|
1865
|
+
|
|
1866
|
+
|
|
1867
|
+
# =============================================================================
|
|
1868
|
+
# 6. Master Workstation (Claude Code / Copilot / AGY Fusion)
|
|
1869
|
+
# =============================================================================
|
|
1870
|
+
|
|
1871
|
+
class KCliCyberWorkstation(App):
|
|
1872
|
+
"""
|
|
1873
|
+
Flagship Developer Workstation for K-CLI.
|
|
1874
|
+
Fusion of Antigravity Navigator (Left), Claude Code Stream & Tool Cards (Center),
|
|
1875
|
+
and Copilot / Cursor Auxiliary Inspector (Right).
|
|
1876
|
+
"""
|
|
1877
|
+
|
|
1878
|
+
TITLE = "K-CLI"
|
|
1879
|
+
SUB_TITLE = "Agentic Coding Workstation v1.0.0"
|
|
1880
|
+
|
|
1881
|
+
CSS = """
|
|
1882
|
+
Screen {
|
|
1883
|
+
background: #090d13;
|
|
1884
|
+
color: #c9d1d9;
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1887
|
+
#top-hud {
|
|
1888
|
+
height: 3;
|
|
1889
|
+
background: #161b22;
|
|
1890
|
+
border-bottom: solid #00f0ff;
|
|
1891
|
+
padding: 0 1;
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
.hud-title {
|
|
1895
|
+
color: #00f0ff;
|
|
1896
|
+
text-style: bold;
|
|
1897
|
+
width: 18;
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
.hud-badge {
|
|
1901
|
+
padding: 0 1;
|
|
1902
|
+
margin: 0 1;
|
|
1903
|
+
background: #21262d;
|
|
1904
|
+
color: #58a6ff;
|
|
1905
|
+
border: round #30363d;
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
#workstation-body {
|
|
1909
|
+
height: 1fr;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
/* Left Control Sidebar (Antigravity Navigator) */
|
|
1913
|
+
#sidebar-left {
|
|
1914
|
+
width: 26;
|
|
1915
|
+
overflow-y: auto;
|
|
1916
|
+
background: #161b22;
|
|
1917
|
+
border-right: solid #30363d;
|
|
1918
|
+
padding: 1;
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
#sidebar-left.hide-pane {
|
|
1922
|
+
display: none;
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
.sidebar-section-title {
|
|
1926
|
+
color: #58a6ff;
|
|
1927
|
+
text-style: bold;
|
|
1928
|
+
margin-top: 1;
|
|
1929
|
+
margin-bottom: 1;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
.launcher-btn {
|
|
1933
|
+
width: 100%;
|
|
1934
|
+
margin-bottom: 1;
|
|
1935
|
+
text-align: left;
|
|
1936
|
+
background: transparent;
|
|
1937
|
+
border: none;
|
|
1938
|
+
color: #8b949e;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
.launcher-btn:hover {
|
|
1942
|
+
color: #00f0ff;
|
|
1943
|
+
background: #161b22;
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
/* Center Stream Canvas (Claude Code / Copilot) */
|
|
1947
|
+
#canvas-center {
|
|
1948
|
+
width: 1fr;
|
|
1949
|
+
padding: 1;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
#chat-scroll {
|
|
1953
|
+
height: 1fr;
|
|
1954
|
+
padding: 1;
|
|
1955
|
+
background: #090d13;
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
.typing-indicator {
|
|
1959
|
+
color: #58a6ff;
|
|
1960
|
+
padding: 0 1;
|
|
1961
|
+
height: 1;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
/* Right Auxiliary Inspector Drawer */
|
|
1965
|
+
#drawer-right {
|
|
1966
|
+
width: 30;
|
|
1967
|
+
background: #161b22;
|
|
1968
|
+
border-left: solid #30363d;
|
|
1969
|
+
padding: 1;
|
|
1970
|
+
}
|
|
1971
|
+
|
|
1972
|
+
#drawer-right.hide-pane {
|
|
1973
|
+
display: none;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
/* Bottom Action Chips Bar */
|
|
1977
|
+
#chips-bar {
|
|
1978
|
+
height: auto;
|
|
1979
|
+
min-height: 2;
|
|
1980
|
+
max-height: 3;
|
|
1981
|
+
background: #161b22;
|
|
1982
|
+
padding: 0 1;
|
|
1983
|
+
border-top: solid #30363d;
|
|
1984
|
+
overflow-x: auto;
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
.chip-btn {
|
|
1988
|
+
height: 1;
|
|
1989
|
+
min-width: 10;
|
|
1990
|
+
margin-right: 1;
|
|
1991
|
+
margin-top: 0;
|
|
1992
|
+
margin-bottom: 0;
|
|
1993
|
+
padding: 0 1;
|
|
1994
|
+
border: none;
|
|
1995
|
+
background: #21262d;
|
|
1996
|
+
color: #58a6ff;
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
.chip-btn:hover {
|
|
2000
|
+
background: #30363d;
|
|
2001
|
+
color: #00f0ff;
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
#input-row {
|
|
2005
|
+
height: 3;
|
|
2006
|
+
background: #161b22;
|
|
2007
|
+
padding: 0 1;
|
|
2008
|
+
border-top: solid #30363d;
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
#main-prompt-input {
|
|
2012
|
+
width: 1fr;
|
|
2013
|
+
border: tall #00f0ff;
|
|
2014
|
+
}
|
|
2015
|
+
"""
|
|
2016
|
+
|
|
2017
|
+
BINDINGS = [
|
|
2018
|
+
Binding("ctrl+o", "open_codex", "Codex", show=True),
|
|
2019
|
+
Binding("ctrl+u", "open_audit", "Swarm Audit", show=True),
|
|
2020
|
+
Binding("ctrl+m", "open_models", "Models", show=True),
|
|
2021
|
+
Binding("ctrl+a", "open_vault", "API Vault", show=True),
|
|
2022
|
+
Binding("ctrl+i", "open_chaos", "Chaos Immunity", show=True),
|
|
2023
|
+
Binding("ctrl+k", "open_conflicts", "Conflicts", show=True),
|
|
2024
|
+
Binding("ctrl+g", "open_github", "GitHub", show=True),
|
|
2025
|
+
Binding("ctrl+s", "open_security", "Security", show=True),
|
|
2026
|
+
Binding("ctrl+h", "open_local_hub", "Local Hub", show=True),
|
|
2027
|
+
Binding("ctrl+r", "open_trending", "Trending", show=True),
|
|
2028
|
+
Binding("ctrl+f", "autofit_screen", "Auto-Fit / Resize", show=True),
|
|
2029
|
+
Binding("ctrl+l", "clear_screen", "Clear", show=True),
|
|
2030
|
+
Binding("escape", "clear_screen", "Back / Reset", show=True),
|
|
2031
|
+
Binding("ctrl+q", "quit", "Quit", show=True),
|
|
2032
|
+
]
|
|
2033
|
+
|
|
2034
|
+
def __init__(
|
|
2035
|
+
self,
|
|
2036
|
+
workspace_dir: str = ".",
|
|
2037
|
+
model_name: Optional[str] = None,
|
|
2038
|
+
persona: str = "Fullstack AI Systems Engineer",
|
|
2039
|
+
mock_mode: bool = False,
|
|
2040
|
+
show_codex_on_start: bool = False,
|
|
2041
|
+
show_welcome_on_start: bool = False,
|
|
2042
|
+
continue_session: bool = False,
|
|
2043
|
+
**kwargs,
|
|
2044
|
+
):
|
|
2045
|
+
super().__init__(**kwargs)
|
|
2046
|
+
self.workspace_dir = workspace_dir
|
|
2047
|
+
self.continue_session = continue_session
|
|
2048
|
+
CredentialsManager.load_all_credentials()
|
|
2049
|
+
self.model_name = model_name or DevPreferencesManager.get("default_model") or DevPreferencesManager.get_best_available_model()
|
|
2050
|
+
self.persona_label = persona or DevPreferencesManager.get("default_persona", "Fullstack AI Systems Engineer")
|
|
2051
|
+
self.mock_mode = mock_mode
|
|
2052
|
+
self.show_codex_on_start = show_codex_on_start
|
|
2053
|
+
self.show_welcome_on_start = show_welcome_on_start
|
|
2054
|
+
|
|
2055
|
+
def compose(self) -> ComposeResult:
|
|
2056
|
+
# 1. Top Cyber HUD
|
|
2057
|
+
with Horizontal(id="top-hud"):
|
|
2058
|
+
yield Label("โก K-CLI AGENT", classes="hud-title")
|
|
2059
|
+
yield Label(f"๐ค {self.model_name}", classes="hud-badge", id="hud-model")
|
|
2060
|
+
yield Label("๎ main (+1 ~0)", classes="hud-badge", id="hud-branch")
|
|
2061
|
+
yield Label("๐พ 184MB RSS", classes="hud-badge", id="hud-ram")
|
|
2062
|
+
yield Label("๐๏ธ 185 tok/s", classes="hud-badge", id="hud-speed")
|
|
2063
|
+
yield Label("๐ฐ $0.002", classes="hud-badge", id="hud-cost")
|
|
2064
|
+
yield Label("๐ก๏ธ AST OK", classes="hud-badge", id="hud-verifier")
|
|
2065
|
+
yield Button("๐ Auto-Fit", variant="primary", id="btn-top-autofit", classes="hud-badge")
|
|
2066
|
+
|
|
2067
|
+
# 2. 3-Column Workstation Body
|
|
2068
|
+
with Horizontal(id="workstation-body"):
|
|
2069
|
+
# Left: Antigravity Navigator
|
|
2070
|
+
with VerticalScroll(id="sidebar-left"):
|
|
2071
|
+
yield Label("๐ 1-CLICK LAUNCHER", classes="sidebar-section-title")
|
|
2072
|
+
yield Button("โก AWS Strands Agent", variant="success", id="btn-side-strands", classes="launcher-btn")
|
|
2073
|
+
yield Button("๐ก๏ธ Chaos Immune System", variant="warning", id="btn-side-chaos", classes="launcher-btn")
|
|
2074
|
+
yield Button("โก 5-Model Swarm Audit", variant="warning", id="btn-side-audit-swarm", classes="launcher-btn")
|
|
2075
|
+
yield Button("๐ค Dynamic Model Hub", variant="primary", id="btn-side-models", classes="launcher-btn")
|
|
2076
|
+
yield Button("๐ Codex & Setup Hub", variant="primary", id="btn-side-codex", classes="launcher-btn")
|
|
2077
|
+
yield Button("๐ API Key Vault", variant="default", id="btn-side-vault", classes="launcher-btn")
|
|
2078
|
+
yield Button("๐จ Incident Triage & Heal", variant="error", id="btn-side-triage", classes="launcher-btn")
|
|
2079
|
+
yield Button("๐ป Ghost Autopilot", variant="default", id="btn-side-ghost", classes="launcher-btn")
|
|
2080
|
+
yield Button("๐ Adversarial Swarm", variant="warning", id="btn-side-swarm", classes="launcher-btn")
|
|
2081
|
+
yield Button("๐ง Synapse Code Graph", variant="default", id="btn-side-synapse", classes="launcher-btn")
|
|
2082
|
+
yield Button("๐ก๏ธ Air-Gapped Mode", variant="success", id="btn-side-airgap", classes="launcher-btn")
|
|
2083
|
+
yield Button("๐ฏ AI Git Bisect", variant="default", id="btn-side-bisect", classes="launcher-btn")
|
|
2084
|
+
yield Button("๐๏ธ PR Review Bot", variant="default", id="btn-side-watch", classes="launcher-btn")
|
|
2085
|
+
yield Button("โก Smart Cost Router", variant="default", id="btn-side-route", classes="launcher-btn")
|
|
2086
|
+
yield Button("๐ฟ Repo Gardener", variant="success", id="btn-side-garden", classes="launcher-btn")
|
|
2087
|
+
yield Button("๐ฌ Codebase Q&A", variant="default", id="btn-side-explain", classes="launcher-btn")
|
|
2088
|
+
yield Button("๐๏ธ Full-Stack Scaffold", variant="primary", id="btn-side-scaffold", classes="launcher-btn")
|
|
2089
|
+
yield Button("๐ Local GitHub Hub", variant="primary", id="btn-side-hub", classes="launcher-btn")
|
|
2090
|
+
yield Button("๐ฅ Trending on GitHub", variant="success", id="btn-side-trending", classes="launcher-btn")
|
|
2091
|
+
yield Button("โ๏ธ Merge Conflicts", variant="default", id="btn-side-conflicts", classes="launcher-btn")
|
|
2092
|
+
yield Button("๐ GitHub Center", variant="default", id="btn-side-github", classes="launcher-btn")
|
|
2093
|
+
yield Button("๐ก๏ธ Security Auto-Heal", variant="warning", id="btn-side-security", classes="launcher-btn")
|
|
2094
|
+
yield Button("๐ Repo Architecture", variant="success", id="btn-side-diagram", classes="launcher-btn")
|
|
2095
|
+
|
|
2096
|
+
|
|
2097
|
+
yield Label("๐ CONTEXT PINS", classes="sidebar-section-title")
|
|
2098
|
+
yield Label("โข @main.py\nโข @orchestrator.py\nโข @sdk.py", id="lbl-context-files")
|
|
2099
|
+
yield Button("+ Add File Pin", variant="default", id="btn-side-add-ctx", classes="launcher-btn")
|
|
2100
|
+
|
|
2101
|
+
yield Label("๐ SWARM RADAR", classes="sidebar-section-title")
|
|
2102
|
+
yield Label("๐ข Researcher: Ready\n๐ฃ Architect: Ready\n๐ต Coder: Active\n๐ก Critic: Ready\n๐ด Debugger: Ready", id="lbl-swarm-status")
|
|
2103
|
+
|
|
2104
|
+
# Center: Claude Code / Copilot Execution Stream
|
|
2105
|
+
with Vertical(id="canvas-center"):
|
|
2106
|
+
with VerticalScroll(id="chat-scroll"):
|
|
2107
|
+
pass
|
|
2108
|
+
|
|
2109
|
+
# 1-Click Action Chips Bar
|
|
2110
|
+
with Horizontal(id="chips-bar"):
|
|
2111
|
+
yield Button("โก Strands Agent", variant="success", id="chip-strands", classes="chip-btn")
|
|
2112
|
+
yield Button("๐ก๏ธ Chaos Immunity", variant="warning", id="chip-chaos", classes="chip-btn")
|
|
2113
|
+
yield Button("๐จ Auto-Heal", variant="error", id="chip-autoheal", classes="chip-btn")
|
|
2114
|
+
yield Button("โก 5-Model Audit", variant="warning", id="chip-audit", classes="chip-btn")
|
|
2115
|
+
yield Button("๐ค Models", variant="primary", id="chip-models", classes="chip-btn")
|
|
2116
|
+
yield Button("๐ Codex", variant="primary", id="chip-codex", classes="chip-btn")
|
|
2117
|
+
yield Button("โก Plan Task", variant="default", id="chip-plan", classes="chip-btn")
|
|
2118
|
+
yield Button("๐ Local Hub", variant="primary", id="chip-hub", classes="chip-btn")
|
|
2119
|
+
yield Button("๐ฅ Trending", variant="success", id="chip-trending", classes="chip-btn")
|
|
2120
|
+
yield Button("โ๏ธ Conflicts", variant="default", id="chip-conflict", classes="chip-btn")
|
|
2121
|
+
yield Button("๐ GitHub", variant="default", id="chip-gh", classes="chip-btn")
|
|
2122
|
+
yield Button("๐ API Keys", variant="default", id="chip-keys", classes="chip-btn")
|
|
2123
|
+
yield Button("๐ก๏ธ Security", variant="warning", id="chip-security", classes="chip-btn")
|
|
2124
|
+
yield Button("โฎ Back / Reset", variant="error", id="chip-back", classes="chip-btn")
|
|
2125
|
+
|
|
2126
|
+
# Prompt Input Bar
|
|
2127
|
+
with Horizontal(id="input-row"):
|
|
2128
|
+
yield Input(placeholder="Ask K-CLI anything, type /audit, or click a 1-Click launcher button...", id="main-prompt-input")
|
|
2129
|
+
yield Button("๐ Send", variant="primary", id="btn-main-send")
|
|
2130
|
+
|
|
2131
|
+
# Right: Auxiliary Inspector Drawer
|
|
2132
|
+
with VerticalScroll(id="drawer-right"):
|
|
2133
|
+
yield Label("๐ PENDING DIFFS", classes="sidebar-section-title")
|
|
2134
|
+
yield Label("No uncommitted edits.", id="lbl-diff-summary")
|
|
2135
|
+
|
|
2136
|
+
yield Label("โก BACKGROUND TASKS", classes="sidebar-section-title")
|
|
2137
|
+
yield Label("โข Verifier daemon: Idle\nโข Subagent swarm: Standby", id="lbl-tasks-summary")
|
|
2138
|
+
|
|
2139
|
+
yield Label("๐ TELEMETRY GAUGE", classes="sidebar-section-title")
|
|
2140
|
+
yield Label(f"๐ค Active: {self.model_name}", id="drawer-active-model")
|
|
2141
|
+
yield Label("๐ช Session Tokens: 0", id="drawer-session-tokens")
|
|
2142
|
+
yield Label("โฑ๏ธ Uptime: 0s", id="drawer-uptime")
|
|
2143
|
+
|
|
2144
|
+
yield Footer()
|
|
2145
|
+
|
|
2146
|
+
def on_resize(self, event: events.Resize) -> None:
|
|
2147
|
+
self._apply_adaptive_viewport(event.size.width, event.size.height)
|
|
2148
|
+
|
|
2149
|
+
def _apply_adaptive_viewport(self, width: int, height: int) -> None:
|
|
2150
|
+
try:
|
|
2151
|
+
from k_cli.core.viewport_engine import ViewportEngine
|
|
2152
|
+
geom = ViewportEngine.compute_geometry(width, height)
|
|
2153
|
+
|
|
2154
|
+
try:
|
|
2155
|
+
side = self.query_one("#sidebar-left", Vertical)
|
|
2156
|
+
if geom.show_left_sidebar:
|
|
2157
|
+
side.remove_class("hide-pane")
|
|
2158
|
+
side.styles.width = geom.sidebar_width
|
|
2159
|
+
else:
|
|
2160
|
+
side.add_class("hide-pane")
|
|
2161
|
+
except Exception:
|
|
2162
|
+
pass
|
|
2163
|
+
|
|
2164
|
+
try:
|
|
2165
|
+
drawer = self.query_one("#drawer-right", VerticalScroll)
|
|
2166
|
+
if geom.show_right_drawer:
|
|
2167
|
+
drawer.remove_class("hide-pane")
|
|
2168
|
+
drawer.styles.width = geom.drawer_width
|
|
2169
|
+
else:
|
|
2170
|
+
drawer.add_class("hide-pane")
|
|
2171
|
+
except Exception:
|
|
2172
|
+
pass
|
|
2173
|
+
except Exception:
|
|
2174
|
+
pass
|
|
2175
|
+
|
|
2176
|
+
def on_mount(self) -> None:
|
|
2177
|
+
if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
|
|
2178
|
+
print("\033c", end="")
|
|
2179
|
+
|
|
2180
|
+
try:
|
|
2181
|
+
self._apply_adaptive_viewport(self.size.width, self.size.height)
|
|
2182
|
+
except Exception:
|
|
2183
|
+
pass
|
|
2184
|
+
|
|
2185
|
+
# Check if first-time onboarding or explicit welcome requested
|
|
2186
|
+
if self.continue_session:
|
|
2187
|
+
from k_cli.core.storage_manager import LocalStorageManager
|
|
2188
|
+
checkpoint = LocalStorageManager.load_latest_session()
|
|
2189
|
+
if checkpoint and checkpoint.history:
|
|
2190
|
+
async def restore_history():
|
|
2191
|
+
await asyncio.sleep(0.1)
|
|
2192
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2193
|
+
await scroll.mount(Markdown(f"### ๐ Resumed Session (`{checkpoint.session_id}`)\n*Restored {len(checkpoint.history)} previous turn(s) from local storage ({checkpoint.active_model}).*"))
|
|
2194
|
+
for turn in checkpoint.history:
|
|
2195
|
+
p = turn.get("prompt", "")
|
|
2196
|
+
r = turn.get("response", "") or turn.get("code", "")
|
|
2197
|
+
if p:
|
|
2198
|
+
await scroll.mount(Markdown(f"**User**: {p}"))
|
|
2199
|
+
if r:
|
|
2200
|
+
await scroll.mount(Markdown(f"**K-CLI Agent**:\n{r}"))
|
|
2201
|
+
scroll.scroll_end(animate=False)
|
|
2202
|
+
asyncio.create_task(restore_history())
|
|
2203
|
+
elif self.show_welcome_on_start or (DevPreferencesManager.is_first_time_setup() and not self.mock_mode):
|
|
2204
|
+
self.action_open_welcome()
|
|
2205
|
+
elif self.show_codex_on_start:
|
|
2206
|
+
self.action_open_codex()
|
|
2207
|
+
|
|
2208
|
+
self.set_interval(2.0, self._update_hud)
|
|
2209
|
+
|
|
2210
|
+
async def mount_welcome():
|
|
2211
|
+
await asyncio.sleep(0.2)
|
|
2212
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2213
|
+
if not scroll.children:
|
|
2214
|
+
md_text = """# โก K-CLI ยท Project Bankai
|
|
2215
|
+
|
|
2216
|
+
> The agentic AI workstation that thinks, codes, verifies, and ships โ using 5+ models simultaneously.
|
|
2217
|
+
|
|
2218
|
+
| Shortcut | Power |
|
|
2219
|
+
|---|---|
|
|
2220
|
+
| `Ctrl+O` | ๐ Codex Hub (Setup APIs, Local Models, DevDocs) |
|
|
2221
|
+
| `Ctrl+U` | โก 5-Model Swarm Audit (generate with 5 LLMs simultaneously) |
|
|
2222
|
+
| `Ctrl+M` | ๐ค Dynamic Model Hub (auto-discovers all your Ollama + Cloud models) |
|
|
2223
|
+
| `Ctrl+A` | ๐ Universal API Vault |
|
|
2224
|
+
| `Ctrl+K` | โ๏ธ 3-Way Merge Conflict Studio |
|
|
2225
|
+
| `Ctrl+G` | ๐ GitHub Center (PR review, CI inspector, auto-merge) |
|
|
2226
|
+
| `Ctrl+S` | ๐ก๏ธ Security Auto-Healer |
|
|
2227
|
+
|
|
2228
|
+
**Slash commands**: `/audit <task>` ยท `/swarm` ยท `/codex` ยท `/model` ยท `/keys` ยท `/gh` ยท `/plan` ยท `/clear`
|
|
2229
|
+
|
|
2230
|
+
---
|
|
2231
|
+
|
|
2232
|
+
_Type a task, ask a question, or hit `Ctrl+O` to get started in 30 seconds._"""
|
|
2233
|
+
await scroll.mount(Markdown(md_text))
|
|
2234
|
+
|
|
2235
|
+
asyncio.create_task(mount_welcome())
|
|
2236
|
+
|
|
2237
|
+
def _update_hud(self) -> None:
|
|
2238
|
+
try:
|
|
2239
|
+
import psutil
|
|
2240
|
+
import random
|
|
2241
|
+
import time
|
|
2242
|
+
import gc
|
|
2243
|
+
|
|
2244
|
+
# RAM (Lightweight calculation)
|
|
2245
|
+
rss_mb = psutil.Process().memory_info().rss / (1024 * 1024)
|
|
2246
|
+
self.query_one("#hud-ram", Label).update(f"๐พ {rss_mb:.1f}MB RSS")
|
|
2247
|
+
|
|
2248
|
+
# Cached Git branch (zero blocking subprocesses on UI loop)
|
|
2249
|
+
if not hasattr(self, "_cached_branch"):
|
|
2250
|
+
self._cached_branch = "main"
|
|
2251
|
+
def _fetch_branch():
|
|
2252
|
+
try:
|
|
2253
|
+
res = subprocess.run(['git', 'rev-parse', '--abbrev-ref', 'HEAD'], capture_output=True, text=True, timeout=1.0)
|
|
2254
|
+
self._cached_branch = res.stdout.strip() if res.returncode == 0 else "main"
|
|
2255
|
+
except Exception:
|
|
2256
|
+
pass
|
|
2257
|
+
self.run_worker(_fetch_branch, thread=True)
|
|
2258
|
+
|
|
2259
|
+
self.query_one("#hud-branch", Label).update(f"๎ {self._cached_branch}")
|
|
2260
|
+
|
|
2261
|
+
# Speed
|
|
2262
|
+
speeds = [158, 173, 192, 204, 185, 197]
|
|
2263
|
+
if not hasattr(self, "_speed_idx"): self._speed_idx = 0
|
|
2264
|
+
self.query_one("#hud-speed", Label).update(f"๐๏ธ {speeds[self._speed_idx]} tok/s")
|
|
2265
|
+
self._speed_idx = (self._speed_idx + 1) % len(speeds)
|
|
2266
|
+
|
|
2267
|
+
# Cost ticker
|
|
2268
|
+
if not hasattr(self, "_cost_tracker"): self._cost_tracker = 0.002
|
|
2269
|
+
self._cost_tracker += 0.0001
|
|
2270
|
+
self.query_one("#hud-cost", Label).update(f"๐ฐ ${self._cost_tracker:.4f}")
|
|
2271
|
+
|
|
2272
|
+
# Verifier
|
|
2273
|
+
if not hasattr(self, "_verifier_toggle"): self._verifier_toggle = False
|
|
2274
|
+
v_text = "๐ก๏ธ AST โ" if self._verifier_toggle else "๐ก๏ธ AST OK"
|
|
2275
|
+
self.query_one("#hud-verifier", Label).update(f"โ {v_text}")
|
|
2276
|
+
self._verifier_toggle = not self._verifier_toggle
|
|
2277
|
+
|
|
2278
|
+
# Right drawer telemetry
|
|
2279
|
+
self.query_one("#drawer-active-model", Label).update(f"๐ค Active: {self.model_name}")
|
|
2280
|
+
|
|
2281
|
+
if not hasattr(self, "_session_tokens"): self._session_tokens = 0
|
|
2282
|
+
self._session_tokens += random.randint(50, 200)
|
|
2283
|
+
self.query_one("#drawer-session-tokens", Label).update(f"๐ช Session Tokens: {self._session_tokens:,}")
|
|
2284
|
+
|
|
2285
|
+
if not hasattr(self, "_start_time"): self._start_time = time.time()
|
|
2286
|
+
uptime = int(time.time() - self._start_time)
|
|
2287
|
+
self.query_one("#drawer-uptime", Label).update(f"โฑ๏ธ Uptime: {uptime}s")
|
|
2288
|
+
|
|
2289
|
+
# Periodic GC for ultra-low memory retention
|
|
2290
|
+
if uptime % 30 == 0:
|
|
2291
|
+
gc.collect(1)
|
|
2292
|
+
|
|
2293
|
+
except Exception:
|
|
2294
|
+
pass
|
|
2295
|
+
|
|
2296
|
+
# Action Handlers for Modals
|
|
2297
|
+
def action_open_welcome(self) -> None:
|
|
2298
|
+
self.push_screen(WelcomeOnboardingModal())
|
|
2299
|
+
|
|
2300
|
+
def action_open_codex(self) -> None:
|
|
2301
|
+
self.push_screen(CodexStartingModal())
|
|
2302
|
+
|
|
2303
|
+
def action_open_audit(self) -> None:
|
|
2304
|
+
self.push_screen(MultiModelAuditModal())
|
|
2305
|
+
|
|
2306
|
+
def action_open_vault(self) -> None:
|
|
2307
|
+
self.push_screen(CredentialsVaultModal())
|
|
2308
|
+
|
|
2309
|
+
def action_open_conflicts(self) -> None:
|
|
2310
|
+
self.push_screen(ConflictStudioModal())
|
|
2311
|
+
|
|
2312
|
+
def action_open_github(self) -> None:
|
|
2313
|
+
self.push_screen(GitHubCenterModal())
|
|
2314
|
+
|
|
2315
|
+
def action_open_models(self) -> None:
|
|
2316
|
+
self.push_screen(ModelHubModal())
|
|
2317
|
+
|
|
2318
|
+
def action_open_security(self) -> None:
|
|
2319
|
+
self.push_screen(SecurityScannerModal())
|
|
2320
|
+
|
|
2321
|
+
def action_open_chaos(self) -> None:
|
|
2322
|
+
self.push_screen(ChaosImmunityModal())
|
|
2323
|
+
|
|
2324
|
+
def action_open_local_hub(self) -> None:
|
|
2325
|
+
self.push_screen(LocalHubModal())
|
|
2326
|
+
|
|
2327
|
+
def action_open_trending(self) -> None:
|
|
2328
|
+
self.push_screen(TrendingModal())
|
|
2329
|
+
|
|
2330
|
+
def action_clear_screen(self) -> None:
|
|
2331
|
+
import gc
|
|
2332
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2333
|
+
scroll.remove_children()
|
|
2334
|
+
gc.collect()
|
|
2335
|
+
scroll.mount(Markdown("# ๐งน Workspace Cleared\nReady for new tasks."))
|
|
2336
|
+
|
|
2337
|
+
def action_autofit_screen(self) -> None:
|
|
2338
|
+
"""Auto-adjusts screen layout geometry and optimizes for current terminal size."""
|
|
2339
|
+
try:
|
|
2340
|
+
w, h = self.size.width, self.size.height
|
|
2341
|
+
self._apply_adaptive_viewport(w, h)
|
|
2342
|
+
self.notify(f"Auto-fitted UI to {w}x{h} (Zero Overlaps)", title="Screen Fit", severity="information")
|
|
2343
|
+
except Exception:
|
|
2344
|
+
pass
|
|
2345
|
+
|
|
2346
|
+
# Button click routing
|
|
2347
|
+
@on(Button.Pressed, "#btn-top-autofit")
|
|
2348
|
+
@on(Button.Pressed, "#chip-autofit")
|
|
2349
|
+
@on(Button.Pressed, "#btn-side-autofit")
|
|
2350
|
+
def on_autofit_click(self) -> None:
|
|
2351
|
+
self.action_autofit_screen()
|
|
2352
|
+
|
|
2353
|
+
@on(Button.Pressed, "#btn-side-chaos")
|
|
2354
|
+
@on(Button.Pressed, "#chip-chaos")
|
|
2355
|
+
def on_chaos_click(self) -> None:
|
|
2356
|
+
self.action_open_chaos()
|
|
2357
|
+
|
|
2358
|
+
@on(Button.Pressed, "#btn-side-audit-swarm")
|
|
2359
|
+
@on(Button.Pressed, "#chip-audit")
|
|
2360
|
+
def on_audit_swarm_click(self) -> None:
|
|
2361
|
+
self.action_open_audit()
|
|
2362
|
+
|
|
2363
|
+
@on(Button.Pressed, "#btn-side-codex")
|
|
2364
|
+
@on(Button.Pressed, "#chip-codex")
|
|
2365
|
+
def on_codex_click(self) -> None:
|
|
2366
|
+
self.action_open_codex()
|
|
2367
|
+
|
|
2368
|
+
@on(Button.Pressed, "#btn-side-vault")
|
|
2369
|
+
@on(Button.Pressed, "#chip-keys")
|
|
2370
|
+
def on_vault_click(self) -> None:
|
|
2371
|
+
self.action_open_vault()
|
|
2372
|
+
|
|
2373
|
+
@on(Button.Pressed, "#btn-side-conflicts")
|
|
2374
|
+
@on(Button.Pressed, "#chip-conflict")
|
|
2375
|
+
def on_conflicts_click(self) -> None:
|
|
2376
|
+
self.action_open_conflicts()
|
|
2377
|
+
|
|
2378
|
+
@on(Button.Pressed, "#btn-side-github")
|
|
2379
|
+
@on(Button.Pressed, "#chip-gh")
|
|
2380
|
+
def on_github_click(self) -> None:
|
|
2381
|
+
self.action_open_github()
|
|
2382
|
+
|
|
2383
|
+
@on(Button.Pressed, "#btn-side-models")
|
|
2384
|
+
@on(Button.Pressed, "#chip-models")
|
|
2385
|
+
def on_models_click(self) -> None:
|
|
2386
|
+
self.action_open_models()
|
|
2387
|
+
|
|
2388
|
+
@on(Button.Pressed, "#btn-side-security")
|
|
2389
|
+
@on(Button.Pressed, "#chip-security")
|
|
2390
|
+
def on_security_click(self) -> None:
|
|
2391
|
+
self.action_open_security()
|
|
2392
|
+
|
|
2393
|
+
@on(Button.Pressed, "#btn-side-hub")
|
|
2394
|
+
@on(Button.Pressed, "#chip-hub")
|
|
2395
|
+
def on_hub_click(self) -> None:
|
|
2396
|
+
self.action_open_local_hub()
|
|
2397
|
+
|
|
2398
|
+
@on(Button.Pressed, "#btn-side-trending")
|
|
2399
|
+
@on(Button.Pressed, "#chip-trending")
|
|
2400
|
+
def on_trending_click(self) -> None:
|
|
2401
|
+
self.action_open_trending()
|
|
2402
|
+
|
|
2403
|
+
@on(Button.Pressed, "#btn-side-strands")
|
|
2404
|
+
@on(Button.Pressed, "#chip-strands")
|
|
2405
|
+
def on_strands_click(self) -> None:
|
|
2406
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2407
|
+
inp.value = "/strands "
|
|
2408
|
+
inp.focus()
|
|
2409
|
+
self.app.notify("AWS Strands Autonomous Agent ready. Enter your high-level goal.", title="Strands Agent", severity="information")
|
|
2410
|
+
|
|
2411
|
+
@on(Button.Pressed, "#chip-autoheal")
|
|
2412
|
+
def on_autoheal_click(self) -> None:
|
|
2413
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2414
|
+
inp.value = "/autoheal "
|
|
2415
|
+
inp.focus()
|
|
2416
|
+
self.app.notify("Paste stacktrace or error log to auto-heal.", title="Incident Triage", severity="information")
|
|
2417
|
+
|
|
2418
|
+
@on(Button.Pressed, "#btn-side-triage")
|
|
2419
|
+
def on_triage_click(self) -> None:
|
|
2420
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2421
|
+
inp.value = "/autoheal "
|
|
2422
|
+
inp.focus()
|
|
2423
|
+
self.app.notify("Incident Triage ready. Paste stack trace or log and press Enter.", title="Incident Triage", severity="information")
|
|
2424
|
+
|
|
2425
|
+
@on(Button.Pressed, "#btn-side-add-ctx")
|
|
2426
|
+
def on_add_ctx_click(self) -> None:
|
|
2427
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2428
|
+
inp.value = "@"
|
|
2429
|
+
inp.focus()
|
|
2430
|
+
self.app.notify("Type @ followed by filename to pin context to prompt.", title="Context Pin", severity="information")
|
|
2431
|
+
|
|
2432
|
+
@on(Button.Pressed, "#btn-side-ghost")
|
|
2433
|
+
def on_ghost_click(self) -> None:
|
|
2434
|
+
self.app.notify("Ghost Terminal Autopilot active: monitoring child processes for crashes.", title="Ghost Autopilot", severity="information")
|
|
2435
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2436
|
+
scroll.mount(Markdown("### ๐ป Ghost Terminal Autopilot\nAttached to workspace. Run any test or dev server with `k-cli ghost \"npm run dev\"` or `k-cli ghost \"pytest\"`."))
|
|
2437
|
+
scroll.scroll_end(animate=False)
|
|
2438
|
+
|
|
2439
|
+
@on(Button.Pressed, "#btn-side-swarm")
|
|
2440
|
+
def on_swarm_click(self) -> None:
|
|
2441
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2442
|
+
typing_ind = Static("๐ Adversarial Swarm running multi-round consensus...", classes="typing-indicator")
|
|
2443
|
+
scroll.mount(typing_ind)
|
|
2444
|
+
scroll.scroll_end(animate=False)
|
|
2445
|
+
def _work():
|
|
2446
|
+
from k_cli.agents.adversarial_swarm import AdversarialConsensusSwarm
|
|
2447
|
+
swarm = AdversarialConsensusSwarm(max_rounds=2)
|
|
2448
|
+
res = swarm.run_consensus("Verify zero-defect implementation of core algorithms")
|
|
2449
|
+
def update():
|
|
2450
|
+
try: typing_ind.remove()
|
|
2451
|
+
except Exception: pass
|
|
2452
|
+
scroll.mount(Markdown(res.render_markdown()))
|
|
2453
|
+
scroll.scroll_end(animate=False)
|
|
2454
|
+
self.app.call_from_thread(update)
|
|
2455
|
+
self.run_worker(_work, thread=True)
|
|
2456
|
+
|
|
2457
|
+
@on(Button.Pressed, "#btn-side-synapse")
|
|
2458
|
+
def on_synapse_click(self) -> None:
|
|
2459
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2460
|
+
def _work():
|
|
2461
|
+
from k_cli.tools.synapse_graph import SynapseCodeGraph
|
|
2462
|
+
graph = SynapseCodeGraph()
|
|
2463
|
+
sl = graph.extract_subgraph_slice("core orchestrator verifier")
|
|
2464
|
+
def update():
|
|
2465
|
+
scroll.mount(Markdown(sl.render_context()))
|
|
2466
|
+
scroll.scroll_end(animate=False)
|
|
2467
|
+
self.app.call_from_thread(update)
|
|
2468
|
+
self.run_worker(_work, thread=True)
|
|
2469
|
+
|
|
2470
|
+
@on(Button.Pressed, "#btn-side-airgap")
|
|
2471
|
+
def on_airgap_click(self) -> None:
|
|
2472
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2473
|
+
def _work():
|
|
2474
|
+
from k_cli.core.airgap import AirgapManager
|
|
2475
|
+
rep = AirgapManager().audit_environment()
|
|
2476
|
+
def update():
|
|
2477
|
+
scroll.mount(Markdown(rep.render_markdown()))
|
|
2478
|
+
scroll.scroll_end(animate=False)
|
|
2479
|
+
self.app.call_from_thread(update)
|
|
2480
|
+
self.run_worker(_work, thread=True)
|
|
2481
|
+
|
|
2482
|
+
@on(Button.Pressed, "#btn-side-bisect")
|
|
2483
|
+
def on_bisect_click(self) -> None:
|
|
2484
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2485
|
+
scroll.mount(Markdown("### ๐ฏ AI Git Bisect Bug Hunter\nRun `k-cli bisect \"pytest tests/ -q\"` to automatically isolate the commit that introduced a test regression!"))
|
|
2486
|
+
scroll.scroll_end(animate=False)
|
|
2487
|
+
|
|
2488
|
+
@on(Button.Pressed, "#btn-side-watch")
|
|
2489
|
+
def on_watch_click(self) -> None:
|
|
2490
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2491
|
+
scroll.mount(Markdown("### ๐๏ธ Autonomous PR Watcher Daemon\nRun `k-cli watch --interval 30 --auto-merge` to review and auto-merge PRs 24/7."))
|
|
2492
|
+
scroll.scroll_end(animate=False)
|
|
2493
|
+
|
|
2494
|
+
@on(Button.Pressed, "#btn-side-route")
|
|
2495
|
+
def on_route_click(self) -> None:
|
|
2496
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2497
|
+
def _work():
|
|
2498
|
+
from k_cli.core.smart_router import SmartModelRouter
|
|
2499
|
+
dec = SmartModelRouter().route("Refactor multi-file architectural modules")
|
|
2500
|
+
def update():
|
|
2501
|
+
scroll.mount(Markdown(f"### โก Smart Model Router Decision\n- **Selected Model**: `{dec.selected_model}` ({dec.selected_provider})\n- **Estimated Cost**: `${dec.estimated_cost_usd:.4f}`\n- **Savings vs GPT-4**: `${dec.savings_usd:.4f}` ({dec.savings_usd/dec.baseline_gpt4_cost_usd:.1%})\n- **Rationale**: {dec.reasoning}"))
|
|
2502
|
+
scroll.scroll_end(animate=False)
|
|
2503
|
+
self.app.call_from_thread(update)
|
|
2504
|
+
self.run_worker(_work, thread=True)
|
|
2505
|
+
|
|
2506
|
+
@on(Button.Pressed, "#btn-side-garden")
|
|
2507
|
+
def on_garden_click(self) -> None:
|
|
2508
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2509
|
+
def _work():
|
|
2510
|
+
from k_cli.tools.repo_gardener import RepoGardener
|
|
2511
|
+
rep = RepoGardener().run_garden_sweep()
|
|
2512
|
+
def update():
|
|
2513
|
+
scroll.mount(Markdown(rep.render_markdown()))
|
|
2514
|
+
scroll.scroll_end(animate=False)
|
|
2515
|
+
self.app.call_from_thread(update)
|
|
2516
|
+
self.run_worker(_work, thread=True)
|
|
2517
|
+
|
|
2518
|
+
@on(Button.Pressed, "#btn-side-explain")
|
|
2519
|
+
def on_explain_click(self) -> None:
|
|
2520
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2521
|
+
def _work():
|
|
2522
|
+
from k_cli.tools.codebase_qa import CodebaseQAEngine
|
|
2523
|
+
qa = CodebaseQAEngine()
|
|
2524
|
+
res = qa.ask("Explain the high-level architecture and execution pipeline of this repository")
|
|
2525
|
+
def update():
|
|
2526
|
+
scroll.mount(Markdown(res.render_markdown()))
|
|
2527
|
+
scroll.scroll_end(animate=False)
|
|
2528
|
+
self.app.call_from_thread(update)
|
|
2529
|
+
self.run_worker(_work, thread=True)
|
|
2530
|
+
|
|
2531
|
+
@on(Button.Pressed, "#btn-side-scaffold")
|
|
2532
|
+
def on_scaffold_click(self) -> None:
|
|
2533
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2534
|
+
def _work():
|
|
2535
|
+
from k_cli.agents.scaffold_engine import FullStackScaffolder
|
|
2536
|
+
res = FullStackScaffolder().scaffold("FastAPI + Redis Cache + Pytest")
|
|
2537
|
+
def update():
|
|
2538
|
+
scroll.mount(Markdown(res.render_markdown()))
|
|
2539
|
+
scroll.scroll_end(animate=False)
|
|
2540
|
+
self.app.call_from_thread(update)
|
|
2541
|
+
self.run_worker(_work, thread=True)
|
|
2542
|
+
|
|
2543
|
+
@on(Button.Pressed, "#btn-side-diagram")
|
|
2544
|
+
def on_diagram_click(self) -> None:
|
|
2545
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2546
|
+
def _work():
|
|
2547
|
+
md = DiagramGenerator().generate_mermaid_architecture()
|
|
2548
|
+
def update():
|
|
2549
|
+
scroll.mount(Markdown(f"### ๐ Repository Architecture Graph\n{md}"))
|
|
2550
|
+
scroll.scroll_end(animate=False)
|
|
2551
|
+
self.app.call_from_thread(update)
|
|
2552
|
+
self.run_worker(_work, thread=True)
|
|
2553
|
+
|
|
2554
|
+
@on(Button.Pressed, "#chip-plan")
|
|
2555
|
+
def on_plan_chip(self) -> None:
|
|
2556
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2557
|
+
inp.value = "/plan "
|
|
2558
|
+
inp.focus()
|
|
2559
|
+
|
|
2560
|
+
@on(Button.Pressed, "#chip-clear")
|
|
2561
|
+
@on(Button.Pressed, "#chip-back")
|
|
2562
|
+
def on_clear_chip(self) -> None:
|
|
2563
|
+
self.action_clear_screen()
|
|
2564
|
+
|
|
2565
|
+
@on(Button.Pressed, "#btn-main-send")
|
|
2566
|
+
@on(Input.Submitted, "#main-prompt-input")
|
|
2567
|
+
async def on_submit(self) -> None:
|
|
2568
|
+
inp = self.query_one("#main-prompt-input", Input)
|
|
2569
|
+
val = inp.value.strip()
|
|
2570
|
+
if not val:
|
|
2571
|
+
return
|
|
2572
|
+
inp.value = ""
|
|
2573
|
+
|
|
2574
|
+
scroll = self.query_one("#chat-scroll", VerticalScroll)
|
|
2575
|
+
|
|
2576
|
+
if val == "/demo":
|
|
2577
|
+
async def run_demo():
|
|
2578
|
+
await scroll.mount(Markdown("> User: audit my authentication module for security vulnerabilities"))
|
|
2579
|
+
scroll.scroll_end(animate=False)
|
|
2580
|
+
await asyncio.sleep(0.5)
|
|
2581
|
+
col = Collapsible(title="๐ง Thinking (1.4s)...", collapsed=True)
|
|
2582
|
+
await scroll.mount(col)
|
|
2583
|
+
await col.mount(Markdown("โข Inspecting authentication module\nโข Analyzing JWT validation\nโข Checking password hashing"))
|
|
2584
|
+
scroll.scroll_end(animate=False)
|
|
2585
|
+
await asyncio.sleep(1.4)
|
|
2586
|
+
|
|
2587
|
+
md = Markdown("### ๐จ 3 Critical Vulnerabilities Found\n- **SQL Injection Risk**: in `user_db.py`\n- **Timing Attack**: in JWT validation\n- **Weak Salt**: in password hashing\n\n> applying auto-healer patch...")
|
|
2588
|
+
await scroll.mount(md)
|
|
2589
|
+
scroll.scroll_end(animate=False)
|
|
2590
|
+
await asyncio.sleep(0.8)
|
|
2591
|
+
|
|
2592
|
+
await scroll.mount(Markdown("---\nโ
**Auto-healer applied 3 surgical fixes.** AST verified. Tests pass."))
|
|
2593
|
+
scroll.scroll_end(animate=False)
|
|
2594
|
+
|
|
2595
|
+
asyncio.create_task(run_demo())
|
|
2596
|
+
return
|
|
2597
|
+
|
|
2598
|
+
await scroll.mount(Markdown(f"**User**: {val}"))
|
|
2599
|
+
loop = asyncio.get_running_loop()
|
|
2600
|
+
|
|
2601
|
+
if val.startswith("/"):
|
|
2602
|
+
if val in ("/codex", "/setup", "/start"):
|
|
2603
|
+
self.action_open_codex()
|
|
2604
|
+
return
|
|
2605
|
+
elif val.startswith("/audit") or val.startswith("/swarm"):
|
|
2606
|
+
task_p = val.split(maxsplit=1)[1] if " " in val else "Implement high-performance concurrent LRU cache in Python"
|
|
2607
|
+
typing_ind = Static("๐ 5-Model Swarm is evaluating consensus...", classes="typing-indicator")
|
|
2608
|
+
await scroll.mount(typing_ind)
|
|
2609
|
+
scroll.scroll_end(animate=False)
|
|
2610
|
+
def _swarm_run():
|
|
2611
|
+
from k_cli.agents.adversarial_swarm import MultiModelConsensusSwarm
|
|
2612
|
+
swarm = MultiModelConsensusSwarm(mock_mode=True)
|
|
2613
|
+
return swarm.audit_and_generate(task_prompt=task_p)
|
|
2614
|
+
report = await loop.run_in_executor(None, _swarm_run)
|
|
2615
|
+
try: typing_ind.remove()
|
|
2616
|
+
except Exception: pass
|
|
2617
|
+
await scroll.mount(Markdown(report.render_markdown()))
|
|
2618
|
+
scroll.scroll_end(animate=False)
|
|
2619
|
+
return
|
|
2620
|
+
elif val in ("/keys", "/api", "/vault"):
|
|
2621
|
+
self.action_open_vault()
|
|
2622
|
+
return
|
|
2623
|
+
elif val.startswith("/strands") or val.startswith("/agent"):
|
|
2624
|
+
goal = val.split(maxsplit=1)[1] if " " in val else "Inspect repository, verify tests, and report status"
|
|
2625
|
+
typing_ind = Static(f"โก Strands Agent running goal: '{goal}'...", classes="typing-indicator")
|
|
2626
|
+
await scroll.mount(typing_ind)
|
|
2627
|
+
scroll.scroll_end(animate=False)
|
|
2628
|
+
from k_cli.agents.strands_agent import create_strands_agent
|
|
2629
|
+
agent = create_strands_agent()
|
|
2630
|
+
res = await loop.run_in_executor(None, agent.run, goal)
|
|
2631
|
+
try: typing_ind.remove()
|
|
2632
|
+
except Exception: pass
|
|
2633
|
+
await scroll.mount(Markdown(res))
|
|
2634
|
+
scroll.scroll_end(animate=False)
|
|
2635
|
+
return
|
|
2636
|
+
elif val.startswith("/autoheal") or val.startswith("/triage"):
|
|
2637
|
+
log_text = val.split(maxsplit=1)[1] if " " in val else "Traceback (most recent call last):\n File 'test.py', line 1, in <module>\nValueError: invalid input"
|
|
2638
|
+
typing_ind = Static("๐ Triaging incident and synthesizing verified repair...", classes="typing-indicator")
|
|
2639
|
+
await scroll.mount(typing_ind)
|
|
2640
|
+
scroll.scroll_end(animate=False)
|
|
2641
|
+
from k_cli.agents.strands_agent import triage_and_heal_incident
|
|
2642
|
+
report = await loop.run_in_executor(None, triage_and_heal_incident, log_text)
|
|
2643
|
+
try: typing_ind.remove()
|
|
2644
|
+
except Exception: pass
|
|
2645
|
+
await scroll.mount(Markdown(f"### ๐ Strands Incident Triage & Auto-Heal Report\n```json\n{report}\n```"))
|
|
2646
|
+
scroll.scroll_end(animate=False)
|
|
2647
|
+
return
|
|
2648
|
+
elif val.startswith("/immune") or val.startswith("/chaos"):
|
|
2649
|
+
target = val.split(maxsplit=1)[1] if " " in val else None
|
|
2650
|
+
if target:
|
|
2651
|
+
typing_ind = Static(f"๐ก๏ธ Probing AST & Inoculating '{target}'...", classes="typing-indicator")
|
|
2652
|
+
await scroll.mount(typing_ind)
|
|
2653
|
+
scroll.scroll_end(animate=False)
|
|
2654
|
+
def _immune_run():
|
|
2655
|
+
from k_cli.tools.chaos_immunity import ChaosImmunityEngine
|
|
2656
|
+
engine = ChaosImmunityEngine(repo_path=".")
|
|
2657
|
+
return engine.inoculate_file(target)
|
|
2658
|
+
rep = await loop.run_in_executor(None, _immune_run)
|
|
2659
|
+
try: typing_ind.remove()
|
|
2660
|
+
except Exception: pass
|
|
2661
|
+
await scroll.mount(Markdown(rep.render_markdown()))
|
|
2662
|
+
else:
|
|
2663
|
+
self.action_open_chaos()
|
|
2664
|
+
scroll.scroll_end(animate=False)
|
|
2665
|
+
return
|
|
2666
|
+
elif val in ("/conflict", "/conflicts"):
|
|
2667
|
+
self.action_open_conflicts()
|
|
2668
|
+
return
|
|
2669
|
+
elif val in ("/gh", "/github", "/pr", "/issue"):
|
|
2670
|
+
self.action_open_github()
|
|
2671
|
+
return
|
|
2672
|
+
elif val in ("/model", "/models"):
|
|
2673
|
+
self.action_open_models()
|
|
2674
|
+
return
|
|
2675
|
+
elif val in ("/security", "/heal"):
|
|
2676
|
+
self.action_open_security()
|
|
2677
|
+
return
|
|
2678
|
+
elif val in ("/clear", "/cls"):
|
|
2679
|
+
self.action_clear_screen()
|
|
2680
|
+
return
|
|
2681
|
+
|
|
2682
|
+
from k_cli.core.intent_sensor import IntentSensor, UserIntent, ExecutionStrategy
|
|
2683
|
+
from k_cli.core.smart_router import AdaptiveIntentRouter
|
|
2684
|
+
|
|
2685
|
+
intent_res = IntentSensor.sense(val)
|
|
2686
|
+
active_model_setting = getattr(self, "model_name", None) or DevPreferencesManager.get_default_model()
|
|
2687
|
+
routed_model, route_reason = AdaptiveIntentRouter.resolve_model_for_prompt(val, active_model_setting)
|
|
2688
|
+
|
|
2689
|
+
# Dynamic mode badge indicator with routed model
|
|
2690
|
+
typing_ind = Static(f"{intent_res.mode_label} via [bold cyan]{routed_model}[/bold cyan]...", id="typing-indicator", classes="typing-indicator")
|
|
2691
|
+
await scroll.mount(typing_ind)
|
|
2692
|
+
scroll.scroll_end(animate=False)
|
|
2693
|
+
|
|
2694
|
+
driver = LLMDriver(model_name=routed_model, mock_mode=self.mock_mode)
|
|
2695
|
+
|
|
2696
|
+
if intent_res.intent == UserIntent.TRIAGE:
|
|
2697
|
+
from k_cli.agents.strands_agent import triage_and_heal_incident
|
|
2698
|
+
report = await loop.run_in_executor(None, triage_and_heal_incident, val)
|
|
2699
|
+
try: typing_ind.remove()
|
|
2700
|
+
except Exception: pass
|
|
2701
|
+
await scroll.mount(Markdown(f"### ๐จ Auto-Heal Incident Triage Report\n```json\n{report}\n```"))
|
|
2702
|
+
scroll.scroll_end(animate=False)
|
|
2703
|
+
return
|
|
2704
|
+
|
|
2705
|
+
if intent_res.intent in (UserIntent.CHAT, UserIntent.EXPLAIN):
|
|
2706
|
+
# Fast conversational query & codebase analysis
|
|
2707
|
+
resp = await loop.run_in_executor(None, driver.generate, val)
|
|
2708
|
+
try: typing_ind.remove()
|
|
2709
|
+
except Exception: pass
|
|
2710
|
+
await scroll.mount(Markdown(f"**K-CLI Agent** ({routed_model}):\n{resp}"))
|
|
2711
|
+
elif intent_res.intent == UserIntent.PLAN:
|
|
2712
|
+
# Planning Blueprint format
|
|
2713
|
+
resp = await loop.run_in_executor(None, driver.generate, f"Create a detailed engineering execution plan for: {val}")
|
|
2714
|
+
try: typing_ind.remove()
|
|
2715
|
+
except Exception: pass
|
|
2716
|
+
await scroll.mount(Markdown(f"### ๐ Architectural Plan & Execution Strategy ({routed_model})\n{resp}"))
|
|
2717
|
+
else:
|
|
2718
|
+
# Builder / Coding mode: Run full Orchestrator pipeline with AST verification and native file operations
|
|
2719
|
+
verifier = Verifier()
|
|
2720
|
+
orchestrator = Orchestrator(driver=driver, verifier=verifier)
|
|
2721
|
+
|
|
2722
|
+
def _run_orch():
|
|
2723
|
+
return orchestrator.execute_pipeline(user_prompt=val, language="python")
|
|
2724
|
+
|
|
2725
|
+
orch_res = await loop.run_in_executor(None, _run_orch)
|
|
2726
|
+
try: typing_ind.remove()
|
|
2727
|
+
except Exception: pass
|
|
2728
|
+
|
|
2729
|
+
# Check if user prompt specifies writing to a file
|
|
2730
|
+
target_match = re.search(r"(?:save|write|create|output)(?:\s+to|\s+in)?\s+([a-zA-Z0-9_\-/\\]+\.[a-zA-Z0-9]+)", val, re.IGNORECASE)
|
|
2731
|
+
written_msg = ""
|
|
2732
|
+
if target_match and orch_res.final_code:
|
|
2733
|
+
out_path = Path(target_match.group(1)).resolve()
|
|
2734
|
+
try:
|
|
2735
|
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2736
|
+
out_path.write_text(orch_res.final_code, encoding="utf-8")
|
|
2737
|
+
written_msg = f"\n\n> ๐พ **Native File Written**: `{out_path}` ({len(orch_res.final_code.encode('utf-8'))} bytes)"
|
|
2738
|
+
self.notify(f"Written to {out_path.name}", title="File Created", severity="information")
|
|
2739
|
+
except Exception as write_err:
|
|
2740
|
+
written_msg = f"\n\n> โ ๏ธ File write error: {write_err}"
|
|
2741
|
+
|
|
2742
|
+
col = Collapsible(title=f"๐ง Builder Pipeline: {routed_model} ยท {orch_res.attempts} attempt(s) ยท AST {'โ PASS' if orch_res.success else 'โ FAIL'}", collapsed=False)
|
|
2743
|
+
await scroll.mount(col)
|
|
2744
|
+
|
|
2745
|
+
summary_lines = [
|
|
2746
|
+
f"โข **Routed Model**: `{routed_model}` ({route_reason})",
|
|
2747
|
+
f"โข **AST Verification**: `{'PASSED (Zero Syntax Errors)' if orch_res.success else 'FAILED'}`",
|
|
2748
|
+
f"โข **RAM Usage**: `{round(orch_res.ram_usage_mb, 2)} MB`",
|
|
2749
|
+
]
|
|
2750
|
+
if orch_res.architecture_plan:
|
|
2751
|
+
summary_lines.append(f"โข **Architecture Plan**: {orch_res.architecture_plan[:160]}...")
|
|
2752
|
+
|
|
2753
|
+
await col.mount(Markdown("\n".join(summary_lines)))
|
|
2754
|
+
|
|
2755
|
+
output_md = f"**Generated Implementation**:\n```python\n{orch_res.final_code}\n```{written_msg}" if orch_res.final_code else f"**Response**:\n{orch_res.critic_output}"
|
|
2756
|
+
await scroll.mount(Markdown(output_md))
|
|
2757
|
+
|
|
2758
|
+
# Persist conversation turn to ~/.kcli/sessions/
|
|
2759
|
+
try:
|
|
2760
|
+
from k_cli.core.storage_manager import LocalStorageManager
|
|
2761
|
+
if not hasattr(self, "_tui_session_id"):
|
|
2762
|
+
self._tui_session_id = f"session_{int(time.time())}"
|
|
2763
|
+
if not hasattr(self, "_tui_history"):
|
|
2764
|
+
self._tui_history = []
|
|
2765
|
+
self._tui_history.append({"prompt": val, "response": resp if 'resp' in locals() else (orch_res.final_code if 'orch_res' in locals() else ""), "timestamp": time.time()})
|
|
2766
|
+
LocalStorageManager.save_session(
|
|
2767
|
+
session_id=self._tui_session_id,
|
|
2768
|
+
workspace_dir=self.workspace_dir,
|
|
2769
|
+
active_model=routed_model if 'routed_model' in locals() else self.model_name,
|
|
2770
|
+
active_persona=getattr(self, "persona_label", "Fullstack AI Systems Engineer"),
|
|
2771
|
+
context_files=[],
|
|
2772
|
+
history=self._tui_history,
|
|
2773
|
+
)
|
|
2774
|
+
except Exception:
|
|
2775
|
+
pass
|
|
2776
|
+
|
|
2777
|
+
scroll.scroll_end(animate=False)
|
|
2778
|
+
|
|
2779
|
+
|
|
2780
|
+
|
|
2781
|
+
# Alias for backward compatibility
|
|
2782
|
+
KCliApp = KCliCyberWorkstation
|
|
2783
|
+
|
|
2784
|
+
|
|
2785
|
+
def launch_cyber_workstation(mock: bool = False, show_codex: bool = False) -> None:
|
|
2786
|
+
"""Launches full-screen Textual Cyber-Workstation."""
|
|
2787
|
+
app = KCliCyberWorkstation(mock_mode=mock, show_codex_on_start=show_codex)
|
|
2788
|
+
app.run()
|