copilot-tracer 1.0.1

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/dist/setup.js ADDED
@@ -0,0 +1,216 @@
1
+ /**
2
+ * copilot-tracer setup — auto-detect copilot CLI + VS Code and inject OTLP env config
3
+ *
4
+ * What it does:
5
+ * 1. Detect copilot CLI (which copilot)
6
+ * 2. Detect VS Code installation + built-in copilot (v1.99+)
7
+ * 3. Inject OTEL env vars into:
8
+ * - Shell profile (~/.zshrc / ~/.bashrc / ~/.zprofile)
9
+ * - VS Code settings.json (terminal.integrated.env.osx)
10
+ * 4. Print a summary and next steps
11
+ */
12
+ import { execSync } from 'child_process';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import os from 'os';
16
+ const OTEL_ENDPOINT_KEY = 'OTEL_EXPORTER_OTLP_ENDPOINT';
17
+ const OTEL_CONTENT_KEY = 'OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT';
18
+ const OTEL_ENABLED_KEY = 'COPILOT_OTEL_ENABLED';
19
+ function otelEnvBlock(port) {
20
+ return [
21
+ `# >>> copilot-tracer OTLP config (auto-added) >>>`,
22
+ `export ${OTEL_ENDPOINT_KEY}=http://localhost:${port}`,
23
+ `export ${OTEL_CONTENT_KEY}=true`,
24
+ `export ${OTEL_ENABLED_KEY}=true`,
25
+ `# <<< copilot-tracer <<<`,
26
+ ].join('\n');
27
+ }
28
+ function vscodeEnvBlock(port) {
29
+ return {
30
+ [OTEL_ENDPOINT_KEY]: `http://localhost:${port}`,
31
+ [OTEL_CONTENT_KEY]: 'true',
32
+ [OTEL_ENABLED_KEY]: 'true',
33
+ };
34
+ }
35
+ // ── Detection helpers ─────────────────────────────────────────────────────────
36
+ function detectCopilotCli() {
37
+ try {
38
+ const p = execSync('which copilot', { encoding: 'utf8' }).trim();
39
+ const v = execSync('copilot --version 2>/dev/null || true', { encoding: 'utf8' }).trim();
40
+ return { found: true, path: p, version: v.split('\n')[0] };
41
+ }
42
+ catch {
43
+ return { found: false };
44
+ }
45
+ }
46
+ function detectVSCode() {
47
+ try {
48
+ const v = execSync('code --version 2>/dev/null', { encoding: 'utf8' }).trim();
49
+ const lines = v.split('\n');
50
+ const version = lines[0];
51
+ const major = parseInt(version.split('.')[0], 10);
52
+ const minor = parseInt(version.split('.')[1], 10);
53
+ // Copilot built-in since VS Code 1.99
54
+ const hasBuiltinCopilot = major > 1 || (major === 1 && minor >= 99);
55
+ return { found: true, version, hasBuiltinCopilot };
56
+ }
57
+ catch {
58
+ // Try app bundle directly
59
+ const appPath = '/Applications/Visual Studio Code.app';
60
+ if (fs.existsSync(appPath)) {
61
+ return { found: true, hasBuiltinCopilot: true, version: 'unknown (app found)' };
62
+ }
63
+ return { found: false, hasBuiltinCopilot: false };
64
+ }
65
+ }
66
+ function detectShellProfile() {
67
+ const candidates = [
68
+ path.join(os.homedir(), '.zshrc'),
69
+ path.join(os.homedir(), '.zprofile'),
70
+ path.join(os.homedir(), '.bash_profile'),
71
+ path.join(os.homedir(), '.bashrc'),
72
+ ];
73
+ for (const p of candidates) {
74
+ if (fs.existsSync(p))
75
+ return p;
76
+ }
77
+ // Default to .zshrc (create it)
78
+ return path.join(os.homedir(), '.zshrc');
79
+ }
80
+ function getVSCodeSettingsPath() {
81
+ return path.join(os.homedir(), 'Library/Application Support/Code/User/settings.json');
82
+ }
83
+ // ── Patchers ──────────────────────────────────────────────────────────────────
84
+ function patchShellProfile(profilePath, port) {
85
+ const content = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
86
+ const block = otelEnvBlock(port);
87
+ // Already has our block?
88
+ if (content.includes('copilot-tracer OTLP config')) {
89
+ // Check if port matches
90
+ if (content.includes(`http://localhost:${port}`)) {
91
+ return { action: 'already_set' };
92
+ }
93
+ // Port changed — update
94
+ const updated = content.replace(/# >>> copilot-tracer OTLP config[\s\S]*?# <<< copilot-tracer <<</, block);
95
+ fs.writeFileSync(profilePath, updated, 'utf8');
96
+ return { action: 'updated' };
97
+ }
98
+ // Append
99
+ const newContent = content.trimEnd() + '\n\n' + block + '\n';
100
+ fs.writeFileSync(profilePath, newContent, 'utf8');
101
+ return { action: 'added' };
102
+ }
103
+ function patchVSCodeSettings(settingsPath, port) {
104
+ if (!fs.existsSync(settingsPath)) {
105
+ return { action: 'skipped', reason: 'settings.json not found' };
106
+ }
107
+ let settings;
108
+ try {
109
+ settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
110
+ }
111
+ catch {
112
+ return { action: 'skipped', reason: 'could not parse settings.json' };
113
+ }
114
+ const envKey = 'terminal.integrated.env.osx';
115
+ const existing = (settings[envKey] ?? {});
116
+ const newEnv = vscodeEnvBlock(port);
117
+ // Check if already set correctly
118
+ if (existing[OTEL_ENDPOINT_KEY] === `http://localhost:${port}` &&
119
+ existing[OTEL_CONTENT_KEY] === 'true' &&
120
+ existing[OTEL_ENABLED_KEY] === 'true') {
121
+ return { action: 'already_set' };
122
+ }
123
+ const wasSet = !!existing[OTEL_ENDPOINT_KEY];
124
+ settings[envKey] = { ...existing, ...newEnv };
125
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf8');
126
+ return { action: wasSet ? 'updated' : 'added' };
127
+ }
128
+ // ── Main setup ────────────────────────────────────────────────────────────────
129
+ export function runSetup(port) {
130
+ const CHECK = '✅';
131
+ const WARN = '⚠️ ';
132
+ const INFO = '📍';
133
+ const ARROW = '→';
134
+ console.log('\n╔════════════════════════════════════════════════╗');
135
+ console.log('║ Copilot Tracer — Auto Setup ║');
136
+ console.log('╚════════════════════════════════════════════════╝\n');
137
+ // 1. Detect copilot CLI
138
+ const cli = detectCopilotCli();
139
+ if (cli.found) {
140
+ console.log(`${CHECK} GitHub Copilot CLI detected`);
141
+ console.log(` ${INFO} Path : ${cli.path}`);
142
+ if (cli.version)
143
+ console.log(` ${INFO} Version: ${cli.version}`);
144
+ }
145
+ else {
146
+ console.log(`${WARN} GitHub Copilot CLI not found`);
147
+ console.log(` Install: npm install -g @github/copilot`);
148
+ }
149
+ // 2. Detect VS Code
150
+ const vscode = detectVSCode();
151
+ if (vscode.found) {
152
+ console.log(`\n${CHECK} Visual Studio Code detected`);
153
+ if (vscode.version)
154
+ console.log(` ${INFO} Version: ${vscode.version}`);
155
+ if (vscode.hasBuiltinCopilot) {
156
+ console.log(` ${CHECK} Built-in Copilot (v1.99+) — will be configured`);
157
+ }
158
+ else {
159
+ console.log(` ${WARN} VS Code version may not have built-in Copilot`);
160
+ }
161
+ }
162
+ else {
163
+ console.log(`\n${WARN} Visual Studio Code not found — skipping VS Code config`);
164
+ }
165
+ // 3. Patch shell profile
166
+ const profilePath = detectShellProfile();
167
+ if (profilePath) {
168
+ const result = patchShellProfile(profilePath, port);
169
+ const label = path.basename(profilePath);
170
+ if (result.action === 'added') {
171
+ console.log(`\n${CHECK} Shell profile patched: ${label}`);
172
+ console.log(` ${ARROW} Added OTEL env vars (endpoint, content capture)`);
173
+ console.log(` ${ARROW} Run: source ${profilePath}`);
174
+ }
175
+ else if (result.action === 'updated') {
176
+ console.log(`\n${CHECK} Shell profile updated: ${label}`);
177
+ console.log(` ${ARROW} Updated port to ${port}`);
178
+ console.log(` ${ARROW} Run: source ${profilePath}`);
179
+ }
180
+ else {
181
+ console.log(`\n${CHECK} Shell profile: already configured (${label})`);
182
+ }
183
+ }
184
+ // 4. Patch VS Code settings
185
+ if (vscode.found) {
186
+ const settingsPath = getVSCodeSettingsPath();
187
+ const result = patchVSCodeSettings(settingsPath, port);
188
+ if (result.action === 'added') {
189
+ console.log(`\n${CHECK} VS Code settings patched`);
190
+ console.log(` ${ARROW} Added terminal.integrated.env.osx with OTEL vars`);
191
+ console.log(` ${ARROW} Restart VS Code to apply`);
192
+ }
193
+ else if (result.action === 'updated') {
194
+ console.log(`\n${CHECK} VS Code settings updated`);
195
+ console.log(` ${ARROW} Updated port to ${port}`);
196
+ console.log(` ${ARROW} Restart VS Code to apply`);
197
+ }
198
+ else if (result.action === 'already_set') {
199
+ console.log(`\n${CHECK} VS Code settings: already configured`);
200
+ }
201
+ else {
202
+ console.log(`\n${WARN} VS Code settings: ${result.reason}`);
203
+ }
204
+ }
205
+ // 5. Summary
206
+ console.log('\n────────────────────────────────────────────────');
207
+ console.log(' Next steps:\n');
208
+ console.log(` 1. source ${profilePath ?? '~/.zshrc'} (apply to current terminal)`);
209
+ if (vscode.found)
210
+ console.log(' 2. Restart VS Code');
211
+ console.log(` 3. node dist/cli.js --ui web --port ${port} --no-proxy`);
212
+ console.log(` 4. Open http://localhost:${port}`);
213
+ console.log(` 5. Use copilot normally — traces appear automatically`);
214
+ console.log('\n Tracer endpoint: http://localhost:' + port + '/v1/traces');
215
+ console.log('────────────────────────────────────────────────\n');
216
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,57 @@
1
+ import express from 'express';
2
+ import { createServer } from 'http';
3
+ import { Server } from 'socket.io';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import { getTraces, getTrace, getSessionSummary } from './db.js';
7
+ import { traceEvents } from './proxy.js';
8
+ import { registerOtlpRoutes } from './otlpReceiver.js';
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ export function startWebServer(port = 4747, sessionId) {
11
+ const app = express();
12
+ const httpServer = createServer(app);
13
+ const io = new Server(httpServer, { cors: { origin: '*' } });
14
+ // Serve static web UI
15
+ app.use(express.static(path.join(__dirname, '../web')));
16
+ // Register OTLP receiver routes
17
+ app.use(express.json({ limit: '10mb' }));
18
+ registerOtlpRoutes(app, sessionId ?? 'default');
19
+ // API
20
+ app.get('/api/traces', (req, res) => {
21
+ const sid = req.query.sessionId || undefined; // undefined = all sessions
22
+ const traces = getTraces(sid, 200);
23
+ res.json(traces);
24
+ });
25
+ app.get('/api/traces/:id', (req, res) => {
26
+ const trace = getTrace(req.params.id);
27
+ if (!trace)
28
+ return res.status(404).json({ error: 'Not found' });
29
+ res.json(trace);
30
+ });
31
+ app.get('/api/summary', (req, res) => {
32
+ const sid = req.query.sessionId || sessionId;
33
+ if (!sid)
34
+ return res.json(null);
35
+ res.json(getSessionSummary(sid));
36
+ });
37
+ // Socket.io — push real-time updates
38
+ io.on('connection', (socket) => {
39
+ // Send current state on connect
40
+ const sid = sessionId;
41
+ socket.emit('init', {
42
+ traces: getTraces(sid, 200),
43
+ summary: sid ? getSessionSummary(sid) : null,
44
+ });
45
+ const onUpdate = (entry) => socket.emit('trace:update', entry);
46
+ const onDone = (entry) => socket.emit('trace:done', entry);
47
+ traceEvents.on('trace:update', onUpdate);
48
+ traceEvents.on('trace:done', onDone);
49
+ socket.on('disconnect', () => {
50
+ traceEvents.off('trace:update', onUpdate);
51
+ traceEvents.off('trace:done', onDone);
52
+ });
53
+ });
54
+ httpServer.listen(port, () => {
55
+ console.log(`\n 🌐 Copilot Tracer Web UI: http://localhost:${port}\n`);
56
+ });
57
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "copilot-tracer",
3
+ "version": "1.0.1",
4
+ "description": "Real-time monitor and tracing tool for GitHub Copilot CLI — tracks tokens, AI credits, MCP/skill/agent calls with console and web UI",
5
+ "keywords": [
6
+ "copilot",
7
+ "github-copilot",
8
+ "tracing",
9
+ "observability",
10
+ "opentelemetry",
11
+ "tokens",
12
+ "ai-credits"
13
+ ],
14
+ "license": "MIT",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "https://github.com/chuongnd/copilot-tracer.git"
18
+ },
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "files": [
23
+ "dist/",
24
+ "web/",
25
+ "README.md"
26
+ ],
27
+ "main": "dist/index.js",
28
+ "bin": {
29
+ "copilot-tracer": "dist/cli.js"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "prepare": "tsc",
34
+ "dev": "tsx src/cli.ts",
35
+ "console": "tsx src/cli.ts --ui console",
36
+ "web": "tsx src/cli.ts --ui web",
37
+ "start": "node dist/cli.js",
38
+ "publish:npm": "bash scripts/publish.sh"
39
+ },
40
+ "dependencies": {
41
+ "@opentelemetry/otlp-transformer": "^0.221.0",
42
+ "better-sqlite3": "^13.0.2",
43
+ "chalk": "^5.3.0",
44
+ "cli-table3": "^0.6.4",
45
+ "commander": "^12.0.0",
46
+ "date-fns": "^3.3.1",
47
+ "express": "^4.18.2",
48
+ "ink": "^5.0.1",
49
+ "ink-table": "^3.0.0",
50
+ "open": "^10.1.0",
51
+ "react": "^18.2.0",
52
+ "socket.io": "^4.7.4"
53
+ },
54
+ "devDependencies": {
55
+ "@types/better-sqlite3": "^7.6.8",
56
+ "@types/express": "^4.17.21",
57
+ "@types/node": "^20.0.0",
58
+ "@types/react": "^18.2.0",
59
+ "tsx": "^4.7.0",
60
+ "typescript": "^5.4.0"
61
+ },
62
+ "type": "module"
63
+ }