copilot-tracer 1.0.3 → 1.0.5

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/README.md CHANGED
@@ -1,177 +1,152 @@
1
1
  # copilot-tracer
2
2
 
3
- Real-time tracing and monitoring tool for **GitHub Copilot CLI** and **VS Code Copilot extension**.
3
+ Real-time tracing and prompt-refinement companion for **GitHub Copilot CLI** and **VS Code Copilot extension**.
4
4
 
5
- Captures every prompt, response, token usage, AI credits, tool calls, skill invocations and duration — all in one place. Works via native **OpenTelemetry (OTLP)** integration built into GitHub Copilot. No wrapper, no binary replacement, no ACP proxy needed.
5
+ Captures every prompt, response, token usage, AI credits, tool calls, and duration — all in one place. Runs as a background daemon that collects data from all your projects automatically. Includes a web dashboard with project overview and per-project live tracing.
6
6
 
7
7
  ---
8
8
 
9
9
  ## Features
10
10
 
11
- - **Zero-intrusion capture** — uses Copilot's built-in OTel support. Set 2 env vars, done.
12
- - **Works everywhere** — captures both Copilot CLI (`copilot -p "..."`) and VS Code Copilot Chat
13
- - **Real-time web UI** — live dashboard at `http://localhost:4747` with dark theme
14
- - **Full prompt & response** — see exactly what you sent and what Copilot replied
15
- - **Token breakdown** — input, output, cached, reasoning, written tokens per request
11
+ - **Daemon mode** — install once, run forever. Collects traces from all projects automatically
12
+ - **Auto project detection** — detects project from `github.copilot.git.repository` in OTLP spans
13
+ - **Zero-intrusion capture** — uses Copilot's built-in OTel support. Set env vars, done.
14
+ - **Works everywhere** — captures both Copilot CLI and VS Code Copilot Chat
15
+ - **Dashboard** — overview of all projects with token usage, credits, and session counts
16
+ - **Live tracer** — real-time trace table per project with detail panel
17
+ - **Prompt refinement** — rewrites prompts with stronger instructions and less noise
16
18
  - **AI Credits tracking** — matches exactly what Copilot terminal reports (e.g. `2.59 cr`)
17
- - **Tool call visibility** — see every tool/skill/MCP invoked during a session
18
19
  - **Persistent storage** — SQLite at `~/.copilot-tracer/traces.db`, survives restarts
19
- - **Console + Web UI** — CLI table view or browser dashboard, your choice
20
20
 
21
21
  ---
22
22
 
23
- ## Prerequisites
24
-
25
- - Node.js v18+ (tested on v24)
26
- - GitHub Copilot CLI (`copilot` command available in terminal)
27
- - VS Code 1.99+ with built-in Copilot (no extension install needed)
28
-
29
- ---
30
-
31
- ## Install
23
+ ## Quick Start (one-time setup)
32
24
 
33
25
  ```bash
34
26
  npm install -g copilot-tracer
27
+ copilot-tracer --setup --daemon
35
28
  ```
36
29
 
37
- Requires Node.js v18+. The `copilot-tracer` command is available globally after install.
38
-
39
- > **Building from source**
40
- > ```bash
41
- > git clone https://github.com/chuongnd/copilot-tracer
42
- > cd copilot-tracer && npm install && npx tsc
43
- > npm link # registers global command from local build
44
- > ```
30
+ This will:
31
+ 1. Detect your Copilot CLI and VS Code installation
32
+ 2. Patch `~/.zshrc` with OTEL env vars
33
+ 3. Patch VS Code `settings.json` with terminal env vars
34
+ 4. Start the daemon on port 4747
45
35
 
46
- ---
47
-
48
- ## Setup (one-time)
49
-
50
- Run the auto-setup command. It detects your Copilot CLI and VS Code installation and injects the required config automatically:
36
+ Then apply env vars in your current shell:
51
37
 
52
38
  ```bash
53
- copilot-tracer --setup
39
+ source ~/.zshrc
54
40
  ```
55
41
 
