copilot-tracer 1.0.9 → 1.0.11

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.
Files changed (2) hide show
  1. package/dist/setup.js +105 -15
  2. package/package.json +1 -1
package/dist/setup.js CHANGED
@@ -5,8 +5,9 @@
5
5
  * 1. Detect copilot CLI (which copilot)
6
6
  * 2. Detect VS Code installation + built-in copilot (v1.99+)
7
7
  * 3. Inject OTEL env vars into:
8
- * - Shell profile (~/.zshrc / ~/.bashrc / ~/.zprofile)
9
- * - VS Code settings.json (terminal.integrated.env.osx)
8
+ * - Shell profile: ~/.zshrc / ~/.bashrc / ~/.zprofile (macOS/Linux),
9
+ * $PROFILE — Documents\PowerShell\Microsoft.PowerShell_profile.ps1 (Windows)
10
+ * - VS Code settings.json (terminal.integrated.env.osx / .linux / .windows)
10
11
  * 4. Print a summary and next steps
11
12
  */
12
13
  import { execSync } from 'child_process';
@@ -23,7 +24,10 @@ const OTEL_TRACES_EXPORTER_KEY = 'OTEL_TRACES_EXPORTER';
23
24
  const OTEL_PROTOCOL_KEY = 'OTEL_EXPORTER_OTLP_PROTOCOL';
24
25
  const OTEL_LOG_PROMPTS_KEY = 'OTEL_LOG_USER_PROMPTS';
25
26
  const OTEL_LOG_RESPONSES_KEY = 'OTEL_LOG_ASSISTANT_RESPONSES';
