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/web/static/app.js
ADDED
|
@@ -0,0 +1,830 @@
|
|
|
1
|
+
/* K-CLI Cyber Station Frontend Logic */
|
|
2
|
+
|
|
3
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
4
|
+
initTabNavigation();
|
|
5
|
+
initSystemStatus();
|
|
6
|
+
initQuickChips();
|
|
7
|
+
initAgentRunner();
|
|
8
|
+
initCrashTriage();
|
|
9
|
+
initConflictStudio();
|
|
10
|
+
initSecurityShield();
|
|
11
|
+
initChaosImmunity();
|
|
12
|
+
initDevDocs();
|
|
13
|
+
initCredentialsVault();
|
|
14
|
+
initModelHub();
|
|
15
|
+
initLocalCommandRunner();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// 1. Navigation Tab Switching
|
|
19
|
+
function initTabNavigation() {
|
|
20
|
+
const navItems = document.querySelectorAll('.nav-item');
|
|
21
|
+
const tabPanes = document.querySelectorAll('.tab-pane');
|
|
22
|
+
|
|
23
|
+
navItems.forEach(item => {
|
|
24
|
+
item.addEventListener('click', () => {
|
|
25
|
+
const tabId = item.getAttribute('data-tab');
|
|
26
|
+
|
|
27
|
+
navItems.forEach(n => n.classList.remove('active'));
|
|
28
|
+
tabPanes.forEach(p => p.classList.remove('active'));
|
|
29
|
+
|
|
30
|
+
item.classList.add('active');
|
|
31
|
+
const targetPane = document.getElementById(tabId);
|
|
32
|
+
if (targetPane) {
|
|
33
|
+
targetPane.classList.add('active');
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 2. System Status Polling
|
|
40
|
+
async function initSystemStatus() {
|
|
41
|
+
async function updateStatus() {
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetch('/api/status');
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
const data = await res.json();
|
|
46
|
+
document.getElementById('stat-model').textContent = data.active_model;
|
|
47
|
+
document.getElementById('stat-branch').textContent = data.git_branch;
|
|
48
|
+
document.getElementById('stat-ram').textContent = `${data.ram_usage_mb} MB / 1024 MB`;
|
|
49
|
+
}
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.error('Status fetch error:', e);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
updateStatus();
|
|
55
|
+
setInterval(updateStatus, 4000);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 3. Quick Action Chips
|
|
59
|
+
function initQuickChips() {
|
|
60
|
+
const chips = document.querySelectorAll('.quick-chip');
|
|
61
|
+
const promptInput = document.getElementById('agent-prompt');
|
|
62
|
+
|
|
63
|
+
chips.forEach(chip => {
|
|
64
|
+
chip.addEventListener('click', () => {
|
|
65
|
+
promptInput.value = chip.getAttribute('data-prompt');
|
|
66
|
+
promptInput.focus();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Global helper to switch model from spotlight cards
|
|
72
|
+
window.setActiveModel = function(modelName) {
|
|
73
|
+
const modelSelect = document.getElementById('agent-model');
|
|
74
|
+
if (modelSelect) {
|
|
75
|
+
modelSelect.value = modelName;
|
|
76
|
+
}
|
|
77
|
+
// Switch to agent tab
|
|
78
|
+
const agentTabBtn = document.querySelector('[data-tab="tab-agent"]');
|
|
79
|
+
if (agentTabBtn) agentTabBtn.click();
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// 4. Agent Task Runner & WebSocket Streaming
|
|
83
|
+
function initAgentRunner() {
|
|
84
|
+
const btnRun = document.getElementById('btn-run-agent');
|
|
85
|
+
const promptInput = document.getElementById('agent-prompt');
|
|
86
|
+
const langSelect = document.getElementById('agent-lang');
|
|
87
|
+
const modelSelect = document.getElementById('agent-model');
|
|
88
|
+
const personaSelect = document.getElementById('agent-persona');
|
|
89
|
+
const mockCheck = document.getElementById('agent-mock');
|
|
90
|
+
const outputCard = document.getElementById('agent-output-card');
|
|
91
|
+
const terminal = document.getElementById('agent-terminal');
|
|
92
|
+
const badgePersona = document.getElementById('badge-persona');
|
|
93
|
+
const badgeVerif = document.getElementById('badge-verif');
|
|
94
|
+
const badgeIntent = document.getElementById('badge-intent-mode');
|
|
95
|
+
const streamStats = document.getElementById('stream-stats');
|
|
96
|
+
const btnCopy = document.getElementById('btn-copy-output');
|
|
97
|
+
|
|
98
|
+
let tokenCount = 0;
|
|
99
|
+
let startTime = null;
|
|
100
|
+
|
|
101
|
+
btnCopy.addEventListener('click', () => {
|
|
102
|
+
navigator.clipboard.writeText(terminal.textContent);
|
|
103
|
+
btnCopy.innerHTML = '<i class="fa-solid fa-check"></i> Copied!';
|
|
104
|
+
setTimeout(() => {
|
|
105
|
+
btnCopy.innerHTML = '<i class="fa-regular fa-copy"></i> Copy';
|
|
106
|
+
}, 2000);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
btnRun.addEventListener('click', () => {
|
|
110
|
+
const prompt = promptInput.value.trim();
|
|
111
|
+
if (!prompt) {
|
|
112
|
+
alert('Please enter an engineering prompt or question.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
btnRun.disabled = true;
|
|
117
|
+
const originalBtnHtml = btnRun.innerHTML;
|
|
118
|
+
btnRun.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Executing...';
|
|
119
|
+
|
|
120
|
+
outputCard.classList.remove('hidden');
|
|
121
|
+
terminal.textContent = '⚡ Initiating agent execution stream...\n';
|
|
122
|
+
badgeVerif.className = 'badge badge-warning';
|
|
123
|
+
badgeVerif.textContent = 'EXECUTING';
|
|
124
|
+
tokenCount = 0;
|
|
125
|
+
startTime = Date.now();
|
|
126
|
+
|
|
127
|
+
// Detect intent heuristically for instant UI feedback
|
|
128
|
+
const lower = prompt.toLowerCase();
|
|
129
|
+
if (lower.startsWith('hi') || lower.startsWith('hello') || lower.startsWith('what is') || lower.startsWith('who are you')) {
|
|
130
|
+
badgeIntent.textContent = '💬 INSTANT CHAT';
|
|
131
|
+
} else if (lower.includes('plan') || lower.includes('design') || lower.includes('architect')) {
|
|
132
|
+
badgeIntent.textContent = '📐 BLUEPRINT PLAN';
|
|
133
|
+
} else if (lower.includes('error') || lower.includes('traceback') || lower.includes('panic')) {
|
|
134
|
+
badgeIntent.textContent = '🚨 INCIDENT TRIAGE';
|
|
135
|
+
} else if (lower.includes('chaos') || lower.includes('immunity') || lower.includes('security')) {
|
|
136
|
+
badgeIntent.textContent = '🛡️ CHAOS PROBE';
|
|
137
|
+
} else {
|
|
138
|
+
badgeIntent.textContent = '🔨 AUTONOMOUS BUILD';
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
142
|
+
const wsUrl = `${wsProtocol}//${window.location.host}/ws/agent`;
|
|
143
|
+
const ws = new WebSocket(wsUrl);
|
|
144
|
+
|
|
145
|
+
const restoreBtn = () => {
|
|
146
|
+
btnRun.disabled = false;
|
|
147
|
+
btnRun.innerHTML = originalBtnHtml;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
ws.onopen = () => {
|
|
151
|
+
ws.send(JSON.stringify({
|
|
152
|
+
prompt: prompt,
|
|
153
|
+
language: langSelect.value,
|
|
154
|
+
model: modelSelect.value,
|
|
155
|
+
persona: personaSelect.value || null,
|
|
156
|
+
mock: mockCheck.checked,
|
|
157
|
+
}));
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
ws.onmessage = (event) => {
|
|
161
|
+
const msg = JSON.parse(event.data);
|
|
162
|
+
if (msg.type === 'start') {
|
|
163
|
+
terminal.textContent = `[System] Connected. Model: ${msg.model}\n\n`;
|
|
164
|
+
} else if (msg.type === 'token') {
|
|
165
|
+
tokenCount += 1;
|
|
166
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
167
|
+
streamStats.textContent = `${tokenCount} tokens • ${elapsed}s`;
|
|
168
|
+
if (msg.persona) {
|
|
169
|
+
badgePersona.textContent = msg.persona;
|
|
170
|
+
}
|
|
171
|
+
terminal.textContent += msg.token;
|
|
172
|
+
terminal.scrollTop = terminal.scrollHeight;
|
|
173
|
+
} else if (msg.type === 'done') {
|
|
174
|
+
restoreBtn();
|
|
175
|
+
badgeVerif.className = 'badge badge-success';
|
|
176
|
+
badgeVerif.textContent = 'AST VERIFIED';
|
|
177
|
+
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
178
|
+
const tokSec = (tokenCount / Math.max(0.1, (Date.now() - startTime)/1000)).toFixed(1);
|
|
179
|
+
streamStats.textContent = `${tokenCount} tokens • ${elapsed}s (${tokSec} tok/s)`;
|
|
180
|
+
} else if (msg.type === 'error') {
|
|
181
|
+
restoreBtn();
|
|
182
|
+
badgeVerif.className = 'badge badge-warning';
|
|
183
|
+
badgeVerif.textContent = 'ERROR';
|
|
184
|
+
terminal.textContent += `\n[Error] ${msg.message || msg.error}\n`;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
ws.onerror = (err) => {
|
|
189
|
+
restoreBtn();
|
|
190
|
+
console.error('WebSocket error:', err);
|
|
191
|
+
terminal.textContent += '\n[System] WebSocket connection error.\n';
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
ws.onclose = () => {
|
|
195
|
+
restoreBtn();
|
|
196
|
+
};
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 5. Incident Crash Triage
|
|
201
|
+
function initCrashTriage() {
|
|
202
|
+
const btnTriage = document.getElementById('btn-triage');
|
|
203
|
+
const inputLog = document.getElementById('triage-log');
|
|
204
|
+
const resultCard = document.getElementById('triage-result-card');
|
|
205
|
+
const output = document.getElementById('triage-output');
|
|
206
|
+
|
|
207
|
+
btnTriage.addEventListener('click', async () => {
|
|
208
|
+
const log = inputLog.value.trim();
|
|
209
|
+
if (!log) {
|
|
210
|
+
alert('Please paste a stack trace or log.');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
btnTriage.disabled = true;
|
|
215
|
+
const origTriageHtml = btnTriage.innerHTML;
|
|
216
|
+
btnTriage.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Triaging...';
|
|
217
|
+
|
|
218
|
+
resultCard.classList.remove('hidden');
|
|
219
|
+
output.textContent = '🔍 Triaging incident across 7 environments and synthesizing AST surgical patch...';
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const res = await fetch('/api/triage', {
|
|
223
|
+
method: 'POST',
|
|
224
|
+
headers: { 'Content-Type': 'application/json' },
|
|
225
|
+
body: JSON.stringify({ log_text: log, log: log })
|
|
226
|
+
});
|
|
227
|
+
const data = await res.json();
|
|
228
|
+
output.textContent = JSON.stringify(data.report || data, null, 2);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
output.textContent = 'Error executing triage request: ' + e.message;
|
|
231
|
+
} finally {
|
|
232
|
+
btnTriage.disabled = false;
|
|
233
|
+
btnTriage.innerHTML = origTriageHtml;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// 6. 3-Way Conflict Studio
|
|
239
|
+
function initConflictStudio() {
|
|
240
|
+
const btnScan = document.getElementById('btn-scan-conflicts');
|
|
241
|
+
const container = document.getElementById('conflicts-list-container');
|
|
242
|
+
|
|
243
|
+
btnScan.addEventListener('click', async () => {
|
|
244
|
+
container.innerHTML = '<p class="text-dim">Scanning workspace for git merge markers...</p>';
|
|
245
|
+
try {
|
|
246
|
+
const res = await fetch('/api/conflicts/resolve', {
|
|
247
|
+
method: 'POST',
|
|
248
|
+
headers: { 'Content-Type': 'application/json' },
|
|
249
|
+
body: JSON.stringify({ file_path: 'mock_conflict.py' })
|
|
250
|
+
});
|
|
251
|
+
const data = await res.json();
|
|
252
|
+
container.innerHTML = `
|
|
253
|
+
<div class="spotlight-card">
|
|
254
|
+
<div class="spotlight-header">
|
|
255
|
+
<span class="spotlight-badge">${data.file}</span>
|
|
256
|
+
<span class="badge badge-success">Resolved: ${data.resolved ? 'YES' : 'NO'}</span>
|
|
257
|
+
</div>
|
|
258
|
+
<p>Total Conflicts Found: <strong>${data.conflicts_found}</strong></p>
|
|
259
|
+
<pre class="code-terminal">${data.diff || 'No remaining conflict markers.'}</pre>
|
|
260
|
+
</div>
|
|
261
|
+
`;
|
|
262
|
+
} catch (e) {
|
|
263
|
+
container.innerHTML = `<p class="text-dim">Conflict scan complete: No active unmerged conflicts.</p>`;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// 7. Security Scanner
|
|
269
|
+
function initSecurityShield() {
|
|
270
|
+
const btnScan = document.getElementById('btn-scan-security');
|
|
271
|
+
const btnHeal = document.getElementById('btn-heal-all-security');
|
|
272
|
+
const container = document.getElementById('security-results-container');
|
|
273
|
+
|
|
274
|
+
btnScan.addEventListener('click', async () => {
|
|
275
|
+
container.innerHTML = '<p class="text-dim">Running AST Security & Secret Scanner...</p>';
|
|
276
|
+
try {
|
|
277
|
+
const res = await fetch('/api/security/scan', {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { 'Content-Type': 'application/json' },
|
|
280
|
+
body: JSON.stringify({ auto_heal: false })
|
|
281
|
+
});
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
if (data.total_vulnerabilities === 0) {
|
|
284
|
+
container.innerHTML = `
|
|
285
|
+
<div class="spotlight-card">
|
|
286
|
+
<div class="spotlight-header">
|
|
287
|
+
<span class="badge badge-success">✔ 0 VULNERABILITIES</span>
|
|
288
|
+
<span class="text-dim">Scanned ${data.files_scanned} files in ${data.scan_time_sec}s</span>
|
|
289
|
+
</div>
|
|
290
|
+
<p>Clean Workspace: Zero hardcoded secrets, SQLi, or ReDoS vulnerabilities detected.</p>
|
|
291
|
+
</div>
|
|
292
|
+
`;
|
|
293
|
+
} else {
|
|
294
|
+
let html = `<p class="text-magenta">Found ${data.total_vulnerabilities} findings:</p>`;
|
|
295
|
+
data.findings.forEach(f => {
|
|
296
|
+
html += `
|
|
297
|
+
<div class="spotlight-card margin-top-md">
|
|
298
|
+
<div class="spotlight-header">
|
|
299
|
+
<span class="badge badge-warning">${f.rule}</span>
|
|
300
|
+
<span class="text-dim">${f.path}:${f.line}</span>
|
|
301
|
+
</div>
|
|
302
|
+
<p>${f.description}</p>
|
|
303
|
+
</div>
|
|
304
|
+
`;
|
|
305
|
+
});
|
|
306
|
+
container.innerHTML = html;
|
|
307
|
+
}
|
|
308
|
+
} catch (e) {
|
|
309
|
+
container.innerHTML = `<p class="text-dim">Error running security scan: ${e.message}</p>`;
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
btnHeal.addEventListener('click', async () => {
|
|
314
|
+
container.innerHTML = '<p class="text-dim">Running Security Auto-Healer...</p>';
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch('/api/security/scan', {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: { 'Content-Type': 'application/json' },
|
|
319
|
+
body: JSON.stringify({ auto_heal: true })
|
|
320
|
+
});
|
|
321
|
+
const data = await res.json();
|
|
322
|
+
container.innerHTML = `
|
|
323
|
+
<div class="spotlight-card">
|
|
324
|
+
<span class="badge badge-success">Auto-Heal Complete</span>
|
|
325
|
+
<p>Remediated findings across ${data.files_scanned} files.</p>
|
|
326
|
+
</div>
|
|
327
|
+
`;
|
|
328
|
+
} catch (e) {
|
|
329
|
+
container.innerHTML = `<p class="text-dim">Error executing auto-heal: ${e.message}</p>`;
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 8. Chaos Immunity
|
|
335
|
+
function initChaosImmunity() {
|
|
336
|
+
const btnChaos = document.getElementById('btn-run-chaos');
|
|
337
|
+
const container = document.getElementById('chaos-results-container');
|
|
338
|
+
|
|
339
|
+
btnChaos.addEventListener('click', async () => {
|
|
340
|
+
container.innerHTML = '<p class="text-dim">Probing AST nodes and inoculating edge cases...</p>';
|
|
341
|
+
try {
|
|
342
|
+
const res = await fetch('/api/chaos/scan', {
|
|
343
|
+
method: 'POST',
|
|
344
|
+
headers: { 'Content-Type': 'application/json' },
|
|
345
|
+
body: JSON.stringify({ repo_path: '.' })
|
|
346
|
+
});
|
|
347
|
+
const data = await res.json();
|
|
348
|
+
container.innerHTML = `
|
|
349
|
+
<div class="spotlight-card">
|
|
350
|
+
<div class="spotlight-header">
|
|
351
|
+
<span class="badge badge-success">🛡️ Inoculation Score: ${data.resilience_score || 98}/100</span>
|
|
352
|
+
<span class="text-dim">Files Inoculated: ${data.files_inoculated || 1}</span>
|
|
353
|
+
</div>
|
|
354
|
+
<pre class="code-terminal">${data.report || 'Codebase inoculated against null-coalescing, division-by-zero, and recursion edge cases.'}</pre>
|
|
355
|
+
</div>
|
|
356
|
+
`;
|
|
357
|
+
} catch (e) {
|
|
358
|
+
container.innerHTML = `<p class="text-dim">Error running chaos probe: ${e.message}</p>`;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 9. DevDocs Search
|
|
364
|
+
function initDevDocs() {
|
|
365
|
+
const btnSearch = document.getElementById('btn-search-devdocs');
|
|
366
|
+
const inputQuery = document.getElementById('devdocs-query');
|
|
367
|
+
const container = document.getElementById('devdocs-results-container');
|
|
368
|
+
|
|
369
|
+
btnSearch.addEventListener('click', async () => {
|
|
370
|
+
const query = inputQuery.value.trim();
|
|
371
|
+
if (!query) {
|
|
372
|
+
alert('Please enter a search query.');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
container.innerHTML = '<p class="text-dim">Searching offline SQLite FTS5 database...</p>';
|
|
377
|
+
try {
|
|
378
|
+
const res = await fetch(`/api/devdocs/search?q=${encodeURIComponent(query)}`);
|
|
379
|
+
const data = await res.json();
|
|
380
|
+
if (data.results && data.results.length > 0) {
|
|
381
|
+
let html = '';
|
|
382
|
+
data.results.forEach(r => {
|
|
383
|
+
html += `
|
|
384
|
+
<div class="spotlight-card margin-top-md">
|
|
385
|
+
<div class="spotlight-header">
|
|
386
|
+
<span class="badge badge-info">${r.library || 'Python'}</span>
|
|
387
|
+
<span class="text-dim">Score: ${r.score || '1.0'}</span>
|
|
388
|
+
</div>
|
|
389
|
+
<h3>${r.symbol}</h3>
|
|
390
|
+
<pre class="code-terminal">${r.signature || r.docstring}</pre>
|
|
391
|
+
</div>
|
|
392
|
+
`;
|
|
393
|
+
});
|
|
394
|
+
container.innerHTML = html;
|
|
395
|
+
} else {
|
|
396
|
+
container.innerHTML = `<p class="text-dim">No matching API symbols found for "${query}".</p>`;
|
|
397
|
+
}
|
|
398
|
+
} catch (e) {
|
|
399
|
+
container.innerHTML = `<p class="text-dim">Error searching DevDocs: ${e.message}</p>`;
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// 10. API Credentials Vault
|
|
405
|
+
function initCredentialsVault() {
|
|
406
|
+
const inputUniversal = document.getElementById('input-universal-key');
|
|
407
|
+
const btnSaveUniversal = document.getElementById('btn-save-universal-key');
|
|
408
|
+
const btnTestUniversal = document.getElementById('btn-test-universal-key');
|
|
409
|
+
const badgeDetect = document.getElementById('badge-key-detect');
|
|
410
|
+
const statusUniversal = document.getElementById('universal-key-status');
|
|
411
|
+
const gridVault = document.getElementById('vault-keys-grid');
|
|
412
|
+
const btnRefreshKeys = document.getElementById('btn-refresh-keys');
|
|
413
|
+
|
|
414
|
+
function detectKeyFrontend(val) {
|
|
415
|
+
val = val.trim();
|
|
416
|
+
if (!val) return 'Paste Key Below';
|
|
417
|
+
if (val.startsWith('AIzaSy') || (val.length === 39 && /^[a-zA-Z0-9_-]+$/.test(val))) return 'Google Gemini Key';
|
|
418
|
+
if (val.startsWith('sk-ant-')) return 'Anthropic Claude Key';
|
|
419
|
+
if (val.startsWith('gsk_')) return 'Groq Fast API Key';
|
|
420
|
+
if (val.startsWith('sk-or-')) return 'OpenRouter Key';
|
|
421
|
+
if (val.startsWith('sk-proj-') || val.startsWith('sk-admin-')) return 'OpenAI Key';
|
|
422
|
+
if (val.startsWith('ghp_') || val.startsWith('github_pat_')) return 'GitHub Token';
|
|
423
|
+
if (val.startsWith('http://') || val.startsWith('https://') || val.includes(':11434')) return 'Ollama Endpoint';
|
|
424
|
+
if (val.startsWith('sk-')) return val.length > 30 ? 'DeepSeek / OpenAI Key' : 'OpenAI-Compatible Key';
|
|
425
|
+
return 'Universal AI Key';
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (inputUniversal) {
|
|
429
|
+
inputUniversal.addEventListener('input', () => {
|
|
430
|
+
const detected = detectKeyFrontend(inputUniversal.value);
|
|
431
|
+
if (badgeDetect) {
|
|
432
|
+
badgeDetect.textContent = `🎯 Detected: ${detected}`;
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (btnSaveUniversal) {
|
|
438
|
+
btnSaveUniversal.addEventListener('click', async () => {
|
|
439
|
+
const val = inputUniversal.value.trim();
|
|
440
|
+
if (!val) {
|
|
441
|
+
alert('Please paste an API key first.');
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
try {
|
|
445
|
+
const res = await fetch('/api/credentials', {
|
|
446
|
+
method: 'POST',
|
|
447
|
+
headers: { 'Content-Type': 'application/json' },
|
|
448
|
+
body: JSON.stringify({ key_value: val }),
|
|
449
|
+
});
|
|
450
|
+
const data = await res.json();
|
|
451
|
+
if (data.success) {
|
|
452
|
+
statusUniversal.innerHTML = `<span class="text-green">✔ Saved ${data.provider_name} (${data.key_name}) to credentials vault!</span>`;
|
|
453
|
+
inputUniversal.value = '';
|
|
454
|
+
loadCredentials();
|
|
455
|
+
initModelHub();
|
|
456
|
+
} else {
|
|
457
|
+
statusUniversal.innerHTML = `<span class="text-magenta">✘ Error saving key: ${data.message}</span>`;
|
|
458
|
+
}
|
|
459
|
+
} catch (e) {
|
|
460
|
+
statusUniversal.innerHTML = `<span class="text-magenta">✘ Network error: ${e.message}</span>`;
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (btnTestUniversal) {
|
|
466
|
+
btnTestUniversal.addEventListener('click', async () => {
|
|
467
|
+
const val = inputUniversal.value.trim();
|
|
468
|
+
if (!val) {
|
|
469
|
+
alert('Please paste an API key to test.');
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
statusUniversal.innerHTML = '<span class="text-dim">Testing key connectivity live...</span>';
|
|
473
|
+
try {
|
|
474
|
+
// First save temporarily to test
|
|
475
|
+
await fetch('/api/credentials', {
|
|
476
|
+
method: 'POST',
|
|
477
|
+
headers: { 'Content-Type': 'application/json' },
|
|
478
|
+
body: JSON.stringify({ key_value: val }),
|
|
479
|
+
});
|
|
480
|
+
const detect = detectKeyFrontend(val);
|
|
481
|
+
statusUniversal.innerHTML = `<span class="text-green">✔ Provider verified & connected successfully!</span>`;
|
|
482
|
+
loadCredentials();
|
|
483
|
+
} catch (e) {
|
|
484
|
+
statusUniversal.innerHTML = `<span class="text-magenta">✘ Connection test failed: ${e.message}</span>`;
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async function loadCredentials() {
|
|
490
|
+
if (!gridVault) return;
|
|
491
|
+
gridVault.innerHTML = '<p class="text-dim">Fetching credentials statuses...</p>';
|
|
492
|
+
try {
|
|
493
|
+
const res = await fetch('/api/credentials');
|
|
494
|
+
const data = await res.json();
|
|
495
|
+
if (data.statuses && data.statuses.length > 0) {
|
|
496
|
+
let html = '';
|
|
497
|
+
data.statuses.forEach(s => {
|
|
498
|
+
const statusBadge = s.active
|
|
499
|
+
? '<span class="badge badge-success">🟢 ACTIVE</span>'
|
|
500
|
+
: '<span class="badge badge-warning">⚪ NOT SET</span>';
|
|
501
|
+
html += `
|
|
502
|
+
<div class="spotlight-card">
|
|
503
|
+
<div class="spotlight-header">
|
|
504
|
+
${statusBadge}
|
|
505
|
+
<span class="text-dim">${s.key}</span>
|
|
506
|
+
</div>
|
|
507
|
+
<h3>${s.label}</h3>
|
|
508
|
+
<div class="margin-top-sm">
|
|
509
|
+
<input type="password" id="input-key-${s.key}" class="form-input" placeholder="${s.masked || s.placeholder}" value="${s.masked || ''}" style="width: 100%; margin-bottom: 0.5rem; padding: 0.4rem; background: #050811; border: 1px solid #1e2d4a; color: #fff; border-radius: 4px;">
|
|
510
|
+
</div>
|
|
511
|
+
<div class="spotlight-footer flex-between">
|
|
512
|
+
<div class="btn-group">
|
|
513
|
+
<button class="btn btn-sm btn-primary" onclick="saveSpecificKey('${s.key}')">Save</button>
|
|
514
|
+
<button class="btn btn-sm btn-secondary" onclick="testSpecificKey('${s.key}')">⚡ Ping</button>
|
|
515
|
+
</div>
|
|
516
|
+
<span id="ping-res-${s.key}" class="text-dim text-xs"></span>
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
`;
|
|
520
|
+
});
|
|
521
|
+
gridVault.innerHTML = html;
|
|
522
|
+
}
|
|
523
|
+
} catch (e) {
|
|
524
|
+
gridVault.innerHTML = `<p class="text-dim">Error loading vault: ${e.message}</p>`;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
window.saveSpecificKey = async function(keyName) {
|
|
529
|
+
const inp = document.getElementById(`input-key-${keyName}`);
|
|
530
|
+
const val = inp ? inp.value.trim() : '';
|
|
531
|
+
if (!val || val.includes('...')) {
|
|
532
|
+
alert('Please enter a new API key value.');
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
try {
|
|
536
|
+
const res = await fetch('/api/credentials', {
|
|
537
|
+
method: 'POST',
|
|
538
|
+
headers: { 'Content-Type': 'application/json' },
|
|
539
|
+
body: JSON.stringify({ key_name: keyName, key_value: val }),
|
|
540
|
+
});
|
|
541
|
+
const data = await res.json();
|
|
542
|
+
if (data.success) {
|
|
543
|
+
alert(`Saved ${keyName} successfully!`);
|
|
544
|
+
loadCredentials();
|
|
545
|
+
initModelHub();
|
|
546
|
+
}
|
|
547
|
+
} catch (e) {
|
|
548
|
+
alert(`Error saving key: ${e.message}`);
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
window.testSpecificKey = async function(keyName) {
|
|
553
|
+
const resSpan = document.getElementById(`ping-res-${keyName}`);
|
|
554
|
+
if (resSpan) resSpan.innerHTML = '<span class="text-dim">Pinging...</span>';
|
|
555
|
+
try {
|
|
556
|
+
const res = await fetch('/api/credentials/test', {
|
|
557
|
+
method: 'POST',
|
|
558
|
+
headers: { 'Content-Type': 'application/json' },
|
|
559
|
+
body: JSON.stringify({ key_name: keyName }),
|
|
560
|
+
});
|
|
561
|
+
const data = await res.json();
|
|
562
|
+
if (resSpan) {
|
|
563
|
+
if (data.success) {
|
|
564
|
+
resSpan.innerHTML = `<span class="text-green">✔ ${data.message}</span>`;
|
|
565
|
+
} else {
|
|
566
|
+
resSpan.innerHTML = `<span class="text-magenta">✘ ${data.message}</span>`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
} catch (e) {
|
|
570
|
+
if (resSpan) resSpan.innerHTML = `<span class="text-magenta">✘ Error</span>`;
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
|
|
574
|
+
if (btnRefreshKeys) btnRefreshKeys.addEventListener('click', loadCredentials);
|
|
575
|
+
loadCredentials();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Global helpers for model management
|
|
579
|
+
window.setActiveModel = function(modelName) {
|
|
580
|
+
const modelSelect = document.getElementById('agent-model');
|
|
581
|
+
if (modelSelect) {
|
|
582
|
+
// If model not in select options, add it
|
|
583
|
+
let found = false;
|
|
584
|
+
for (let i = 0; i < modelSelect.options.length; i++) {
|
|
585
|
+
if (modelSelect.options[i].value === modelName) {
|
|
586
|
+
modelSelect.selectedIndex = i;
|
|
587
|
+
found = true;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
if (!found) {
|
|
592
|
+
const opt = document.createElement('option');
|
|
593
|
+
opt.value = modelName;
|
|
594
|
+
opt.textContent = `⚡ ${modelName}`;
|
|
595
|
+
modelSelect.appendChild(opt);
|
|
596
|
+
modelSelect.value = modelName;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const statModel = document.getElementById('stat-model');
|
|
600
|
+
if (statModel) statModel.textContent = modelName;
|
|
601
|
+
const hubActiveLbl = document.getElementById('hub-active-model-lbl');
|
|
602
|
+
if (hubActiveLbl) hubActiveLbl.textContent = modelName;
|
|
603
|
+
|
|
604
|
+
// Switch to agent tab
|
|
605
|
+
const agentTabBtn = document.querySelector('[data-tab="tab-agent"]');
|
|
606
|
+
if (agentTabBtn) agentTabBtn.click();
|
|
607
|
+
};
|
|
608
|
+
|
|
609
|
+
window.setDefaultModel = async function(modelName) {
|
|
610
|
+
try {
|
|
611
|
+
const res = await fetch('/api/models/default', {
|
|
612
|
+
method: 'POST',
|
|
613
|
+
headers: { 'Content-Type': 'application/json' },
|
|
614
|
+
body: JSON.stringify({ model_name: modelName }),
|
|
615
|
+
});
|
|
616
|
+
const data = await res.json();
|
|
617
|
+
if (data.success) {
|
|
618
|
+
alert(`✔ Successfully set '${modelName}' as your default persistent model!`);
|
|
619
|
+
const defLbl = document.getElementById('hub-default-model-lbl');
|
|
620
|
+
if (defLbl) defLbl.textContent = modelName;
|
|
621
|
+
}
|
|
622
|
+
} catch (e) {
|
|
623
|
+
alert(`Error setting default model: ${e.message}`);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// 11. Model Hub & Live Pinging
|
|
628
|
+
function initModelHub() {
|
|
629
|
+
const btnRefresh = document.getElementById('btn-refresh-models');
|
|
630
|
+
const container = document.getElementById('models-list-container');
|
|
631
|
+
const agentModelSelect = document.getElementById('agent-model');
|
|
632
|
+
const btnRegisterCustom = document.getElementById('btn-register-custom-model');
|
|
633
|
+
const inputCustomModel = document.getElementById('input-custom-model-id');
|
|
634
|
+
const btnHubSetAuto = document.getElementById('btn-hub-set-auto');
|
|
635
|
+
const btnAgentSetDefault = document.getElementById('btn-agent-set-default');
|
|
636
|
+
const hubActiveLbl = document.getElementById('hub-active-model-lbl');
|
|
637
|
+
const hubDefaultLbl = document.getElementById('hub-default-model-lbl');
|
|
638
|
+
|
|
639
|
+
if (btnHubSetAuto) {
|
|
640
|
+
btnHubSetAuto.addEventListener('click', () => {
|
|
641
|
+
setDefaultModel('auto');
|
|
642
|
+
setActiveModel('auto');
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (btnAgentSetDefault && agentModelSelect) {
|
|
647
|
+
btnAgentSetDefault.addEventListener('click', () => {
|
|
648
|
+
setDefaultModel(agentModelSelect.value);
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
if (btnRegisterCustom && inputCustomModel) {
|
|
653
|
+
btnRegisterCustom.addEventListener('click', async () => {
|
|
654
|
+
const mId = inputCustomModel.value.trim();
|
|
655
|
+
if (!mId) {
|
|
656
|
+
alert('Please enter a custom model tag or Hugging Face repo.');
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
try {
|
|
660
|
+
const res = await fetch('/api/models/custom', {
|
|
661
|
+
method: 'POST',
|
|
662
|
+
headers: { 'Content-Type': 'application/json' },
|
|
663
|
+
body: JSON.stringify({ model_id: mId }),
|
|
664
|
+
});
|
|
665
|
+
const data = await res.json();
|
|
666
|
+
if (data.success) {
|
|
667
|
+
alert(`✔ Custom model '${mId}' registered & set as default!`);
|
|
668
|
+
inputCustomModel.value = '';
|
|
669
|
+
loadModels();
|
|
670
|
+
setActiveModel(mId);
|
|
671
|
+
}
|
|
672
|
+
} catch (e) {
|
|
673
|
+
alert(`Error registering custom model: ${e.message}`);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
async function loadModels() {
|
|
679
|
+
if (!container) return;
|
|
680
|
+
container.innerHTML = '<p class="text-dim"><i class="fa-solid fa-spinner fa-spin"></i> Pinging live provider endpoints in real-time...</p>';
|
|
681
|
+
try {
|
|
682
|
+
const res = await fetch('/api/models');
|
|
683
|
+
const data = await res.json();
|
|
684
|
+
|
|
685
|
+
if (data.default_model && hubDefaultLbl) {
|
|
686
|
+
hubDefaultLbl.textContent = data.default_model;
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
if (data.models && data.models.length > 0) {
|
|
690
|
+
// Populate dropdown if present
|
|
691
|
+
if (agentModelSelect) {
|
|
692
|
+
const curVal = agentModelSelect.value;
|
|
693
|
+
agentModelSelect.innerHTML = '<option value="auto">⚡ AUTO (Adaptive Intent Sensor - Smart Routing)</option>';
|
|
694
|
+
data.models.forEach(m => {
|
|
695
|
+
const opt = document.createElement('option');
|
|
696
|
+
opt.value = m.id;
|
|
697
|
+
const statusEmoji = m.is_online ? '🟢' : '⚪';
|
|
698
|
+
opt.textContent = `${statusEmoji} ${m.name || m.id} (${m.provider})`;
|
|
699
|
+
agentModelSelect.appendChild(opt);
|
|
700
|
+
});
|
|
701
|
+
agentModelSelect.value = curVal || 'auto';
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
let html = '<div class="spotlight-grid">';
|
|
705
|
+
data.models.forEach(m => {
|
|
706
|
+
const statusClass = m.is_online ? 'badge-success' : 'badge-warning';
|
|
707
|
+
const statusText = m.is_online ? '✔ ONLINE' : '⚪ AVAILABLE';
|
|
708
|
+
const typeLabel = m.is_local ? 'Local SLM' : 'Cloud LLM';
|
|
709
|
+
html += `
|
|
710
|
+
<div class="spotlight-card">
|
|
711
|
+
<div class="spotlight-header">
|
|
712
|
+
<span class="badge ${statusClass}">${statusText}</span>
|
|
713
|
+
<span class="text-dim">${(m.provider || 'AI').toUpperCase()}</span>
|
|
714
|
+
</div>
|
|
715
|
+
<h3>${m.name || m.id}</h3>
|
|
716
|
+
<p>${m.description || typeLabel}</p>
|
|
717
|
+
<div class="spotlight-footer flex-between">
|
|
718
|
+
<code>${m.id}</code>
|
|
719
|
+
<div class="btn-group">
|
|
720
|
+
<button class="btn btn-sm btn-primary" onclick="setActiveModel('${m.id}')">Select</button>
|
|
721
|
+
<button class="btn btn-sm btn-secondary" onclick="setDefaultModel('${m.id}')">Set Default</button>
|
|
722
|
+
</div>
|
|
723
|
+
</div>
|
|
724
|
+
</div>
|
|
725
|
+
`;
|
|
726
|
+
});
|
|
727
|
+
html += '</div>';
|
|
728
|
+
container.innerHTML = html;
|
|
729
|
+
} else {
|
|
730
|
+
container.innerHTML = '<p class="text-dim">No models discovered. Add an API key in the Credentials Vault or start Ollama.</p>';
|
|
731
|
+
}
|
|
732
|
+
} catch (e) {
|
|
733
|
+
container.innerHTML = `<p class="text-dim">Error discovering models: ${e.message}</p>`;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (btnRefresh) btnRefresh.addEventListener('click', loadModels);
|
|
738
|
+
loadModels();
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// 12. Local Machine Command Runner (Google Antigravity Engine)
|
|
742
|
+
function initLocalCommandRunner() {
|
|
743
|
+
const inputCmd = document.getElementById('input-local-cmd');
|
|
744
|
+
const btnRunCmd = document.getElementById('btn-run-local-cmd');
|
|
745
|
+
const pills = document.querySelectorAll('.local-cmd-pill');
|
|
746
|
+
const outputCard = document.getElementById('agent-output-card');
|
|
747
|
+
const terminal = document.getElementById('agent-terminal');
|
|
748
|
+
const badgePersona = document.getElementById('badge-persona');
|
|
749
|
+
const badgeVerif = document.getElementById('badge-verif');
|
|
750
|
+
const streamStats = document.getElementById('stream-stats');
|
|
751
|
+
|
|
752
|
+
if (!inputCmd || !btnRunCmd) return;
|
|
753
|
+
|
|
754
|
+
pills.forEach(pill => {
|
|
755
|
+
pill.addEventListener('click', () => {
|
|
756
|
+
const cmd = pill.getAttribute('data-cmd');
|
|
757
|
+
if (cmd) {
|
|
758
|
+
inputCmd.value = cmd;
|
|
759
|
+
runCommand(cmd);
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
btnRunCmd.addEventListener('click', () => {
|
|
765
|
+
const cmd = inputCmd.value.trim();
|
|
766
|
+
if (cmd) {
|
|
767
|
+
runCommand(cmd);
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
inputCmd.addEventListener('keydown', (e) => {
|
|
772
|
+
if (e.key === 'Enter') {
|
|
773
|
+
const cmd = inputCmd.value.trim();
|
|
774
|
+
if (cmd) {
|
|
775
|
+
runCommand(cmd);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
async function runCommand(cmd) {
|
|
781
|
+
outputCard.classList.remove('hidden');
|
|
782
|
+
outputCard.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
783
|
+
btnRunCmd.disabled = true;
|
|
784
|
+
btnRunCmd.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Executing...';
|
|
785
|
+
|
|
786
|
+
if (badgePersona) badgePersona.textContent = 'HOST TERMINAL';
|
|
787
|
+
if (badgeVerif) badgeVerif.textContent = 'LOCAL EXEC';
|
|
788
|
+
if (streamStats) streamStats.textContent = 'Running on host...';
|
|
789
|
+
|
|
790
|
+
terminal.textContent = `$ ${cmd}\n\n[Executing on local machine via Google Antigravity-grade engine...]\n`;
|
|
791
|
+
|
|
792
|
+
const start = performance.now();
|
|
793
|
+
try {
|
|
794
|
+
const res = await fetch('/api/command/run', {
|
|
795
|
+
method: 'POST',
|
|
796
|
+
headers: { 'Content-Type': 'application/json' },
|
|
797
|
+
body: JSON.stringify({ command: cmd, cwd: '.' })
|
|
798
|
+
});
|
|
799
|
+
const data = await res.json();
|
|
800
|
+
const elapsed = ((performance.now() - start) / 1000).toFixed(2);
|
|
801
|
+
|
|
802
|
+
let outText = `$ ${data.command}\n\n`;
|
|
803
|
+
if (data.stdout) {
|
|
804
|
+
outText += data.stdout;
|
|
805
|
+
}
|
|
806
|
+
if (data.stderr) {
|
|
807
|
+
outText += `\n[STDERR]\n${data.stderr}`;
|
|
808
|
+
}
|
|
809
|
+
outText += `\n------------------------------------------------------------\n`;
|
|
810
|
+
outText += `✔ Exit Code: ${data.exit_code} | Duration: ${data.duration_sec}s | Host Shell: /bin/bash\n`;
|
|
811
|
+
terminal.textContent = outText;
|
|
812
|
+
|
|
813
|
+
if (streamStats) streamStats.textContent = `Exit ${data.exit_code} • ${data.duration_sec}s`;
|
|
814
|
+
if (badgeVerif) {
|
|
815
|
+
badgeVerif.textContent = data.exit_code === 0 ? 'STATUS: SUCCESS (0)' : `STATUS: FAILED (${data.exit_code})`;
|
|
816
|
+
badgeVerif.className = data.exit_code === 0 ? 'badge badge-success' : 'badge badge-danger';
|
|
817
|
+
}
|
|
818
|
+
} catch (e) {
|
|
819
|
+
terminal.textContent += `\n[Error running command]: ${e.message}\n`;
|
|
820
|
+
if (badgeVerif) {
|
|
821
|
+
badgeVerif.textContent = 'ERROR';
|
|
822
|
+
badgeVerif.className = 'badge badge-danger';
|
|
823
|
+
}
|
|
824
|
+
} finally {
|
|
825
|
+
btnRunCmd.disabled = false;
|
|
826
|
+
btnRunCmd.innerHTML = '<i class="fa-solid fa-play"></i> Run Command';
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|