ai-otel-setup 1.0.4 → 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.
Files changed (2) hide show
  1. package/cli.js +26 -5
  2. package/package.json +1 -1
package/cli.js CHANGED
@@ -280,10 +280,32 @@ function stripLegacyCodexHook(text) {
280
280
 
281
281
  function stripLegacyCodexHooksFlag(text) {
282
282
  // Codex 把 [features].codex_hooks 重命名为 [features].hooks,旧 key 启动时触发 deprecation 警告
283
- // 删 = true 这行,由 managed 块统一写 hooks = true;= false 是显式 opt-out,保留
283
+ // 删 = true 这行,由 ensureFeaturesHooksTrue 统一写 hooks = true;= false 是显式 opt-out,保留
284
284
  return text.replace(/^[ \t]*codex_hooks[ \t]*=[ \t]*true[ \t]*\r?\n/gm, "");
285
285
  }
286
286
 
287
+ function ensureFeaturesHooksTrue(text) {
288
+ // 在用户已有的 [features] 块原地插入 hooks = true(如缺失);没有 [features] 就新建。
289
+ // 不能写在 managed 块里——TOML 1.0 禁止同名 table 重复声明,会被严格解析器拒绝。
290
+ const lines = text.split(/\r?\n/);
291
+ let featuresIdx = -1;
292
+ let hooksKeyExists = false;
293
+ for (let i = 0; i < lines.length; i++) {
294
+ if (featuresIdx === -1) {
295
+ if (/^\s*\[features\]\s*$/.test(lines[i])) featuresIdx = i;
296
+ continue;
297
+ }
298
+ if (/^\s*\[/.test(lines[i])) break; // 下一个 section,结束 [features] 主块扫描
299
+ if (/^[ \t]*hooks[ \t]*=/.test(lines[i])) hooksKeyExists = true;
300
+ }
301
+ if (featuresIdx >= 0) {
302
+ if (hooksKeyExists) return text; // 任何 hooks = ... 都尊重,不覆盖用户显式选择
303
+ lines.splice(featuresIdx + 1, 0, "hooks = true");
304
+ return lines.join("\n");
305
+ }
306
+ return text.trimEnd() + "\n\n[features]\nhooks = true\n";
307
+ }
308
+
287
309
  function buildCodexManagedBlock(endpoint, hookDest, launcherDest) {
288
310
  // exporter / trace_exporter / metrics_exporter 是 externally-tagged enum:
289
311
  // - 写 scalar `exporter = "otlp-grpc"`:codex 解析为 unit variant,因为
@@ -293,11 +315,9 @@ function buildCodexManagedBlock(endpoint, hookDest, launcherDest) {
293
315
  // - 只写 table `[otel.exporter."otlp-grpc"]`:✓ codex 把它解析为
294
316
  // OtlpGrpc { endpoint },tag 来自 key 名。
295
317
  // 官方 sample 之所以能 `exporter = "none"`,是因为 None 本身就是 unit variant。
318
+ // [features].hooks = true 由 ensureFeaturesHooksTrue 写到用户块里,避免重复声明 [features]
296
319
  return [
297
320
  CODEX_MANAGED_BEGIN,
298
- "[features]",
299
- "hooks = true",
300
- "",
301
321
  "[otel]",
302
322
  'environment = "prod"',
303
323
  "log_user_prompt = false",
@@ -336,11 +356,12 @@ function installCodex(home, endpoint) {
336
356
  const bak = backup(configPath);
337
357
  let existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
338
358
 
339
- // 四步去重:先剥离上一次的 managed 块,再清掉旧 schema 残留
359
+ // 先剥离上一次的 managed 块和旧 schema 残留,再保证用户块里有 hooks = true
340
360
  existing = stripCodexManagedBlock(existing);
341
361
  existing = stripLegacyCodexOtel(existing);
342
362
  existing = stripLegacyCodexHook(existing);
343
363
  existing = stripLegacyCodexHooksFlag(existing);
364
+ existing = ensureFeaturesHooksTrue(existing);
344
365
 
345
366
  // hook 同目录的 endpoint.json:hook 脚本运行时读它拿 logs endpoint,避免依赖
346
367
  // shell 前缀注入 env(cmd.exe 不认那种语法,跨平台必须改成走文件)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-otel-setup",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "One-shot installer for AI CLI OpenTelemetry forwarding. Writes Claude Code, Codex CLI, and Gemini CLI telemetry config in a single npx command.",
5
5
  "bin": {
6
6
  "ai-otel-setup": "cli.js",