26
- function otelEnvBlock(port) {
27
+ function isWindows() {
28
+ return process.platform === 'win32';
29
+ }
30
+ function otelEnvBlockPosix(port) {
27
31
  return [
28
32
  `# >>> copilot-tracer OTLP config (auto-added) >>>`,
29
33
  `export ${OTEL_ENDPOINT_KEY}=http://localhost:${port}`,
@@ -75,6 +79,53 @@ function otelEnvBlock(port) {
75
79
  `# <<< copilot-tracer <<<`,
76
80
  ].join('\n');
77
81
  }
82
+ function otelEnvBlockPowerShell(port) {
83
+ return [
84
+ `# >>> copilot-tracer OTLP config (auto-added) >>>`,
85
+ `$env:${OTEL_ENDPOINT_KEY} = "http://localhost:${port}"`,
86
+ `$env:${OTEL_CONTENT_KEY} = "true"`,
87
+ `$env:${OTEL_ENABLED_KEY} = "true"`,
88
+ `$env:${CLAUDE_TELEMETRY_KEY} = "1"`,
89
+ `$env:${CLAUDE_TRACES_KEY} = "1"`,
90
+ `$env:${OTEL_LOGS_EXPORTER_KEY} = "otlp"`,
91
+ `$env:${OTEL_TRACES_EXPORTER_KEY} = "otlp"`,
92
+ `$env:${OTEL_PROTOCOL_KEY} = "http/json"`,
93
+ `$env:${OTEL_LOG_PROMPTS_KEY} = "1"`,
94
+ `$env:${OTEL_LOG_RESPONSES_KEY} = "1"`,
95
+ ``,
96
+ `# Tag every copilot prompt with the terminal folder it ran from, so the`,
97
+ `# tracer can attribute it to the right project. OTLP carries no working-dir,`,
98
+ `# so we inject it via OTEL_RESOURCE_ATTRIBUTES (percent-encoded).`,
99
+ `function copilot {`,
100
+ ` $_wd = (Get-Location).Path.Replace('\\','/')`,
101
+ ` $_wd = [uri]::EscapeDataString($_wd).Replace('%2F','/')`,
102
+ ` if ($env:OTEL_RESOURCE_ATTRIBUTES) {`,
103
+ ` $parts = $env:OTEL_RESOURCE_ATTRIBUTES -split ',' | Where-Object { $_ -notmatch '^github\\.copilot\\.working_dir=' }`,
104
+ ` $env:OTEL_RESOURCE_ATTRIBUTES = $parts -join ','`,
105
+ ` if ($env:OTEL_RESOURCE_ATTRIBUTES) { $env:OTEL_RESOURCE_ATTRIBUTES += ',' }`,
106
+ ` }`,
107
+ ` $env:OTEL_RESOURCE_ATTRIBUTES = "$($env:OTEL_RESOURCE_ATTRIBUTES)github.copilot.working_dir=$_wd"`,
108
+ ` $cmd = Get-Command copilot -CommandType Application,ExternalScript -ErrorAction SilentlyContinue | Select-Object -First 1`,
109
+ ` if ($cmd) { & $cmd.Source @args } else { & copilot.cmd @args }`,
110
+ `}`,
111
+ ``,
112
+ `function claude {`,
113
+ ` $_wd = (Get-Location).Path.Replace('\\','/')`,
114
+ ` $_wd = [uri]::EscapeDataString($_wd).Replace('%2F','/')`,
115
+ ` if ($env:OTEL_RESOURCE_ATTRIBUTES) {`,
116
+ ` $env:OTEL_RESOURCE_ATTRIBUTES += ",claude_code.working_dir=$_wd"`,
117
+ ` } else {`,
118
+ ` $env:OTEL_RESOURCE_ATTRIBUTES = "claude_code.working_dir=$_wd"`,
119
+ ` }`,
120
+ ` $cmd = Get-Command claude -CommandType Application,ExternalScript -ErrorAction SilentlyContinue | Select-Object -First 1`,
121
+ ` if ($cmd) { & $cmd.Source @args } else { & claude.cmd @args }`,
122
+ `}`,
123
+ `# <<< copilot-tracer <<<`,
124
+ ].join('\n');
125
+ }
126
+ function otelEnvBlock(port) {
127
+ return isWindows() ? otelEnvBlockPowerShell(port) : otelEnvBlockPosix(port);
128
+ }
78
129
  function vscodeEnvBlock(port) {
79
130
  return {
80
131
  [OTEL_ENDPOINT_KEY]: `http://localhost:${port}`,
@@ -205,9 +256,15 @@ export function patchClaudeSettings(settingsPath, port) {
205
256
  // ── Detection helpers ─────────────────────────────────────────────────────────
206
257
  function detectCopilotCli() {
207
258
  try {
208
- const p = execSync('which copilot', { encoding: 'utf8' }).trim();
209
- const v = execSync('copilot --version 2>/dev/null || true', { encoding: 'utf8' }).trim();
210
- return { found: true, path: p, version: v.split('\n')[0] };
259
+ const p = execSync(isWindows() ? 'where copilot' : 'which copilot', { encoding: 'utf8' }).trim().split('\n')[0];
260
+ let version;
261
+ try {
262
+ version = execSync('copilot --version', { encoding: 'utf8' }).trim().split('\n')[0];
263
+ }
264
+ catch {
265
+ // version lookup is best-effort; presence of the binary is what matters
266
+ }
267
+ return { found: true, path: p, version };
211
268
  }
212
269
  catch {
213
270
  return { found: false };
@@ -225,15 +282,33 @@ function detectVSCode() {
225
282
  return { found: true, version, hasBuiltinCopilot };
226
283
  }
227
284
  catch {
228
- // Try app bundle directly
229
- const appPath = '/Applications/Visual Studio Code.app';
230
- if (fs.existsSync(appPath)) {
285
+ // Try well-known install locations directly
286
+ const appPath = process.platform === 'darwin'
287
+ ? '/Applications/Visual Studio Code.app'
288
+ : isWindows()
289
+ ? path.join(process.env.LOCALAPPDATA ?? path.join(os.homedir(), 'AppData', 'Local'), 'Programs', 'Microsoft VS Code', 'Code.exe')
290
+ : null;
291
+ if (appPath && fs.existsSync(appPath)) {
231
292
  return { found: true, hasBuiltinCopilot: true, version: 'unknown (app found)' };
232
293
  }
233
294
  return { found: false, hasBuiltinCopilot: false };
234
295
  }
235
296
  }
236
297
  function detectShellProfile() {
298
+ if (isWindows()) {
299
+ const candidates = [
300
+ // PowerShell 7+ ($PROFILE for `pwsh`)
301
+ path.join(os.homedir(), 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1'),
302
+ // Windows PowerShell 5.1 ($PROFILE for `powershell.exe`)
303
+ path.join(os.homedir(), 'Documents', 'WindowsPowerShell', 'Microsoft.PowerShell_profile.ps1'),
304
+ ];
305
+ for (const p of candidates) {
306
+ if (fs.existsSync(p))
307
+ return p;
308
+ }
309
+ // Default to the PowerShell 7+ profile (create it)
310
+ return candidates[0];
311
+ }
237
312
  const candidates = [
238
313
  path.join(os.homedir(), '.zshrc'),
239
314
  path.join(os.homedir(), '.zprofile'),
@@ -247,10 +322,22 @@ function detectShellProfile() {
247
322
  // Default to .zshrc (create it)
248
323
  return path.join(os.homedir(), '.zshrc');
249
324
  }
325
+ /** VS Code's per-platform key for `terminal.integrated.env.*`. */
326
+ function getVSCodeEnvKey() {
327
+ if (process.platform === 'darwin')
328
+ return 'terminal.integrated.env.osx';
329
+ if (isWindows())
330
+ return 'terminal.integrated.env.windows';
331
+ return 'terminal.integrated.env.linux';
332
+ }
250
333
  function getVSCodeSettingsPath() {
251
334
  if (process.platform === 'darwin') {
252
335
  return path.join(os.homedir(), 'Library/Application Support/Code/User/settings.json');
253
336
  }
337
+ if (isWindows()) {
338
+ const appData = process.env.APPDATA ?? path.join(os.homedir(), 'AppData', 'Roaming');
339
+ return path.join(appData, 'Code', 'User', 'settings.json');
340
+ }
254
341
  // Linux (and WSL)
255
342
  return path.join(os.homedir(), '.config/Code/User/settings.json');
256
343
  }
@@ -258,6 +345,9 @@ function getVSCodeSettingsPath() {
258
345
  function patchShellProfile(profilePath, port) {
259
346
  const content = fs.existsSync(profilePath) ? fs.readFileSync(profilePath, 'utf8') : '';
260
347
  const block = otelEnvBlock(port);
348
+ // The PowerShell profile dir (Documents\PowerShell or \WindowsPowerShell) often
349
+ // doesn't exist yet — unlike ~/.zshrc's parent, which is always the home dir.
350
+ fs.mkdirSync(path.dirname(profilePath), { recursive: true });
261
351
  // Already has our block?
262
352
  if (content.includes('copilot-tracer OTLP config')) {
263
353
  // Only skip if the embedded block is byte-identical to what we'd generate now —
@@ -287,7 +377,7 @@ function patchVSCodeSettings(settingsPath, port) {
287
377
  catch {
288
378
  return { action: 'skipped', reason: 'could not parse settings.json' };
289
379
  }
290
- const envKey = 'terminal.integrated.env.osx';
380
+ const envKey = getVSCodeEnvKey();
291
381
  const existing = (settings[envKey] ?? {});
292
382
  const newEnv = vscodeEnvBlock(port);
293
383
  // Already set correctly only if every current key/value is present — checking a
@@ -342,16 +432,17 @@ export function runSetup(port, silent = false) {
342
432
  if (profilePath) {
343
433
  const result = patchShellProfile(profilePath, port);
344
434
  const label = path.basename(profilePath);
435
+ const reload = isWindows() ? `. ${profilePath}` : `source ${profilePath}`;
345
436
  if (result.action === 'added') {
346
437
  console.log(`\n${CHECK} Shell profile patched: ${label}`);
347
438
  console.log(` ${ARROW} Added Copilot + Claude Code OTLP env vars`);
348
439
  console.log(` ${ARROW} Added copilot() wrapper — tags each prompt with the terminal folder`);
349
- console.log(` ${ARROW} Run: source ${profilePath}`);
440
+ console.log(` ${ARROW} Run: ${reload}`);
350
441
  }
351
442
  else if (result.action === 'updated') {
352
443
  console.log(`\n${CHECK} Shell profile updated: ${label}`);
353
444
  console.log(` ${ARROW} Updated port to ${port} + Claude Code/Copilot OTLP config`);
354
- console.log(` ${ARROW} Run: source ${profilePath}`);
445
+ console.log(` ${ARROW} Run: ${reload}`);
355
446
  }
356
447
  else {
357
448
  console.log(`\n${CHECK} Shell profile: already configured (${label})`);
@@ -363,7 +454,7 @@ export function runSetup(port, silent = false) {
363
454
  const result = patchVSCodeSettings(settingsPath, port);
364
455
  if (result.action === 'added') {
365
456
  console.log(`\n${CHECK} VS Code settings patched`);
366
- console.log(` ${ARROW} Added terminal.integrated.env.osx with OTEL vars`);
457
+ console.log(` ${ARROW} Added ${getVSCodeEnvKey()} with OTEL vars`);
367
458
  console.log(` ${ARROW} Restart VS Code to apply`);
368
459
  }
369
460
  else if (result.action === 'updated') {
@@ -411,10 +502,9 @@ export function runSetup(port, silent = false) {
411
502
  process.env[OTEL_LOG_RESPONSES_KEY] = '1';
412
503
  // 7. Summary
413
504
  if (!silent) {
414
- const profileBase = profilePath ? path.basename(profilePath) : '.zshrc';
415
505
  console.log('\n────────────────────────────────────────────────');
416
506
  console.log(' One manual step required:\n');
417
- console.log(` source ~/${profileBase}`);
507
+ console.log(` ${isWindows() ? `. ${profilePath}` : `source ${profilePath}`}`);
418
508
  console.log(` (opens a new terminal already? — env is already active there)`);
419
509
  if (vscode.found)
420
510
  console.log('\n Restart VS Code once to pick up the new terminal env.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-tracer",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "description": "Real-time tracing and prompt-refinement companion for GitHub Copilot CLI and VS Code Copilot — tracks tokens, AI credits, tool calls, and refined prompts with console and web UI",
5
5
  "keywords": [
6
6
  "copilot",