@codetelemetry/connect 0.1.0 → 0.1.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.
@@ -7,6 +7,7 @@ export declare function normalizeRepositoryRemote(value: string): string;
7
7
  export declare function selectCodexOtelExporterLine(raw: string): string;
8
8
  export declare function repositoryHookSource(options?: {
9
9
  codexConfigPath?: string;
10
+ claudeSettingsPath?: string;
10
11
  }): string;
11
12
  export declare function quotedCommand(filePath: string, platform?: string): string;
12
13
  export declare function validateCodexRepositoryObservationHook(configPath: string): void;
@@ -111,6 +111,9 @@ export function repositoryHookSource(options = {}) {
111
111
  const codexConfigPath = options.codexConfigPath
112
112
  ? path.resolve(options.codexConfigPath)
113
113
  : "";
114
+ const claudeSettingsPath = options.claudeSettingsPath
115
+ ? path.resolve(options.claudeSettingsPath)
116
+ : "";
114
117
  return String.raw `"use strict";
115
118
  const crypto = require("node:crypto");
116
119
  const fs = require("node:fs");
@@ -119,6 +122,7 @@ const https = require("node:https");
119
122
  const path = require("node:path");
120
123
  const { execFileSync } = require("node:child_process");
121
124
  const codexConfigPath = ${JSON.stringify(codexConfigPath)};
125
+ const claudeSettingsPath = ${JSON.stringify(claudeSettingsPath)};
122
126
  const observationStatePath = path.join(__dirname, ${JSON.stringify(REPOSITORY_HOOK_STATE_FILE)});
123
127
  const observationLockPath = observationStatePath + ".lock";
124
128
  const maxObservationStateEntries = ${REPOSITORY_HOOK_STATE_MAX_ENTRIES};
@@ -302,8 +306,24 @@ function codexTelemetryConfig() {
302
306
  };
303
307
  } catch { return {}; }
304
308
  }
309
+ function claudeTelemetryConfig() {
310
+ if (!claudeSettingsPath) return {};
311
+ try {
312
+ // Claude Code does not pass the OTEL_* exporter variables from settings.json
313
+ // into hook processes, so read the live env block at hook time. As with
314
+ // config.toml, the bearer key is never copied into this script and key
315
+ // rotation does not change the trusted hook definition.
316
+ const env = JSON.parse(fs.readFileSync(claudeSettingsPath, "utf8")).env || {};
317
+ return {
318
+ endpoint: String(env.OTEL_EXPORTER_OTLP_ENDPOINT || ""),
319
+ headers: otlpAuthHeaders(String(env.OTEL_EXPORTER_OTLP_HEADERS || "")),
320
+ };
321
+ } catch { return {}; }
322
+ }
305
323
  function environmentAuthHeaders() {
306
- const raw = String(process.env.OTEL_EXPORTER_OTLP_HEADERS || "");
324
+ return otlpAuthHeaders(String(process.env.OTEL_EXPORTER_OTLP_HEADERS || ""));
325
+ }
326
+ function otlpAuthHeaders(raw) {
307
327
  for (const part of raw.split(",")) {
308
328
  const separator = part.indexOf("=");
309
329
  if (separator < 0 || part.slice(0, separator).trim().toLowerCase() !== "authorization") continue;
@@ -316,15 +336,28 @@ function environmentAuthHeaders() {
316
336
  }
317
337
  return {};
318
338
  }
339
+ function logsEndpoint(base) {
340
+ const trimmed = String(base || "").replace(/\/$/, "");
341
+ if (!trimmed) return "";
342
+ return trimmed.endsWith("/v1/logs") ? trimmed : trimmed + "/v1/logs";
343
+ }
319
344
  function deliveryDestination() {
320
- const base = String(process.env.OTEL_EXPORTER_OTLP_ENDPOINT || "").replace(/\/$/, "");
345
+ const base = logsEndpoint(process.env.OTEL_EXPORTER_OTLP_ENDPOINT);
321
346
  // Endpoint and credentials are one provenance-bearing choice. In particular,
322
- // never attach the key from config.toml to an environment-provided endpoint.
347
+ // never attach the key from config.toml or settings.json to an
348
+ // environment-provided endpoint.
323
349
  if (base) return {
324
- endpoint: base.endsWith("/v1/logs") ? base : base + "/v1/logs",
350
+ endpoint: base,
325
351
  headers: environmentAuthHeaders(),
326
352
  source: "environment",
327
353
  };
354
+ const settings = claudeTelemetryConfig();
355
+ const settingsEndpoint = logsEndpoint(settings.endpoint);
356
+ if (settingsEndpoint) return {
357
+ endpoint: settingsEndpoint,
358
+ headers: settings.headers || {},
359
+ source: "settings",
360
+ };
328
361
  const config = codexTelemetryConfig();
329
362
  const authorization = String(config.authorization || "");
330
363
  return {
@@ -590,7 +623,7 @@ export function installRepositoryObservationHook(settings, settingsPath) {
590
623
  const directory = path.dirname(path.resolve(settingsPath));
591
624
  const hookPath = path.join(directory, REPOSITORY_HOOK_FILE);
592
625
  fs.mkdirSync(directory, { recursive: true });
593
- fs.writeFileSync(hookPath, repositoryHookSource(), { mode: 0o700 });
626
+ fs.writeFileSync(hookPath, repositoryHookSource({ claudeSettingsPath: settingsPath }), { mode: 0o700 });
594
627
  try {
595
628
  fs.chmodSync(hookPath, 0o700);
596
629
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codetelemetry/connect",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Public-safe CLI and library for connecting Claude Code or Codex to CodeTelemetry.",
5
5
  "type": "module",
6
6
  "license": "MIT",