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 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.3 --help
13
- npx copilot-metrics@0.1.3 init
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.3 init
42
- npx copilot-metrics@0.1.3 paths --json
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
- Print VS Code Insiders Copilot Chat OpenTelemetry settings:
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.3 setup vscode
60
+ npx copilot-metrics@0.1.5 setup copilot-cli
53
61
  ```
54
62
 
55
- Print Copilot CLI OpenTelemetry environment exports:
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.3 setup copilot-cli
66
+ npx copilot-metrics@0.1.5 setup
59
67
  ```
60
68
 
61
- This is optional. Use it only when you also want Copilot CLI OTel JSONL output.
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.3 hooks preview --scope local --surface both
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.3 hooks install --scope local --surface both
77
- npx copilot-metrics@0.1.3 hooks install --scope global --surface both
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.3 store init
88
- npx copilot-metrics@0.1.3 import --source vscode --file ~/.local/share/copilot-metrics/telemetry/vscode-copilot-otel.jsonl
89
- npx copilot-metrics@0.1.3 import --source copilot-cli --file ~/.local/share/copilot-metrics/telemetry/copilot-cli-otel.jsonl
90
- npx copilot-metrics@0.1.3 import --source copilot-session --file ~/.copilot/session-state/<session-id>/events.jsonl
91
- npx copilot-metrics@0.1.3 import --source hooks --file ~/.local/share/copilot-metrics/hooks/copilot-hooks.jsonl
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.3 report labels
102
- npx copilot-metrics@0.1.3 report label DEMO-12345
103
- npx copilot-metrics@0.1.3 report label DEMO-12345 --detail
104
- npx copilot-metrics@0.1.3 report models
105
- npx copilot-metrics@0.1.3 report repos
106
- npx copilot-metrics@0.1.3 report unattributed
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.3 report labels --json
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. Label reports include input, output, cache read, cache creation, and reasoning token totals. Labels seen only in hooks remain visible as hook-only evidence with zero usage records, so attribution hints do not imply token-bearing usage.
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.3`.
179
- - Local OTLP collector mode is not included in `0.1.3`.
180
- - Richer prompt/content capture and redaction controls are not included in `0.1.3`.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-metrics",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Local-first Copilot usage telemetry setup and reporting tools.",
5
5
  "type": "commonjs",
6
6
  "homepage": "https://github.com/nnexai/copilot-metrics#readme",
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
- formatLabelSummary,
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 vscode [--json]
74
- copilot-metrics setup copilot-cli [--json]
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
- const settings = vscodeSettings(paths);
193
- writeOutput(io.stdout, json ? settings : formatVscode(settings), json);
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
- const env = copilotCliEnvironment(paths);
198
- writeOutput(io.stdout, json ? env : formatCopilotCli(env), json);
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({ env: io.env, cwd: io.cwd, home: flags.home, command: io.commandPath });
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(formatLabelSummary(summary, details), diagnostics), json);
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(formatLabelSummary(summary), diagnostics), json);
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') {