56
- What it does:
57
- - Detects `copilot` CLI path and version
58
- - Detects VS Code version and confirms built-in Copilot
59
- - Patches `~/.zshrc` (or `~/.bashrc`) with OTEL env vars
60
- - Patches VS Code `settings.json` with `terminal.integrated.env.osx` block
42
+ Restart VS Code once. After that, the daemon collects traces from all your Copilot sessions automatically.
61
43
 
62
- Example output:
63
- ```
64
- ✅ GitHub Copilot CLI detected — /usr/bin/copilot v1.0.77
65
- ✅ Visual Studio Code detected — 1.131.0 (Built-in Copilot)
66
- ✅ Shell profile patched: .zshrc
67
- ✅ VS Code settings patched
68
- ```
44
+ Open **http://localhost:4747** to see the dashboard.
69
45
 
70
- Then apply the env vars:
46
+ ---
47
+
48
+ ## How It Works
71
49
 
72
- ```bash
73
- source ~/.zshrc
74
50
  ```
51
+ ┌─────────────────────────────────────────────────────────┐
52
+ │ copilot-tracer --daemon (runs once, stays running) │
53
+ │ │
54
+ │ OTLP Receiver ← Copilot CLI + VS Code │
55
+ │ (auto-detects project from github.copilot.git.repository)│
56
+ │ │
57
+ │ SQLite DB → Dashboard + Live Tracer (Socket.io) │
58
+ └─────────────────────────────────────────────────────────┘
75
59
 
76
- Restart VS Code completely (Cmd+Q, then reopen).
60
+ Copilot CLI / VS Code Copilot Chat
61
+
62
+ │ OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4747
63
+
64
+ POST /v1/traces (OpenTelemetry OTLP JSON)
65
+
66
+
67
+ copilot-tracer parses spans → tokens, credits, tool calls
68
+
69
+
70
+ SQLite DB (~/.copilot-tracer/traces.db)
71
+
72
+ ├→ Dashboard: all projects overview
73
+ └→ Live Tracer: real-time per-project view
74
+ ```
77
75
 
78
76
  ---
79
77
 
80
- ## Manual Setup (alternative)
78
+ ## Usage
81
79
 
82
- If you prefer to configure manually, add these to `~/.zshrc`:
80
+ ### Daemon mode (recommended)
83
81
 
84
82
  ```bash
85
- export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4747
86
- export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
87
- export COPILOT_OTEL_ENABLED=true
88
- ```
83
+ # First time: setup + start daemon
84
+ copilot-tracer --setup --daemon
89
85
 
90
- For VS Code, add to `~/Library/Application Support/Code/User/settings.json`:
86
+ # Subsequent starts
87
+ copilot-tracer --daemon
91
88
 
92
- ```json
93
- "terminal.integrated.env.osx": {
94
- "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4747",
95
- "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "true",
96
- "COPILOT_OTEL_ENABLED": "true"
97
- }
89
+ # Custom port
90
+ copilot-tracer --daemon --port 8080
98
91
  ```
99
92
 
100
- ---
93
+ ### Normal mode (legacy)
101
94
 
102
- ## Start Tracer
95
+ Per-session mode with optional ACP proxy for live CLI tracing:
103
96
 
104
97
  ```bash
105
- copilot-tracer --ui web --port 4747 --no-proxy
106
- ```
98
+ # Web UI only (read from DB)
99
+ copilot-tracer --ui web --no-proxy
107
100
 
108
- Open **http://localhost:4747** shows "waiting for copilot CLI activity".
109
-
110
- ---
101
+ # With project path
102
+ copilot-tracer --ui web --no-proxy --project-path /path/to/repo
111
103
 
112
- ## Use Copilot Normally
104
+ # With ACP proxy (wraps copilot CLI)
105
+ copilot-tracer --ui web
106
+ ```
113
107
 
114
- No change to how you use Copilot. Just run as usual:
108
+ ### Setup only
115
109
 
116
110
  ```bash
117
- # Copilot CLI
118
- copilot -p "how to convert microservice to modular" --allow-all-tools
119
-
120
- # Or use Copilot Chat in VS Code
111
+ # Just patch env vars without starting
112
+ copilot-tracer --setup
121
113
  ```
122
114
 
