copilot-metrics 0.1.3 → 0.1.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/CHANGELOG.md +23 -0
- package/README.md +44 -29
- package/package.json +1 -1
- package/src/cli.js +59 -15
- package/src/ingest.js +287 -5
- package/src/jsonl.js +6 -4
- package/src/otel.js +40 -5
- package/src/pricing.js +12 -3
- package/src/reports.js +88 -32
- package/src/setup.js +103 -21
- package/src/sqlite-store.js +199 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.5 - 2026-05-31
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- VS Code Copilot token usage is now attributed to Jira labels by matching OTel `gen_ai.response.id` values to VS Code chat session `responseId` values.
|
|
8
|
+
- Existing local stores with older VS Code usage rows are repaired by backfilling missing response IDs from already imported raw OTel records.
|
|
9
|
+
- VS Code chat session files are parsed only in memory for label extraction; full chat content is not persisted in the metrics store.
|
|
10
|
+
- Versioned model IDs such as dated Copilot telemetry model names now use the canonical per-token pricing row when one is available, so estimates show what the token usage would cost even during included or `0x` periods.
|
|
11
|
+
|
|
12
|
+
## 0.1.4 - 2026-05-31
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- `setup` now performs setup by default: VS Code settings are merged into user settings and Copilot hooks are installed for the selected scope. `--print` keeps the old print-only behavior.
|
|
17
|
+
- `report label <id>` now includes a per-model breakdown by default while `report labels` remains accumulated by label.
|
|
18
|
+
- Human reports rename `Credits`/`Status` to clearer `AI Credits est.` and `Usage status` wording.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `hooks --surface both` now installs both Copilot CLI and VS Code hook event names.
|
|
23
|
+
- Report auto-import skips already imported Copilot session-state files and imported JSONL lines instead of reparsing all historical session files on each report.
|
|
24
|
+
- Existing config files are upgraded with the Copilot session-state source instead of being left stale.
|
|
25
|
+
|
|
3
26
|
## 0.1.3 - 2026-05-31
|
|
4
27
|
|
|
5
28
|
### Fixed
|
package/README.md
CHANGED
|
@@ -9,8 +9,8 @@ Costs are estimates, not official billing records. GitHub billing remains the so
|
|
|
9
9
|
From npm:
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
|
-
npx copilot-metrics@0.1.
|
|
13
|
-
npx copilot-metrics@0.1.
|
|
12
|
+
npx copilot-metrics@0.1.5 --help
|
|
13
|
+
npx copilot-metrics@0.1.5 init
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
From this checkout:
|
|
@@ -38,27 +38,35 @@ export COPILOT_METRICS_HOME=/path/to/copilot-metrics-data
|
|
|
38
38
|
Useful commands:
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
-
npx copilot-metrics@0.1.
|
|
42
|
-
npx copilot-metrics@0.1.
|
|
41
|
+
npx copilot-metrics@0.1.5 init
|
|
42
|
+
npx copilot-metrics@0.1.5 paths --json
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
`init` only creates the central data directory and local config. It does not modify editor or hook settings. `setup` performs integration setup for the current machine/workspace.
|
|
46
|
+
|
|
45
47
|
## Configure Telemetry
|
|
46
48
|
|
|
47
49
|
For Copilot CLI, `init` plus hooks are enough for local token reporting. Reports import Copilot's local session-state `events.jsonl` files and extract shutdown usage totals without requiring telemetry environment variables.
|
|
48
50
|
|
|
49
|
-
|
|
51
|
+
Install VS Code Copilot Chat OpenTelemetry settings:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx copilot-metrics@0.1.5 setup vscode
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Install Copilot CLI hooks for the current workspace:
|
|
50
58
|
|
|
51
59
|
```bash
|
|
52
|
-
npx copilot-metrics@0.1.
|
|
60
|
+
npx copilot-metrics@0.1.5 setup copilot-cli
|
|
53
61
|
```
|
|
54
62
|
|
|
55
|
-
|
|
63
|
+
Or set up both VS Code settings and workspace hooks in one command:
|
|
56
64
|
|
|
57
65
|
```bash
|
|
58
|
-
npx copilot-metrics@0.1.
|
|
66
|
+
npx copilot-metrics@0.1.5 setup
|
|
59
67
|
```
|
|
60
68
|
|
|
61
|
-
|
|
69
|
+
Use `setup vscode --print` or `setup copilot-cli --print` to print the settings/optional environment exports without writing files. Copilot CLI OTel exports are optional because CLI token usage is read from local session-state files.
|
|
62
70
|
|
|
63
71
|
Content capture is disabled by default. Do not enable richer prompt capture unless you explicitly accept the privacy tradeoff.
|
|
64
72
|
|
|
@@ -67,14 +75,14 @@ Content capture is disabled by default. Do not enable richer prompt capture unle
|
|
|
67
75
|
Preview repo-local hook config. The default `--surface both` emits the Copilot CLI lower camel case hook format:
|
|
68
76
|
|
|
69
77
|
```bash
|
|
70
|
-
npx copilot-metrics@0.1.
|
|
78
|
+
npx copilot-metrics@0.1.5 hooks preview --scope local --surface both
|
|
71
79
|
```
|
|
72
80
|
|
|
73
81
|
Install repo-local or user-global hook config:
|
|
74
82
|
|
|
75
83
|
```bash
|
|
76
|
-
npx copilot-metrics@0.1.
|
|
77
|
-
npx copilot-metrics@0.1.
|
|
84
|
+
npx copilot-metrics@0.1.5 hooks install --scope local --surface both
|
|
85
|
+
npx copilot-metrics@0.1.5 hooks install --scope global --surface both
|
|
78
86
|
```
|
|
79
87
|
|
|
80
88
|
Local install writes `.github/hooks/copilot-metrics.json`. Global install updates `~/.copilot/settings.json` idempotently, replacing prior `copilot-metrics` hook entries while preserving other settings and hooks. Use `--surface vscode` for VS Code-only PascalCase events or `--surface copilot-cli` for CLI-native lower camel case events. The hook logger writes redacted JSONL metadata to the central data directory. It extracts Jira-style labels such as `DEMO-12345` from safe metadata and does not store full prompt text by default.
|
|
@@ -84,35 +92,40 @@ Local install writes `.github/hooks/copilot-metrics.json`. Global install update
|
|
|
84
92
|
Initialize the local SQLite store and import JSONL files manually:
|
|
85
93
|
|
|
86
94
|
```bash
|
|
87
|
-
npx copilot-metrics@0.1.
|
|
88
|
-
npx copilot-metrics@0.1.
|
|
89
|
-
npx copilot-metrics@0.1.
|
|
90
|
-
npx copilot-metrics@0.1.
|
|
91
|
-
npx copilot-metrics@0.1.
|
|
95
|
+
npx copilot-metrics@0.1.5 store init
|
|
96
|
+
npx copilot-metrics@0.1.5 import --source vscode --file ~/.local/share/copilot-metrics/telemetry/vscode-copilot-otel.jsonl
|
|
97
|
+
npx copilot-metrics@0.1.5 import --source copilot-cli --file ~/.local/share/copilot-metrics/telemetry/copilot-cli-otel.jsonl
|
|
98
|
+
npx copilot-metrics@0.1.5 import --source copilot-session --file ~/.copilot/session-state/<session-id>/events.jsonl
|
|
99
|
+
npx copilot-metrics@0.1.5 import --source vscode-chat --file ~/.config/Code\ -\ Insiders/User/workspaceStorage/<workspace-id>/chatSessions/<session-id>.jsonl
|
|
100
|
+
npx copilot-metrics@0.1.5 import --source hooks --file ~/.local/share/copilot-metrics/hooks/copilot-hooks.jsonl
|
|
92
101
|
```
|
|
93
102
|
|
|
94
|
-
Imports persist raw records, normalized LLM usage records, hook events, label evidence, and import warnings. Re-importing the same JSONL rows is idempotent. For Copilot session-state files, only shutdown usage rows are persisted; prompt-bearing session events are used in memory for label extraction and context and are not stored as raw records.
|
|
103
|
+
Imports persist raw records, normalized LLM usage records, hook events, label evidence, and import warnings. Re-importing the same JSONL rows is idempotent. For Copilot session-state files, only shutdown usage rows are persisted; prompt-bearing session events are used in memory for label extraction and context and are not stored as raw records. VS Code chat session files are also parsed only in memory, then reduced to label evidence linked to VS Code OTel usage by exact response ID.
|
|
95
104
|
|
|
96
105
|
## Reports
|
|
97
106
|
|
|
98
107
|
Run local reports from the SQLite store:
|
|
99
108
|
|
|
100
109
|
```bash
|
|
101
|
-
npx copilot-metrics@0.1.
|
|
102
|
-
npx copilot-metrics@0.1.
|
|
103
|
-
npx copilot-metrics@0.1.
|
|
104
|
-
npx copilot-metrics@0.1.
|
|
105
|
-
npx copilot-metrics@0.1.
|
|
106
|
-
npx copilot-metrics@0.1.
|
|
110
|
+
npx copilot-metrics@0.1.5 report labels
|
|
111
|
+
npx copilot-metrics@0.1.5 report label DEMO-12345
|
|
112
|
+
npx copilot-metrics@0.1.5 report label DEMO-12345 --detail
|
|
113
|
+
npx copilot-metrics@0.1.5 report models
|
|
114
|
+
npx copilot-metrics@0.1.5 report repos
|
|
115
|
+
npx copilot-metrics@0.1.5 report unattributed
|
|
107
116
|
```
|
|
108
117
|
|
|
109
118
|
Every report supports `--json`:
|
|
110
119
|
|
|
111
120
|
```bash
|
|
112
|
-
npx copilot-metrics@0.1.
|
|
121
|
+
npx copilot-metrics@0.1.5 report labels --json
|
|
113
122
|
```
|
|
114
123
|
|
|
115
|
-
Report commands automatically import newly appended configured VS Code OTel, optional Copilot CLI OTel, Copilot CLI session-state, and hook JSONL files before querying.
|
|
124
|
+
Report commands automatically import newly appended configured VS Code OTel, VS Code chat session metadata, optional Copilot CLI OTel, Copilot CLI session-state, and hook JSONL files before querying. Repeated reports skip already imported session-state files and already imported JSONL lines.
|
|
125
|
+
|
|
126
|
+
`report labels` shows accumulated totals per label. `report label <id>` shows the selected label summary plus a per-model breakdown by default. Label reports include input, output, cache read, cache creation, and reasoning token totals. Labels seen only in hooks remain visible as `evidence-only` with zero usage records, so attribution hints do not imply token-bearing usage.
|
|
127
|
+
|
|
128
|
+
`AI Credits est.` is a local what-would-this-cost estimate derived from the token pricing table, not a claim that the interaction was billed today. Some included or request-based models can appear as `0x` in Copilot while still having published per-token prices. The project treats 1 AI Credit as $0.01 for estimates; GitHub billing remains the source of truth.
|
|
116
129
|
|
|
117
130
|
## Attribution Model
|
|
118
131
|
|
|
@@ -120,6 +133,8 @@ The default extractor finds Jira-style labels such as `DEMO-12345` from safe met
|
|
|
120
133
|
|
|
121
134
|
Attribution is stored as evidence with source, field, session, repo, branch, cwd, confidence, and related usage or hook record IDs. This makes the data useful for later analysis, such as deciding whether a label was the main task or a sidetrack.
|
|
122
135
|
|
|
136
|
+
For VS Code Copilot Chat, token records from OTel are linked to chat labels by exact response ID. The OTel `gen_ai.response.id` value must match the VS Code chat session `responseId`; timestamp-only attribution is not used.
|
|
137
|
+
|
|
123
138
|
Full prompt content is not stored by default. Prompt-like fields are only used to extract labels and the stored source value is reduced to the matched label.
|
|
124
139
|
|
|
125
140
|
## Custom Label Extractors
|
|
@@ -175,7 +190,7 @@ The manual prompt performs one harmless tool call so Copilot CLI hook execution
|
|
|
175
190
|
## Current Limits
|
|
176
191
|
|
|
177
192
|
- Costs are estimates, not official billing records.
|
|
178
|
-
- Official GitHub usage report reconciliation is not included in `0.1.
|
|
179
|
-
- Local OTLP collector mode is not included in `0.1.
|
|
180
|
-
- Richer prompt/content capture and redaction controls are not included in `0.1.
|
|
193
|
+
- Official GitHub usage report reconciliation is not included in `0.1.5`.
|
|
194
|
+
- Local OTLP collector mode is not included in `0.1.5`.
|
|
195
|
+
- Richer prompt/content capture and redaction controls are not included in `0.1.5`.
|
|
181
196
|
- Dashboard views are deferred until the CLI/query model proves useful.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ const { resolvePaths } = require('./paths');
|
|
|
4
4
|
const {
|
|
5
5
|
ensureDataDirs,
|
|
6
6
|
vscodeSettings,
|
|
7
|
+
installVscodeSettings,
|
|
7
8
|
copilotCliEnvironment,
|
|
8
9
|
shellExports,
|
|
9
10
|
hookConfig,
|
|
@@ -17,12 +18,13 @@ const { MODEL_PRICES, PRICING_VERSION } = require('./pricing');
|
|
|
17
18
|
const {
|
|
18
19
|
labelOverview,
|
|
19
20
|
labelSummary,
|
|
21
|
+
labelModelBreakdown,
|
|
20
22
|
labelDetails,
|
|
21
23
|
modelReport,
|
|
22
24
|
repoReport,
|
|
23
25
|
unattributedReport,
|
|
24
26
|
formatLabels,
|
|
25
|
-
|
|
27
|
+
formatLabelReport,
|
|
26
28
|
formatModels,
|
|
27
29
|
formatRepos,
|
|
28
30
|
formatUnattributed,
|
|
@@ -70,13 +72,14 @@ function helpText() {
|
|
|
70
72
|
Usage:
|
|
71
73
|
copilot-metrics init [--json]
|
|
72
74
|
copilot-metrics paths [--json]
|
|
73
|
-
copilot-metrics setup
|
|
74
|
-
copilot-metrics setup
|
|
75
|
+
copilot-metrics setup [all] [--scope local|global] [--json]
|
|
76
|
+
copilot-metrics setup vscode [--settings-file <path>] [--json]
|
|
77
|
+
copilot-metrics setup copilot-cli [--scope local|global] [--json]
|
|
75
78
|
copilot-metrics hooks preview [--scope local|global] [--surface both|vscode|copilot-cli] [--json]
|
|
76
79
|
copilot-metrics hooks install [--scope local|global] [--surface both|vscode|copilot-cli] [--json]
|
|
77
80
|
copilot-metrics hook-log --event <name>
|
|
78
81
|
copilot-metrics store init [--json]
|
|
79
|
-
copilot-metrics import --source vscode|copilot-cli|copilot-session|hooks --file <path> [--json]
|
|
82
|
+
copilot-metrics import --source vscode|vscode-chat|copilot-cli|copilot-session|hooks --file <path> [--json]
|
|
80
83
|
copilot-metrics report labels [--json]
|
|
81
84
|
copilot-metrics report label <id> [--detail] [--json]
|
|
82
85
|
copilot-metrics report models [--json]
|
|
@@ -111,6 +114,13 @@ function formatVscode(settings) {
|
|
|
111
114
|
].join('\n');
|
|
112
115
|
}
|
|
113
116
|
|
|
117
|
+
function formatVscodeInstall(results) {
|
|
118
|
+
return [
|
|
119
|
+
`Installed VS Code Copilot telemetry settings: ${results.map((result) => result.target).join(', ')}`,
|
|
120
|
+
'Content capture is disabled.',
|
|
121
|
+
].join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
114
124
|
function formatCopilotCli(env) {
|
|
115
125
|
return [
|
|
116
126
|
'Export these variables before running Copilot CLI:',
|
|
@@ -122,6 +132,13 @@ function formatCopilotCli(env) {
|
|
|
122
132
|
].join('\n');
|
|
123
133
|
}
|
|
124
134
|
|
|
135
|
+
function formatCopilotCliSetup(result) {
|
|
136
|
+
return [
|
|
137
|
+
`Installed ${result.scope} Copilot hook config: ${result.target}`,
|
|
138
|
+
'Copilot CLI token usage is imported from local session-state; OTel exports are optional.',
|
|
139
|
+
].join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
125
142
|
function telemetryDiagnostics(importResults) {
|
|
126
143
|
const hookResult = importResults.find((result) => result.source === 'hooks' && result.raw_records > 0);
|
|
127
144
|
if (!hookResult) return [];
|
|
@@ -189,23 +206,49 @@ async function main(args, io) {
|
|
|
189
206
|
|
|
190
207
|
if (command === 'setup') {
|
|
191
208
|
if (subcommand === 'vscode') {
|
|
192
|
-
|
|
193
|
-
|
|
209
|
+
ensureDataDirs(paths);
|
|
210
|
+
if (flags.print === true || flags.dryRun === true) {
|
|
211
|
+
const settings = vscodeSettings(paths);
|
|
212
|
+
writeOutput(io.stdout, json ? settings : formatVscode(settings), json);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const results = installVscodeSettings(paths, { env: io.env, target: flags.settingsFile });
|
|
216
|
+
writeOutput(io.stdout, json ? { installed: results } : formatVscodeInstall(results), json);
|
|
194
217
|
return;
|
|
195
218
|
}
|
|
196
219
|
if (subcommand === 'copilot-cli') {
|
|
197
|
-
|
|
198
|
-
|
|
220
|
+
ensureDataDirs(paths);
|
|
221
|
+
if (flags.print === true || flags.dryRun === true) {
|
|
222
|
+
const env = copilotCliEnvironment(paths);
|
|
223
|
+
writeOutput(io.stdout, json ? env : formatCopilotCli(env), json);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const result = installHook(paths, {
|
|
227
|
+
cwd: io.cwd,
|
|
228
|
+
scope: flags.scope || 'local',
|
|
229
|
+
surface: 'copilot-cli',
|
|
230
|
+
command: io.commandPath,
|
|
231
|
+
});
|
|
232
|
+
writeOutput(io.stdout, json ? { installed: result } : formatCopilotCliSetup({ ...result, scope: flags.scope || 'local' }), json);
|
|
199
233
|
return;
|
|
200
234
|
}
|
|
201
235
|
if (!subcommand || subcommand === 'all') {
|
|
202
|
-
const snapshot = setupSnapshot({
|
|
236
|
+
const snapshot = setupSnapshot({
|
|
237
|
+
env: io.env,
|
|
238
|
+
cwd: io.cwd,
|
|
239
|
+
home: flags.home,
|
|
240
|
+
command: io.commandPath,
|
|
241
|
+
install: flags.print !== true && flags.dryRun !== true,
|
|
242
|
+
scope: flags.scope || 'local',
|
|
243
|
+
surface: flags.surface || 'both',
|
|
244
|
+
target: flags.settingsFile,
|
|
245
|
+
});
|
|
203
246
|
writeOutput(io.stdout, json ? snapshot : [
|
|
204
247
|
formatPaths(snapshot.paths),
|
|
205
248
|
'',
|
|
206
|
-
formatVscode(snapshot.vscode),
|
|
249
|
+
snapshot.vscodeInstalled.length ? formatVscodeInstall(snapshot.vscodeInstalled) : formatVscode(snapshot.vscode),
|
|
207
250
|
'',
|
|
208
|
-
formatCopilotCli(snapshot.copilotCli),
|
|
251
|
+
snapshot.hooksInstalled ? formatCopilotCliSetup({ ...snapshot.hooksInstalled, scope: flags.scope || 'local' }) : formatCopilotCli(snapshot.copilotCli),
|
|
209
252
|
].join('\n'), json);
|
|
210
253
|
return;
|
|
211
254
|
}
|
|
@@ -262,8 +305,8 @@ async function main(args, io) {
|
|
|
262
305
|
: source === 'hooks'
|
|
263
306
|
? paths.hookEventsJsonl
|
|
264
307
|
: null);
|
|
265
|
-
if (!['vscode', 'copilot-cli', 'copilot-session', 'hooks'].includes(source)) {
|
|
266
|
-
throw new Error('import requires --source vscode|copilot-cli|copilot-session|hooks');
|
|
308
|
+
if (!['vscode', 'vscode-chat', 'copilot-cli', 'copilot-session', 'hooks'].includes(source)) {
|
|
309
|
+
throw new Error('import requires --source vscode|vscode-chat|copilot-cli|copilot-session|hooks');
|
|
267
310
|
}
|
|
268
311
|
if (!file) throw new Error('import requires --file <path>');
|
|
269
312
|
ensureDataDirs(paths);
|
|
@@ -301,12 +344,13 @@ async function main(args, io) {
|
|
|
301
344
|
const label = rest[2];
|
|
302
345
|
if (!label) throw new Error('report label requires <id>');
|
|
303
346
|
const summary = await labelSummary(paths.usageDb, label);
|
|
347
|
+
const models = await labelModelBreakdown(paths.usageDb, label);
|
|
304
348
|
if (flags.detail === true) {
|
|
305
349
|
const details = await labelDetails(paths.usageDb, label);
|
|
306
|
-
writeOutput(io.stdout, json ? { label: summary, details, diagnostics } : appendDiagnostics(
|
|
350
|
+
writeOutput(io.stdout, json ? { label: summary, models, details, diagnostics } : appendDiagnostics(formatLabelReport(summary, models, details), diagnostics), json);
|
|
307
351
|
return;
|
|
308
352
|
}
|
|
309
|
-
writeOutput(io.stdout, json ? { label: summary, diagnostics } : appendDiagnostics(
|
|
353
|
+
writeOutput(io.stdout, json ? { label: summary, models, diagnostics } : appendDiagnostics(formatLabelReport(summary, models), diagnostics), json);
|
|
310
354
|
return;
|
|
311
355
|
}
|
|
312
356
|
if (subcommand === 'models') {
|