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,157 @@
|
|
|
1
|
+
export interface TelemetryConfig {
|
|
2
|
+
/** Whether telemetry is enabled (opt-in, default: false) */
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
/** Storage directory for telemetry data */
|
|
5
|
+
dataDir: string;
|
|
6
|
+
/** Whether to include model performance metrics */
|
|
7
|
+
trackModelPerformance: boolean;
|
|
8
|
+
/** Whether to include tool usage stats */
|
|
9
|
+
trackToolUsage: boolean;
|
|
10
|
+
/** Whether to include session metrics */
|
|
11
|
+
trackSessionMetrics: boolean;
|
|
12
|
+
/** Retention period in days (default: 90) */
|
|
13
|
+
retentionDays: number;
|
|
14
|
+
/** Anonymous session ID hash (no PII) */
|
|
15
|
+
anonymousId: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SessionMetric {
|
|
18
|
+
sessionId: string;
|
|
19
|
+
duration: number;
|
|
20
|
+
messageCount: number;
|
|
21
|
+
modelUsed: string;
|
|
22
|
+
inputTokens: number;
|
|
23
|
+
outputTokens: number;
|
|
24
|
+
cost: number;
|
|
25
|
+
toolsCalled: number;
|
|
26
|
+
timestamp: number;
|
|
27
|
+
}
|
|
28
|
+
export interface ToolUsageMetric {
|
|
29
|
+
toolName: string;
|
|
30
|
+
callCount: number;
|
|
31
|
+
successCount: number;
|
|
32
|
+
errorCount: number;
|
|
33
|
+
avgDurationMs: number;
|
|
34
|
+
lastUsed: number;
|
|
35
|
+
}
|
|
36
|
+
export interface ModelPerformanceMetric {
|
|
37
|
+
model: string;
|
|
38
|
+
requestCount: number;
|
|
39
|
+
totalInputTokens: number;
|
|
40
|
+
totalOutputTokens: number;
|
|
41
|
+
totalCost: number;
|
|
42
|
+
avgLatencyMs: number;
|
|
43
|
+
errorCount: number;
|
|
44
|
+
p50LatencyMs: number;
|
|
45
|
+
p95LatencyMs: number;
|
|
46
|
+
p99LatencyMs: number;
|
|
47
|
+
}
|
|
48
|
+
export interface TelemetryReport {
|
|
49
|
+
generatedAt: number;
|
|
50
|
+
period: {
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
};
|
|
54
|
+
totalSessions: number;
|
|
55
|
+
totalMessages: number;
|
|
56
|
+
totalTokens: {
|
|
57
|
+
input: number;
|
|
58
|
+
output: number;
|
|
59
|
+
};
|
|
60
|
+
totalCost: number;
|
|
61
|
+
toolUsage: ToolUsageMetric[];
|
|
62
|
+
modelPerformance: ModelPerformanceMetric[];
|
|
63
|
+
sessionMetrics: SessionMetric[];
|
|
64
|
+
}
|
|
65
|
+
export declare class TelemetrySystem {
|
|
66
|
+
private config;
|
|
67
|
+
private sessionMetrics;
|
|
68
|
+
private toolUsageMap;
|
|
69
|
+
private modelPerformanceMap;
|
|
70
|
+
private currentSessionStart;
|
|
71
|
+
private currentSessionMessages;
|
|
72
|
+
private currentSessionTools;
|
|
73
|
+
private currentSessionModel;
|
|
74
|
+
private currentSessionInputTokens;
|
|
75
|
+
private currentSessionOutputTokens;
|
|
76
|
+
private currentSessionCost;
|
|
77
|
+
constructor(config?: Partial<TelemetryConfig>);
|
|
78
|
+
/**
|
|
79
|
+
* Check if telemetry is enabled
|
|
80
|
+
*/
|
|
81
|
+
isEnabled(): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Enable telemetry (opt-in)
|
|
84
|
+
*/
|
|
85
|
+
enable(): void;
|
|
86
|
+
/**
|
|
87
|
+
* Disable telemetry
|
|
88
|
+
*/
|
|
89
|
+
disable(): void;
|
|
90
|
+
/**
|
|
91
|
+
* Toggle telemetry on/off
|
|
92
|
+
*/
|
|
93
|
+
toggle(): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Record a session start
|
|
96
|
+
*/
|
|
97
|
+
startSession(sessionId: string, model: string): void;
|
|
98
|
+
/**
|
|
99
|
+
* Record a message in the current session
|
|
100
|
+
*/
|
|
101
|
+
recordMessage(): void;
|
|
102
|
+
/**
|
|
103
|
+
* Record a tool call
|
|
104
|
+
*/
|
|
105
|
+
recordToolCall(toolName: string, durationMs: number, success: boolean): void;
|
|
106
|
+
/**
|
|
107
|
+
* Record model performance data
|
|
108
|
+
*/
|
|
109
|
+
recordModelPerformance(model: string, inputTokens: number, outputTokens: number, cost: number, latencyMs: number, error: boolean): void;
|
|
110
|
+
/**
|
|
111
|
+
* End the current session and record its metrics
|
|
112
|
+
*/
|
|
113
|
+
endSession(sessionId: string): void;
|
|
114
|
+
/**
|
|
115
|
+
* Get all tool usage metrics
|
|
116
|
+
*/
|
|
117
|
+
getToolUsageMetrics(): ToolUsageMetric[];
|
|
118
|
+
/**
|
|
119
|
+
* Get model performance metrics
|
|
120
|
+
*/
|
|
121
|
+
getModelPerformanceMetrics(): ModelPerformanceMetric[];
|
|
122
|
+
/**
|
|
123
|
+
* Get session metrics
|
|
124
|
+
*/
|
|
125
|
+
getSessionMetrics(): SessionMetric[];
|
|
126
|
+
/**
|
|
127
|
+
* Generate a full telemetry report
|
|
128
|
+
*/
|
|
129
|
+
generateReport(startTimestamp?: number, endTimestamp?: number): TelemetryReport;
|
|
130
|
+
/**
|
|
131
|
+
* Export telemetry data as JSON string
|
|
132
|
+
*/
|
|
133
|
+
exportJSON(pretty?: boolean): string;
|
|
134
|
+
/**
|
|
135
|
+
* Export telemetry data to a file
|
|
136
|
+
*/
|
|
137
|
+
exportToFile(filePath: string): void;
|
|
138
|
+
/**
|
|
139
|
+
* Print a summary of telemetry data
|
|
140
|
+
*/
|
|
141
|
+
printSummary(): void;
|
|
142
|
+
/**
|
|
143
|
+
* Clear all telemetry data
|
|
144
|
+
*/
|
|
145
|
+
clearData(): void;
|
|
146
|
+
/**
|
|
147
|
+
* Get current config
|
|
148
|
+
*/
|
|
149
|
+
getConfig(): TelemetryConfig;
|
|
150
|
+
private percentile;
|
|
151
|
+
private ensureDataDir;
|
|
152
|
+
private persistConfig;
|
|
153
|
+
private persistData;
|
|
154
|
+
private loadPersistedData;
|
|
155
|
+
private cleanupOldData;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Telemetry System
|
|
3
|
+
// Anonymous usage tracking (opt-in), session metrics,
|
|
4
|
+
// tool usage, model performance. Privacy-first: no PII.
|
|
5
|
+
// ============================================================
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
// -----------------------------------------------------------
|
|
11
|
+
// Internal helpers
|
|
12
|
+
// -----------------------------------------------------------
|
|
13
|
+
const DEFAULT_DATA_DIR = join(homedir(), '.neuro', 'telemetry');
|
|
14
|
+
function generateAnonymousId() {
|
|
15
|
+
const chars = 'abcdef0123456789';
|
|
16
|
+
let id = '';
|
|
17
|
+
for (let i = 0; i < 16; i++) {
|
|
18
|
+
id += chars[Math.floor(Math.random() * chars.length)];
|
|
19
|
+
}
|
|
20
|
+
return id;
|
|
21
|
+
}
|
|
22
|
+
function defaultConfig() {
|
|
23
|
+
return {
|
|
24
|
+
enabled: false,
|
|
25
|
+
dataDir: DEFAULT_DATA_DIR,
|
|
26
|
+
trackModelPerformance: true,
|
|
27
|
+
trackToolUsage: true,
|
|
28
|
+
trackSessionMetrics: true,
|
|
29
|
+
retentionDays: 90,
|
|
30
|
+
anonymousId: generateAnonymousId(),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// -----------------------------------------------------------
|
|
34
|
+
// TelemetrySystem
|
|
35
|
+
// -----------------------------------------------------------
|
|
36
|
+
export class TelemetrySystem {
|
|
37
|
+
config;
|
|
38
|
+
sessionMetrics = [];
|
|
39
|
+
toolUsageMap = new Map();
|
|
40
|
+
modelPerformanceMap = new Map();
|
|
41
|
+
currentSessionStart = Date.now();
|
|
42
|
+
currentSessionMessages = 0;
|
|
43
|
+
currentSessionTools = 0;
|
|
44
|
+
currentSessionModel = '';
|
|
45
|
+
currentSessionInputTokens = 0;
|
|
46
|
+
currentSessionOutputTokens = 0;
|
|
47
|
+
currentSessionCost = 0;
|
|
48
|
+
constructor(config) {
|
|
49
|
+
this.config = { ...defaultConfig(), ...config };
|
|
50
|
+
this.ensureDataDir();
|
|
51
|
+
this.loadPersistedData();
|
|
52
|
+
}
|
|
53
|
+
// ----------------------------------------------------------
|
|
54
|
+
// Public API
|
|
55
|
+
// ----------------------------------------------------------
|
|
56
|
+
/**
|
|
57
|
+
* Check if telemetry is enabled
|
|
58
|
+
*/
|
|
59
|
+
isEnabled() {
|
|
60
|
+
return this.config.enabled;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Enable telemetry (opt-in)
|
|
64
|
+
*/
|
|
65
|
+
enable() {
|
|
66
|
+
this.config.enabled = true;
|
|
67
|
+
this.persistConfig();
|
|
68
|
+
console.log(chalk.green('Telemetry enabled. No PII is collected.'));
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Disable telemetry
|
|
72
|
+
*/
|
|
73
|
+
disable() {
|
|
74
|
+
this.config.enabled = false;
|
|
75
|
+
this.persistConfig();
|
|
76
|
+
console.log(chalk.gray('Telemetry disabled.'));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Toggle telemetry on/off
|
|
80
|
+
*/
|
|
81
|
+
toggle() {
|
|
82
|
+
this.config.enabled = !this.config.enabled;
|
|
83
|
+
this.persistConfig();
|
|
84
|
+
return this.config.enabled;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Record a session start
|
|
88
|
+
*/
|
|
89
|
+
startSession(sessionId, model) {
|
|
90
|
+
if (!this.config.enabled || !this.config.trackSessionMetrics)
|
|
91
|
+
return;
|
|
92
|
+
this.currentSessionStart = Date.now();
|
|
93
|
+
this.currentSessionMessages = 0;
|
|
94
|
+
this.currentSessionTools = 0;
|
|
95
|
+
this.currentSessionModel = model;
|
|
96
|
+
this.currentSessionInputTokens = 0;
|
|
97
|
+
this.currentSessionOutputTokens = 0;
|
|
98
|
+
this.currentSessionCost = 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Record a message in the current session
|
|
102
|
+
*/
|
|
103
|
+
recordMessage() {
|
|
104
|
+
if (!this.config.enabled || !this.config.trackSessionMetrics)
|
|
105
|
+
return;
|
|
106
|
+
this.currentSessionMessages++;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Record a tool call
|
|
110
|
+
*/
|
|
111
|
+
recordToolCall(toolName, durationMs, success) {
|
|
112
|
+
if (!this.config.enabled || !this.config.trackToolUsage)
|
|
113
|
+
return;
|
|
114
|
+
this.currentSessionTools++;
|
|
115
|
+
let metric = this.toolUsageMap.get(toolName);
|
|
116
|
+
if (!metric) {
|
|
117
|
+
metric = {
|
|
118
|
+
toolName,
|
|
119
|
+
callCount: 0,
|
|
120
|
+
successCount: 0,
|
|
121
|
+
errorCount: 0,
|
|
122
|
+
avgDurationMs: 0,
|
|
123
|
+
lastUsed: Date.now(),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
const totalDuration = metric.avgDurationMs * metric.callCount + durationMs;
|
|
127
|
+
metric.callCount++;
|
|
128
|
+
if (success)
|
|
129
|
+
metric.successCount++;
|
|
130
|
+
else
|
|
131
|
+
metric.errorCount++;
|
|
132
|
+
metric.avgDurationMs = totalDuration / metric.callCount;
|
|
133
|
+
metric.lastUsed = Date.now();
|
|
134
|
+
this.toolUsageMap.set(toolName, metric);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Record model performance data
|
|
138
|
+
*/
|
|
139
|
+
recordModelPerformance(model, inputTokens, outputTokens, cost, latencyMs, error) {
|
|
140
|
+
if (!this.config.enabled || !this.config.trackModelPerformance)
|
|
141
|
+
return;
|
|
142
|
+
this.currentSessionInputTokens += inputTokens;
|
|
143
|
+
this.currentSessionOutputTokens += outputTokens;
|
|
144
|
+
this.currentSessionCost += cost;
|
|
145
|
+
let perf = this.modelPerformanceMap.get(model);
|
|
146
|
+
if (!perf) {
|
|
147
|
+
perf = { latencies: [], errors: 0, requests: 0, inputTokens: 0, outputTokens: 0, cost: 0 };
|
|
148
|
+
}
|
|
149
|
+
perf.requests++;
|
|
150
|
+
perf.inputTokens += inputTokens;
|
|
151
|
+
perf.outputTokens += outputTokens;
|
|
152
|
+
perf.cost += cost;
|
|
153
|
+
perf.latencies.push(latencyMs);
|
|
154
|
+
if (error)
|
|
155
|
+
perf.errors++;
|
|
156
|
+
// Keep only last 1000 latency samples
|
|
157
|
+
if (perf.latencies.length > 1000) {
|
|
158
|
+
perf.latencies = perf.latencies.slice(-1000);
|
|
159
|
+
}
|
|
160
|
+
this.modelPerformanceMap.set(model, perf);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* End the current session and record its metrics
|
|
164
|
+
*/
|
|
165
|
+
endSession(sessionId) {
|
|
166
|
+
if (!this.config.enabled || !this.config.trackSessionMetrics)
|
|
167
|
+
return;
|
|
168
|
+
const metric = {
|
|
169
|
+
sessionId,
|
|
170
|
+
duration: Date.now() - this.currentSessionStart,
|
|
171
|
+
messageCount: this.currentSessionMessages,
|
|
172
|
+
modelUsed: this.currentSessionModel,
|
|
173
|
+
inputTokens: this.currentSessionInputTokens,
|
|
174
|
+
outputTokens: this.currentSessionOutputTokens,
|
|
175
|
+
cost: this.currentSessionCost,
|
|
176
|
+
toolsCalled: this.currentSessionTools,
|
|
177
|
+
timestamp: Date.now(),
|
|
178
|
+
};
|
|
179
|
+
this.sessionMetrics.push(metric);
|
|
180
|
+
this.persistData();
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Get all tool usage metrics
|
|
184
|
+
*/
|
|
185
|
+
getToolUsageMetrics() {
|
|
186
|
+
return Array.from(this.toolUsageMap.values());
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get model performance metrics
|
|
190
|
+
*/
|
|
191
|
+
getModelPerformanceMetrics() {
|
|
192
|
+
const metrics = [];
|
|
193
|
+
for (const [model, perf] of this.modelPerformanceMap) {
|
|
194
|
+
const sorted = [...perf.latencies].sort((a, b) => a - b);
|
|
195
|
+
const p50 = this.percentile(sorted, 50);
|
|
196
|
+
const p95 = this.percentile(sorted, 95);
|
|
197
|
+
const p99 = this.percentile(sorted, 99);
|
|
198
|
+
const avg = sorted.length > 0 ? sorted.reduce((a, b) => a + b, 0) / sorted.length : 0;
|
|
199
|
+
metrics.push({
|
|
200
|
+
model,
|
|
201
|
+
requestCount: perf.requests,
|
|
202
|
+
totalInputTokens: perf.inputTokens,
|
|
203
|
+
totalOutputTokens: perf.outputTokens,
|
|
204
|
+
totalCost: perf.cost,
|
|
205
|
+
avgLatencyMs: Math.round(avg),
|
|
206
|
+
errorCount: perf.errors,
|
|
207
|
+
p50LatencyMs: Math.round(p50),
|
|
208
|
+
p95LatencyMs: Math.round(p95),
|
|
209
|
+
p99LatencyMs: Math.round(p99),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return metrics;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Get session metrics
|
|
216
|
+
*/
|
|
217
|
+
getSessionMetrics() {
|
|
218
|
+
return [...this.sessionMetrics];
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Generate a full telemetry report
|
|
222
|
+
*/
|
|
223
|
+
generateReport(startTimestamp, endTimestamp) {
|
|
224
|
+
const end = endTimestamp || Date.now();
|
|
225
|
+
const start = startTimestamp || end - 30 * 24 * 60 * 60 * 1000; // default: last 30 days
|
|
226
|
+
const filteredSessions = this.sessionMetrics.filter(s => s.timestamp >= start && s.timestamp <= end);
|
|
227
|
+
return {
|
|
228
|
+
generatedAt: Date.now(),
|
|
229
|
+
period: { start, end },
|
|
230
|
+
totalSessions: filteredSessions.length,
|
|
231
|
+
totalMessages: filteredSessions.reduce((sum, s) => sum + s.messageCount, 0),
|
|
232
|
+
totalTokens: {
|
|
233
|
+
input: filteredSessions.reduce((sum, s) => sum + s.inputTokens, 0),
|
|
234
|
+
output: filteredSessions.reduce((sum, s) => sum + s.outputTokens, 0),
|
|
235
|
+
},
|
|
236
|
+
totalCost: filteredSessions.reduce((sum, s) => sum + s.cost, 0),
|
|
237
|
+
toolUsage: this.getToolUsageMetrics(),
|
|
238
|
+
modelPerformance: this.getModelPerformanceMetrics(),
|
|
239
|
+
sessionMetrics: filteredSessions,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Export telemetry data as JSON string
|
|
244
|
+
*/
|
|
245
|
+
exportJSON(pretty) {
|
|
246
|
+
const report = this.generateReport();
|
|
247
|
+
return pretty ? JSON.stringify(report, null, 2) : JSON.stringify(report);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Export telemetry data to a file
|
|
251
|
+
*/
|
|
252
|
+
exportToFile(filePath) {
|
|
253
|
+
try {
|
|
254
|
+
const data = this.exportJSON(true);
|
|
255
|
+
writeFileSync(filePath, data, 'utf-8');
|
|
256
|
+
console.log(chalk.green(`Telemetry data exported to ${filePath}`));
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
console.log(chalk.red(`Failed to export telemetry: ${error instanceof Error ? error.message : String(error)}`));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Print a summary of telemetry data
|
|
264
|
+
*/
|
|
265
|
+
printSummary() {
|
|
266
|
+
if (!this.config.enabled) {
|
|
267
|
+
console.log(chalk.gray('Telemetry is disabled. Use /telemetry on to enable.'));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const report = this.generateReport();
|
|
271
|
+
console.log('');
|
|
272
|
+
console.log(chalk.bold('--- NeuroCLI Telemetry Report ---'));
|
|
273
|
+
console.log('');
|
|
274
|
+
console.log(` Anonymous ID: ${chalk.cyan(this.config.anonymousId)}`);
|
|
275
|
+
console.log(` Period: ${new Date(report.period.start).toLocaleDateString()} - ${new Date(report.period.end).toLocaleDateString()}`);
|
|
276
|
+
console.log('');
|
|
277
|
+
console.log(` Total Sessions: ${chalk.yellow(String(report.totalSessions))}`);
|
|
278
|
+
console.log(` Total Messages: ${chalk.yellow(String(report.totalMessages))}`);
|
|
279
|
+
console.log(` Total Tokens In: ${chalk.yellow(report.totalTokens.input.toLocaleString())}`);
|
|
280
|
+
console.log(` Total Tokens Out:${chalk.yellow(report.totalTokens.output.toLocaleString())}`);
|
|
281
|
+
console.log(` Total Cost: ${chalk.green('$' + report.totalCost.toFixed(4))}`);
|
|
282
|
+
console.log('');
|
|
283
|
+
// Tool usage
|
|
284
|
+
if (report.toolUsage.length > 0) {
|
|
285
|
+
console.log(chalk.bold(' Tool Usage:'));
|
|
286
|
+
for (const tool of report.toolUsage.sort((a, b) => b.callCount - a.callCount).slice(0, 10)) {
|
|
287
|
+
const successRate = tool.callCount > 0 ? ((tool.successCount / tool.callCount) * 100).toFixed(1) : '0';
|
|
288
|
+
console.log(` ${chalk.cyan(tool.toolName)}: ${tool.callCount} calls (${successRate}% success, avg ${Math.round(tool.avgDurationMs)}ms)`);
|
|
289
|
+
}
|
|
290
|
+
console.log('');
|
|
291
|
+
}
|
|
292
|
+
// Model performance
|
|
293
|
+
if (report.modelPerformance.length > 0) {
|
|
294
|
+
console.log(chalk.bold(' Model Performance:'));
|
|
295
|
+
for (const model of report.modelPerformance) {
|
|
296
|
+
console.log(` ${chalk.cyan(model.model)}:`);
|
|
297
|
+
console.log(` Requests: ${model.requestCount} | Errors: ${model.errorCount}`);
|
|
298
|
+
console.log(` Latency: p50=${model.p50LatencyMs}ms p95=${model.p95LatencyMs}ms p99=${model.p99LatencyMs}ms`);
|
|
299
|
+
console.log(` Cost: $${model.totalCost.toFixed(4)}`);
|
|
300
|
+
}
|
|
301
|
+
console.log('');
|
|
302
|
+
}
|
|
303
|
+
console.log(chalk.bold('---------------------------------'));
|
|
304
|
+
console.log('');
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Clear all telemetry data
|
|
308
|
+
*/
|
|
309
|
+
clearData() {
|
|
310
|
+
this.sessionMetrics = [];
|
|
311
|
+
this.toolUsageMap.clear();
|
|
312
|
+
this.modelPerformanceMap.clear();
|
|
313
|
+
this.persistData();
|
|
314
|
+
console.log(chalk.green('Telemetry data cleared.'));
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get current config
|
|
318
|
+
*/
|
|
319
|
+
getConfig() {
|
|
320
|
+
return { ...this.config };
|
|
321
|
+
}
|
|
322
|
+
// ----------------------------------------------------------
|
|
323
|
+
// Private helpers
|
|
324
|
+
// ----------------------------------------------------------
|
|
325
|
+
percentile(sorted, p) {
|
|
326
|
+
if (sorted.length === 0)
|
|
327
|
+
return 0;
|
|
328
|
+
const index = Math.ceil((p / 100) * sorted.length) - 1;
|
|
329
|
+
return sorted[Math.max(0, index)];
|
|
330
|
+
}
|
|
331
|
+
ensureDataDir() {
|
|
332
|
+
if (!existsSync(this.config.dataDir)) {
|
|
333
|
+
mkdirSync(this.config.dataDir, { recursive: true });
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
persistConfig() {
|
|
337
|
+
try {
|
|
338
|
+
const configPath = join(this.config.dataDir, 'config.json');
|
|
339
|
+
writeFileSync(configPath, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
340
|
+
}
|
|
341
|
+
catch { /* Silently fail */ }
|
|
342
|
+
}
|
|
343
|
+
persistData() {
|
|
344
|
+
if (!this.config.enabled)
|
|
345
|
+
return;
|
|
346
|
+
try {
|
|
347
|
+
this.ensureDataDir();
|
|
348
|
+
// Persist session metrics
|
|
349
|
+
const sessionPath = join(this.config.dataDir, 'sessions.json');
|
|
350
|
+
writeFileSync(sessionPath, JSON.stringify(this.sessionMetrics, null, 2), 'utf-8');
|
|
351
|
+
// Persist tool usage
|
|
352
|
+
const toolPath = join(this.config.dataDir, 'tools.json');
|
|
353
|
+
writeFileSync(toolPath, JSON.stringify(Array.from(this.toolUsageMap.entries()), null, 2), 'utf-8');
|
|
354
|
+
// Persist model performance
|
|
355
|
+
const modelPath = join(this.config.dataDir, 'models.json');
|
|
356
|
+
const serializableModels = Array.from(this.modelPerformanceMap.entries()).map(([key, val]) => ({
|
|
357
|
+
key,
|
|
358
|
+
...val,
|
|
359
|
+
}));
|
|
360
|
+
writeFileSync(modelPath, JSON.stringify(serializableModels, null, 2), 'utf-8');
|
|
361
|
+
// Cleanup old data
|
|
362
|
+
this.cleanupOldData();
|
|
363
|
+
}
|
|
364
|
+
catch { /* Silently fail */ }
|
|
365
|
+
}
|
|
366
|
+
loadPersistedData() {
|
|
367
|
+
try {
|
|
368
|
+
// Load config
|
|
369
|
+
const configPath = join(this.config.dataDir, 'config.json');
|
|
370
|
+
if (existsSync(configPath)) {
|
|
371
|
+
const raw = readFileSync(configPath, 'utf-8');
|
|
372
|
+
const saved = JSON.parse(raw);
|
|
373
|
+
this.config = { ...this.config, ...saved };
|
|
374
|
+
}
|
|
375
|
+
if (!this.config.enabled)
|
|
376
|
+
return;
|
|
377
|
+
// Load session metrics
|
|
378
|
+
const sessionPath = join(this.config.dataDir, 'sessions.json');
|
|
379
|
+
if (existsSync(sessionPath)) {
|
|
380
|
+
const raw = readFileSync(sessionPath, 'utf-8');
|
|
381
|
+
this.sessionMetrics = JSON.parse(raw);
|
|
382
|
+
}
|
|
383
|
+
// Load tool usage
|
|
384
|
+
const toolPath = join(this.config.dataDir, 'tools.json');
|
|
385
|
+
if (existsSync(toolPath)) {
|
|
386
|
+
const raw = readFileSync(toolPath, 'utf-8');
|
|
387
|
+
const entries = JSON.parse(raw);
|
|
388
|
+
this.toolUsageMap = new Map(entries);
|
|
389
|
+
}
|
|
390
|
+
// Load model performance
|
|
391
|
+
const modelPath = join(this.config.dataDir, 'models.json');
|
|
392
|
+
if (existsSync(modelPath)) {
|
|
393
|
+
const raw = readFileSync(modelPath, 'utf-8');
|
|
394
|
+
const items = JSON.parse(raw);
|
|
395
|
+
this.modelPerformanceMap = new Map(items.map(item => [item.key, {
|
|
396
|
+
latencies: item.latencies,
|
|
397
|
+
errors: item.errors,
|
|
398
|
+
requests: item.requests,
|
|
399
|
+
inputTokens: item.inputTokens,
|
|
400
|
+
outputTokens: item.outputTokens,
|
|
401
|
+
cost: item.cost,
|
|
402
|
+
}]));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch { /* Silently fail - telemetry should never block the CLI */ }
|
|
406
|
+
}
|
|
407
|
+
cleanupOldData() {
|
|
408
|
+
const cutoff = Date.now() - this.config.retentionDays * 24 * 60 * 60 * 1000;
|
|
409
|
+
this.sessionMetrics = this.sessionMetrics.filter(s => s.timestamp >= cutoff);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export interface TerminalCapabilities {
|
|
2
|
+
osc52: boolean;
|
|
3
|
+
trueColor: boolean;
|
|
4
|
+
unicode: boolean;
|
|
5
|
+
iterm2: boolean;
|
|
6
|
+
kitty: boolean;
|
|
7
|
+
sixel: boolean;
|
|
8
|
+
title: boolean;
|
|
9
|
+
hyperlinks: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface TerminalSize {
|
|
12
|
+
rows: number;
|
|
13
|
+
cols: number;
|
|
14
|
+
}
|
|
15
|
+
export interface SplitPane {
|
|
16
|
+
id: string;
|
|
17
|
+
label: string;
|
|
18
|
+
content: string[];
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
scrollOffset: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SplitPaneLayout {
|
|
24
|
+
panes: SplitPane[];
|
|
25
|
+
gap: number;
|
|
26
|
+
borderStyle: 'single' | 'double' | 'round' | 'none';
|
|
27
|
+
}
|
|
28
|
+
export interface ProgressBarOptions {
|
|
29
|
+
total: number;
|
|
30
|
+
current: number;
|
|
31
|
+
width?: number;
|
|
32
|
+
label?: string;
|
|
33
|
+
completeChar?: string;
|
|
34
|
+
incompleteChar?: string;
|
|
35
|
+
showPercent?: boolean;
|
|
36
|
+
showETA?: boolean;
|
|
37
|
+
startTime?: number;
|
|
38
|
+
}
|
|
39
|
+
export interface SpinnerOptions {
|
|
40
|
+
frames?: string[];
|
|
41
|
+
interval?: number;
|
|
42
|
+
label?: string;
|
|
43
|
+
color?: 'cyan' | 'green' | 'yellow' | 'red' | 'magenta' | 'blue';
|
|
44
|
+
}
|
|
45
|
+
export declare class TerminalUX {
|
|
46
|
+
private capabilities;
|
|
47
|
+
private spinnerInterval;
|
|
48
|
+
private spinnerActive;
|
|
49
|
+
constructor();
|
|
50
|
+
/**
|
|
51
|
+
* Copy text to the terminal clipboard using the OSC 52 escape sequence.
|
|
52
|
+
* Works in remote SSH sessions when the terminal emulator supports it.
|
|
53
|
+
* Returns true if the sequence was written, false if OSC 52 is unsupported.
|
|
54
|
+
*/
|
|
55
|
+
copyToClipboard(text: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Check whether the current terminal supports OSC 52 clipboard operations.
|
|
58
|
+
*/
|
|
59
|
+
isOSC52Supported(): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Launch an interactive TUI command (e.g. vim, top, htop) and capture its
|
|
62
|
+
* exit code and any output. The child process inherits stdio so the user
|
|
63
|
+
* can interact with the TUI directly.
|
|
64
|
+
*/
|
|
65
|
+
launchInteractive(command: string, options?: {
|
|
66
|
+
rows?: number;
|
|
67
|
+
cols?: number;
|
|
68
|
+
}): Promise<{
|
|
69
|
+
exitCode: number;
|
|
70
|
+
output: string;
|
|
71
|
+
}>;
|
|
72
|
+
/**
|
|
73
|
+
* Detect whether a command is a known TUI / interactive application.
|
|
74
|
+
*/
|
|
75
|
+
isTUIApp(command: string): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Render a split-pane layout to a string. Each pane has a label and content
|
|
78
|
+
* lines, rendered side-by-side with borders.
|
|
79
|
+
*/
|
|
80
|
+
renderSplitPane(layout: SplitPaneLayout): string;
|
|
81
|
+
/**
|
|
82
|
+
* Apply chalk-based syntax highlighting to a code string for the given language.
|
|
83
|
+
* Returns a chalk-colored string suitable for terminal output.
|
|
84
|
+
*/
|
|
85
|
+
highlight(code: string, language: string): string;
|
|
86
|
+
/**
|
|
87
|
+
* Highlight a unified diff string with color coding.
|
|
88
|
+
* - Added lines: green
|
|
89
|
+
* - Removed lines: red
|
|
90
|
+
* - Header lines (---, +++, @@): cyan
|
|
91
|
+
* - Context lines: dim gray
|
|
92
|
+
*/
|
|
93
|
+
highlightDiff(diff: string): string;
|
|
94
|
+
/**
|
|
95
|
+
* Return detected terminal capabilities.
|
|
96
|
+
*/
|
|
97
|
+
getTerminalCapabilities(): TerminalCapabilities;
|
|
98
|
+
/**
|
|
99
|
+
* Return the current terminal dimensions.
|
|
100
|
+
*/
|
|
101
|
+
getTerminalSize(): TerminalSize;
|
|
102
|
+
/**
|
|
103
|
+
* Check whether the terminal supports 24-bit true color.
|
|
104
|
+
*/
|
|
105
|
+
supportsTrueColor(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Check whether the terminal supports Unicode characters (beyond ASCII).
|
|
108
|
+
*/
|
|
109
|
+
supportsUnicode(): boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Render a progress bar as a string.
|
|
112
|
+
*/
|
|
113
|
+
renderProgressBar(options: ProgressBarOptions): string;
|
|
114
|
+
/**
|
|
115
|
+
* Start a spinner animation on the terminal.
|
|
116
|
+
* Returns a stop function.
|
|
117
|
+
*/
|
|
118
|
+
startSpinner(options?: SpinnerOptions): {
|
|
119
|
+
stop: (finalMessage?: string) => void;
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Render a multi-step progress indicator (checklist).
|
|
123
|
+
*/
|
|
124
|
+
renderStepProgress(steps: Array<{
|
|
125
|
+
label: string;
|
|
126
|
+
status: 'pending' | 'running' | 'done' | 'failed';
|
|
127
|
+
}>): string;
|
|
128
|
+
private detectCapabilities;
|
|
129
|
+
private highlightLine;
|
|
130
|
+
private parseCommand;
|
|
131
|
+
private truncateToWidth;
|
|
132
|
+
private formatDuration;
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=terminal-ux.d.ts.map
|