@osfactory/har 0.16.2 → 0.18.0

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/dist/index.js CHANGED
@@ -765,14 +765,14 @@ var require_url_state_machine = __commonJS({
765
765
  return url.replace(/\u0009|\u000A|\u000D/g, "");
766
766
  }
767
767
  function shortenPath(url) {
768
- const path48 = url.path;
769
- if (path48.length === 0) {
768
+ const path49 = url.path;
769
+ if (path49.length === 0) {
770
770
  return;
771
771
  }
772
- if (url.scheme === "file" && path48.length === 1 && isNormalizedWindowsDriveLetter(path48[0])) {
772
+ if (url.scheme === "file" && path49.length === 1 && isNormalizedWindowsDriveLetter(path49[0])) {
773
773
  return;
774
774
  }
775
- path48.pop();
775
+ path49.pop();
776
776
  }
777
777
  function includesCredentials(url) {
778
778
  return url.username !== "" || url.password !== "";
@@ -2092,7 +2092,7 @@ var require_lib2 = __commonJS({
2092
2092
  let accum = [];
2093
2093
  let accumBytes = 0;
2094
2094
  let abort = false;
2095
- return new Body.Promise(function(resolve33, reject) {
2095
+ return new Body.Promise(function(resolve34, reject) {
2096
2096
  let resTimeout;
2097
2097
  if (_this4.timeout) {
2098
2098
  resTimeout = setTimeout(function() {
@@ -2126,7 +2126,7 @@ var require_lib2 = __commonJS({
2126
2126
  }
2127
2127
  clearTimeout(resTimeout);
2128
2128
  try {
2129
- resolve33(Buffer.concat(accum, accumBytes));
2129
+ resolve34(Buffer.concat(accum, accumBytes));
2130
2130
  } catch (err) {
2131
2131
  reject(new FetchError(`Could not create Buffer from response body for ${_this4.url}: ${err.message}`, "system", err));
2132
2132
  }
@@ -2801,7 +2801,7 @@ var require_lib2 = __commonJS({
2801
2801
  throw new Error("native promise missing, set fetch.Promise to your favorite alternative");
2802
2802
  }
2803
2803
  Body.Promise = fetch3.Promise;
2804
- return new fetch3.Promise(function(resolve33, reject) {
2804
+ return new fetch3.Promise(function(resolve34, reject) {
2805
2805
  const request = new Request3(url, opts);
2806
2806
  const options = getNodeRequestOptions(request);
2807
2807
  const send = (options.protocol === "https:" ? https : http).request;
@@ -2934,7 +2934,7 @@ var require_lib2 = __commonJS({
2934
2934
  requestOpts.body = void 0;
2935
2935
  requestOpts.headers.delete("content-length");
2936
2936
  }
2937
- resolve33(fetch3(new Request3(locationURL, requestOpts)));
2937
+ resolve34(fetch3(new Request3(locationURL, requestOpts)));
2938
2938
  finalize();
2939
2939
  return;
2940
2940
  }
@@ -2955,7 +2955,7 @@ var require_lib2 = __commonJS({
2955
2955
  const codings = headers.get("Content-Encoding");
2956
2956
  if (!request.compress || request.method === "HEAD" || codings === null || res.statusCode === 204 || res.statusCode === 304) {
2957
2957
  response = new Response3(body, response_options);
2958
- resolve33(response);
2958
+ resolve34(response);
2959
2959
  return;
2960
2960
  }
2961
2961
  const zlibOptions = {
@@ -2965,7 +2965,7 @@ var require_lib2 = __commonJS({
2965
2965
  if (codings == "gzip" || codings == "x-gzip") {
2966
2966
  body = body.pipe(zlib.createGunzip(zlibOptions));
2967
2967
  response = new Response3(body, response_options);
2968
- resolve33(response);
2968
+ resolve34(response);
2969
2969
  return;
2970
2970
  }
2971
2971
  if (codings == "deflate" || codings == "x-deflate") {
@@ -2977,12 +2977,12 @@ var require_lib2 = __commonJS({
2977
2977
  body = body.pipe(zlib.createInflateRaw());
2978
2978
  }
2979
2979
  response = new Response3(body, response_options);
2980
- resolve33(response);
2980
+ resolve34(response);
2981
2981
  });
2982
2982
  raw.on("end", function() {
2983
2983
  if (!response) {
2984
2984
  response = new Response3(body, response_options);
2985
- resolve33(response);
2985
+ resolve34(response);
2986
2986
  }
2987
2987
  });
2988
2988
  return;
@@ -2990,11 +2990,11 @@ var require_lib2 = __commonJS({
2990
2990
  if (codings == "br" && typeof zlib.createBrotliDecompress === "function") {
2991
2991
  body = body.pipe(zlib.createBrotliDecompress());
2992
2992
  response = new Response3(body, response_options);
2993
- resolve33(response);
2993
+ resolve34(response);
2994
2994
  return;
2995
2995
  }
2996
2996
  response = new Response3(body, response_options);
2997
- resolve33(response);
2997
+ resolve34(response);
2998
2998
  });
2999
2999
  writeToStream(req, request);
3000
3000
  });
@@ -6351,14 +6351,14 @@ __export(fileFromPath_exports, {
6351
6351
  fileFromPathSync: () => fileFromPathSync,
6352
6352
  isFile: () => isFile
6353
6353
  });
6354
- function createFileFromPath(path48, { mtimeMs, size }, filenameOrOptions, options = {}) {
6354
+ function createFileFromPath(path49, { mtimeMs, size }, filenameOrOptions, options = {}) {
6355
6355
  let filename;
6356
6356
  if (isPlainObject_default2(filenameOrOptions)) {
6357
6357
  [options, filename] = [filenameOrOptions, void 0];
6358
6358
  } else {
6359
6359
  filename = filenameOrOptions;
6360
6360
  }
6361
- const file = new FileFromPath({ path: path48, size, lastModified: mtimeMs });
6361
+ const file = new FileFromPath({ path: path49, size, lastModified: mtimeMs });
6362
6362
  if (!filename) {
6363
6363
  filename = file.name;
6364
6364
  }
@@ -6367,13 +6367,13 @@ function createFileFromPath(path48, { mtimeMs, size }, filenameOrOptions, option
6367
6367
  lastModified: file.lastModified
6368
6368
  });
6369
6369
  }
6370
- function fileFromPathSync(path48, filenameOrOptions, options = {}) {
6371
- const stats = (0, import_fs5.statSync)(path48);
6372
- return createFileFromPath(path48, stats, filenameOrOptions, options);
6370
+ function fileFromPathSync(path49, filenameOrOptions, options = {}) {
6371
+ const stats = (0, import_fs5.statSync)(path49);
6372
+ return createFileFromPath(path49, stats, filenameOrOptions, options);
6373
6373
  }
6374
- async function fileFromPath2(path48, filenameOrOptions, options) {
6375
- const stats = await import_fs5.promises.stat(path48);
6376
- return createFileFromPath(path48, stats, filenameOrOptions, options);
6374
+ async function fileFromPath2(path49, filenameOrOptions, options) {
6375
+ const stats = await import_fs5.promises.stat(path49);
6376
+ return createFileFromPath(path49, stats, filenameOrOptions, options);
6377
6377
  }
6378
6378
  var import_fs5, import_path5, import_node_domexception, __classPrivateFieldSet5, __classPrivateFieldGet6, _FileFromPath_path, _FileFromPath_start, MESSAGE, FileFromPath;
6379
6379
  var init_fileFromPath = __esm({
@@ -6433,6 +6433,412 @@ var init_fileFromPath = __esm({
6433
6433
  }
6434
6434
  });
6435
6435
 
6436
+ // src/core/control-config.ts
6437
+ function getControlApiUrl() {
6438
+ return process.env.HAR_CONTROL_API_URL ?? DEFAULT_CONTROL_API_URL;
6439
+ }
6440
+ function isControlEnabled() {
6441
+ return process.env.HAR_CONTROL_DISABLED !== "true";
6442
+ }
6443
+ var DEFAULT_CONTROL_API_URL;
6444
+ var init_control_config = __esm({
6445
+ "src/core/control-config.ts"() {
6446
+ "use strict";
6447
+ DEFAULT_CONTROL_API_URL = "http://localhost:3847";
6448
+ }
6449
+ });
6450
+
6451
+ // src/core/telemetry-config.ts
6452
+ function getPreferencePath() {
6453
+ if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
6454
+ return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
6455
+ }
6456
+ return path34.join(os9.homedir(), ".har", "telemetry.json");
6457
+ }
6458
+ function parseEnvOverride(raw) {
6459
+ if (raw === void 0 || raw === "") return void 0;
6460
+ const normalized = raw.trim().toLowerCase();
6461
+ if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
6462
+ if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
6463
+ return void 0;
6464
+ }
6465
+ function normalizeSignals(enabled, raw) {
6466
+ if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
6467
+ return {
6468
+ metrics: raw?.metrics !== false,
6469
+ logs: raw?.logs !== false,
6470
+ prompts: raw?.prompts === true,
6471
+ traces: raw?.traces !== false
6472
+ };
6473
+ }
6474
+ function readTelemetryPreference() {
6475
+ const preferencePath = getPreferencePath();
6476
+ try {
6477
+ if (!fs34.existsSync(preferencePath)) {
6478
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
6479
+ }
6480
+ const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
6481
+ const enabled = parsed.enabled !== false;
6482
+ return {
6483
+ enabled,
6484
+ signals: normalizeSignals(enabled, parsed.signals),
6485
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
6486
+ };
6487
+ } catch {
6488
+ return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
6489
+ }
6490
+ }
6491
+ function writeTelemetryPreference(enabled, signals) {
6492
+ const current = readTelemetryPreference();
6493
+ const nextSignals = normalizeSignals(enabled, {
6494
+ ...current.signals,
6495
+ ...signals
6496
+ });
6497
+ const preference = {
6498
+ enabled,
6499
+ signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
6500
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
6501
+ };
6502
+ const preferencePath = getPreferencePath();
6503
+ fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
6504
+ fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
6505
+ return preference;
6506
+ }
6507
+ function isTelemetryEnabled() {
6508
+ const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
6509
+ if (envOverride !== void 0) return envOverride;
6510
+ return readTelemetryPreference().enabled;
6511
+ }
6512
+ function getTelemetrySignals() {
6513
+ if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
6514
+ return readTelemetryPreference().signals;
6515
+ }
6516
+ function getTelemetryPreferencePath() {
6517
+ return getPreferencePath();
6518
+ }
6519
+ var fs34, os9, path34, DEFAULT_SIGNALS_ON, DEFAULT_SIGNALS_OFF, TELEMETRY_SIGNALS;
6520
+ var init_telemetry_config = __esm({
6521
+ "src/core/telemetry-config.ts"() {
6522
+ "use strict";
6523
+ fs34 = __toESM(require("fs"));
6524
+ os9 = __toESM(require("os"));
6525
+ path34 = __toESM(require("path"));
6526
+ DEFAULT_SIGNALS_ON = {
6527
+ metrics: true,
6528
+ logs: true,
6529
+ prompts: false,
6530
+ /** Hooks are traces-first; usage metrics are derived from span gen_ai.usage.* in Mission Control. */
6531
+ traces: true
6532
+ };
6533
+ DEFAULT_SIGNALS_OFF = {
6534
+ metrics: false,
6535
+ logs: false,
6536
+ prompts: false,
6537
+ traces: false
6538
+ };
6539
+ TELEMETRY_SIGNALS = [
6540
+ "Traces (default): Cursor / Claude / Codex activity via opentelemetry-hooks \u2192 Mission Control",
6541
+ "Logs/events (default): hook lifecycle logs without prompt bodies",
6542
+ "Metrics (default): token usage derived from span gen_ai.usage.* attributes",
6543
+ "Prompts (opt-in): user prompt text via IDE_OTEL_CAPTURE_TEXT \u2014 har telemetry on --prompts (also fills Mission Control purpose)",
6544
+ "Fallback: har control sync harvests local Claude/Codex session files when hooks telemetry is missing"
6545
+ ];
6546
+ }
6547
+ });
6548
+
6549
+ // src/core/otel-hooks.ts
6550
+ var otel_hooks_exports = {};
6551
+ __export(otel_hooks_exports, {
6552
+ OTEL_HOOKS_AGENTS: () => OTEL_HOOKS_AGENTS,
6553
+ OTEL_HOOKS_PACKAGE: () => OTEL_HOOKS_PACKAGE,
6554
+ buildOtelHooksConfig: () => buildOtelHooksConfig,
6555
+ disableOtelHooksExport: () => disableOtelHooksExport,
6556
+ ensureOtelHooks: () => ensureOtelHooks,
6557
+ getOtelHooksConfigPath: () => getOtelHooksConfigPath,
6558
+ getOtelHooksHome: () => getOtelHooksHome,
6559
+ getOtelHooksWrapperPath: () => getOtelHooksWrapperPath,
6560
+ rewriteHookCommandsToWrapper: () => rewriteHookCommandsToWrapper,
6561
+ writeOtelHooksConfig: () => writeOtelHooksConfig,
6562
+ writeOtelHooksWrapper: () => writeOtelHooksWrapper
6563
+ });
6564
+ function getOtelHooksHome() {
6565
+ if (process.env.HAR_OTEL_HOOKS_HOME) {
6566
+ return path40.resolve(process.env.HAR_OTEL_HOOKS_HOME);
6567
+ }
6568
+ return path40.join(os12.homedir(), ".har", "otel-hooks");
6569
+ }
6570
+ function getOtelHooksConfigPath(hooksHome = getOtelHooksHome()) {
6571
+ return path40.join(hooksHome, "otel_config.json");
6572
+ }
6573
+ function getOtelHooksWrapperPath(hooksHome = getOtelHooksHome()) {
6574
+ return path40.join(hooksHome, "run-otel-hook.sh");
6575
+ }
6576
+ function buildOtelHooksConfig(options) {
6577
+ const enabled = options?.enabled ?? isTelemetryEnabled();
6578
+ const signals = getTelemetrySignals();
6579
+ const apiUrl = (options?.apiUrl ?? getControlApiUrl()).replace(/\/$/, "");
6580
+ return {
6581
+ OTEL_EXPORTER_OTLP_ENDPOINT: enabled ? `${apiUrl}/api/otel/v1/traces` : null,
6582
+ OTEL_EXPORTER_OTLP_PROTOCOL: "http/json",
6583
+ OTEL_SERVICE_NAME: "har-ide-agent",
6584
+ IDE_OTEL_BATCH_ON_STOP: "true",
6585
+ IDE_OTEL_ENABLE_LOGS: enabled && signals.logs ? "true" : "false",
6586
+ IDE_OTEL_LOG_ALL_EVENTS: enabled && signals.logs ? "true" : "false",
6587
+ IDE_OTEL_CAPTURE_TEXT: enabled && signals.prompts ? "true" : "false",
6588
+ IDE_OTEL_MASK_PROMPTS: "false",
6589
+ OTEL_RESOURCE_ATTRIBUTES: options?.resourceAttributes ?? null
6590
+ };
6591
+ }
6592
+ function writeOtelHooksConfig(config2, hooksHome = getOtelHooksHome()) {
6593
+ fs38.mkdirSync(hooksHome, { recursive: true });
6594
+ const configPath = getOtelHooksConfigPath(hooksHome);
6595
+ const payload = {
6596
+ _comment: "Managed by har telemetry \u2014 do not edit by hand; use: har telemetry on|off",
6597
+ ...config2
6598
+ };
6599
+ fs38.writeFileSync(configPath, `${JSON.stringify(payload, null, 2)}
6600
+ `);
6601
+ return configPath;
6602
+ }
6603
+ function writeOtelHooksWrapper(otelHookCommand, hooksHome = getOtelHooksHome()) {
6604
+ fs38.mkdirSync(hooksHome, { recursive: true });
6605
+ const wrapperPath = getOtelHooksWrapperPath(hooksHome);
6606
+ const script = `#!/usr/bin/env bash
6607
+ # Managed by har telemetry \u2014 invokes opentelemetry-hooks with HAR config home.
6608
+ set -euo pipefail
6609
+ export IDE_OTEL_HOOK_HOME="${hooksHome}"
6610
+ exec "${otelHookCommand}" "$@"
6611
+ `;
6612
+ fs38.writeFileSync(wrapperPath, script, { mode: 493 });
6613
+ try {
6614
+ fs38.chmodSync(wrapperPath, 493);
6615
+ } catch {
6616
+ }
6617
+ return wrapperPath;
6618
+ }
6619
+ function commandExists(command2) {
6620
+ const result = (0, import_child_process9.spawnSync)("sh", ["-c", `command -v ${JSON.stringify(command2)}`], {
6621
+ encoding: "utf8"
6622
+ });
6623
+ return result.status === 0 && Boolean(result.stdout?.trim());
6624
+ }
6625
+ function resolveOtelHookCommand() {
6626
+ if (commandExists("otel-hook")) {
6627
+ const which = (0, import_child_process9.spawnSync)("sh", ["-c", "command -v otel-hook"], { encoding: "utf8" });
6628
+ return which.stdout?.trim() || "otel-hook";
6629
+ }
6630
+ return null;
6631
+ }
6632
+ function tryInstallPackage() {
6633
+ if (commandExists("uv")) {
6634
+ const result = (0, import_child_process9.spawnSync)(
6635
+ "uv",
6636
+ ["tool", "install", "--force", OTEL_HOOKS_PACKAGE],
6637
+ { encoding: "utf8", timeout: 18e4 }
6638
+ );
6639
+ if (result.status === 0) {
6640
+ return { ok: true, detail: `uv tool install ${OTEL_HOOKS_PACKAGE}` };
6641
+ }
6642
+ }
6643
+ if (commandExists("pipx")) {
6644
+ const result = (0, import_child_process9.spawnSync)("pipx", ["install", "--force", OTEL_HOOKS_PACKAGE], {
6645
+ encoding: "utf8",
6646
+ timeout: 18e4
6647
+ });
6648
+ if (result.status === 0) {
6649
+ return { ok: true, detail: `pipx install ${OTEL_HOOKS_PACKAGE}` };
6650
+ }
6651
+ return {
6652
+ ok: false,
6653
+ detail: `pipx install failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6654
+ };
6655
+ }
6656
+ if (commandExists("pip3") || commandExists("pip")) {
6657
+ const pip = commandExists("pip3") ? "pip3" : "pip";
6658
+ const result = (0, import_child_process9.spawnSync)(
6659
+ pip,
6660
+ ["install", "--user", OTEL_HOOKS_PACKAGE],
6661
+ { encoding: "utf8", timeout: 18e4 }
6662
+ );
6663
+ if (result.status === 0) {
6664
+ return { ok: true, detail: `${pip} install --user ${OTEL_HOOKS_PACKAGE}` };
6665
+ }
6666
+ return {
6667
+ ok: false,
6668
+ detail: `${pip} install failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6669
+ };
6670
+ }
6671
+ return {
6672
+ ok: false,
6673
+ detail: "Neither uv, pipx, nor pip3/pip found; install Python tooling to use opentelemetry-hooks"
6674
+ };
6675
+ }
6676
+ function runSetupAgent(agent, otelHookCommand, hooksHome) {
6677
+ const result = (0, import_child_process9.spawnSync)(otelHookCommand, ["setup", "--agent", agent], {
6678
+ encoding: "utf8",
6679
+ env: {
6680
+ ...process.env,
6681
+ IDE_OTEL_HOOK_HOME: hooksHome
6682
+ },
6683
+ timeout: 6e4
6684
+ });
6685
+ if (result.status === 0) {
6686
+ return { ok: true, detail: `setup --agent ${agent}` };
6687
+ }
6688
+ return {
6689
+ ok: false,
6690
+ detail: `setup --agent ${agent} failed: ${(result.stderr || result.stdout || "").trim().slice(0, 400)}`
6691
+ };
6692
+ }
6693
+ function rewriteHookCommandsToWrapper(filePath, wrapperPath) {
6694
+ if (!fs38.existsSync(filePath)) return false;
6695
+ let raw;
6696
+ try {
6697
+ raw = fs38.readFileSync(filePath, "utf8");
6698
+ } catch {
6699
+ return false;
6700
+ }
6701
+ let parsed;
6702
+ try {
6703
+ parsed = JSON.parse(raw);
6704
+ } catch {
6705
+ return false;
6706
+ }
6707
+ if (!parsed || typeof parsed !== "object") return false;
6708
+ const quoted = JSON.stringify(wrapperPath);
6709
+ const rewriteCommand = (command2) => {
6710
+ if (!command2.includes("otel-hook") && !command2.includes("otel_hook.py")) return command2;
6711
+ if (command2.includes(wrapperPath)) return command2;
6712
+ return command2.replace(
6713
+ /(?:^|[;&|]\s*)(?:env\s+[^=\s]+=\S+\s+)*(?:python3?\s+)?(?:\S*otel[_-]hook\S*)/,
6714
+ (match) => match.replace(/(?:python3?\s+)?(?:\S*otel[_-]hook\S*)/, quoted.slice(1, -1))
6715
+ );
6716
+ };
6717
+ const walk = (node) => {
6718
+ let changed = false;
6719
+ if (Array.isArray(node)) {
6720
+ for (const item of node) changed = walk(item) || changed;
6721
+ return changed;
6722
+ }
6723
+ if (!node || typeof node !== "object") return false;
6724
+ const obj = node;
6725
+ if (typeof obj.command === "string") {
6726
+ const next = rewriteCommand(obj.command);
6727
+ if (next !== obj.command) {
6728
+ obj.command = next;
6729
+ changed = true;
6730
+ }
6731
+ }
6732
+ for (const value of Object.values(obj)) changed = walk(value) || changed;
6733
+ return changed;
6734
+ };
6735
+ if (!walk(parsed)) return false;
6736
+ fs38.writeFileSync(filePath, `${JSON.stringify(parsed, null, 2)}
6737
+ `);
6738
+ return true;
6739
+ }
6740
+ function rewriteKnownHookRegistrations(wrapperPath) {
6741
+ const home = os12.homedir();
6742
+ const candidates = [
6743
+ path40.join(home, ".cursor", "hooks.json"),
6744
+ path40.join(home, ".claude", "settings.json"),
6745
+ path40.join(home, ".codex", "hooks.json")
6746
+ ];
6747
+ for (const filePath of candidates) {
6748
+ try {
6749
+ rewriteHookCommandsToWrapper(filePath, wrapperPath);
6750
+ } catch {
6751
+ }
6752
+ }
6753
+ }
6754
+ function ensureOtelHooks(options) {
6755
+ const hooksHome = getOtelHooksHome();
6756
+ const errors = [];
6757
+ const agentsSetup = [];
6758
+ fs38.mkdirSync(hooksHome, { recursive: true });
6759
+ let otelHookCommand = resolveOtelHookCommand();
6760
+ if (!otelHookCommand) {
6761
+ const installed = tryInstallPackage();
6762
+ if (!installed.ok) {
6763
+ errors.push(installed.detail);
6764
+ const configPath2 = writeOtelHooksConfig(
6765
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6766
+ hooksHome
6767
+ );
6768
+ return {
6769
+ ok: false,
6770
+ hooksHome,
6771
+ configPath: configPath2,
6772
+ wrapperPath: getOtelHooksWrapperPath(hooksHome),
6773
+ otelHookCommand: null,
6774
+ warning: installed.detail,
6775
+ agentsSetup,
6776
+ errors
6777
+ };
6778
+ }
6779
+ otelHookCommand = resolveOtelHookCommand();
6780
+ if (!otelHookCommand) {
6781
+ errors.push("opentelemetry-hooks installed but otel-hook is not on PATH");
6782
+ const configPath2 = writeOtelHooksConfig(
6783
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6784
+ hooksHome
6785
+ );
6786
+ return {
6787
+ ok: false,
6788
+ hooksHome,
6789
+ configPath: configPath2,
6790
+ wrapperPath: getOtelHooksWrapperPath(hooksHome),
6791
+ otelHookCommand: null,
6792
+ warning: errors[errors.length - 1],
6793
+ agentsSetup,
6794
+ errors
6795
+ };
6796
+ }
6797
+ }
6798
+ const configPath = writeOtelHooksConfig(
6799
+ buildOtelHooksConfig({ resourceAttributes: options?.resourceAttributes }),
6800
+ hooksHome
6801
+ );
6802
+ const wrapperPath = writeOtelHooksWrapper(otelHookCommand, hooksHome);
6803
+ if (options?.setupAgents !== false && isTelemetryEnabled()) {
6804
+ for (const agent of OTEL_HOOKS_AGENTS) {
6805
+ const setup = runSetupAgent(agent, otelHookCommand, hooksHome);
6806
+ if (setup.ok) agentsSetup.push(agent);
6807
+ else errors.push(setup.detail);
6808
+ }
6809
+ rewriteKnownHookRegistrations(wrapperPath);
6810
+ }
6811
+ const ok = errors.length === 0;
6812
+ return {
6813
+ ok,
6814
+ hooksHome,
6815
+ configPath,
6816
+ wrapperPath,
6817
+ otelHookCommand,
6818
+ message: ok ? `opentelemetry-hooks ready (${agentsSetup.join(", ") || "config only"}) \u2192 ${configPath}` : void 0,
6819
+ warning: errors.length ? errors.join("; ") : void 0,
6820
+ agentsSetup,
6821
+ errors
6822
+ };
6823
+ }
6824
+ function disableOtelHooksExport() {
6825
+ return writeOtelHooksConfig(buildOtelHooksConfig({ enabled: false }));
6826
+ }
6827
+ var import_child_process9, fs38, os12, path40, OTEL_HOOKS_PACKAGE, OTEL_HOOKS_AGENTS;
6828
+ var init_otel_hooks = __esm({
6829
+ "src/core/otel-hooks.ts"() {
6830
+ "use strict";
6831
+ import_child_process9 = require("child_process");
6832
+ fs38 = __toESM(require("fs"));
6833
+ os12 = __toESM(require("os"));
6834
+ path40 = __toESM(require("path"));
6835
+ init_control_config();
6836
+ init_telemetry_config();
6837
+ OTEL_HOOKS_PACKAGE = "opentelemetry-hooks==0.14.0";
6838
+ OTEL_HOOKS_AGENTS = ["cursor", "claude", "codex"];
6839
+ }
6840
+ });
6841
+
6436
6842
  // node_modules/ajv/dist/compile/codegen/code.js
6437
6843
  var require_code = __commonJS({
6438
6844
  "node_modules/ajv/dist/compile/codegen/code.js"(exports2) {
@@ -9384,7 +9790,7 @@ var require_compile = __commonJS({
9384
9790
  const schOrFunc = root.refs[ref];
9385
9791
  if (schOrFunc)
9386
9792
  return schOrFunc;
9387
- let _sch = resolve33.call(this, root, ref);
9793
+ let _sch = resolve34.call(this, root, ref);
9388
9794
  if (_sch === void 0) {
9389
9795
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
9390
9796
  const { schemaId } = this.opts;
@@ -9411,7 +9817,7 @@ var require_compile = __commonJS({
9411
9817
  function sameSchemaEnv(s1, s2) {
9412
9818
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
9413
9819
  }
9414
- function resolve33(root, ref) {
9820
+ function resolve34(root, ref) {
9415
9821
  let sch;
9416
9822
  while (typeof (sch = this.refs[ref]) == "string")
9417
9823
  ref = sch;
@@ -9629,8 +10035,8 @@ var require_utils2 = __commonJS({
9629
10035
  }
9630
10036
  return ind;
9631
10037
  }
9632
- function removeDotSegments(path48) {
9633
- let input = path48;
10038
+ function removeDotSegments(path49) {
10039
+ let input = path49;
9634
10040
  const output = [];
9635
10041
  let nextSlash = -1;
9636
10042
  let len = 0;
@@ -9882,8 +10288,8 @@ var require_schemes = __commonJS({
9882
10288
  wsComponent.secure = void 0;
9883
10289
  }
9884
10290
  if (wsComponent.resourceName) {
9885
- const [path48, query] = wsComponent.resourceName.split("?");
9886
- wsComponent.path = path48 && path48 !== "/" ? path48 : void 0;
10291
+ const [path49, query] = wsComponent.resourceName.split("?");
10292
+ wsComponent.path = path49 && path49 !== "/" ? path49 : void 0;
9887
10293
  wsComponent.query = query;
9888
10294
  wsComponent.resourceName = void 0;
9889
10295
  }
@@ -10042,7 +10448,7 @@ var require_fast_uri = __commonJS({
10042
10448
  }
10043
10449
  return uri;
10044
10450
  }
10045
- function resolve33(baseURI, relativeURI, options) {
10451
+ function resolve34(baseURI, relativeURI, options) {
10046
10452
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
10047
10453
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
10048
10454
  schemelessOptions.skipEscape = true;
@@ -10300,7 +10706,7 @@ var require_fast_uri = __commonJS({
10300
10706
  var fastUri = {
10301
10707
  SCHEMES,
10302
10708
  normalize: normalize3,
10303
- resolve: resolve33,
10709
+ resolve: resolve34,
10304
10710
  resolveComponent,
10305
10711
  equal,
10306
10712
  serialize,
@@ -14565,11 +14971,11 @@ var parser = new YargsParser({
14565
14971
  resolve: import_path2.resolve,
14566
14972
  // TODO: figure out a way to combine ESM and CJS coverage, such that
14567
14973
  // we can exercise all the lines below:
14568
- require: (path48) => {
14974
+ require: (path49) => {
14569
14975
  if (typeof require !== "undefined") {
14570
- return require(path48);
14571
- } else if (path48.match(/\.json$/)) {
14572
- return JSON.parse((0, import_fs2.readFileSync)(path48, "utf8"));
14976
+ return require(path49);
14977
+ } else if (path49.match(/\.json$/)) {
14978
+ return JSON.parse((0, import_fs2.readFileSync)(path49, "utf8"));
14573
14979
  } else {
14574
14980
  throw Error("only .json config files are supported in ESM");
14575
14981
  }
@@ -17095,12 +17501,12 @@ var YargsInstance = class {
17095
17501
  async getCompletion(args, done) {
17096
17502
  argsert("<array> [function]", [args, done], arguments.length);
17097
17503
  if (!done) {
17098
- return new Promise((resolve33, reject) => {
17504
+ return new Promise((resolve34, reject) => {
17099
17505
  __classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
17100
17506
  if (err)
17101
17507
  reject(err);
17102
17508
  else
17103
- resolve33(completions);
17509
+ resolve34(completions);
17104
17510
  });
17105
17511
  });
17106
17512
  } else {
@@ -19250,8 +19656,8 @@ function getErrorMap() {
19250
19656
 
19251
19657
  // node_modules/zod/v3/helpers/parseUtil.js
19252
19658
  var makeIssue = (params) => {
19253
- const { data, path: path48, errorMaps, issueData } = params;
19254
- const fullPath = [...path48, ...issueData.path || []];
19659
+ const { data, path: path49, errorMaps, issueData } = params;
19660
+ const fullPath = [...path49, ...issueData.path || []];
19255
19661
  const fullIssue = {
19256
19662
  ...issueData,
19257
19663
  path: fullPath
@@ -19367,11 +19773,11 @@ var errorUtil;
19367
19773
 
19368
19774
  // node_modules/zod/v3/types.js
19369
19775
  var ParseInputLazyPath = class {
19370
- constructor(parent, value, path48, key) {
19776
+ constructor(parent, value, path49, key) {
19371
19777
  this._cachedPath = [];
19372
19778
  this.parent = parent;
19373
19779
  this.data = value;
19374
- this._path = path48;
19780
+ this._path = path49;
19375
19781
  this._key = key;
19376
19782
  }
19377
19783
  get path() {
@@ -22992,7 +23398,6 @@ var SlotRegistryEntrySchema = external_exports.object({
22992
23398
  /** Random per-session chars; absent in root mode. */
22993
23399
  suffix: external_exports.string().optional(),
22994
23400
  createdAt: external_exports.string(),
22995
- purpose: external_exports.string().optional(),
22996
23401
  ports: external_exports.record(external_exports.number()).optional(),
22997
23402
  previewUrls: external_exports.record(external_exports.string()).optional(),
22998
23403
  status: external_exports.enum(["starting", "active", "failed", "completed"]).default("active"),
@@ -23038,6 +23443,7 @@ var AgentSlotStatusSchema = external_exports.object({
23038
23443
  baseBranch: external_exports.string().optional(),
23039
23444
  baseCommit: external_exports.string().optional(),
23040
23445
  sessionCreatedAt: external_exports.string().optional(),
23446
+ /** Derived from first captured user prompt (OTEL hooks); not set at launch. */
23041
23447
  purpose: external_exports.string().optional(),
23042
23448
  sessionStatus: external_exports.enum(["starting", "active", "failed", "completed"]).optional(),
23043
23449
  lastError: external_exports.string().optional(),
@@ -23106,7 +23512,7 @@ var ValidationRecordSchema = external_exports.object({
23106
23512
  var SyncValidationsInputSchema = external_exports.object({
23107
23513
  validations: external_exports.array(ValidationRecordSchema)
23108
23514
  });
23109
- var AgentToolSchema = external_exports.enum(["claude_code", "codex"]);
23515
+ var AgentToolSchema = external_exports.enum(["claude_code", "codex", "cursor"]);
23110
23516
  var UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
23111
23517
  var AgentSessionUsageSchema = external_exports.object({
23112
23518
  sessionKey: external_exports.string().min(1),
@@ -23410,16 +23816,16 @@ async function handleAgentSkills(options) {
23410
23816
  }
23411
23817
  async function askYesNo(question) {
23412
23818
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
23413
- return new Promise((resolve33) => {
23819
+ return new Promise((resolve34) => {
23414
23820
  process.stderr.write(`${question} `);
23415
23821
  rl.once("line", (answer) => {
23416
23822
  rl.close();
23417
23823
  const trimmed = answer.trim();
23418
23824
  if (trimmed === "") {
23419
- resolve33(true);
23825
+ resolve34(true);
23420
23826
  return;
23421
23827
  }
23422
- resolve33(/^y(es)?$/i.test(trimmed));
23828
+ resolve34(/^y(es)?$/i.test(trimmed));
23423
23829
  });
23424
23830
  });
23425
23831
  }
@@ -23498,7 +23904,7 @@ function handleRemove(argv) {
23498
23904
  }
23499
23905
 
23500
23906
  // src/cli/commands/env.ts
23501
- var path41 = __toESM(require("path"));
23907
+ var path42 = __toESM(require("path"));
23502
23908
 
23503
23909
  // src/core/harness.ts
23504
23910
  var fs20 = __toESM(require("fs"));
@@ -24277,13 +24683,13 @@ var MultipartBody = class {
24277
24683
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
24278
24684
  var import_web = require("node:stream/web");
24279
24685
  var fileFromPathWarned = false;
24280
- async function fileFromPath3(path48, ...args) {
24686
+ async function fileFromPath3(path49, ...args) {
24281
24687
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
24282
24688
  if (!fileFromPathWarned) {
24283
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path48)}) instead`);
24689
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path49)}) instead`);
24284
24690
  fileFromPathWarned = true;
24285
24691
  }
24286
- return await _fileFromPath(path48, ...args);
24692
+ return await _fileFromPath(path49, ...args);
24287
24693
  }
24288
24694
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
24289
24695
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24779,8 +25185,8 @@ async function defaultParseResponse(props) {
24779
25185
  }
24780
25186
  var APIPromise = class _APIPromise extends Promise {
24781
25187
  constructor(responsePromise, parseResponse = defaultParseResponse) {
24782
- super((resolve33) => {
24783
- resolve33(null);
25188
+ super((resolve34) => {
25189
+ resolve34(null);
24784
25190
  });
24785
25191
  this.responsePromise = responsePromise;
24786
25192
  this.parseResponse = parseResponse;
@@ -24880,29 +25286,29 @@ var APIClient = class {
24880
25286
  defaultIdempotencyKey() {
24881
25287
  return `stainless-node-retry-${uuid4()}`;
24882
25288
  }
24883
- get(path48, opts) {
24884
- return this.methodRequest("get", path48, opts);
25289
+ get(path49, opts) {
25290
+ return this.methodRequest("get", path49, opts);
24885
25291
  }
24886
- post(path48, opts) {
24887
- return this.methodRequest("post", path48, opts);
25292
+ post(path49, opts) {
25293
+ return this.methodRequest("post", path49, opts);
24888
25294
  }
24889
- patch(path48, opts) {
24890
- return this.methodRequest("patch", path48, opts);
25295
+ patch(path49, opts) {
25296
+ return this.methodRequest("patch", path49, opts);
24891
25297
  }
24892
- put(path48, opts) {
24893
- return this.methodRequest("put", path48, opts);
25298
+ put(path49, opts) {
25299
+ return this.methodRequest("put", path49, opts);
24894
25300
  }
24895
- delete(path48, opts) {
24896
- return this.methodRequest("delete", path48, opts);
25301
+ delete(path49, opts) {
25302
+ return this.methodRequest("delete", path49, opts);
24897
25303
  }
24898
- methodRequest(method, path48, opts) {
25304
+ methodRequest(method, path49, opts) {
24899
25305
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24900
25306
  const body = opts2 && isBlobLike(opts2?.body) ? new DataView(await opts2.body.arrayBuffer()) : opts2?.body instanceof DataView ? opts2.body : opts2?.body instanceof ArrayBuffer ? new DataView(opts2.body) : opts2 && ArrayBuffer.isView(opts2?.body) ? new DataView(opts2.body.buffer) : opts2?.body;
24901
- return { method, path: path48, ...opts2, body };
25307
+ return { method, path: path49, ...opts2, body };
24902
25308
  }));
24903
25309
  }
24904
- getAPIList(path48, Page, opts) {
24905
- return this.requestAPIList(Page, { method: "get", path: path48, ...opts });
25310
+ getAPIList(path49, Page, opts) {
25311
+ return this.requestAPIList(Page, { method: "get", path: path49, ...opts });
24906
25312
  }
24907
25313
  calculateContentLength(body) {
24908
25314
  if (typeof body === "string") {
@@ -24920,10 +25326,10 @@ var APIClient = class {
24920
25326
  return null;
24921
25327
  }
24922
25328
  buildRequest(options) {
24923
- const { method, path: path48, query, headers = {} } = options;
25329
+ const { method, path: path49, query, headers = {} } = options;
24924
25330
  const body = ArrayBuffer.isView(options.body) || options.__binaryRequest && typeof options.body === "string" ? options.body : isMultipartBody(options.body) ? options.body.body : options.body ? JSON.stringify(options.body, null, 2) : null;
24925
25331
  const contentLength = this.calculateContentLength(body);
24926
- const url = this.buildURL(path48, query);
25332
+ const url = this.buildURL(path49, query);
24927
25333
  if ("timeout" in options)
24928
25334
  validatePositiveInteger("timeout", options.timeout);
24929
25335
  const timeout = options.timeout ?? this.timeout;
@@ -25032,8 +25438,8 @@ var APIClient = class {
25032
25438
  const request = this.makeRequest(options, null);
25033
25439
  return new PagePromise(this, request, Page);
25034
25440
  }
25035
- buildURL(path48, query) {
25036
- const url = isAbsoluteURL(path48) ? new URL(path48) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path48.startsWith("/") ? path48.slice(1) : path48));
25441
+ buildURL(path49, query) {
25442
+ const url = isAbsoluteURL(path49) ? new URL(path49) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path49.startsWith("/") ? path49.slice(1) : path49));
25037
25443
  const defaultQuery = this.defaultQuery();
25038
25444
  if (!isEmptyObj(defaultQuery)) {
25039
25445
  query = { ...defaultQuery, ...query };
@@ -25318,7 +25724,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
25318
25724
  var isAbsoluteURL = (url) => {
25319
25725
  return startsWithSchemeRegexp.test(url);
25320
25726
  };
25321
- var sleep = (ms) => new Promise((resolve33) => setTimeout(resolve33, ms));
25727
+ var sleep = (ms) => new Promise((resolve34) => setTimeout(resolve34, ms));
25322
25728
  var validatePositiveInteger = (name, n2) => {
25323
25729
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
25324
25730
  throw new AnthropicError(`${name} must be an integer`);
@@ -25809,12 +26215,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25809
26215
  }
25810
26216
  return this._emit("error", new AnthropicError(String(error3)));
25811
26217
  });
25812
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve33, reject) => {
25813
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve33, "f");
26218
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve34, reject) => {
26219
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve34, "f");
25814
26220
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
25815
26221
  }), "f");
25816
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve33, reject) => {
25817
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve33, "f");
26222
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve34, reject) => {
26223
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve34, "f");
25818
26224
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
25819
26225
  }), "f");
25820
26226
  __classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
@@ -25943,11 +26349,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25943
26349
  * const message = await stream.emitted('message') // rejects if the stream errors
25944
26350
  */
25945
26351
  emitted(event) {
25946
- return new Promise((resolve33, reject) => {
26352
+ return new Promise((resolve34, reject) => {
25947
26353
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
25948
26354
  if (event !== "error")
25949
26355
  this.once("error", reject);
25950
- this.once(event, resolve33);
26356
+ this.once(event, resolve34);
25951
26357
  });
25952
26358
  }
25953
26359
  async done() {
@@ -26174,7 +26580,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
26174
26580
  if (done) {
26175
26581
  return { value: void 0, done: true };
26176
26582
  }
26177
- return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26583
+ return new Promise((resolve34, reject) => readQueue.push({ resolve: resolve34, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26178
26584
  }
26179
26585
  const chunk = pushQueue.shift();
26180
26586
  return { value: chunk, done: false };
@@ -26322,12 +26728,12 @@ var MessageStream = class _MessageStream {
26322
26728
  }
26323
26729
  return this._emit("error", new AnthropicError(String(error3)));
26324
26730
  });
26325
- __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve33, reject) => {
26326
- __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve33, "f");
26731
+ __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve34, reject) => {
26732
+ __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve34, "f");
26327
26733
  __classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
26328
26734
  }), "f");
26329
- __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve33, reject) => {
26330
- __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve33, "f");
26735
+ __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve34, reject) => {
26736
+ __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve34, "f");
26331
26737
  __classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
26332
26738
  }), "f");
26333
26739
  __classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
@@ -26456,11 +26862,11 @@ var MessageStream = class _MessageStream {
26456
26862
  * const message = await stream.emitted('message') // rejects if the stream errors
26457
26863
  */
26458
26864
  emitted(event) {
26459
- return new Promise((resolve33, reject) => {
26865
+ return new Promise((resolve34, reject) => {
26460
26866
  __classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
26461
26867
  if (event !== "error")
26462
26868
  this.once("error", reject);
26463
- this.once(event, resolve33);
26869
+ this.once(event, resolve34);
26464
26870
  });
26465
26871
  }
26466
26872
  async done() {
@@ -26687,7 +27093,7 @@ var MessageStream = class _MessageStream {
26687
27093
  if (done) {
26688
27094
  return { value: void 0, done: true };
26689
27095
  }
26690
- return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
27096
+ return new Promise((resolve34, reject) => readQueue.push({ resolve: resolve34, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26691
27097
  }
26692
27098
  const chunk = pushQueue.shift();
26693
27099
  return { value: chunk, done: false };
@@ -26947,11 +27353,11 @@ async function promptApplyAgentMdProposal(repoPath) {
26947
27353
  }
26948
27354
  async function askYesNo2(question) {
26949
27355
  const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
26950
- return new Promise((resolve33) => {
27356
+ return new Promise((resolve34) => {
26951
27357
  process.stderr.write(`${question} `);
26952
27358
  rl.once("line", (answer) => {
26953
27359
  rl.close();
26954
- resolve33(/^y(es)?$/i.test(answer.trim()));
27360
+ resolve34(/^y(es)?$/i.test(answer.trim()));
26955
27361
  });
26956
27362
  });
26957
27363
  }
@@ -27181,7 +27587,7 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
27181
27587
  }
27182
27588
  async function askUser(question, options) {
27183
27589
  const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
27184
- return new Promise((resolve33) => {
27590
+ return new Promise((resolve34) => {
27185
27591
  process.stderr.write("\n");
27186
27592
  process.stderr.write(`\u2753 ${question}
27187
27593
  `);
@@ -27192,7 +27598,7 @@ async function askUser(question, options) {
27192
27598
  process.stderr.write("> ");
27193
27599
  rl.once("line", (answer) => {
27194
27600
  rl.close();
27195
- resolve33(answer.trim());
27601
+ resolve34(answer.trim());
27196
27602
  });
27197
27603
  });
27198
27604
  }
@@ -27336,7 +27742,7 @@ function runScript(scriptPath, args = [], options = {}) {
27336
27742
  function runScriptCapture(scriptPath, args = [], options = {}) {
27337
27743
  const stream = options.stream ?? false;
27338
27744
  const { stream: _stream, ...spawnOptions } = options;
27339
- return new Promise((resolve33) => {
27745
+ return new Promise((resolve34) => {
27340
27746
  const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
27341
27747
  ...spawnOptions,
27342
27748
  stdio: ["inherit", "pipe", "pipe"]
@@ -27351,13 +27757,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
27351
27757
  stderr += d2;
27352
27758
  if (stream) process.stderr.write(d2);
27353
27759
  });
27354
- proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27760
+ proc.on("close", (code) => resolve34({ stdout, stderr, code: code ?? 0 }));
27355
27761
  });
27356
27762
  }
27357
27763
  function runShellCommand(command2, options = {}) {
27358
27764
  const stream = options.stream ?? false;
27359
27765
  const { stream: _stream, ...spawnOptions } = options;
27360
- return new Promise((resolve33) => {
27766
+ return new Promise((resolve34) => {
27361
27767
  const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
27362
27768
  ...spawnOptions,
27363
27769
  stdio: ["inherit", "pipe", "pipe"]
@@ -27372,7 +27778,7 @@ function runShellCommand(command2, options = {}) {
27372
27778
  stderr += d2;
27373
27779
  if (stream) process.stderr.write(d2);
27374
27780
  });
27375
- proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27781
+ proc.on("close", (code) => resolve34({ stdout, stderr, code: code ?? 0 }));
27376
27782
  });
27377
27783
  }
27378
27784
 
@@ -28536,16 +28942,16 @@ async function promptScaffoldCursorRule(exists) {
28536
28942
  }
28537
28943
  async function askYesNo3(question) {
28538
28944
  const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
28539
- return new Promise((resolve33) => {
28945
+ return new Promise((resolve34) => {
28540
28946
  process.stderr.write(`${question} `);
28541
28947
  rl.once("line", (answer) => {
28542
28948
  rl.close();
28543
28949
  const trimmed = answer.trim();
28544
28950
  if (trimmed === "") {
28545
- resolve33(true);
28951
+ resolve34(true);
28546
28952
  return;
28547
28953
  }
28548
- resolve33(/^y(es)?$/i.test(trimmed));
28954
+ resolve34(/^y(es)?$/i.test(trimmed));
28549
28955
  });
28550
28956
  });
28551
28957
  }
@@ -28652,6 +29058,19 @@ function readSlotRegistry(repoPath, agentId) {
28652
29058
  }
28653
29059
 
28654
29060
  // src/core/local-executor.ts
29061
+ function quoteShellArg(arg) {
29062
+ if (/^[A-Za-z0-9_./:=+-]+$/.test(arg)) return arg;
29063
+ return `'${arg.replace(/'/g, `'\\''`)}'`;
29064
+ }
29065
+ function buildLaunchFlagArgs(flags) {
29066
+ const args = [];
29067
+ if (flags.worktree === false) args.push("--no-worktree");
29068
+ if (flags.claude) args.push("--claude");
29069
+ if (flags.confirmReplace) args.push("--replace");
29070
+ if (flags.force) args.push("--force");
29071
+ if (flags.resume) args.push("--resume");
29072
+ return args;
29073
+ }
28655
29074
  function resolveRepoPath(repoPath) {
28656
29075
  return path24.resolve(repoPath);
28657
29076
  }
@@ -28729,18 +29148,11 @@ function buildExecutionPlan(repoPath, stage, options) {
28729
29148
  };
28730
29149
  const cwd = stage.cwd ? path24.resolve(resolvedRepo, stage.cwd) : resolvedRepo;
28731
29150
  const extraArgs = options.args ?? [];
28732
- const launchFlagArgs = [];
28733
- if (stage.kind === "launch" && options.launchFlags) {
28734
- if (options.launchFlags.worktree === false) launchFlagArgs.push("--no-worktree");
28735
- if (options.launchFlags.claude) launchFlagArgs.push("--claude");
28736
- if (options.launchFlags.confirmReplace) launchFlagArgs.push("--replace");
28737
- if (options.launchFlags.force) launchFlagArgs.push("--force");
28738
- if (options.launchFlags.resume) launchFlagArgs.push("--resume");
28739
- }
29151
+ const launchFlagArgs = stage.kind === "launch" && options.launchFlags ? buildLaunchFlagArgs(options.launchFlags) : [];
28740
29152
  if (stage.command) {
28741
29153
  let shellCommand = substituteAgentId(stage.command, options.agentId);
28742
29154
  for (const extra of [...extraArgs, ...launchFlagArgs]) {
28743
- shellCommand = `${shellCommand} ${extra}`;
29155
+ shellCommand = `${shellCommand} ${quoteShellArg(extra)}`;
28744
29156
  }
28745
29157
  return { mode: "shell", shellCommand, args: [], cwd, env: mergedEnv };
28746
29158
  }
@@ -28833,15 +29245,7 @@ var localScriptExecutor = new LocalScriptExecutor();
28833
29245
 
28834
29246
  // src/core/control-sync.ts
28835
29247
  var path38 = __toESM(require("path"));
28836
-
28837
- // src/core/control-config.ts
28838
- var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
28839
- function getControlApiUrl() {
28840
- return process.env.HAR_CONTROL_API_URL ?? DEFAULT_CONTROL_API_URL;
28841
- }
28842
- function isControlEnabled() {
28843
- return process.env.HAR_CONTROL_DISABLED !== "true";
28844
- }
29248
+ init_control_config();
28845
29249
 
28846
29250
  // src/core/control-registry.ts
28847
29251
  var fs26 = __toESM(require("fs"));
@@ -29087,6 +29491,7 @@ var path30 = __toESM(require("path"));
29087
29491
  var import_child_process4 = require("child_process");
29088
29492
  var fs29 = __toESM(require("fs"));
29089
29493
  var path28 = __toESM(require("path"));
29494
+ init_control_config();
29090
29495
 
29091
29496
  // src/core/slot-ports.ts
29092
29497
  var import_child_process3 = require("child_process");
@@ -29793,7 +30198,6 @@ function collectSlotStatus(harnessRoot, agentId, runs, pm2Procs) {
29793
30198
  baseBranch: session?.baseBranch,
29794
30199
  baseCommit: session?.baseCommit,
29795
30200
  sessionCreatedAt: session?.createdAt,
29796
- purpose: session?.purpose,
29797
30201
  sessionStatus: session?.status,
29798
30202
  lastError: session?.lastError,
29799
30203
  resumeHint,
@@ -30032,105 +30436,19 @@ function createRemoteExecutor(apiUrl, apiKey) {
30032
30436
  return new RemoteExecutor(url, key);
30033
30437
  }
30034
30438
 
30035
- // src/core/telemetry-config.ts
30036
- var fs34 = __toESM(require("fs"));
30037
- var os9 = __toESM(require("os"));
30038
- var path34 = __toESM(require("path"));
30039
- var DEFAULT_SIGNALS_ON = {
30040
- metrics: true,
30041
- logs: true,
30042
- prompts: false,
30043
- traces: false
30044
- };
30045
- var DEFAULT_SIGNALS_OFF = {
30046
- metrics: false,
30047
- logs: false,
30048
- prompts: false,
30049
- traces: false
30050
- };
30051
- function getPreferencePath() {
30052
- if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
30053
- return path34.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
30054
- }
30055
- return path34.join(os9.homedir(), ".har", "telemetry.json");
30056
- }
30057
- function parseEnvOverride(raw) {
30058
- if (raw === void 0 || raw === "") return void 0;
30059
- const normalized = raw.trim().toLowerCase();
30060
- if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
30061
- if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
30062
- return void 0;
30063
- }
30064
- function normalizeSignals(enabled, raw) {
30065
- if (!enabled) return { ...DEFAULT_SIGNALS_OFF };
30066
- return {
30067
- metrics: raw?.metrics !== false,
30068
- logs: raw?.logs !== false,
30069
- prompts: raw?.prompts === true,
30070
- traces: raw?.traces === true
30071
- };
30072
- }
30073
- function readTelemetryPreference() {
30074
- const preferencePath = getPreferencePath();
30075
- try {
30076
- if (!fs34.existsSync(preferencePath)) {
30077
- return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
30078
- }
30079
- const parsed = JSON.parse(fs34.readFileSync(preferencePath, "utf8"));
30080
- const enabled = parsed.enabled !== false;
30081
- return {
30082
- enabled,
30083
- signals: normalizeSignals(enabled, parsed.signals),
30084
- updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
30085
- };
30086
- } catch {
30087
- return { enabled: true, signals: { ...DEFAULT_SIGNALS_ON } };
30088
- }
30089
- }
30090
- function writeTelemetryPreference(enabled, signals) {
30091
- const current = readTelemetryPreference();
30092
- const nextSignals = normalizeSignals(enabled, {
30093
- ...current.signals,
30094
- ...signals
30095
- });
30096
- const preference = {
30097
- enabled,
30098
- signals: enabled ? nextSignals : { ...DEFAULT_SIGNALS_OFF },
30099
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
30100
- };
30101
- const preferencePath = getPreferencePath();
30102
- fs34.mkdirSync(path34.dirname(preferencePath), { recursive: true });
30103
- fs34.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
30104
- return preference;
30105
- }
30106
- function isTelemetryEnabled() {
30107
- const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
30108
- if (envOverride !== void 0) return envOverride;
30109
- return readTelemetryPreference().enabled;
30110
- }
30111
- function getTelemetrySignals() {
30112
- if (!isTelemetryEnabled()) return { ...DEFAULT_SIGNALS_OFF };
30113
- return readTelemetryPreference().signals;
30114
- }
30115
- function getTelemetryPreferencePath() {
30116
- return getPreferencePath();
30117
- }
30118
- var TELEMETRY_SIGNALS = [
30119
- "Metrics (default): tokens and estimated USD cost via OTEL metrics",
30120
- "Logs/events (default): session events (tool calls, api_request) without prompt bodies",
30121
- "Prompts (opt-in): user/assistant text via OTEL_LOG_USER_PROMPTS \u2014 har telemetry on --prompts",
30122
- "Traces (opt-in): thin span ingest when CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1",
30123
- "Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
30124
- ];
30439
+ // src/core/control-sync.ts
30440
+ init_telemetry_config();
30125
30441
 
30126
30442
  // src/core/usage-harvest/claude.ts
30127
30443
  var fs36 = __toESM(require("fs"));
30128
30444
  var os10 = __toESM(require("os"));
30129
30445
  var path36 = __toESM(require("path"));
30446
+ init_telemetry_config();
30130
30447
 
30131
30448
  // src/core/telemetry-env.ts
30132
30449
  var fs35 = __toESM(require("fs"));
30133
30450
  var path35 = __toESM(require("path"));
30451
+ init_control_config();
30134
30452
  function escapeAttrValue(value) {
30135
30453
  return value.replace(/[,=]/g, "_");
30136
30454
  }
@@ -30143,7 +30461,6 @@ function buildOtelResourceAttributes(attrs) {
30143
30461
  ];
30144
30462
  if (attrs.branch) parts.push(`har.branch=${escapeAttrValue(attrs.branch)}`);
30145
30463
  if (attrs.suffix) parts.push(`har.suffix=${escapeAttrValue(attrs.suffix)}`);
30146
- if (attrs.purpose) parts.push(`har.purpose=${escapeAttrValue(attrs.purpose)}`);
30147
30464
  return parts.join(",");
30148
30465
  }
30149
30466
  function buildSessionKey(input) {
@@ -30151,49 +30468,22 @@ function buildSessionKey(input) {
30151
30468
  const stamp = (input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
30152
30469
  return `agent-${input.agentId}-${input.suffix ?? stamp}`;
30153
30470
  }
30154
- function buildTelemetryEnvBlock(attrs, options) {
30471
+ function buildTelemetryEnvBlock(attrs) {
30155
30472
  const apiUrl = getControlApiUrl().replace(/\/$/, "");
30156
30473
  const lines = [
30157
30474
  "",
30158
30475
  "# HAR session attribution (generated)",
30159
30476
  `HAR_SESSION_KEY=${attrs.sessionKey}`,
30160
30477
  `HAR_CONTROL_API_URL=${apiUrl}`,
30161
- `OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`
30478
+ `OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`,
30479
+ "# Agent telemetry: opentelemetry-hooks \u2192 Mission Control (har telemetry on|off)"
30162
30480
  ];
30163
- const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
30164
- if (injectOtel) {
30165
- const signals = getTelemetrySignals();
30166
- lines.push(
30167
- "# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
30168
- "CLAUDE_CODE_ENABLE_TELEMETRY=1",
30169
- "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
30170
- `OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
30171
- );
30172
- if (signals.metrics) {
30173
- lines.push("OTEL_METRICS_EXPORTER=otlp");
30174
- }
30175
- if (signals.logs) {
30176
- lines.push("OTEL_LOGS_EXPORTER=otlp");
30177
- }
30178
- if (signals.prompts) {
30179
- lines.push(
30180
- "OTEL_LOG_USER_PROMPTS=1",
30181
- "OTEL_LOG_ASSISTANT_RESPONSES=1"
30182
- );
30183
- }
30184
- if (signals.traces) {
30185
- lines.push(
30186
- "OTEL_TRACES_EXPORTER=otlp",
30187
- "CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1"
30188
- );
30189
- }
30190
- }
30191
30481
  return lines.join("\n") + "\n";
30192
30482
  }
30193
30483
  var TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
30194
30484
  var TELEMETRY_MARKER_END = "# end HAR telemetry";
30195
- function appendTelemetryEnvToFile(envFilePath, attrs, options) {
30196
- const block = buildTelemetryEnvBlock(attrs, options).trimEnd() + `
30485
+ function appendTelemetryEnvToFile(envFilePath, attrs) {
30486
+ const block = buildTelemetryEnvBlock(attrs).trimEnd() + `
30197
30487
  ${TELEMETRY_MARKER_END}
30198
30488
  `;
30199
30489
  let existing = "";
@@ -30213,22 +30503,6 @@ ${TELEMETRY_MARKER_END}
30213
30503
  const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
30214
30504
  fs35.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30215
30505
  }
30216
- function buildCodexOtelSnippet(attrs) {
30217
- const apiUrl = getControlApiUrl().replace(/\/$/, "");
30218
- const resource = buildOtelResourceAttributes(attrs);
30219
- return `# Merge into ~/.codex/config.toml (HAR does not overwrite your config)
30220
- # Generated for session ${attrs.sessionKey}
30221
-
30222
- [otel]
30223
- exporter = "otlp-http"
30224
- endpoint = "${apiUrl}/api/otel"
30225
- metrics_exporter = "otlp-http"
30226
-
30227
- # Optional: set resource attributes via environment when starting Codex:
30228
- # export OTEL_RESOURCE_ATTRIBUTES="${resource}"
30229
- # export HAR_SESSION_KEY="${attrs.sessionKey}"
30230
- `;
30231
- }
30232
30506
 
30233
30507
  // src/core/usage-harvest/claude.ts
30234
30508
  function pathsMatch(candidate, targets) {
@@ -30566,7 +30840,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
30566
30840
  const deadline = Date.now() + timeoutMs;
30567
30841
  while (Date.now() < deadline) {
30568
30842
  if (await isControlApiReachable(apiUrl)) return true;
30569
- await new Promise((resolve33) => setTimeout(resolve33, intervalMs));
30843
+ await new Promise((resolve34) => setTimeout(resolve34, intervalMs));
30570
30844
  }
30571
30845
  return false;
30572
30846
  }
@@ -30763,6 +31037,9 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
30763
31037
  return { allowed: true, slot: occupied.slot, readiness };
30764
31038
  }
30765
31039
 
31040
+ // src/core/telemetry-ensure.ts
31041
+ init_control_config();
31042
+
30766
31043
  // src/core/control-lifecycle.ts
30767
31044
  var import_child_process8 = require("child_process");
30768
31045
  var path39 = __toESM(require("path"));
@@ -30783,6 +31060,7 @@ function shouldBuildControlLocally() {
30783
31060
  }
30784
31061
 
30785
31062
  // src/core/control-lifecycle.ts
31063
+ init_control_config();
30786
31064
  var CONTROL_CONTAINER_NAME = "har-control";
30787
31065
  var CONTROL_DATA_VOLUME = "har_control_data";
30788
31066
  var CONTROL_CONTAINER_PORT = 3847;
@@ -30891,6 +31169,7 @@ async function startControlAndSync(options) {
30891
31169
  }
30892
31170
 
30893
31171
  // src/core/telemetry-ensure.ts
31172
+ init_telemetry_config();
30894
31173
  async function ensureTelemetryInfrastructure(options) {
30895
31174
  const apiUrl = getControlApiUrl();
30896
31175
  const telemetryEnabled = isTelemetryEnabled();
@@ -30989,7 +31268,8 @@ async function ensureTelemetryInfrastructure(options) {
30989
31268
  }
30990
31269
 
30991
31270
  // src/core/run-service.ts
30992
- var path40 = __toESM(require("path"));
31271
+ init_telemetry_config();
31272
+ var path41 = __toESM(require("path"));
30993
31273
  function extractShellOutput(result) {
30994
31274
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
30995
31275
  const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
@@ -31110,17 +31390,26 @@ var RunService = class {
31110
31390
  };
31111
31391
  }
31112
31392
  let telemetryBanner = "";
31113
- let otelReady = false;
31114
31393
  if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
31115
31394
  const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
31116
- otelReady = ensured.otelReady;
31117
31395
  if (ensured.message) {
31118
31396
  telemetryBanner += `${ensured.message}
31119
- Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemetry off
31397
+ Usage from Cursor / Claude / Codex (opentelemetry-hooks) will appear under Worktrees. Disable: har telemetry off
31120
31398
  `;
31121
31399
  }
31122
31400
  if (ensured.warning) {
31123
31401
  telemetryBanner += `${ensured.warning}
31402
+ `;
31403
+ }
31404
+ try {
31405
+ const { ensureOtelHooks: ensureOtelHooks2 } = await Promise.resolve().then(() => (init_otel_hooks(), otel_hooks_exports));
31406
+ const hooks = ensureOtelHooks2({ setupAgents: true });
31407
+ if (hooks.message) telemetryBanner += `${hooks.message}
31408
+ `;
31409
+ if (hooks.warning) telemetryBanner += `${hooks.warning}
31410
+ `;
31411
+ } catch (err) {
31412
+ telemetryBanner += `opentelemetry-hooks setup skipped: ${err instanceof Error ? err.message : String(err)}
31124
31413
  `;
31125
31414
  }
31126
31415
  }
@@ -31153,21 +31442,16 @@ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemet
31153
31442
  suffix: session.suffix,
31154
31443
  createdAt: session.createdAt
31155
31444
  });
31156
- const envFile = path40.join(session.workDir, `.env.agent.${options.agentId}`);
31445
+ const envFile = path41.join(session.workDir, `.env.agent.${options.agentId}`);
31157
31446
  try {
31158
- appendTelemetryEnvToFile(
31159
- envFile,
31160
- {
31161
- sessionKey,
31162
- agentId: options.agentId,
31163
- repoPath: path40.resolve(options.repoPath),
31164
- workDir: session.workDir,
31165
- branch: session.branch,
31166
- suffix: session.suffix,
31167
- purpose: session.purpose
31168
- },
31169
- { otelReady: otelReady && isTelemetryEnabled() }
31170
- );
31447
+ appendTelemetryEnvToFile(envFile, {
31448
+ sessionKey,
31449
+ agentId: options.agentId,
31450
+ repoPath: path41.resolve(options.repoPath),
31451
+ workDir: session.workDir,
31452
+ branch: session.branch,
31453
+ suffix: session.suffix
31454
+ });
31171
31455
  } catch {
31172
31456
  }
31173
31457
  }
@@ -31526,7 +31810,7 @@ var envCommand = {
31526
31810
  }
31527
31811
  };
31528
31812
  async function handleInit(argv) {
31529
- const repoPath = path41.resolve(argv.repo);
31813
+ const repoPath = path42.resolve(argv.repo);
31530
31814
  header("har env init");
31531
31815
  info(`Repository: ${repoPath}`);
31532
31816
  try {
@@ -31584,7 +31868,7 @@ async function handleInit(argv) {
31584
31868
  }
31585
31869
  }
31586
31870
  async function handleMaintain(argv) {
31587
- const repoPath = path41.resolve(argv.repo);
31871
+ const repoPath = path42.resolve(argv.repo);
31588
31872
  header("har env maintain");
31589
31873
  info(`Repository: ${repoPath}`);
31590
31874
  try {
@@ -31682,7 +31966,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31682
31966
  if (autoYes) {
31683
31967
  const proposal = readAgentMdProposal(repoPath);
31684
31968
  if (proposal) {
31685
- writeFileSafe(path41.join(repoPath, "AGENT.md"), proposal.content);
31969
+ writeFileSafe(path42.join(repoPath, "AGENT.md"), proposal.content);
31686
31970
  clearAgentMdProposal(repoPath);
31687
31971
  info("Applied AGENT.md proposal (--yes)");
31688
31972
  }
@@ -31691,7 +31975,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
31691
31975
  await promptApplyAgentMdProposal(repoPath);
31692
31976
  }
31693
31977
  async function handleAddStage(argv) {
31694
- const repoPath = path41.resolve(argv.repo);
31978
+ const repoPath = path42.resolve(argv.repo);
31695
31979
  const available = listStageTemplateIds();
31696
31980
  if (argv.list) {
31697
31981
  for (const id of available) {
@@ -31768,7 +32052,7 @@ async function handleAddStage(argv) {
31768
32052
  }
31769
32053
  }
31770
32054
  async function handlePreflight(argv) {
31771
- const repo = path41.resolve(argv.repo);
32055
+ const repo = path42.resolve(argv.repo);
31772
32056
  const agentId = validateAgentId(argv.id, repo);
31773
32057
  const result = await preflightEnvironment({
31774
32058
  repoPath: repo,
@@ -31786,7 +32070,7 @@ async function handlePreflight(argv) {
31786
32070
  process.exit(result.code);
31787
32071
  }
31788
32072
  async function handleLaunch(argv) {
31789
- const repo = path41.resolve(argv.repo);
32073
+ const repo = path42.resolve(argv.repo);
31790
32074
  const agentId = validateAgentId(argv.id, repo);
31791
32075
  let confirmReplace = argv.replace;
31792
32076
  let force = argv.force;
@@ -31798,8 +32082,8 @@ async function handleLaunch(argv) {
31798
32082
  console.error(guard.reason ?? "");
31799
32083
  const readline5 = await import("readline");
31800
32084
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
31801
- const answer = await new Promise((resolve33) => {
31802
- rl.question("Replace this slot? [y/N] ", resolve33);
32085
+ const answer = await new Promise((resolve34) => {
32086
+ rl.question("Replace this slot? [y/N] ", resolve34);
31803
32087
  });
31804
32088
  rl.close();
31805
32089
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -31815,8 +32099,8 @@ async function handleLaunch(argv) {
31815
32099
  warn("Worktree has uncommitted changes.");
31816
32100
  const readline5 = await import("readline");
31817
32101
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
31818
- const answer = await new Promise((resolve33) => {
31819
- rl.question("Discard uncommitted changes? [y/N] ", resolve33);
32102
+ const answer = await new Promise((resolve34) => {
32103
+ rl.question("Discard uncommitted changes? [y/N] ", resolve34);
31820
32104
  });
31821
32105
  rl.close();
31822
32106
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -31870,7 +32154,7 @@ async function handleRecover(argv) {
31870
32154
  });
31871
32155
  }
31872
32156
  async function handleVerify(argv) {
31873
- const repo = path41.resolve(argv.repo);
32157
+ const repo = path42.resolve(argv.repo);
31874
32158
  const agentId = validateAgentId(argv.id, repo);
31875
32159
  const result = await runVerification({
31876
32160
  repoPath: repo,
@@ -31882,7 +32166,7 @@ async function handleVerify(argv) {
31882
32166
  process.exit(result.code);
31883
32167
  }
31884
32168
  async function handleTeardown(argv) {
31885
- const repo = path41.resolve(argv.repo);
32169
+ const repo = path42.resolve(argv.repo);
31886
32170
  const agentId = validateAgentId(argv.id, repo);
31887
32171
  const result = await teardownEnvironment({
31888
32172
  repoPath: repo,
@@ -31893,7 +32177,7 @@ async function handleTeardown(argv) {
31893
32177
  process.exit(result.code);
31894
32178
  }
31895
32179
  async function handleComplete(argv) {
31896
- const repo = path41.resolve(argv.repo);
32180
+ const repo = path42.resolve(argv.repo);
31897
32181
  const agentId = validateAgentId(argv.id, repo);
31898
32182
  const result = await completeEnvironment({
31899
32183
  repoPath: repo,
@@ -31914,7 +32198,7 @@ async function handleComplete(argv) {
31914
32198
  process.exit(result.code);
31915
32199
  }
31916
32200
  async function handleStatus(argv) {
31917
- const repoPath = path41.resolve(argv.repo);
32201
+ const repoPath = path42.resolve(argv.repo);
31918
32202
  if (argv.json) {
31919
32203
  const status = collectEnvironmentStatus(repoPath);
31920
32204
  const output = EnvironmentStatusSchema.parse(status);
@@ -31927,7 +32211,7 @@ async function handleStatus(argv) {
31927
32211
  });
31928
32212
  }
31929
32213
  async function handleRunsList(argv) {
31930
- const repoPath = path41.resolve(argv.repo);
32214
+ const repoPath = path42.resolve(argv.repo);
31931
32215
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
31932
32216
  if (argv.json) {
31933
32217
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -31944,7 +32228,7 @@ async function handleRunsList(argv) {
31944
32228
  }
31945
32229
  }
31946
32230
  async function handleRunsGet(argv) {
31947
- const run2 = getRun(path41.resolve(argv.repo), argv.runId);
32231
+ const run2 = getRun(path42.resolve(argv.repo), argv.runId);
31948
32232
  if (!run2) {
31949
32233
  error(`Run not found: ${argv.runId}`);
31950
32234
  process.exit(1);
@@ -32023,7 +32307,8 @@ function printNextSteps(auto2) {
32023
32307
  }
32024
32308
 
32025
32309
  // src/cli/commands/control.ts
32026
- var path42 = __toESM(require("path"));
32310
+ var path43 = __toESM(require("path"));
32311
+ init_control_config();
32027
32312
  var controlCommand = {
32028
32313
  command: "control <subcommand>",
32029
32314
  describe: "Mission Control dashboard (local)",
@@ -32126,7 +32411,7 @@ async function handleDown() {
32126
32411
  process.exit(code);
32127
32412
  }
32128
32413
  async function handleRegister(argv) {
32129
- const repoPath = path42.resolve(argv.repo);
32414
+ const repoPath = path43.resolve(argv.repo);
32130
32415
  header("har control register");
32131
32416
  info(`Repository: ${repoPath}`);
32132
32417
  try {
@@ -32146,7 +32431,7 @@ async function handleRegister(argv) {
32146
32431
  }
32147
32432
  }
32148
32433
  async function handleSync(argv) {
32149
- const repoPath = path42.resolve(argv.repo);
32434
+ const repoPath = path43.resolve(argv.repo);
32150
32435
  try {
32151
32436
  await syncRepoWithControl({
32152
32437
  repoPath,
@@ -32185,7 +32470,7 @@ async function handleWatch(argv) {
32185
32470
  };
32186
32471
  const tick = async () => {
32187
32472
  if (argv.repo) {
32188
- await syncOne(path42.resolve(argv.repo));
32473
+ await syncOne(path43.resolve(argv.repo));
32189
32474
  return;
32190
32475
  }
32191
32476
  try {
@@ -32216,12 +32501,12 @@ async function handleLogin(argv) {
32216
32501
  }
32217
32502
 
32218
32503
  // src/cli/commands/hooks.ts
32219
- var path45 = __toESM(require("path"));
32504
+ var path46 = __toESM(require("path"));
32220
32505
 
32221
32506
  // src/core/hooks.ts
32222
- var fs38 = __toESM(require("fs"));
32223
- var os12 = __toESM(require("os"));
32224
- var path43 = __toESM(require("path"));
32507
+ var fs39 = __toESM(require("fs"));
32508
+ var os13 = __toESM(require("os"));
32509
+ var path44 = __toESM(require("path"));
32225
32510
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
32226
32511
  var MARKER_END = "# <<< har commit gate <<<";
32227
32512
  var PRE_COMMIT_BLOCK = [
@@ -32246,7 +32531,7 @@ function resolveCheckoutRoot(cwd) {
32246
32531
  function resolveHooksDir(checkoutDir) {
32247
32532
  const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
32248
32533
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
32249
- return path43.resolve(checkoutDir, hooksPath);
32534
+ return path44.resolve(checkoutDir, hooksPath);
32250
32535
  }
32251
32536
  function getConfiguredHooksPath(checkoutDir) {
32252
32537
  return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
@@ -32254,9 +32539,9 @@ function getConfiguredHooksPath(checkoutDir) {
32254
32539
  function defaultHarInvocation() {
32255
32540
  const entry = process.argv[1];
32256
32541
  if (entry && entry.endsWith(".js")) {
32257
- return `"${process.execPath}" "${path43.resolve(entry)}"`;
32542
+ return `"${process.execPath}" "${path44.resolve(entry)}"`;
32258
32543
  }
32259
- if (entry) return `"${path43.resolve(entry)}"`;
32544
+ if (entry) return `"${path44.resolve(entry)}"`;
32260
32545
  return "har";
32261
32546
  }
32262
32547
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -32277,40 +32562,40 @@ exit 0
32277
32562
  `;
32278
32563
  }
32279
32564
  function upsertMarkedBlock(filePath, block) {
32280
- if (!fs38.existsSync(filePath)) {
32281
- fs38.writeFileSync(filePath, `#!/bin/sh
32565
+ if (!fs39.existsSync(filePath)) {
32566
+ fs39.writeFileSync(filePath, `#!/bin/sh
32282
32567
  ${block}
32283
32568
  `, { mode: 493 });
32284
32569
  return "created";
32285
32570
  }
32286
- const content = fs38.readFileSync(filePath, "utf8");
32571
+ const content = fs39.readFileSync(filePath, "utf8");
32287
32572
  if (content.includes(MARKER_START)) {
32288
32573
  const pattern = new RegExp(
32289
32574
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
32290
32575
  );
32291
- fs38.writeFileSync(filePath, content.replace(pattern, block));
32292
- fs38.chmodSync(filePath, 493);
32576
+ fs39.writeFileSync(filePath, content.replace(pattern, block));
32577
+ fs39.chmodSync(filePath, 493);
32293
32578
  return "updated";
32294
32579
  }
32295
32580
  const suffix = content.endsWith("\n") ? "" : "\n";
32296
- fs38.writeFileSync(filePath, `${content}${suffix}
32581
+ fs39.writeFileSync(filePath, `${content}${suffix}
32297
32582
  ${block}
32298
32583
  `);
32299
- fs38.chmodSync(filePath, 493);
32584
+ fs39.chmodSync(filePath, 493);
32300
32585
  return "appended";
32301
32586
  }
32302
32587
  function removeMarkedBlock(filePath) {
32303
- if (!fs38.existsSync(filePath)) return false;
32304
- const content = fs38.readFileSync(filePath, "utf8");
32588
+ if (!fs39.existsSync(filePath)) return false;
32589
+ const content = fs39.readFileSync(filePath, "utf8");
32305
32590
  if (!content.includes(MARKER_START)) return false;
32306
32591
  const pattern = new RegExp(
32307
32592
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
32308
32593
  );
32309
32594
  const stripped = content.replace(pattern, "\n");
32310
32595
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
32311
- fs38.rmSync(filePath);
32596
+ fs39.rmSync(filePath);
32312
32597
  } else {
32313
- fs38.writeFileSync(filePath, stripped);
32598
+ fs39.writeFileSync(filePath, stripped);
32314
32599
  }
32315
32600
  return true;
32316
32601
  }
@@ -32332,20 +32617,20 @@ Or re-run with --force to write into that directory anyway.`
32332
32617
  );
32333
32618
  }
32334
32619
  const hooksDir = resolveHooksDir(checkout);
32335
- fs38.mkdirSync(hooksDir, { recursive: true });
32620
+ fs39.mkdirSync(hooksDir, { recursive: true });
32336
32621
  const invocation = options.harInvocation ?? defaultHarInvocation();
32337
- fs38.writeFileSync(
32338
- path43.join(hooksDir, "har-pre-commit"),
32622
+ fs39.writeFileSync(
32623
+ path44.join(hooksDir, "har-pre-commit"),
32339
32624
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
32340
32625
  { mode: 493 }
32341
32626
  );
32342
- fs38.writeFileSync(
32343
- path43.join(hooksDir, "har-post-commit"),
32627
+ fs39.writeFileSync(
32628
+ path44.join(hooksDir, "har-post-commit"),
32344
32629
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
32345
32630
  { mode: 493 }
32346
32631
  );
32347
- const preCommit = upsertMarkedBlock(path43.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32348
- const postCommit = upsertMarkedBlock(path43.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32632
+ const preCommit = upsertMarkedBlock(path44.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32633
+ const postCommit = upsertMarkedBlock(path44.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32349
32634
  ensureValidationsIgnored(checkout);
32350
32635
  return { hooksDir, preCommit, postCommit };
32351
32636
  }
@@ -32354,12 +32639,12 @@ function uninstallHooks(repoPath) {
32354
32639
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
32355
32640
  const hooksDir = resolveHooksDir(checkout);
32356
32641
  let removed = false;
32357
- removed = removeMarkedBlock(path43.join(hooksDir, "pre-commit")) || removed;
32358
- removed = removeMarkedBlock(path43.join(hooksDir, "post-commit")) || removed;
32642
+ removed = removeMarkedBlock(path44.join(hooksDir, "pre-commit")) || removed;
32643
+ removed = removeMarkedBlock(path44.join(hooksDir, "post-commit")) || removed;
32359
32644
  for (const file of ["har-pre-commit", "har-post-commit"]) {
32360
- const full = path43.join(hooksDir, file);
32361
- if (fs38.existsSync(full)) {
32362
- fs38.rmSync(full);
32645
+ const full = path44.join(hooksDir, file);
32646
+ if (fs39.existsSync(full)) {
32647
+ fs39.rmSync(full);
32363
32648
  removed = true;
32364
32649
  }
32365
32650
  }
@@ -32376,9 +32661,9 @@ function getCommitGateConfig(checkoutDir) {
32376
32661
  function isAgentWorktree(checkoutDir) {
32377
32662
  const branch = getCurrentBranch(checkoutDir);
32378
32663
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
32379
- const worktreesRoot = path43.join(os12.homedir(), "worktrees");
32380
- const resolved = path43.resolve(checkoutDir);
32381
- return resolved.startsWith(`${worktreesRoot}${path43.sep}`) && /-agent-\d+$/.test(path43.basename(resolved));
32664
+ const worktreesRoot = path44.join(os13.homedir(), "worktrees");
32665
+ const resolved = path44.resolve(checkoutDir);
32666
+ return resolved.startsWith(`${worktreesRoot}${path44.sep}`) && /-agent-\d+$/.test(path44.basename(resolved));
32382
32667
  }
32383
32668
  function resolveEffectiveMode(checkoutDir, gate) {
32384
32669
  if (!gate.enabled) return "off";
@@ -32391,15 +32676,15 @@ function getHooksStatus(repoPath) {
32391
32676
  const hooksDir = resolveHooksDir(checkout);
32392
32677
  const gate = getCommitGateConfig(checkout);
32393
32678
  const hasBlock = (name) => {
32394
- const file = path43.join(hooksDir, name);
32395
- return fs38.existsSync(file) && fs38.readFileSync(file, "utf8").includes(MARKER_START);
32679
+ const file = path44.join(hooksDir, name);
32680
+ return fs39.existsSync(file) && fs39.readFileSync(file, "utf8").includes(MARKER_START);
32396
32681
  };
32397
32682
  return {
32398
32683
  checkout,
32399
32684
  hooksDir,
32400
32685
  configuredHooksPath: getConfiguredHooksPath(checkout),
32401
- preCommitInstalled: hasBlock("pre-commit") && fs38.existsSync(path43.join(hooksDir, "har-pre-commit")),
32402
- postCommitInstalled: hasBlock("post-commit") && fs38.existsSync(path43.join(hooksDir, "har-post-commit")),
32686
+ preCommitInstalled: hasBlock("pre-commit") && fs39.existsSync(path44.join(hooksDir, "har-pre-commit")),
32687
+ postCommitInstalled: hasBlock("post-commit") && fs39.existsSync(path44.join(hooksDir, "har-post-commit")),
32403
32688
  gate,
32404
32689
  effectiveMode: resolveEffectiveMode(checkout, gate)
32405
32690
  };
@@ -32410,7 +32695,7 @@ function checkCommitGate(cwd) {
32410
32695
  }
32411
32696
  const checkout = resolveCheckoutRoot(cwd);
32412
32697
  if (!checkout) return { exitCode: 0, messages: [] };
32413
- if (!fs38.existsSync(path43.join(checkout, ".har", "stages.json"))) {
32698
+ if (!fs39.existsSync(path44.join(checkout, ".har", "stages.json"))) {
32414
32699
  return { exitCode: 0, messages: [] };
32415
32700
  }
32416
32701
  const gate = getCommitGateConfig(checkout);
@@ -32481,8 +32766,8 @@ async function recordCommitAssociation(cwd) {
32481
32766
  }
32482
32767
 
32483
32768
  // src/core/claude-hooks.ts
32484
- var fs39 = __toESM(require("fs"));
32485
- var path44 = __toESM(require("path"));
32769
+ var fs40 = __toESM(require("fs"));
32770
+ var path45 = __toESM(require("path"));
32486
32771
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
32487
32772
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
32488
32773
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -32493,8 +32778,8 @@ function isHarGuardEntry(entry) {
32493
32778
  );
32494
32779
  }
32495
32780
  function readSettings(settingsPath) {
32496
- if (!fs39.existsSync(settingsPath)) return {};
32497
- const raw = fs39.readFileSync(settingsPath, "utf8").trim();
32781
+ if (!fs40.existsSync(settingsPath)) return {};
32782
+ const raw = fs40.readFileSync(settingsPath, "utf8").trim();
32498
32783
  if (raw === "") return {};
32499
32784
  const parsed = JSON.parse(raw);
32500
32785
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -32503,19 +32788,19 @@ function readSettings(settingsPath) {
32503
32788
  return parsed;
32504
32789
  }
32505
32790
  function writeSettings(settingsPath, settings) {
32506
- fs39.mkdirSync(path44.dirname(settingsPath), { recursive: true });
32507
- fs39.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32791
+ fs40.mkdirSync(path45.dirname(settingsPath), { recursive: true });
32792
+ fs40.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32508
32793
  }
32509
32794
  function installClaudeGuard(repoPath) {
32510
32795
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
32511
32796
  if (!templatePath) {
32512
32797
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
32513
32798
  }
32514
- const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32515
- fs39.mkdirSync(path44.dirname(guardScript), { recursive: true });
32516
- fs39.copyFileSync(templatePath, guardScript);
32517
- fs39.chmodSync(guardScript, 493);
32518
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32799
+ const guardScript = path45.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32800
+ fs40.mkdirSync(path45.dirname(guardScript), { recursive: true });
32801
+ fs40.copyFileSync(templatePath, guardScript);
32802
+ fs40.chmodSync(guardScript, 493);
32803
+ const settingsPath = path45.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32519
32804
  const settings = readSettings(settingsPath);
32520
32805
  const hooks = settings.hooks ?? {};
32521
32806
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -32529,15 +32814,15 @@ function installClaudeGuard(repoPath) {
32529
32814
  }
32530
32815
  function uninstallClaudeGuard(repoPath) {
32531
32816
  let removed = false;
32532
- const guardScript = path44.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32533
- if (fs39.existsSync(guardScript)) {
32534
- fs39.rmSync(guardScript);
32817
+ const guardScript = path45.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32818
+ if (fs40.existsSync(guardScript)) {
32819
+ fs40.rmSync(guardScript);
32535
32820
  removed = true;
32536
- const parent = path44.dirname(guardScript);
32537
- if (fs39.existsSync(parent) && fs39.readdirSync(parent).length === 0) fs39.rmdirSync(parent);
32821
+ const parent = path45.dirname(guardScript);
32822
+ if (fs40.existsSync(parent) && fs40.readdirSync(parent).length === 0) fs40.rmdirSync(parent);
32538
32823
  }
32539
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32540
- if (fs39.existsSync(settingsPath)) {
32824
+ const settingsPath = path45.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32825
+ if (fs40.existsSync(settingsPath)) {
32541
32826
  const settings = readSettings(settingsPath);
32542
32827
  const preToolUse = settings.hooks?.PreToolUse;
32543
32828
  if (preToolUse) {
@@ -32562,8 +32847,8 @@ function uninstallClaudeGuard(repoPath) {
32562
32847
  return { removed };
32563
32848
  }
32564
32849
  function claudeGuardInstalled(repoPath) {
32565
- const settingsPath = path44.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32566
- if (!fs39.existsSync(settingsPath)) return false;
32850
+ const settingsPath = path45.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32851
+ if (!fs40.existsSync(settingsPath)) return false;
32567
32852
  try {
32568
32853
  const settings = readSettings(settingsPath);
32569
32854
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -32579,7 +32864,7 @@ function repoOption(y2) {
32579
32864
  function handleInstall2(argv) {
32580
32865
  try {
32581
32866
  if (argv.claude) {
32582
- const result2 = installClaudeGuard(path45.resolve(argv.repo));
32867
+ const result2 = installClaudeGuard(path46.resolve(argv.repo));
32583
32868
  success("Claude Code worktree guard installed.");
32584
32869
  info(`Guard script: ${result2.guardScript}`);
32585
32870
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -32587,7 +32872,7 @@ function handleInstall2(argv) {
32587
32872
  info("Commit both files so the guard travels with the repo.");
32588
32873
  return;
32589
32874
  }
32590
- const result = installHooks({ repoPath: path45.resolve(argv.repo), force: argv.force });
32875
+ const result = installHooks({ repoPath: path46.resolve(argv.repo), force: argv.force });
32591
32876
  success(`Commit gate installed in ${result.hooksDir}`);
32592
32877
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
32593
32878
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -32600,7 +32885,7 @@ function handleInstall2(argv) {
32600
32885
  function handleUninstall(argv) {
32601
32886
  try {
32602
32887
  if (argv.claude) {
32603
- const result2 = uninstallClaudeGuard(path45.resolve(argv.repo));
32888
+ const result2 = uninstallClaudeGuard(path46.resolve(argv.repo));
32604
32889
  if (result2.removed) {
32605
32890
  success("Claude Code worktree guard removed.");
32606
32891
  } else {
@@ -32608,7 +32893,7 @@ function handleUninstall(argv) {
32608
32893
  }
32609
32894
  return;
32610
32895
  }
32611
- const result = uninstallHooks(path45.resolve(argv.repo));
32896
+ const result = uninstallHooks(path46.resolve(argv.repo));
32612
32897
  if (result.removed) {
32613
32898
  success(`Commit gate removed from ${result.hooksDir}`);
32614
32899
  } else {
@@ -32621,7 +32906,7 @@ function handleUninstall(argv) {
32621
32906
  }
32622
32907
  function handleStatus2(argv) {
32623
32908
  try {
32624
- const status = getHooksStatus(path45.resolve(argv.repo));
32909
+ const status = getHooksStatus(path46.resolve(argv.repo));
32625
32910
  if (argv.json) {
32626
32911
  process.stdout.write(`${JSON.stringify(status, null, 2)}
32627
32912
  `);
@@ -32639,7 +32924,7 @@ function handleStatus2(argv) {
32639
32924
  );
32640
32925
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
32641
32926
  info(
32642
- `Claude guard: ${claudeGuardInstalled(path45.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32927
+ `Claude guard: ${claudeGuardInstalled(path46.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32643
32928
  );
32644
32929
  } catch (err) {
32645
32930
  error(err instanceof Error ? err.message : String(err));
@@ -32900,10 +33185,10 @@ function assignProp(target, prop, value) {
32900
33185
  configurable: true
32901
33186
  });
32902
33187
  }
32903
- function getElementAtPath(obj, path48) {
32904
- if (!path48)
33188
+ function getElementAtPath(obj, path49) {
33189
+ if (!path49)
32905
33190
  return obj;
32906
- return path48.reduce((acc, key) => acc?.[key], obj);
33191
+ return path49.reduce((acc, key) => acc?.[key], obj);
32907
33192
  }
32908
33193
  function promiseAllObject(promisesObj) {
32909
33194
  const keys = Object.keys(promisesObj);
@@ -33223,11 +33508,11 @@ function aborted(x2, startIndex = 0) {
33223
33508
  }
33224
33509
  return false;
33225
33510
  }
33226
- function prefixIssues(path48, issues) {
33511
+ function prefixIssues(path49, issues) {
33227
33512
  return issues.map((iss) => {
33228
33513
  var _a3;
33229
33514
  (_a3 = iss).path ?? (_a3.path = []);
33230
- iss.path.unshift(path48);
33515
+ iss.path.unshift(path49);
33231
33516
  return iss;
33232
33517
  });
33233
33518
  }
@@ -38550,7 +38835,7 @@ var Protocol = class {
38550
38835
  return;
38551
38836
  }
38552
38837
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
38553
- await new Promise((resolve33) => setTimeout(resolve33, pollInterval));
38838
+ await new Promise((resolve34) => setTimeout(resolve34, pollInterval));
38554
38839
  options?.signal?.throwIfAborted();
38555
38840
  }
38556
38841
  } catch (error3) {
@@ -38567,7 +38852,7 @@ var Protocol = class {
38567
38852
  */
38568
38853
  request(request, resultSchema, options) {
38569
38854
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
38570
- return new Promise((resolve33, reject) => {
38855
+ return new Promise((resolve34, reject) => {
38571
38856
  const earlyReject = (error3) => {
38572
38857
  reject(error3);
38573
38858
  };
@@ -38645,7 +38930,7 @@ var Protocol = class {
38645
38930
  if (!parseResult.success) {
38646
38931
  reject(parseResult.error);
38647
38932
  } else {
38648
- resolve33(parseResult.data);
38933
+ resolve34(parseResult.data);
38649
38934
  }
38650
38935
  } catch (error3) {
38651
38936
  reject(error3);
@@ -38906,12 +39191,12 @@ var Protocol = class {
38906
39191
  }
38907
39192
  } catch {
38908
39193
  }
38909
- return new Promise((resolve33, reject) => {
39194
+ return new Promise((resolve34, reject) => {
38910
39195
  if (signal.aborted) {
38911
39196
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
38912
39197
  return;
38913
39198
  }
38914
- const timeoutId = setTimeout(resolve33, interval);
39199
+ const timeoutId = setTimeout(resolve34, interval);
38915
39200
  signal.addEventListener("abort", () => {
38916
39201
  clearTimeout(timeoutId);
38917
39202
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -39701,7 +39986,7 @@ var Server = class extends Protocol {
39701
39986
  };
39702
39987
 
39703
39988
  // src/mcp/server.ts
39704
- var path46 = __toESM(require("path"));
39989
+ var path47 = __toESM(require("path"));
39705
39990
 
39706
39991
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
39707
39992
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -39784,12 +40069,12 @@ var StdioServerTransport = class {
39784
40069
  this.onclose?.();
39785
40070
  }
39786
40071
  send(message) {
39787
- return new Promise((resolve33) => {
40072
+ return new Promise((resolve34) => {
39788
40073
  const json = serializeMessage(message);
39789
40074
  if (this._stdout.write(json)) {
39790
- resolve33();
40075
+ resolve34();
39791
40076
  } else {
39792
- this._stdout.once("drain", resolve33);
40077
+ this._stdout.once("drain", resolve34);
39793
40078
  }
39794
40079
  });
39795
40080
  }
@@ -40333,7 +40618,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
40333
40618
  const input = ControlUpInputSchema.parse({ ...args, repo });
40334
40619
  const result = await startControlAndSync({
40335
40620
  detach: input.detach,
40336
- cwd: path46.resolve(input.repo)
40621
+ cwd: path47.resolve(input.repo)
40337
40622
  });
40338
40623
  if (result.code !== 0) {
40339
40624
  return {
@@ -40396,8 +40681,10 @@ var mcpCommand = {
40396
40681
  };
40397
40682
 
40398
40683
  // src/cli/commands/telemetry.ts
40399
- var fs40 = __toESM(require("fs"));
40400
- var path47 = __toESM(require("path"));
40684
+ var path48 = __toESM(require("path"));
40685
+ init_control_config();
40686
+ init_otel_hooks();
40687
+ init_telemetry_config();
40401
40688
  function printSignals() {
40402
40689
  info("Collected when enabled (local Mission Control only):");
40403
40690
  for (const signal of TELEMETRY_SIGNALS) {
@@ -40421,6 +40708,8 @@ async function handleStatus3(argv) {
40421
40708
  controlApiUrl: apiUrl,
40422
40709
  controlReachable: reachable,
40423
40710
  otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
40711
+ otelHooksHome: getOtelHooksHome(),
40712
+ otelHooksConfig: getOtelHooksConfigPath(),
40424
40713
  signalDescriptions: [...TELEMETRY_SIGNALS]
40425
40714
  };
40426
40715
  if (argv.json) {
@@ -40436,25 +40725,29 @@ async function handleStatus3(argv) {
40436
40725
  );
40437
40726
  info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
40438
40727
  info(`OTLP endpoint: ${payload.otelEndpoint}`);
40728
+ info(`Hooks home: ${payload.otelHooksHome}`);
40439
40729
  printSignals();
40440
40730
  }
40441
40731
  async function handleOn(argv) {
40442
40732
  header("har telemetry on");
40443
40733
  const preference = writeTelemetryPreference(true, {
40444
40734
  prompts: argv.prompts === true ? true : void 0,
40445
- traces: argv.traces === true ? true : void 0
40735
+ traces: true
40446
40736
  });
40447
- success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
40737
+ success("Telemetry enabled. Cursor / Claude / Codex export via opentelemetry-hooks \u2192 Mission Control.");
40448
40738
  info(
40449
40739
  `Signals: metrics=${preference.signals.metrics} logs=${preference.signals.logs} prompts=${preference.signals.prompts} traces=${preference.signals.traces}`
40450
40740
  );
40451
40741
  if (preference.signals.prompts) {
40452
- warn("Prompt/response text will leave the agent machine for local Mission Control storage.");
40742
+ warn("Prompt text will leave the agent machine for local Mission Control (also used as session purpose).");
40453
40743
  }
40454
40744
  printSignals();
40455
40745
  const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40456
40746
  if (result.message) success(result.message);
40457
40747
  if (result.warning) warn(result.warning);
40748
+ const hooks = ensureOtelHooks({ setupAgents: true });
40749
+ if (hooks.message) success(hooks.message);
40750
+ if (hooks.warning) warn(hooks.warning);
40458
40751
  if (result.otelReady) {
40459
40752
  info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
40460
40753
  info("Usage appears under Mission Control \u2192 Worktrees / Usage. Disable: har telemetry off");
@@ -40463,44 +40756,48 @@ async function handleOn(argv) {
40463
40756
  async function handleOff() {
40464
40757
  header("har telemetry off");
40465
40758
  writeTelemetryPreference(false);
40759
+ try {
40760
+ disableOtelHooksExport();
40761
+ } catch (err) {
40762
+ warn(`Could not refresh hooks config: ${err instanceof Error ? err.message : String(err)}`);
40763
+ }
40466
40764
  success("Telemetry disabled.");
40467
- info("Future launches will not inject OTEL exporters or auto-start Mission Control.");
40468
- info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
40765
+ info("Hooks stay registered but OTLP export is cleared in ~/.har/otel-hooks/otel_config.json.");
40766
+ info("Mission Control will not auto-start. Existing usage rows are kept.");
40469
40767
  }
40470
40768
  async function handleWriteEnv(argv) {
40471
- const repoPath = path47.resolve(argv.repo);
40769
+ const repoPath = path48.resolve(argv.repo);
40472
40770
  const harnessRoot = resolveHarnessRoot(repoPath);
40473
40771
  const session = readSlotRegistry(harnessRoot, argv.agentId);
40474
40772
  const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40475
40773
  const branch = argv.branch ?? session?.branch;
40476
40774
  const suffix = argv.suffix ?? session?.suffix;
40477
- const purpose = argv.purpose ?? session?.purpose;
40478
40775
  const sessionKey = argv.sessionKey ?? buildSessionKey({
40479
40776
  branch,
40480
40777
  agentId: argv.agentId,
40481
40778
  suffix,
40482
40779
  createdAt: session?.createdAt
40483
40780
  });
40484
- const envFile = argv.envFile ?? path47.join(workDir, `.env.agent.${argv.agentId}`);
40485
- let otelReady = argv.otelReady;
40486
- if (otelReady === void 0 && isTelemetryEnabled()) {
40487
- const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
40488
- otelReady = ensured.otelReady;
40489
- }
40781
+ const envFile = argv.envFile ?? path48.join(workDir, `.env.agent.${argv.agentId}`);
40490
40782
  const attrs = {
40491
40783
  sessionKey,
40492
40784
  agentId: argv.agentId,
40493
40785
  repoPath,
40494
40786
  workDir,
40495
40787
  branch,
40496
- suffix,
40497
- purpose
40788
+ suffix
40498
40789
  };
40499
- appendTelemetryEnvToFile(envFile, attrs, { otelReady: otelReady !== false && isTelemetryEnabled() });
40500
- success(`Wrote telemetry env to ${envFile}`);
40790
+ appendTelemetryEnvToFile(envFile, attrs);
40791
+ if (isTelemetryEnabled()) {
40792
+ ensureOtelHooks({
40793
+ setupAgents: false,
40794
+ resourceAttributes: buildOtelResourceAttributes(attrs)
40795
+ });
40796
+ }
40797
+ success(`Wrote session attribution to ${envFile}`);
40501
40798
  }
40502
40799
  function handlePrintEnv(argv) {
40503
- const repoPath = path47.resolve(argv.repo);
40800
+ const repoPath = path48.resolve(argv.repo);
40504
40801
  const workDir = argv.workDir ?? repoPath;
40505
40802
  const sessionKey = buildSessionKey({
40506
40803
  branch: argv.branch,
@@ -40508,55 +40805,38 @@ function handlePrintEnv(argv) {
40508
40805
  suffix: argv.suffix
40509
40806
  });
40510
40807
  process.stdout.write(
40511
- buildTelemetryEnvBlock(
40512
- {
40513
- sessionKey,
40514
- agentId: argv.agentId,
40515
- repoPath,
40516
- workDir,
40517
- branch: argv.branch,
40518
- suffix: argv.suffix
40519
- },
40520
- { otelReady: argv.otelReady && isTelemetryEnabled() }
40521
- )
40808
+ buildTelemetryEnvBlock({
40809
+ sessionKey,
40810
+ agentId: argv.agentId,
40811
+ repoPath,
40812
+ workDir,
40813
+ branch: argv.branch,
40814
+ suffix: argv.suffix
40815
+ })
40522
40816
  );
40523
40817
  }
40524
- function handleCodexSnippet(argv) {
40525
- const repoPath = path47.resolve(argv.repo);
40526
- const harnessRoot = resolveHarnessRoot(repoPath);
40527
- const session = readSlotRegistry(harnessRoot, argv.agentId);
40528
- const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40529
- const branch = argv.branch ?? session?.branch;
40530
- const suffix = argv.suffix ?? session?.suffix;
40531
- const sessionKey = buildSessionKey({
40532
- branch,
40533
- agentId: argv.agentId,
40534
- suffix,
40535
- createdAt: session?.createdAt
40536
- });
40537
- const snippet = buildCodexOtelSnippet({
40538
- sessionKey,
40539
- agentId: argv.agentId,
40540
- repoPath,
40541
- workDir,
40542
- branch,
40543
- suffix,
40544
- purpose: session?.purpose
40545
- });
40546
- if (argv.write) {
40547
- const outDir = path47.join(harnessRoot, ".har", "telemetry");
40548
- fs40.mkdirSync(outDir, { recursive: true });
40549
- const outPath = path47.join(outDir, "codex.otel.toml.snippet");
40550
- fs40.writeFileSync(outPath, snippet);
40551
- success(`Wrote ${outPath}`);
40552
- info("Merge the [otel] table into ~/.codex/config.toml manually.");
40553
- return;
40818
+ async function handleInstallHooks() {
40819
+ header("har telemetry install-hooks");
40820
+ if (!isTelemetryEnabled()) {
40821
+ warn("Telemetry is off \u2014 enabling preference so hooks can export.");
40822
+ writeTelemetryPreference(true, { traces: true });
40823
+ }
40824
+ const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40825
+ if (result.message) success(result.message);
40826
+ if (result.warning) warn(result.warning);
40827
+ const hooks = ensureOtelHooks({ setupAgents: true });
40828
+ if (hooks.ok) {
40829
+ success(hooks.message ?? "opentelemetry-hooks installed and agents registered");
40830
+ info(`Config: ${hooks.configPath}`);
40831
+ info(`Wrapper: ${hooks.wrapperPath}`);
40832
+ } else {
40833
+ error(hooks.warning ?? "Failed to install opentelemetry-hooks");
40834
+ process.exitCode = 1;
40554
40835
  }
40555
- process.stdout.write(snippet);
40556
40836
  }
40557
40837
  var telemetryCommand = {
40558
40838
  command: "telemetry <subcommand>",
40559
- describe: "Agent usage telemetry (Claude Code / Codex \u2192 Mission Control)",
40839
+ describe: "Agent usage telemetry (Cursor / Claude / Codex via opentelemetry-hooks \u2192 Mission Control)",
40560
40840
  builder: (yargs) => yargs.command(
40561
40841
  "status",
40562
40842
  "Show telemetry preference and Mission Control reachability",
@@ -40566,34 +40846,40 @@ var telemetryCommand = {
40566
40846
  }
40567
40847
  ).command(
40568
40848
  "on",
40569
- "Enable telemetry (default) and ensure Mission Control is running",
40849
+ "Enable telemetry, ensure Mission Control, install/configure opentelemetry-hooks",
40570
40850
  (y2) => y2.option("prompts", {
40571
40851
  type: "boolean",
40572
40852
  default: false,
40573
- describe: "Opt in to shipping user/assistant prompt text to Mission Control"
40574
- }).option("traces", {
40575
- type: "boolean",
40576
- default: false,
40577
- describe: "Opt in to thin OTEL traces ingest (Claude beta)"
40853
+ describe: "Opt in to shipping user prompt text (also fills Mission Control purpose)"
40578
40854
  }),
40579
40855
  (argv) => {
40580
40856
  void handleOn({
40581
- prompts: argv.prompts,
40582
- traces: argv.traces
40857
+ prompts: argv.prompts
40583
40858
  });
40584
40859
  }
40585
40860
  ).command(
40586
40861
  "off",
40587
- "Disable telemetry (no OTEL injection, no MC auto-start)",
40862
+ "Disable telemetry (clear hooks OTLP endpoint, no MC auto-start)",
40588
40863
  () => {
40589
40864
  },
40590
40865
  () => {
40591
40866
  void handleOff();
40592
40867
  }
40868
+ ).command(
40869
+ "install-hooks",
40870
+ "Install opentelemetry-hooks and register Cursor / Claude / Codex",
40871
+ () => {
40872
+ },
40873
+ () => {
40874
+ void handleInstallHooks().catch((err) => {
40875
+ error(err instanceof Error ? err.message : String(err));
40876
+ process.exitCode = 1;
40877
+ });
40878
+ }
40593
40879
  ).command(
40594
40880
  "write-env",
40595
- "Append session + OTEL env vars to .env.agent.<id>",
40596
- (y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("env-file", { type: "string" }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("purpose", { type: "string" }).option("session-key", { type: "string" }).option("otel-ready", { type: "boolean" }),
40881
+ "Append HAR session attribution to .env.agent.<id> and refresh hooks config",
40882
+ (y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("env-file", { type: "string" }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("session-key", { type: "string" }),
40597
40883
  (argv) => handleWriteEnv({
40598
40884
  agentId: argv["agent-id"],
40599
40885
  repo: argv.repo,
@@ -40601,42 +40887,23 @@ var telemetryCommand = {
40601
40887
  workDir: argv["work-dir"],
40602
40888
  branch: argv.branch,
40603
40889
  suffix: argv.suffix,
40604
- purpose: argv.purpose,
40605
- sessionKey: argv["session-key"],
40606
- otelReady: argv["otel-ready"]
40890
+ sessionKey: argv["session-key"]
40607
40891
  }).catch((err) => {
40608
40892
  error(err instanceof Error ? err.message : String(err));
40609
40893
  process.exitCode = 1;
40610
40894
  })
40611
40895
  ).command(
40612
40896
  "print-env",
40613
- "Print telemetry env block to stdout",
40614
- (y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("otel-ready", { type: "boolean", default: true }),
40897
+ "Print session attribution env block to stdout",
40898
+ (y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }),
40615
40899
  (argv) => handlePrintEnv({
40616
40900
  agentId: argv["agent-id"],
40617
40901
  repo: argv.repo,
40618
40902
  workDir: argv["work-dir"],
40619
40903
  branch: argv.branch,
40620
- suffix: argv.suffix,
40621
- otelReady: argv["otel-ready"]
40622
- })
40623
- ).command(
40624
- "codex-snippet",
40625
- "Print or write a Codex [otel] config snippet",
40626
- (y2) => y2.option("agent-id", { type: "number", demandOption: true }).option("repo", { type: "string", default: "." }).option("work-dir", { type: "string" }).option("branch", { type: "string" }).option("suffix", { type: "string" }).option("write", {
40627
- type: "boolean",
40628
- default: false,
40629
- describe: "Write to .har/telemetry/codex.otel.toml.snippet"
40630
- }),
40631
- (argv) => handleCodexSnippet({
40632
- agentId: argv["agent-id"],
40633
- repo: argv.repo,
40634
- workDir: argv["work-dir"],
40635
- branch: argv.branch,
40636
- suffix: argv.suffix,
40637
- write: argv.write
40904
+ suffix: argv.suffix
40638
40905
  })
40639
- ).demandCommand(1, "Please specify a subcommand: status, on, off, write-env, print-env, codex-snippet"),
40906
+ ).demandCommand(1, "Please specify a subcommand: status, on, off, install-hooks, write-env, print-env"),
40640
40907
  handler: () => {
40641
40908
  }
40642
40909
  };