123
- Traces appear instantly in the web UI as each request completes.
124
-
125
115
  ---
126
116
 
127
- ## How It Works
117
+ ## Dashboard
128
118
 
129
- ```
130
- copilot CLI / VS Code Copilot Chat
131
- |
132
- | reads OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4747
133
- |
134
- ↓ POST /v1/traces (OpenTelemetry OTLP JSON)
135
- copilot-tracer OTLP receiver
136
- |
137
-
138
- Parse spans → extract prompt, response, tokens, credits, tool calls
139
- |
140
-
141
- SQLite DB (~/.copilot-tracer/traces.db)
142
- |
143
-
144
- Web UI (Socket.io real-time) + Console table
145
- ```
119
+ Open http://localhost:4747 after starting the daemon.
146
120
 
147
- Copilot has built-in OpenTelemetry instrumentation. When `OTEL_EXPORTER_OTLP_ENDPOINT` is set, it pushes all trace data to that endpoint automatically — both CLI and VS Code extension.
121
+ - **Summary cards** total projects, sessions, tokens, credits
122
+ - **Project cards** — each project shows path, session count, tokens, credits, last active
123
+ - **Click a project** → opens live tracer filtered to that project
148
124
 
149
125
  ---
150
126
 
151
- ## Web UI
127
+ ## Live Tracer
152
128
 
153
- Open http://localhost:4747 after starting the tracer.
129
+ Real-time trace table for a specific project.
154
130
 
155
131
  **Table columns:**
156
132
  | Date/Time | Prompt | AI Credits | Duration | Cached | Written | Reasoning | Skills | Agents | MCPs |
157
133
 
158
134
  **Interactive features:**
159
- - Click any row → detail panel: full prompt, full response, reasoning text, call graph
135
+ - Click any row → detail panel: full prompt, response, reasoning, call graph
160
136
  - Click AI Credits → cost breakdown per token type
161
- - Click Reasoning count → full reasoning text
162
- - Click Skills / Agents / MCPs pill → filtered call list with input/output/duration
163
- - Real-time updates via Socket.io — no page refresh needed
164
- - Dark theme
137
+ - Click Reasoning → full reasoning text
138
+ - Click Skills / Agents / MCPs → filtered call list
139
+ - Real-time updates via Socket.io
165
140
 
166
141
  ---
167
142
 
168
- ## Console UI
143
+ ## Prompt Refinement
169
144
 
170
- ```bash
171
- copilot-tracer --ui console --no-proxy
172
- ```
145
+ The web UI includes a prompt optimizer. Click "Refine Prompt" in the trace detail panel.
173
146
 
174
- Live updating table in terminal. Same columns as web UI. TOTALS row pinned at top.
147
+ Techniques applied:
148
+ - Role grounding, imperative clarity, output format, chain-of-thought
149
+ - Noise removal, constraint injection, redundancy cleanup
175
150
 
176
151
  ---
177
152
 
@@ -179,30 +154,45 @@ Live updating table in terminal. Same columns as web UI. TOTALS row pinned at to
179
154
 
180
155
  | Flag | Description |
181
156
  |------|-------------|
182
- | `--ui web` | Start web UI (default) |
183
- | `--ui console` | Start console table UI |
184
- | `--ui both` | Both web + console |
185
- | `--port 4747` | Web UI port (default: 4747) |
186
- | `--no-proxy` | Web/console only, no ACP proxy |
187
- | `--session <id>` | Custom session ID |
157
+ | `--daemon` | Run as background daemon (always-on OTLP receiver) |
188
158
  | `--setup` | Auto-detect and configure env vars |
159
+ | `--port <port>` | Web UI port (default: 4747) |
160
+ | `--ui <mode>` | UI mode: console \| web \| both (normal mode only) |
161
+ | `--no-proxy` | Web/console only, no ACP proxy (normal mode only) |
162
+ | `--project-path <path>` | Project source path (normal mode only) |
163
+ | `--session <id>` | Custom session ID (normal mode only) |
189
164
  | `--debug` | Verbose logging |
190
165
 
191
166
  ---
192
167
 
