neuro-cli 4.1.0
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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export interface DashboardConfig {
|
|
2
|
+
/** Host to bind to */
|
|
3
|
+
host: string;
|
|
4
|
+
/** Port for the dashboard server */
|
|
5
|
+
port: number;
|
|
6
|
+
/** Whether to auto-open browser */
|
|
7
|
+
autoOpen: boolean;
|
|
8
|
+
/** Refresh interval for real-time updates in ms */
|
|
9
|
+
refreshInterval: number;
|
|
10
|
+
/** Whether to enable the dashboard */
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface DashboardData {
|
|
14
|
+
sessions: SessionSummary[];
|
|
15
|
+
tokenUsage: TokenUsageChart;
|
|
16
|
+
modelPerformance: ModelPerfData[];
|
|
17
|
+
spending: SpendingData;
|
|
18
|
+
systemInfo: SystemInfoData;
|
|
19
|
+
}
|
|
20
|
+
export interface SessionSummary {
|
|
21
|
+
id: string;
|
|
22
|
+
createdAt: number;
|
|
23
|
+
updatedAt: number;
|
|
24
|
+
messageCount: number;
|
|
25
|
+
model: string;
|
|
26
|
+
totalCost: number;
|
|
27
|
+
description?: string;
|
|
28
|
+
tags: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface TokenUsageChart {
|
|
31
|
+
labels: string[];
|
|
32
|
+
inputTokens: number[];
|
|
33
|
+
outputTokens: number[];
|
|
34
|
+
costs: number[];
|
|
35
|
+
}
|
|
36
|
+
export interface ModelPerfData {
|
|
37
|
+
model: string;
|
|
38
|
+
requestCount: number;
|
|
39
|
+
totalTokens: number;
|
|
40
|
+
totalCost: number;
|
|
41
|
+
avgLatencyMs: number;
|
|
42
|
+
}
|
|
43
|
+
export interface SpendingData {
|
|
44
|
+
todayTotal: number;
|
|
45
|
+
todayByModel: Record<string, number>;
|
|
46
|
+
sessionTotal: number;
|
|
47
|
+
sessionByModel: Record<string, number>;
|
|
48
|
+
dailyLimit: number;
|
|
49
|
+
estimatedDailySpend: number;
|
|
50
|
+
}
|
|
51
|
+
export interface SystemInfoData {
|
|
52
|
+
version: string;
|
|
53
|
+
uptime: number;
|
|
54
|
+
platform: string;
|
|
55
|
+
nodeVersion: string;
|
|
56
|
+
modelsAvailable: number;
|
|
57
|
+
toolsAvailable: number;
|
|
58
|
+
agentsAvailable: number;
|
|
59
|
+
currentModel: string;
|
|
60
|
+
}
|
|
61
|
+
export declare class WebDashboard {
|
|
62
|
+
private config;
|
|
63
|
+
private server;
|
|
64
|
+
private isRunning;
|
|
65
|
+
private startTime;
|
|
66
|
+
private engineRef;
|
|
67
|
+
constructor(config?: Partial<DashboardConfig>);
|
|
68
|
+
/**
|
|
69
|
+
* Set the engine reference for fetching data
|
|
70
|
+
*/
|
|
71
|
+
setEngine(engine: unknown): void;
|
|
72
|
+
/**
|
|
73
|
+
* Start the dashboard server
|
|
74
|
+
*/
|
|
75
|
+
start(): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Stop the dashboard server
|
|
78
|
+
*/
|
|
79
|
+
stop(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Check if dashboard is running
|
|
82
|
+
*/
|
|
83
|
+
getIsRunning(): boolean;
|
|
84
|
+
/**
|
|
85
|
+
* Get dashboard URL
|
|
86
|
+
*/
|
|
87
|
+
getUrl(): string;
|
|
88
|
+
/**
|
|
89
|
+
* Gather dashboard data
|
|
90
|
+
*/
|
|
91
|
+
gatherData(): DashboardData;
|
|
92
|
+
/**
|
|
93
|
+
* Get config
|
|
94
|
+
*/
|
|
95
|
+
getConfig(): DashboardConfig;
|
|
96
|
+
/**
|
|
97
|
+
* Print dashboard status
|
|
98
|
+
*/
|
|
99
|
+
printStatus(): void;
|
|
100
|
+
private handleRequest;
|
|
101
|
+
private getSessionSummaries;
|
|
102
|
+
private getTokenUsageData;
|
|
103
|
+
private getModelPerformanceData;
|
|
104
|
+
private getSpendingData;
|
|
105
|
+
private getSystemInfo;
|
|
106
|
+
private saveConfig;
|
|
107
|
+
private loadConfig;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=web-dashboard.d.ts.map
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Web Dashboard
|
|
3
|
+
// Local web server serving a dashboard page
|
|
4
|
+
// Session history, token usage charts, model performance
|
|
5
|
+
// Real-time session monitoring
|
|
6
|
+
// ============================================================
|
|
7
|
+
import { createServer } from 'http';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'fs';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
12
|
+
// -----------------------------------------------------------
|
|
13
|
+
// Default config
|
|
14
|
+
// -----------------------------------------------------------
|
|
15
|
+
const DASHBOARD_CONFIG_PATH = join(homedir(), '.neuro', 'dashboard-config.json');
|
|
16
|
+
function defaultConfig() {
|
|
17
|
+
return {
|
|
18
|
+
host: '127.0.0.1',
|
|
19
|
+
port: 3142,
|
|
20
|
+
autoOpen: true,
|
|
21
|
+
refreshInterval: 5000,
|
|
22
|
+
enabled: false,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
// -----------------------------------------------------------
|
|
26
|
+
// Dashboard HTML Generator
|
|
27
|
+
// Uses string concatenation to avoid nested template literal issues
|
|
28
|
+
// -----------------------------------------------------------
|
|
29
|
+
function generateDashboardHTML(data, config) {
|
|
30
|
+
const CSS = [
|
|
31
|
+
'* { margin: 0; padding: 0; box-sizing: border-box; }',
|
|
32
|
+
'body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0d1117; color: #c9d1d9; min-height: 100vh; }',
|
|
33
|
+
'.header { background: linear-gradient(135deg, #161b22, #21262d); padding: 20px 30px; border-bottom: 1px solid #30363d; display: flex; align-items: center; justify-content: space-between; }',
|
|
34
|
+
'.header h1 { font-size: 24px; color: #58a6ff; }',
|
|
35
|
+
'.header .version { color: #8b949e; font-size: 14px; }',
|
|
36
|
+
'.header .status { display: flex; align-items: center; gap: 8px; }',
|
|
37
|
+
'.status-dot { width: 8px; height: 8px; border-radius: 50%; background: #3fb950; animation: pulse 2s ease-in-out infinite; }',
|
|
38
|
+
'@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }',
|
|
39
|
+
'.container { max-width: 1400px; margin: 0 auto; padding: 20px; display: grid; grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); gap: 20px; }',
|
|
40
|
+
'.card { background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 20px; }',
|
|
41
|
+
'.card h2 { color: #58a6ff; font-size: 16px; margin-bottom: 16px; }',
|
|
42
|
+
'.stat-row { display: flex; justify-content: space-between; align-items: center; padding: 8px 0; border-bottom: 1px solid #21262d; }',
|
|
43
|
+
'.stat-row:last-child { border-bottom: none; }',
|
|
44
|
+
'.stat-label { color: #8b949e; }',
|
|
45
|
+
'.stat-value { color: #f0f6fc; font-weight: 600; }',
|
|
46
|
+
'.stat-value.green { color: #3fb950; }',
|
|
47
|
+
'.stat-value.yellow { color: #d29922; }',
|
|
48
|
+
'.stat-value.cyan { color: #58a6ff; }',
|
|
49
|
+
'.model-row { display: flex; justify-content: space-between; align-items: center; padding: 10px; margin: 4px 0; background: #0d1117; border-radius: 8px; }',
|
|
50
|
+
'.model-name { color: #79c0ff; font-size: 13px; max-width: 200px; overflow: hidden; text-overflow: ellipsis; }',
|
|
51
|
+
'.model-stats { display: flex; gap: 16px; font-size: 12px; }',
|
|
52
|
+
'.session-item { padding: 10px; margin: 4px 0; background: #0d1117; border-radius: 8px; cursor: pointer; }',
|
|
53
|
+
'.session-item:hover { background: #21262d; }',
|
|
54
|
+
'.session-id { color: #79c0ff; font-size: 13px; }',
|
|
55
|
+
'.session-meta { color: #8b949e; font-size: 12px; margin-top: 4px; }',
|
|
56
|
+
'.full-width { grid-column: 1 / -1; }',
|
|
57
|
+
'.refresh-info { color: #8b949e; font-size: 12px; text-align: center; padding: 10px; }',
|
|
58
|
+
'.empty-state { color: #8b949e; text-align: center; padding: 20px; }',
|
|
59
|
+
].join('\n ');
|
|
60
|
+
// Build model performance section
|
|
61
|
+
const modelRows = data.modelPerformance.length > 0
|
|
62
|
+
? data.modelPerformance.map(m => '<div class="model-row">' +
|
|
63
|
+
'<span class="model-name">' + m.model + '</span>' +
|
|
64
|
+
'<div class="model-stats">' +
|
|
65
|
+
'<span>' + m.requestCount + ' reqs</span>' +
|
|
66
|
+
'<span>' + m.totalTokens.toLocaleString() + ' tokens</span>' +
|
|
67
|
+
'<span>$' + m.totalCost.toFixed(4) + '</span>' +
|
|
68
|
+
'<span>' + m.avgLatencyMs + 'ms avg</span>' +
|
|
69
|
+
'</div></div>').join('\n ')
|
|
70
|
+
: '<div class="empty-state">No model performance data yet</div>';
|
|
71
|
+
// Build session list
|
|
72
|
+
const sessionItems = data.sessions.length > 0
|
|
73
|
+
? data.sessions.slice(0, 10).map(s => '<div class="session-item">' +
|
|
74
|
+
'<div class="session-id">' + s.id.slice(0, 8) + '...</div>' +
|
|
75
|
+
'<div class="session-meta">' +
|
|
76
|
+
s.messageCount + ' messages | ' + (s.model.split('/').pop() || s.model) + ' | $' + s.totalCost.toFixed(4) + ' | ' +
|
|
77
|
+
new Date(s.updatedAt).toLocaleString() +
|
|
78
|
+
'</div></div>').join('\n ')
|
|
79
|
+
: '<div class="empty-state">No sessions yet</div>';
|
|
80
|
+
// Build token usage section
|
|
81
|
+
const totalInput = data.tokenUsage.inputTokens.reduce((a, b) => a + b, 0);
|
|
82
|
+
const totalOutput = data.tokenUsage.outputTokens.reduce((a, b) => a + b, 0);
|
|
83
|
+
const totalCost = data.tokenUsage.costs.reduce((a, b) => a + b, 0);
|
|
84
|
+
const chartSection = data.tokenUsage.labels.length > 0
|
|
85
|
+
? '<div class="bar-chart">' +
|
|
86
|
+
data.tokenUsage.inputTokens.map((val, i) => {
|
|
87
|
+
const maxVal = Math.max(...data.tokenUsage.inputTokens, ...data.tokenUsage.outputTokens, 1);
|
|
88
|
+
const height = Math.max(5, (val / maxVal) * 100);
|
|
89
|
+
return '<div class="bar" style="height: ' + height + '%">' +
|
|
90
|
+
'<div class="bar-label">' + data.tokenUsage.labels[i] + '</div>' +
|
|
91
|
+
'</div>';
|
|
92
|
+
}).join('') +
|
|
93
|
+
'</div>'
|
|
94
|
+
: '<div class="empty-state">No token usage data yet</div>';
|
|
95
|
+
const dailyLimitStr = data.spending.dailyLimit > 0
|
|
96
|
+
? '$' + data.spending.dailyLimit.toFixed(2)
|
|
97
|
+
: 'Unlimited';
|
|
98
|
+
const refreshSec = String(config.refreshInterval / 1000);
|
|
99
|
+
return [
|
|
100
|
+
'<!DOCTYPE html>',
|
|
101
|
+
'<html lang="en">',
|
|
102
|
+
'<head>',
|
|
103
|
+
'<meta charset="UTF-8">',
|
|
104
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1.0">',
|
|
105
|
+
'<title>NeuroCLI Dashboard</title>',
|
|
106
|
+
'<style>',
|
|
107
|
+
' ' + CSS,
|
|
108
|
+
'</style>',
|
|
109
|
+
'</head>',
|
|
110
|
+
'<body>',
|
|
111
|
+
' <div class="header">',
|
|
112
|
+
' <div>',
|
|
113
|
+
' <h1>NeuroCLI Dashboard</h1>',
|
|
114
|
+
' <div class="version">v' + data.systemInfo.version + ' | ' + data.systemInfo.platform + ' | Node ' + data.systemInfo.nodeVersion + '</div>',
|
|
115
|
+
' </div>',
|
|
116
|
+
' <div class="status">',
|
|
117
|
+
' <div class="status-dot"></div>',
|
|
118
|
+
' <span>Live</span>',
|
|
119
|
+
' </div>',
|
|
120
|
+
' </div>',
|
|
121
|
+
'',
|
|
122
|
+
' <div class="container">',
|
|
123
|
+
' <!-- System Overview -->',
|
|
124
|
+
' <div class="card">',
|
|
125
|
+
' <h2>⚙ System Overview</h2>',
|
|
126
|
+
' <div class="stat-row"><span class="stat-label">Current Model</span><span class="stat-value cyan">' + (data.systemInfo.currentModel || 'default') + '</span></div>',
|
|
127
|
+
' <div class="stat-row"><span class="stat-label">Models Available</span><span class="stat-value">' + data.systemInfo.modelsAvailable + '</span></div>',
|
|
128
|
+
' <div class="stat-row"><span class="stat-label">Tools Available</span><span class="stat-value">' + data.systemInfo.toolsAvailable + '</span></div>',
|
|
129
|
+
' <div class="stat-row"><span class="stat-label">Agents Available</span><span class="stat-value">' + data.systemInfo.agentsAvailable + '</span></div>',
|
|
130
|
+
' <div class="stat-row"><span class="stat-label">Uptime</span><span class="stat-value">' + Math.round(data.systemInfo.uptime / 1000) + 's</span></div>',
|
|
131
|
+
' </div>',
|
|
132
|
+
'',
|
|
133
|
+
' <!-- Spending -->',
|
|
134
|
+
' <div class="card">',
|
|
135
|
+
' <h2>💰 Spending</h2>',
|
|
136
|
+
' <div class="stat-row"><span class="stat-label">Today\'s Total</span><span class="stat-value green">$' + data.spending.todayTotal.toFixed(4) + '</span></div>',
|
|
137
|
+
' <div class="stat-row"><span class="stat-label">Session Total</span><span class="stat-value cyan">$' + data.spending.sessionTotal.toFixed(4) + '</span></div>',
|
|
138
|
+
' <div class="stat-row"><span class="stat-label">Daily Limit</span><span class="stat-value">' + dailyLimitStr + '</span></div>',
|
|
139
|
+
' <div class="stat-row"><span class="stat-label">Est. Daily Spend</span><span class="stat-value yellow">$' + data.spending.estimatedDailySpend.toFixed(4) + '</span></div>',
|
|
140
|
+
' </div>',
|
|
141
|
+
'',
|
|
142
|
+
' <!-- Token Usage -->',
|
|
143
|
+
' <div class="card full-width">',
|
|
144
|
+
' <h2>📈 Token Usage</h2>',
|
|
145
|
+
' ' + chartSection,
|
|
146
|
+
' <div class="stat-row"><span class="stat-label">Total Input Tokens</span><span class="stat-value">' + totalInput.toLocaleString() + '</span></div>',
|
|
147
|
+
' <div class="stat-row"><span class="stat-label">Total Output Tokens</span><span class="stat-value">' + totalOutput.toLocaleString() + '</span></div>',
|
|
148
|
+
' <div class="stat-row"><span class="stat-label">Total Cost</span><span class="stat-value green">$' + totalCost.toFixed(4) + '</span></div>',
|
|
149
|
+
' </div>',
|
|
150
|
+
'',
|
|
151
|
+
' <!-- Model Performance -->',
|
|
152
|
+
' <div class="card">',
|
|
153
|
+
' <h2>⚡ Model Performance</h2>',
|
|
154
|
+
' ' + modelRows,
|
|
155
|
+
' </div>',
|
|
156
|
+
'',
|
|
157
|
+
' <!-- Session History -->',
|
|
158
|
+
' <div class="card">',
|
|
159
|
+
' <h2>📁 Session History</h2>',
|
|
160
|
+
' ' + sessionItems,
|
|
161
|
+
' </div>',
|
|
162
|
+
' </div>',
|
|
163
|
+
'',
|
|
164
|
+
' <div class="refresh-info">',
|
|
165
|
+
' Auto-refreshing every ' + refreshSec + 's |',
|
|
166
|
+
' <a href="/api/data" style="color: #58a6ff;">API Endpoint</a> |',
|
|
167
|
+
' <a href="/api/docs" style="color: #58a6ff;">API Docs</a>',
|
|
168
|
+
' </div>',
|
|
169
|
+
'',
|
|
170
|
+
' <script>',
|
|
171
|
+
' setTimeout(function() { location.reload(); }, ' + config.refreshInterval + ');',
|
|
172
|
+
' </script>',
|
|
173
|
+
'</body>',
|
|
174
|
+
'</html>',
|
|
175
|
+
].join('\n');
|
|
176
|
+
}
|
|
177
|
+
// -----------------------------------------------------------
|
|
178
|
+
// WebDashboard
|
|
179
|
+
// -----------------------------------------------------------
|
|
180
|
+
export class WebDashboard {
|
|
181
|
+
config;
|
|
182
|
+
server = null;
|
|
183
|
+
isRunning = false;
|
|
184
|
+
startTime = 0;
|
|
185
|
+
engineRef = null;
|
|
186
|
+
constructor(config) {
|
|
187
|
+
this.config = { ...defaultConfig(), ...config };
|
|
188
|
+
this.loadConfig();
|
|
189
|
+
}
|
|
190
|
+
// ----------------------------------------------------------
|
|
191
|
+
// Public API
|
|
192
|
+
// ----------------------------------------------------------
|
|
193
|
+
/**
|
|
194
|
+
* Set the engine reference for fetching data
|
|
195
|
+
*/
|
|
196
|
+
setEngine(engine) {
|
|
197
|
+
this.engineRef = engine;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Start the dashboard server
|
|
201
|
+
*/
|
|
202
|
+
async start() {
|
|
203
|
+
if (this.isRunning) {
|
|
204
|
+
console.log(chalk.yellow('Dashboard is already running.'));
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
return new Promise((resolve, reject) => {
|
|
208
|
+
this.server = createServer(async (req, res) => {
|
|
209
|
+
await this.handleRequest(req, res);
|
|
210
|
+
});
|
|
211
|
+
this.server.on('error', (err) => {
|
|
212
|
+
console.log(chalk.red('Dashboard error: ' + err.message));
|
|
213
|
+
reject(err);
|
|
214
|
+
});
|
|
215
|
+
this.server.listen(this.config.port, this.config.host, () => {
|
|
216
|
+
this.isRunning = true;
|
|
217
|
+
this.startTime = Date.now();
|
|
218
|
+
const url = 'http://' + this.config.host + ':' + this.config.port;
|
|
219
|
+
console.log(chalk.green('Dashboard running at ' + url));
|
|
220
|
+
// Auto-open browser
|
|
221
|
+
if (this.config.autoOpen) {
|
|
222
|
+
try {
|
|
223
|
+
// @ts-ignore
|
|
224
|
+
import('open').then(mod => {
|
|
225
|
+
mod.default(url).catch(() => { });
|
|
226
|
+
}).catch(() => {
|
|
227
|
+
console.log(chalk.gray('Open ' + url + ' in your browser to view the dashboard.'));
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
console.log(chalk.gray('Open ' + url + ' in your browser to view the dashboard.'));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
resolve();
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Stop the dashboard server
|
|
240
|
+
*/
|
|
241
|
+
async stop() {
|
|
242
|
+
if (!this.isRunning || !this.server)
|
|
243
|
+
return;
|
|
244
|
+
return new Promise((resolve) => {
|
|
245
|
+
this.server.close(() => {
|
|
246
|
+
this.isRunning = false;
|
|
247
|
+
this.server = null;
|
|
248
|
+
console.log(chalk.gray('Dashboard stopped.'));
|
|
249
|
+
resolve();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Check if dashboard is running
|
|
255
|
+
*/
|
|
256
|
+
getIsRunning() {
|
|
257
|
+
return this.isRunning;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Get dashboard URL
|
|
261
|
+
*/
|
|
262
|
+
getUrl() {
|
|
263
|
+
return 'http://' + this.config.host + ':' + this.config.port;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Gather dashboard data
|
|
267
|
+
*/
|
|
268
|
+
gatherData() {
|
|
269
|
+
const sessions = this.getSessionSummaries();
|
|
270
|
+
const tokenUsage = this.getTokenUsageData(sessions);
|
|
271
|
+
const modelPerformance = this.getModelPerformanceData();
|
|
272
|
+
const spending = this.getSpendingData();
|
|
273
|
+
const systemInfo = this.getSystemInfo();
|
|
274
|
+
return {
|
|
275
|
+
sessions,
|
|
276
|
+
tokenUsage,
|
|
277
|
+
modelPerformance,
|
|
278
|
+
spending,
|
|
279
|
+
systemInfo,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Get config
|
|
284
|
+
*/
|
|
285
|
+
getConfig() {
|
|
286
|
+
return { ...this.config };
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Print dashboard status
|
|
290
|
+
*/
|
|
291
|
+
printStatus() {
|
|
292
|
+
console.log('');
|
|
293
|
+
console.log(chalk.bold('--- NeuroCLI Web Dashboard ---'));
|
|
294
|
+
console.log(' Running: ' + (this.isRunning ? chalk.green('yes') : chalk.gray('no')));
|
|
295
|
+
if (this.isRunning) {
|
|
296
|
+
console.log(' URL: ' + chalk.cyan(this.getUrl()));
|
|
297
|
+
console.log(' Uptime: ' + Math.round((Date.now() - this.startTime) / 1000) + 's');
|
|
298
|
+
}
|
|
299
|
+
console.log(' Auto-open: ' + (this.config.autoOpen ? chalk.green('yes') : chalk.gray('no')));
|
|
300
|
+
console.log(' Refresh interval: ' + (this.config.refreshInterval / 1000) + 's');
|
|
301
|
+
console.log('');
|
|
302
|
+
}
|
|
303
|
+
// ----------------------------------------------------------
|
|
304
|
+
// Private helpers
|
|
305
|
+
// ----------------------------------------------------------
|
|
306
|
+
async handleRequest(req, res) {
|
|
307
|
+
const url = req.url || '/';
|
|
308
|
+
// CORS
|
|
309
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
310
|
+
switch (url) {
|
|
311
|
+
case '/':
|
|
312
|
+
case '/dashboard': {
|
|
313
|
+
const data = this.gatherData();
|
|
314
|
+
const html = generateDashboardHTML(data, this.config);
|
|
315
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
316
|
+
res.end(html);
|
|
317
|
+
break;
|
|
318
|
+
}
|
|
319
|
+
case '/api/data': {
|
|
320
|
+
const data = this.gatherData();
|
|
321
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
322
|
+
res.end(JSON.stringify(data));
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
case '/api/sessions': {
|
|
326
|
+
const sessions = this.getSessionSummaries();
|
|
327
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
328
|
+
res.end(JSON.stringify({ sessions }));
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
case '/api/docs': {
|
|
332
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
333
|
+
res.end(JSON.stringify({
|
|
334
|
+
endpoints: [
|
|
335
|
+
{ method: 'GET', path: '/', description: 'Dashboard HTML page' },
|
|
336
|
+
{ method: 'GET', path: '/api/data', description: 'All dashboard data' },
|
|
337
|
+
{ method: 'GET', path: '/api/sessions', description: 'Session summaries' },
|
|
338
|
+
{ method: 'GET', path: '/api/docs', description: 'This documentation' },
|
|
339
|
+
],
|
|
340
|
+
}));
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
default: {
|
|
344
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
345
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
getSessionSummaries() {
|
|
350
|
+
const sessionDir = join(homedir(), '.neuro', 'sessions');
|
|
351
|
+
const sessions = [];
|
|
352
|
+
try {
|
|
353
|
+
if (!existsSync(sessionDir))
|
|
354
|
+
return sessions;
|
|
355
|
+
const files = readdirSync(sessionDir).filter(f => f.endsWith('.json'));
|
|
356
|
+
for (const file of files) {
|
|
357
|
+
try {
|
|
358
|
+
const raw = readFileSync(join(sessionDir, file), 'utf-8');
|
|
359
|
+
const data = JSON.parse(raw);
|
|
360
|
+
sessions.push({
|
|
361
|
+
id: data.id || file.replace('.json', ''),
|
|
362
|
+
createdAt: data.createdAt || 0,
|
|
363
|
+
updatedAt: data.updatedAt || 0,
|
|
364
|
+
messageCount: data.messages?.length || 0,
|
|
365
|
+
model: data.model || 'unknown',
|
|
366
|
+
totalCost: data.totalCost || 0,
|
|
367
|
+
description: data.description,
|
|
368
|
+
tags: data.tags || [],
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
catch { /* skip invalid */ }
|
|
372
|
+
}
|
|
373
|
+
// Sort by updatedAt descending
|
|
374
|
+
sessions.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
375
|
+
}
|
|
376
|
+
catch { /* ignore */ }
|
|
377
|
+
return sessions;
|
|
378
|
+
}
|
|
379
|
+
getTokenUsageData(sessions) {
|
|
380
|
+
const byDate = {};
|
|
381
|
+
for (const session of sessions) {
|
|
382
|
+
const date = new Date(session.updatedAt).toLocaleDateString();
|
|
383
|
+
if (!byDate[date])
|
|
384
|
+
byDate[date] = { input: 0, output: 0, cost: 0 };
|
|
385
|
+
byDate[date].cost += session.totalCost;
|
|
386
|
+
}
|
|
387
|
+
const labels = Object.keys(byDate).slice(-7);
|
|
388
|
+
return {
|
|
389
|
+
labels,
|
|
390
|
+
inputTokens: labels.map(l => byDate[l]?.input || 0),
|
|
391
|
+
outputTokens: labels.map(l => byDate[l]?.output || 0),
|
|
392
|
+
costs: labels.map(l => byDate[l]?.cost || 0),
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
getModelPerformanceData() {
|
|
396
|
+
const telemetryPath = join(homedir(), '.neuro', 'telemetry', 'models.json');
|
|
397
|
+
try {
|
|
398
|
+
if (existsSync(telemetryPath)) {
|
|
399
|
+
const raw = readFileSync(telemetryPath, 'utf-8');
|
|
400
|
+
const items = JSON.parse(raw);
|
|
401
|
+
return items.map(item => ({
|
|
402
|
+
model: item.key,
|
|
403
|
+
requestCount: item.requests,
|
|
404
|
+
totalTokens: item.inputTokens + item.outputTokens,
|
|
405
|
+
totalCost: item.cost,
|
|
406
|
+
avgLatencyMs: item.latencies.length > 0
|
|
407
|
+
? Math.round(item.latencies.reduce((a, b) => a + b, 0) / item.latencies.length)
|
|
408
|
+
: 0,
|
|
409
|
+
}));
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch { /* ignore */ }
|
|
413
|
+
return [];
|
|
414
|
+
}
|
|
415
|
+
getSpendingData() {
|
|
416
|
+
const spendingPath = join(homedir(), '.neuro', 'spending');
|
|
417
|
+
const today = new Date();
|
|
418
|
+
const dateStr = today.getFullYear() + '-' +
|
|
419
|
+
(today.getMonth() + 1).toString().padStart(2, '0') + '-' +
|
|
420
|
+
today.getDate().toString().padStart(2, '0');
|
|
421
|
+
try {
|
|
422
|
+
const filePath = join(spendingPath, dateStr + '.json');
|
|
423
|
+
if (existsSync(filePath)) {
|
|
424
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
425
|
+
const entries = JSON.parse(raw);
|
|
426
|
+
let todayTotal = 0;
|
|
427
|
+
const todayByModel = {};
|
|
428
|
+
for (const entry of entries) {
|
|
429
|
+
todayTotal += entry.cost;
|
|
430
|
+
todayByModel[entry.model] = (todayByModel[entry.model] || 0) + entry.cost;
|
|
431
|
+
}
|
|
432
|
+
return {
|
|
433
|
+
todayTotal,
|
|
434
|
+
todayByModel,
|
|
435
|
+
sessionTotal: todayTotal,
|
|
436
|
+
sessionByModel: todayByModel,
|
|
437
|
+
dailyLimit: 0,
|
|
438
|
+
estimatedDailySpend: todayTotal,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
catch { /* ignore */ }
|
|
443
|
+
return {
|
|
444
|
+
todayTotal: 0,
|
|
445
|
+
todayByModel: {},
|
|
446
|
+
sessionTotal: 0,
|
|
447
|
+
sessionByModel: {},
|
|
448
|
+
dailyLimit: 0,
|
|
449
|
+
estimatedDailySpend: 0,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
getSystemInfo() {
|
|
453
|
+
return {
|
|
454
|
+
version: '3.0.0',
|
|
455
|
+
uptime: this.isRunning ? Date.now() - this.startTime : 0,
|
|
456
|
+
platform: process.platform,
|
|
457
|
+
nodeVersion: process.version,
|
|
458
|
+
modelsAvailable: 23,
|
|
459
|
+
toolsAvailable: 10,
|
|
460
|
+
agentsAvailable: 8,
|
|
461
|
+
currentModel: 'default',
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
saveConfig() {
|
|
465
|
+
try {
|
|
466
|
+
const dir = join(DASHBOARD_CONFIG_PATH, '..');
|
|
467
|
+
if (!existsSync(dir))
|
|
468
|
+
mkdirSync(dir, { recursive: true });
|
|
469
|
+
writeFileSync(DASHBOARD_CONFIG_PATH, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
470
|
+
}
|
|
471
|
+
catch { /* Silently fail */ }
|
|
472
|
+
}
|
|
473
|
+
loadConfig() {
|
|
474
|
+
try {
|
|
475
|
+
if (existsSync(DASHBOARD_CONFIG_PATH)) {
|
|
476
|
+
const raw = readFileSync(DASHBOARD_CONFIG_PATH, 'utf-8');
|
|
477
|
+
const saved = JSON.parse(raw);
|
|
478
|
+
this.config = { ...this.config, ...saved };
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch { /* Silently fail */ }
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=web-dashboard.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export type HookEvent = 'SessionStart' | 'SessionEnd' | 'BeforeAgent' | 'AfterAgent' | 'BeforeModel' | 'AfterModel' | 'BeforeTool' | 'AfterTool' | 'AfterToolFailure' | 'PermissionRequest' | 'PermissionDenied' | 'UserPromptSubmit' | 'PreCompact' | 'PostCompact' | 'SubagentStart' | 'SubagentStop' | 'FileChanged' | 'CwdChanged' | 'ConfigChange';
|
|
2
|
+
export type HookType = 'command' | 'http' | 'prompt' | 'agent';
|
|
3
|
+
export interface HookDefinition {
|
|
4
|
+
event: HookEvent;
|
|
5
|
+
type: HookType;
|
|
6
|
+
command?: string;
|
|
7
|
+
url?: string;
|
|
8
|
+
prompt?: string;
|
|
9
|
+
matcher?: string;
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface HookContext {
|
|
13
|
+
event: HookEvent;
|
|
14
|
+
toolName?: string;
|
|
15
|
+
toolArgs?: Record<string, unknown>;
|
|
16
|
+
toolResult?: string;
|
|
17
|
+
prompt?: string;
|
|
18
|
+
modelResponse?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
workingDirectory: string;
|
|
21
|
+
sessionId: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
}
|
|
24
|
+
export interface HookResult {
|
|
25
|
+
action: 'continue' | 'block' | 'modify' | 'retry';
|
|
26
|
+
modifiedArgs?: Record<string, unknown>;
|
|
27
|
+
modifiedPrompt?: string;
|
|
28
|
+
additionalContext?: string;
|
|
29
|
+
reason?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare class HooksSystem {
|
|
32
|
+
private hooks;
|
|
33
|
+
private globalHooks;
|
|
34
|
+
/**
|
|
35
|
+
* Register a hook
|
|
36
|
+
*/
|
|
37
|
+
register(hook: HookDefinition): void;
|
|
38
|
+
/**
|
|
39
|
+
* Register a global hook that fires on all events
|
|
40
|
+
*/
|
|
41
|
+
registerGlobal(hook: HookDefinition): void;
|
|
42
|
+
/**
|
|
43
|
+
* Unregister a hook
|
|
44
|
+
*/
|
|
45
|
+
unregister(event: HookEvent, index: number): void;
|
|
46
|
+
/**
|
|
47
|
+
* Get all hooks for an event
|
|
48
|
+
*/
|
|
49
|
+
getHooks(event: HookEvent): HookDefinition[];
|
|
50
|
+
/**
|
|
51
|
+
* Execute all hooks for an event
|
|
52
|
+
*/
|
|
53
|
+
execute(event: HookEvent, context: HookContext): Promise<HookResult>;
|
|
54
|
+
/**
|
|
55
|
+
* Execute a single hook
|
|
56
|
+
*/
|
|
57
|
+
private executeHook;
|
|
58
|
+
private executeCommandHook;
|
|
59
|
+
private executeHttpHook;
|
|
60
|
+
/**
|
|
61
|
+
* Load hooks from config
|
|
62
|
+
*/
|
|
63
|
+
loadFromConfig(config: {
|
|
64
|
+
hooks?: HookDefinition[];
|
|
65
|
+
}): void;
|
|
66
|
+
/**
|
|
67
|
+
* List all registered hooks
|
|
68
|
+
*/
|
|
69
|
+
list(): Array<{
|
|
70
|
+
event: HookEvent;
|
|
71
|
+
hooks: HookDefinition[];
|
|
72
|
+
}>;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=hooks.d.ts.map
|