193
- ## Storage
194
-
195
- Traces persist to `~/.copilot-tracer/traces.db` (SQLite). Safe to keep across sessions.
168
+ ## Manual Setup (alternative)
196
169
 
197
- To test the web UI without running a live Copilot session:
170
+ Add to `~/.zshrc`:
198
171
 
199
172
  ```bash
200
- node test-seed.mjs # seeds 4 sample traces (source build only)
201
- copilot-tracer --ui web --no-proxy --session test-session-001
173
+ export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4747
174
+ export OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true
175
+ export COPILOT_OTEL_ENABLED=true
176
+ ```
177
+
178
+ For VS Code, add to `~/Library/Application Support/Code/User/settings.json`:
179
+
180
+ ```json
181
+ "terminal.integrated.env.osx": {
182
+ "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4747",
183
+ "OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT": "true",
184
+ "COPILOT_OTEL_ENABLED": "true"
185
+ }
202
186
  ```
203
187
 
204
188
  ---
205
189
 
190
+ ## Storage
191
+
192
+ Traces persist to `~/.copilot-tracer/traces.db` (SQLite). Safe to keep across sessions.
193
+
194
+ ---
195
+
206
196
  ## Build & Publish
207
197
 
208
198
  ```bash
@@ -213,18 +203,24 @@ npx tsc # compile to dist/
213
203
  To publish a new release to npm:
214
204
 
215
205
  ```bash
216
- # Bump patch version (1.0.0 → 1.0.1), build, publish, git-tag
206
+ # Login first
207
+ npm login
208
+
209
+ # Patch version (1.0.4 → 1.0.5)
217
210
  bash scripts/publish.sh
218
211
 
219
- # Bump minor version
212
+ # Minor version
220
213
  bash scripts/publish.sh minor
221
214
 
222
- # Publish a beta pre-release
215
+ # Major version
216
+ bash scripts/publish.sh major
217
+
218
+ # Beta pre-release
223
219
  bash scripts/publish.sh --tag beta --pre beta
224
220
  ```
225
221
 
226
222
  The script will:
227
- 1. Check npm authentication (`npm login` required first)
223
+ 1. Check npm authentication
228
224
  2. Verify git working tree is clean
229
225
  3. Type-check + build
230
226
  4. Verify `better-sqlite3` native module loads
package/dist/cli.js CHANGED
@@ -4,7 +4,8 @@ import { spawn } from 'child_process';
4
4
  import { randomUUID } from 'crypto';
5
5
  import readline from 'readline';
6
6
  import fs from 'fs';
7
- import { createSession, getTraces, getSessionSummary } from './db.js';
7
+ import path from 'path';
8
+ import { createSession, getTraces, getSessionSummary, ensureProject } from './db.js';
8
9
  import { handleAcpMessage, traceEvents } from './proxy.js';
9
10
  import { renderConsoleTable } from './consoleUi.js';
10
11
  import { startWebServer } from './webServer.js';
@@ -20,91 +21,109 @@ program
20
21
  .option('--session <id>', 'Filter by session ID')
21
22
  .option('--debug', 'Dump ALL raw ACP messages to stderr (use to discover real method names)')
22
23
  .option('--setup', 'Auto-detect copilot CLI + VS Code and configure OTLP env vars')
24
+ .option('--project-path <path>', 'Project source path (defaults to cwd)')
25
+ .option('--daemon', 'Run as background daemon — collects all OTLP data, no ACP proxy')
23
26
  .allowUnknownOption()
24
27
  .parse();
25
28
  const opts = program.opts();
26
29
  const port = parseInt(opts.port);
27
30
  // Handle --setup: patch env files, apply to current process, then fall through to start web UI
28
31
  if (opts.setup) {
29
- runSetup(port);
30
- // Force no-proxy web mode — user just needs to open the browser
31
- opts.proxy = false;
32
- opts.ui = 'web';
33
- }
34
- const sessionId = opts.session || randomUUID();
35
- createSession(sessionId);
36
- console.log(`\n 🤖 Copilot Tracer | Session: ${sessionId}\n`);
37
- // Start Web UI
38
- if (opts.ui === 'web' || opts.ui === 'both') {
39
- startWebServer(port, sessionId);
40
- setTimeout(() => open(`http://localhost:${port}/`), 1500);
32
+ runSetup(port, opts.daemon);
33
+ if (!opts.daemon) {
34
+ opts.proxy = false;
35
+ opts.ui = 'web';
36
+ }
41
37
  }
42
- // Start Console UI refresh loop
43
- if (opts.ui === 'console' || opts.ui === 'both') {
44
- const refreshConsole = () => {
45
- const entries = getTraces(sessionId, 50);
46
- const summary = getSessionSummary(sessionId);
47
- renderConsoleTable(entries, summary ?? undefined);
48
- };
49
- traceEvents.on('trace:update', refreshConsole);
50
- traceEvents.on('trace:done', refreshConsole);
51
- refreshConsole();
38
+ // ── Daemon mode: run as always-on OTLP receiver ───────────────────────────
39
+ if (opts.daemon) {
40
+ console.log(`\n 🤖 Copilot Tracer Daemon Mode`);
41
+ console.log(` 📡 Listening for OTLP traces on port ${port}`);
42
+ console.log(` 🌐 Dashboard: http://localhost:${port}/`);
43
+ console.log(` Press Ctrl+C to stop\n`);
44
+ startWebServer(port);
45
+ process.on('SIGINT', () => { process.exit(0); });
46
+ process.on('SIGTERM', () => { process.exit(0); });
52
47
  }
53
- // ACP Proxy — wrap copilot CLI
54
- if (opts.proxy !== false) {
55
- const copilotArgs = ['--acp', '--stdio', ...program.args];
56
- const child = spawn(opts.cmd, copilotArgs, {
57
- stdio: ['pipe', 'pipe', 'inherit'],
58
- });
59
- if (!child.pid) {
60
- console.error(`\n ❌ Failed to start: ${opts.cmd} ${copilotArgs.join(' ')}`);
61
- console.error(' Make sure GitHub Copilot CLI is installed: npm install -g @github/copilot-cli\n');
62
- process.exit(1);
48
+ else {
49
+ // ── Normal mode: per-session with optional ACP proxy ─────────────────────
50
+ const sessionId = opts.session || randomUUID();
51
+ // Resolve project path and create project record
52
+ const projectPath = opts.projectPath
53
+ ? path.resolve(opts.projectPath)
54
+ : process.cwd();
55
+ const projectId = ensureProject(projectPath);
56
+ createSession(sessionId, projectId);
57
+ console.log(`\n 🤖 Copilot Tracer | Session: ${sessionId}`);
58
+ console.log(` 📁 Project: ${projectPath}\n`);
59
+ // Start Web UI
60
+ if (opts.ui === 'web' || opts.ui === 'both') {
61
+ startWebServer(port, sessionId, projectId);
62
+ setTimeout(() => open(`http://localhost:${port}/`), 1500);
63
63
  }
64
- // Parse newline-delimited JSON (NDJSON) from copilot
65
- const stdinRl = readline.createInterface({ input: process.stdin });
66
- const stdoutRl = readline.createInterface({ input: child.stdout });
67
- const debug = opts.debug === true;
68
- const logFile = debug ? fs.createWriteStream(`/tmp/copilot-tracer-${sessionId.slice(0, 8)}.ndjson`, { flags: 'a' }) : null;
69
- function debugLog(direction, raw, parsed) {
70
- if (!debug)
71
- return;
72
- const entry = JSON.stringify({ ts: new Date().toISOString(), dir: direction, raw, parsed });
73
- process.stderr.write('[TRACER] ' + entry + '\n');
74
- logFile?.write(entry + '\n');
64
+ // Start Console UI refresh loop
65
+ if (opts.ui === 'console' || opts.ui === 'both') {
66
+ const refreshConsole = () => {
67
+ const entries = getTraces(sessionId, 50);
68
+ const summary = getSessionSummary(sessionId);
69
+ renderConsoleTable(entries, summary ?? undefined);
70
+ };
71
+ traceEvents.on('trace:update', refreshConsole);
72
+ traceEvents.on('trace:done', refreshConsole);
73
+ refreshConsole();
75
74
  }
76
- // stdin copilot (user copilot = 'out' direction from user's perspective)
77
- stdinRl.on('line', (line) => {
78
- let parsed;
79
- try {
80
- parsed = JSON.parse(line);
81
- handleAcpMessage(sessionId, parsed, 'out'); // outbound = user sending to copilot
82
- }
83
- catch (e) {
84
- // Not JSON plain text from terminal, not ACP
85
- if (debug)
86
- process.stderr.write(`[TRACER] stdin non-JSON: ${line}\n`);
75
+ // ACP Proxy wrap copilot CLI
76
+ if (opts.proxy !== false) {
77
+ const copilotArgs = ['--acp', '--stdio', ...program.args];
78
+ const child = spawn(opts.cmd, copilotArgs, {
79
+ stdio: ['pipe', 'pipe', 'inherit'],
80
+ });
81
+ if (!child.pid) {
82
+ console.error(`\n ❌ Failed to start: ${opts.cmd} ${copilotArgs.join(' ')}`);
83
+ console.error(' Make sure GitHub Copilot CLI is installed: npm install -g @github/copilot-cli\n');
84
+ process.exit(1);
87
85
  }
88
- debugLog('→ copilot', line, parsed);
89
- child.stdin.write(line + '\n');
90
- });
91
- // copilot stdout (copilot user = 'in' direction from user's perspective)
92
- stdoutRl.on('line', (line) => {
93
- let parsed;
94
- try {
95
- parsed = JSON.parse(line);
96
- handleAcpMessage(sessionId, parsed, 'in'); // inbound = copilot sending to user
86
+ const stdinRl = readline.createInterface({ input: process.stdin });
87
+ const stdoutRl = readline.createInterface({ input: child.stdout });
88
+ const debug = opts.debug === true;
89
+ const logFile = debug ? fs.createWriteStream(`/tmp/copilot-tracer-${sessionId.slice(0, 8)}.ndjson`, { flags: 'a' }) : null;
90
+ function debugLog(direction, raw, parsed) {
91
+ if (!debug)
92
+ return;
93
+ const entry = JSON.stringify({ ts: new Date().toISOString(), dir: direction, raw, parsed });
94
+ process.stderr.write('[TRACER] ' + entry + '\n');
95
+ logFile?.write(entry + '\n');
97
96
  }
98
- catch (e) {
99
- if (debug)
100
- process.stderr.write(`[TRACER] stdout non-JSON: ${line}\n`);
101
- }
102
- debugLog('← copilot', line, parsed);
103
- process.stdout.write(line + '\n');
104
- });
105
- child.on('exit', (code) => {
106
- console.log(`\n Copilot CLI exited (code ${code})\n`);
107
- process.exit(code ?? 0);
108
- });
109
- process.on('SIGINT', () => { child.kill(); process.exit(0); });
97
+ stdinRl.on('line', (line) => {
98
+ let parsed;
99
+ try {
100
+ parsed = JSON.parse(line);
101
+ handleAcpMessage(sessionId, parsed, 'out');
102
+ }
103
+ catch (e) {
104
+ if (debug)
105
+ process.stderr.write(`[TRACER] stdin non-JSON: ${line}\n`);
106
+ }
107
+ debugLog('→ copilot', line, parsed);
108
+ child.stdin.write(line + '\n');
109
+ });
110
+ stdoutRl.on('line', (line) => {
111
+ let parsed;
112
+ try {
113
+ parsed = JSON.parse(line);
114
+ handleAcpMessage(sessionId, parsed, 'in');
115
+ }
116
+ catch (e) {
117
+ if (debug)
118
+ process.stderr.write(`[TRACER] stdout non-JSON: ${line}\n`);
119
+ }
120
+ debugLog('← copilot', line, parsed);
121
+ process.stdout.write(line + '\n');
122
+ });
123
+ child.on('exit', (code) => {
124
+ console.log(`\n Copilot CLI exited (code ${code})\n`);
125
+ process.exit(code ?? 0);
126
+ });
127
+ process.on('SIGINT', () => { child.kill(); process.exit(0); });
128
+ }
110
129
  }