@osfactory/har 0.11.0 → 0.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 path41 = url.path;
769
- if (path41.length === 0) {
768
+ const path47 = url.path;
769
+ if (path47.length === 0) {
770
770
  return;
771
771
  }
772
- if (url.scheme === "file" && path41.length === 1 && isNormalizedWindowsDriveLetter(path41[0])) {
772
+ if (url.scheme === "file" && path47.length === 1 && isNormalizedWindowsDriveLetter(path47[0])) {
773
773
  return;
774
774
  }
775
- path41.pop();
775
+ path47.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(resolve28, reject) {
2095
+ return new Body.Promise(function(resolve33, 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
- resolve28(Buffer.concat(accum, accumBytes));
2129
+ resolve33(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(resolve28, reject) {
2804
+ return new fetch3.Promise(function(resolve33, 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
- resolve28(fetch3(new Request3(locationURL, requestOpts)));
2937
+ resolve33(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
- resolve28(response);
2958
+ resolve33(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
- resolve28(response);
2968
+ resolve33(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
- resolve28(response);
2980
+ resolve33(response);
2981
2981
  });
2982
2982
  raw.on("end", function() {
2983
2983
  if (!response) {
2984
2984
  response = new Response3(body, response_options);
2985
- resolve28(response);
2985
+ resolve33(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
- resolve28(response);
2993
+ resolve33(response);
2994
2994
  return;
2995
2995
  }
2996
2996
  response = new Response3(body, response_options);
2997
- resolve28(response);
2997
+ resolve33(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(path41, { mtimeMs, size }, filenameOrOptions, options = {}) {
6354
+ function createFileFromPath(path47, { 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: path41, size, lastModified: mtimeMs });
6361
+ const file = new FileFromPath({ path: path47, size, lastModified: mtimeMs });
6362
6362
  if (!filename) {
6363
6363
  filename = file.name;
6364
6364
  }
@@ -6367,13 +6367,13 @@ function createFileFromPath(path41, { mtimeMs, size }, filenameOrOptions, option
6367
6367
  lastModified: file.lastModified
6368
6368
  });
6369
6369
  }
6370
- function fileFromPathSync(path41, filenameOrOptions, options = {}) {
6371
- const stats = (0, import_fs5.statSync)(path41);
6372
- return createFileFromPath(path41, stats, filenameOrOptions, options);
6370
+ function fileFromPathSync(path47, filenameOrOptions, options = {}) {
6371
+ const stats = (0, import_fs5.statSync)(path47);
6372
+ return createFileFromPath(path47, stats, filenameOrOptions, options);
6373
6373
  }
6374
- async function fileFromPath2(path41, filenameOrOptions, options) {
6375
- const stats = await import_fs5.promises.stat(path41);
6376
- return createFileFromPath(path41, stats, filenameOrOptions, options);
6374
+ async function fileFromPath2(path47, filenameOrOptions, options) {
6375
+ const stats = await import_fs5.promises.stat(path47);
6376
+ return createFileFromPath(path47, 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({
@@ -9384,7 +9384,7 @@ var require_compile = __commonJS({
9384
9384
  const schOrFunc = root.refs[ref];
9385
9385
  if (schOrFunc)
9386
9386
  return schOrFunc;
9387
- let _sch = resolve28.call(this, root, ref);
9387
+ let _sch = resolve33.call(this, root, ref);
9388
9388
  if (_sch === void 0) {
9389
9389
  const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
9390
9390
  const { schemaId } = this.opts;
@@ -9411,7 +9411,7 @@ var require_compile = __commonJS({
9411
9411
  function sameSchemaEnv(s1, s2) {
9412
9412
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
9413
9413
  }
9414
- function resolve28(root, ref) {
9414
+ function resolve33(root, ref) {
9415
9415
  let sch;
9416
9416
  while (typeof (sch = this.refs[ref]) == "string")
9417
9417
  ref = sch;
@@ -9629,8 +9629,8 @@ var require_utils2 = __commonJS({
9629
9629
  }
9630
9630
  return ind;
9631
9631
  }
9632
- function removeDotSegments(path41) {
9633
- let input = path41;
9632
+ function removeDotSegments(path47) {
9633
+ let input = path47;
9634
9634
  const output = [];
9635
9635
  let nextSlash = -1;
9636
9636
  let len = 0;
@@ -9882,8 +9882,8 @@ var require_schemes = __commonJS({
9882
9882
  wsComponent.secure = void 0;
9883
9883
  }
9884
9884
  if (wsComponent.resourceName) {
9885
- const [path41, query] = wsComponent.resourceName.split("?");
9886
- wsComponent.path = path41 && path41 !== "/" ? path41 : void 0;
9885
+ const [path47, query] = wsComponent.resourceName.split("?");
9886
+ wsComponent.path = path47 && path47 !== "/" ? path47 : void 0;
9887
9887
  wsComponent.query = query;
9888
9888
  wsComponent.resourceName = void 0;
9889
9889
  }
@@ -10042,7 +10042,7 @@ var require_fast_uri = __commonJS({
10042
10042
  }
10043
10043
  return uri;
10044
10044
  }
10045
- function resolve28(baseURI, relativeURI, options) {
10045
+ function resolve33(baseURI, relativeURI, options) {
10046
10046
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
10047
10047
  const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
10048
10048
  schemelessOptions.skipEscape = true;
@@ -10300,7 +10300,7 @@ var require_fast_uri = __commonJS({
10300
10300
  var fastUri = {
10301
10301
  SCHEMES,
10302
10302
  normalize: normalize3,
10303
- resolve: resolve28,
10303
+ resolve: resolve33,
10304
10304
  resolveComponent,
10305
10305
  equal,
10306
10306
  serialize,
@@ -13276,12 +13276,12 @@ var require_dist = __commonJS({
13276
13276
  throw new Error(`Unknown format "${name}"`);
13277
13277
  return f2;
13278
13278
  };
13279
- function addFormats(ajv, list, fs35, exportName) {
13279
+ function addFormats(ajv, list, fs40, exportName) {
13280
13280
  var _a3;
13281
13281
  var _b2;
13282
13282
  (_a3 = (_b2 = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b2.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
13283
13283
  for (const f2 of list)
13284
- ajv.addFormat(f2, fs35[f2]);
13284
+ ajv.addFormat(f2, fs40[f2]);
13285
13285
  }
13286
13286
  module2.exports = exports2 = formatsPlugin;
13287
13287
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -14565,11 +14565,11 @@ var parser = new YargsParser({
14565
14565
  resolve: import_path2.resolve,
14566
14566
  // TODO: figure out a way to combine ESM and CJS coverage, such that
14567
14567
  // we can exercise all the lines below:
14568
- require: (path41) => {
14568
+ require: (path47) => {
14569
14569
  if (typeof require !== "undefined") {
14570
- return require(path41);
14571
- } else if (path41.match(/\.json$/)) {
14572
- return JSON.parse((0, import_fs2.readFileSync)(path41, "utf8"));
14570
+ return require(path47);
14571
+ } else if (path47.match(/\.json$/)) {
14572
+ return JSON.parse((0, import_fs2.readFileSync)(path47, "utf8"));
14573
14573
  } else {
14574
14574
  throw Error("only .json config files are supported in ESM");
14575
14575
  }
@@ -15912,13 +15912,13 @@ function usage(yargs, shim3) {
15912
15912
  };
15913
15913
  self.stringifiedValues = function stringifiedValues(values, separator) {
15914
15914
  let string3 = "";
15915
- const sep3 = separator || ", ";
15915
+ const sep5 = separator || ", ";
15916
15916
  const array2 = [].concat(values);
15917
15917
  if (!values || !array2.length)
15918
15918
  return string3;
15919
15919
  array2.forEach((value) => {
15920
15920
  if (string3.length)
15921
- string3 += sep3;
15921
+ string3 += sep5;
15922
15922
  string3 += JSON.stringify(value);
15923
15923
  });
15924
15924
  return string3;
@@ -17095,12 +17095,12 @@ var YargsInstance = class {
17095
17095
  async getCompletion(args, done) {
17096
17096
  argsert("<array> [function]", [args, done], arguments.length);
17097
17097
  if (!done) {
17098
- return new Promise((resolve28, reject) => {
17098
+ return new Promise((resolve33, reject) => {
17099
17099
  __classPrivateFieldGet(this, _YargsInstance_completion, "f").getCompletion(args, (err, completions) => {
17100
17100
  if (err)
17101
17101
  reject(err);
17102
17102
  else
17103
- resolve28(completions);
17103
+ resolve33(completions);
17104
17104
  });
17105
17105
  });
17106
17106
  } else {
@@ -19250,8 +19250,8 @@ function getErrorMap() {
19250
19250
 
19251
19251
  // node_modules/zod/v3/helpers/parseUtil.js
19252
19252
  var makeIssue = (params) => {
19253
- const { data, path: path41, errorMaps, issueData } = params;
19254
- const fullPath = [...path41, ...issueData.path || []];
19253
+ const { data, path: path47, errorMaps, issueData } = params;
19254
+ const fullPath = [...path47, ...issueData.path || []];
19255
19255
  const fullIssue = {
19256
19256
  ...issueData,
19257
19257
  path: fullPath
@@ -19367,11 +19367,11 @@ var errorUtil;
19367
19367
 
19368
19368
  // node_modules/zod/v3/types.js
19369
19369
  var ParseInputLazyPath = class {
19370
- constructor(parent, value, path41, key) {
19370
+ constructor(parent, value, path47, key) {
19371
19371
  this._cachedPath = [];
19372
19372
  this.parent = parent;
19373
19373
  this.data = value;
19374
- this._path = path41;
19374
+ this._path = path47;
19375
19375
  this._key = key;
19376
19376
  }
19377
19377
  get path() {
@@ -23106,6 +23106,29 @@ var ValidationRecordSchema = external_exports.object({
23106
23106
  var SyncValidationsInputSchema = external_exports.object({
23107
23107
  validations: external_exports.array(ValidationRecordSchema)
23108
23108
  });
23109
+ var AgentToolSchema = external_exports.enum(["claude_code", "codex"]);
23110
+ var UsageSourceSchema = external_exports.enum(["otel", "harvest"]);
23111
+ var AgentSessionUsageSchema = external_exports.object({
23112
+ sessionKey: external_exports.string().min(1),
23113
+ agentId: external_exports.number().int().min(HAR_AGENT_SLOT_MIN),
23114
+ agentTool: AgentToolSchema,
23115
+ workDir: external_exports.string().optional(),
23116
+ branch: external_exports.string().optional(),
23117
+ suffix: external_exports.string().optional(),
23118
+ tokensInput: external_exports.number().nonnegative().default(0),
23119
+ tokensOutput: external_exports.number().nonnegative().default(0),
23120
+ tokensCacheRead: external_exports.number().nonnegative().default(0),
23121
+ tokensCacheCreation: external_exports.number().nonnegative().default(0),
23122
+ tokensTotal: external_exports.number().nonnegative().default(0),
23123
+ costUsd: external_exports.number().nonnegative().nullable().optional(),
23124
+ modelBreakdown: external_exports.record(external_exports.unknown()).optional(),
23125
+ sources: external_exports.array(UsageSourceSchema).default([]),
23126
+ firstSeenAt: external_exports.string(),
23127
+ lastSeenAt: external_exports.string()
23128
+ });
23129
+ var SyncUsageInputSchema = external_exports.object({
23130
+ usage: external_exports.array(AgentSessionUsageSchema)
23131
+ });
23109
23132
 
23110
23133
  // src/harness/manifest.ts
23111
23134
  var GENERATOR_VERSION = "0.4.0";
@@ -23371,16 +23394,16 @@ async function handleAgentSkills(options) {
23371
23394
  }
23372
23395
  async function askYesNo(question) {
23373
23396
  const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
23374
- return new Promise((resolve28) => {
23397
+ return new Promise((resolve33) => {
23375
23398
  process.stderr.write(`${question} `);
23376
23399
  rl.once("line", (answer) => {
23377
23400
  rl.close();
23378
23401
  const trimmed = answer.trim();
23379
23402
  if (trimmed === "") {
23380
- resolve28(true);
23403
+ resolve33(true);
23381
23404
  return;
23382
23405
  }
23383
- resolve28(/^y(es)?$/i.test(trimmed));
23406
+ resolve33(/^y(es)?$/i.test(trimmed));
23384
23407
  });
23385
23408
  });
23386
23409
  }
@@ -23459,7 +23482,7 @@ function handleRemove(argv) {
23459
23482
  }
23460
23483
 
23461
23484
  // src/cli/commands/env.ts
23462
- var path34 = __toESM(require("path"));
23485
+ var path40 = __toESM(require("path"));
23463
23486
 
23464
23487
  // src/core/harness.ts
23465
23488
  var fs19 = __toESM(require("fs"));
@@ -23965,13 +23988,13 @@ var MultipartBody = class {
23965
23988
  // node_modules/@anthropic-ai/sdk/_shims/node-runtime.mjs
23966
23989
  var import_web = require("node:stream/web");
23967
23990
  var fileFromPathWarned = false;
23968
- async function fileFromPath3(path41, ...args) {
23991
+ async function fileFromPath3(path47, ...args) {
23969
23992
  const { fileFromPath: _fileFromPath } = await Promise.resolve().then(() => (init_fileFromPath(), fileFromPath_exports));
23970
23993
  if (!fileFromPathWarned) {
23971
- console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path41)}) instead`);
23994
+ console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path47)}) instead`);
23972
23995
  fileFromPathWarned = true;
23973
23996
  }
23974
- return await _fileFromPath(path41, ...args);
23997
+ return await _fileFromPath(path47, ...args);
23975
23998
  }
23976
23999
  var defaultHttpAgent = new import_agentkeepalive.default({ keepAlive: true, timeout: 5 * 60 * 1e3 });
23977
24000
  var defaultHttpsAgent = new import_agentkeepalive.default.HttpsAgent({ keepAlive: true, timeout: 5 * 60 * 1e3 });
@@ -24467,8 +24490,8 @@ async function defaultParseResponse(props) {
24467
24490
  }
24468
24491
  var APIPromise = class _APIPromise extends Promise {
24469
24492
  constructor(responsePromise, parseResponse = defaultParseResponse) {
24470
- super((resolve28) => {
24471
- resolve28(null);
24493
+ super((resolve33) => {
24494
+ resolve33(null);
24472
24495
  });
24473
24496
  this.responsePromise = responsePromise;
24474
24497
  this.parseResponse = parseResponse;
@@ -24568,29 +24591,29 @@ var APIClient = class {
24568
24591
  defaultIdempotencyKey() {
24569
24592
  return `stainless-node-retry-${uuid4()}`;
24570
24593
  }
24571
- get(path41, opts) {
24572
- return this.methodRequest("get", path41, opts);
24594
+ get(path47, opts) {
24595
+ return this.methodRequest("get", path47, opts);
24573
24596
  }
24574
- post(path41, opts) {
24575
- return this.methodRequest("post", path41, opts);
24597
+ post(path47, opts) {
24598
+ return this.methodRequest("post", path47, opts);
24576
24599
  }
24577
- patch(path41, opts) {
24578
- return this.methodRequest("patch", path41, opts);
24600
+ patch(path47, opts) {
24601
+ return this.methodRequest("patch", path47, opts);
24579
24602
  }
24580
- put(path41, opts) {
24581
- return this.methodRequest("put", path41, opts);
24603
+ put(path47, opts) {
24604
+ return this.methodRequest("put", path47, opts);
24582
24605
  }
24583
- delete(path41, opts) {
24584
- return this.methodRequest("delete", path41, opts);
24606
+ delete(path47, opts) {
24607
+ return this.methodRequest("delete", path47, opts);
24585
24608
  }
24586
- methodRequest(method, path41, opts) {
24609
+ methodRequest(method, path47, opts) {
24587
24610
  return this.request(Promise.resolve(opts).then(async (opts2) => {
24588
24611
  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;
24589
- return { method, path: path41, ...opts2, body };
24612
+ return { method, path: path47, ...opts2, body };
24590
24613
  }));
24591
24614
  }
24592
- getAPIList(path41, Page, opts) {
24593
- return this.requestAPIList(Page, { method: "get", path: path41, ...opts });
24615
+ getAPIList(path47, Page, opts) {
24616
+ return this.requestAPIList(Page, { method: "get", path: path47, ...opts });
24594
24617
  }
24595
24618
  calculateContentLength(body) {
24596
24619
  if (typeof body === "string") {
@@ -24608,10 +24631,10 @@ var APIClient = class {
24608
24631
  return null;
24609
24632
  }
24610
24633
  buildRequest(options) {
24611
- const { method, path: path41, query, headers = {} } = options;
24634
+ const { method, path: path47, query, headers = {} } = options;
24612
24635
  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;
24613
24636
  const contentLength = this.calculateContentLength(body);
24614
- const url = this.buildURL(path41, query);
24637
+ const url = this.buildURL(path47, query);
24615
24638
  if ("timeout" in options)
24616
24639
  validatePositiveInteger("timeout", options.timeout);
24617
24640
  const timeout = options.timeout ?? this.timeout;
@@ -24720,8 +24743,8 @@ var APIClient = class {
24720
24743
  const request = this.makeRequest(options, null);
24721
24744
  return new PagePromise(this, request, Page);
24722
24745
  }
24723
- buildURL(path41, query) {
24724
- const url = isAbsoluteURL(path41) ? new URL(path41) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path41.startsWith("/") ? path41.slice(1) : path41));
24746
+ buildURL(path47, query) {
24747
+ const url = isAbsoluteURL(path47) ? new URL(path47) : new URL(this.baseURL + (this.baseURL.endsWith("/") && path47.startsWith("/") ? path47.slice(1) : path47));
24725
24748
  const defaultQuery = this.defaultQuery();
24726
24749
  if (!isEmptyObj(defaultQuery)) {
24727
24750
  query = { ...defaultQuery, ...query };
@@ -25006,7 +25029,7 @@ var startsWithSchemeRegexp = new RegExp("^(?:[a-z]+:)?//", "i");
25006
25029
  var isAbsoluteURL = (url) => {
25007
25030
  return startsWithSchemeRegexp.test(url);
25008
25031
  };
25009
- var sleep = (ms) => new Promise((resolve28) => setTimeout(resolve28, ms));
25032
+ var sleep = (ms) => new Promise((resolve33) => setTimeout(resolve33, ms));
25010
25033
  var validatePositiveInteger = (name, n2) => {
25011
25034
  if (typeof n2 !== "number" || !Number.isInteger(n2)) {
25012
25035
  throw new AnthropicError(`${name} must be an integer`);
@@ -25497,12 +25520,12 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25497
25520
  }
25498
25521
  return this._emit("error", new AnthropicError(String(error3)));
25499
25522
  });
25500
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve28, reject) => {
25501
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve28, "f");
25523
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_connectedPromise, new Promise((resolve33, reject) => {
25524
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise, resolve33, "f");
25502
25525
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectConnectedPromise, reject, "f");
25503
25526
  }), "f");
25504
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve28, reject) => {
25505
- __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve28, "f");
25527
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_endPromise, new Promise((resolve33, reject) => {
25528
+ __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise, resolve33, "f");
25506
25529
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_rejectEndPromise, reject, "f");
25507
25530
  }), "f");
25508
25531
  __classPrivateFieldGet8(this, _PromptCachingBetaMessageStream_connectedPromise, "f").catch(() => {
@@ -25631,11 +25654,11 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25631
25654
  * const message = await stream.emitted('message') // rejects if the stream errors
25632
25655
  */
25633
25656
  emitted(event) {
25634
- return new Promise((resolve28, reject) => {
25657
+ return new Promise((resolve33, reject) => {
25635
25658
  __classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_catchingPromiseCreated, true, "f");
25636
25659
  if (event !== "error")
25637
25660
  this.once("error", reject);
25638
- this.once(event, resolve28);
25661
+ this.once(event, resolve33);
25639
25662
  });
25640
25663
  }
25641
25664
  async done() {
@@ -25862,7 +25885,7 @@ var PromptCachingBetaMessageStream = class _PromptCachingBetaMessageStream {
25862
25885
  if (done) {
25863
25886
  return { value: void 0, done: true };
25864
25887
  }
25865
- return new Promise((resolve28, reject) => readQueue.push({ resolve: resolve28, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
25888
+ return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
25866
25889
  }
25867
25890
  const chunk = pushQueue.shift();
25868
25891
  return { value: chunk, done: false };
@@ -26010,12 +26033,12 @@ var MessageStream = class _MessageStream {
26010
26033
  }
26011
26034
  return this._emit("error", new AnthropicError(String(error3)));
26012
26035
  });
26013
- __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve28, reject) => {
26014
- __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve28, "f");
26036
+ __classPrivateFieldSet8(this, _MessageStream_connectedPromise, new Promise((resolve33, reject) => {
26037
+ __classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise, resolve33, "f");
26015
26038
  __classPrivateFieldSet8(this, _MessageStream_rejectConnectedPromise, reject, "f");
26016
26039
  }), "f");
26017
- __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve28, reject) => {
26018
- __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve28, "f");
26040
+ __classPrivateFieldSet8(this, _MessageStream_endPromise, new Promise((resolve33, reject) => {
26041
+ __classPrivateFieldSet8(this, _MessageStream_resolveEndPromise, resolve33, "f");
26019
26042
  __classPrivateFieldSet8(this, _MessageStream_rejectEndPromise, reject, "f");
26020
26043
  }), "f");
26021
26044
  __classPrivateFieldGet9(this, _MessageStream_connectedPromise, "f").catch(() => {
@@ -26144,11 +26167,11 @@ var MessageStream = class _MessageStream {
26144
26167
  * const message = await stream.emitted('message') // rejects if the stream errors
26145
26168
  */
26146
26169
  emitted(event) {
26147
- return new Promise((resolve28, reject) => {
26170
+ return new Promise((resolve33, reject) => {
26148
26171
  __classPrivateFieldSet8(this, _MessageStream_catchingPromiseCreated, true, "f");
26149
26172
  if (event !== "error")
26150
26173
  this.once("error", reject);
26151
- this.once(event, resolve28);
26174
+ this.once(event, resolve33);
26152
26175
  });
26153
26176
  }
26154
26177
  async done() {
@@ -26375,7 +26398,7 @@ var MessageStream = class _MessageStream {
26375
26398
  if (done) {
26376
26399
  return { value: void 0, done: true };
26377
26400
  }
26378
- return new Promise((resolve28, reject) => readQueue.push({ resolve: resolve28, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26401
+ return new Promise((resolve33, reject) => readQueue.push({ resolve: resolve33, reject })).then((chunk2) => chunk2 ? { value: chunk2, done: false } : { value: void 0, done: true });
26379
26402
  }
26380
26403
  const chunk = pushQueue.shift();
26381
26404
  return { value: chunk, done: false };
@@ -26635,11 +26658,11 @@ async function promptApplyAgentMdProposal(repoPath) {
26635
26658
  }
26636
26659
  async function askYesNo2(question) {
26637
26660
  const rl = readline2.createInterface({ input: process.stdin, output: process.stderr });
26638
- return new Promise((resolve28) => {
26661
+ return new Promise((resolve33) => {
26639
26662
  process.stderr.write(`${question} `);
26640
26663
  rl.once("line", (answer) => {
26641
26664
  rl.close();
26642
- resolve28(/^y(es)?$/i.test(answer.trim()));
26665
+ resolve33(/^y(es)?$/i.test(answer.trim()));
26643
26666
  });
26644
26667
  });
26645
26668
  }
@@ -26869,7 +26892,7 @@ async function executeAuthoringToolCall(toolName, input, repoPath, harnessDir, c
26869
26892
  }
26870
26893
  async function askUser(question, options) {
26871
26894
  const rl = readline3.createInterface({ input: process.stdin, output: process.stderr });
26872
- return new Promise((resolve28) => {
26895
+ return new Promise((resolve33) => {
26873
26896
  process.stderr.write("\n");
26874
26897
  process.stderr.write(`\u2753 ${question}
26875
26898
  `);
@@ -26880,7 +26903,7 @@ async function askUser(question, options) {
26880
26903
  process.stderr.write("> ");
26881
26904
  rl.once("line", (answer) => {
26882
26905
  rl.close();
26883
- resolve28(answer.trim());
26906
+ resolve33(answer.trim());
26884
26907
  });
26885
26908
  });
26886
26909
  }
@@ -27024,7 +27047,7 @@ function runScript(scriptPath, args = [], options = {}) {
27024
27047
  function runScriptCapture(scriptPath, args = [], options = {}) {
27025
27048
  const stream = options.stream ?? false;
27026
27049
  const { stream: _stream, ...spawnOptions } = options;
27027
- return new Promise((resolve28) => {
27050
+ return new Promise((resolve33) => {
27028
27051
  const proc = (0, import_child_process.spawn)("bash", [scriptPath, ...args], {
27029
27052
  ...spawnOptions,
27030
27053
  stdio: ["inherit", "pipe", "pipe"]
@@ -27039,13 +27062,13 @@ function runScriptCapture(scriptPath, args = [], options = {}) {
27039
27062
  stderr += d2;
27040
27063
  if (stream) process.stderr.write(d2);
27041
27064
  });
27042
- proc.on("close", (code) => resolve28({ stdout, stderr, code: code ?? 0 }));
27065
+ proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27043
27066
  });
27044
27067
  }
27045
27068
  function runShellCommand(command2, options = {}) {
27046
27069
  const stream = options.stream ?? false;
27047
27070
  const { stream: _stream, ...spawnOptions } = options;
27048
- return new Promise((resolve28) => {
27071
+ return new Promise((resolve33) => {
27049
27072
  const proc = (0, import_child_process.spawn)("bash", ["-lc", command2], {
27050
27073
  ...spawnOptions,
27051
27074
  stdio: ["inherit", "pipe", "pipe"]
@@ -27060,7 +27083,7 @@ function runShellCommand(command2, options = {}) {
27060
27083
  stderr += d2;
27061
27084
  if (stream) process.stderr.write(d2);
27062
27085
  });
27063
- proc.on("close", (code) => resolve28({ stdout, stderr, code: code ?? 0 }));
27086
+ proc.on("close", (code) => resolve33({ stdout, stderr, code: code ?? 0 }));
27064
27087
  });
27065
27088
  }
27066
27089
 
@@ -28392,16 +28415,16 @@ async function promptScaffoldCursorRule(exists) {
28392
28415
  }
28393
28416
  async function askYesNo3(question) {
28394
28417
  const rl = readline4.createInterface({ input: process.stdin, output: process.stderr });
28395
- return new Promise((resolve28) => {
28418
+ return new Promise((resolve33) => {
28396
28419
  process.stderr.write(`${question} `);
28397
28420
  rl.once("line", (answer) => {
28398
28421
  rl.close();
28399
28422
  const trimmed = answer.trim();
28400
28423
  if (trimmed === "") {
28401
- resolve28(true);
28424
+ resolve33(true);
28402
28425
  return;
28403
28426
  }
28404
- resolve28(/^y(es)?$/i.test(trimmed));
28427
+ resolve33(/^y(es)?$/i.test(trimmed));
28405
28428
  });
28406
28429
  });
28407
28430
  }
@@ -28688,7 +28711,7 @@ var LocalScriptExecutor = class {
28688
28711
  var localScriptExecutor = new LocalScriptExecutor();
28689
28712
 
28690
28713
  // src/core/control-sync.ts
28691
- var path33 = __toESM(require("path"));
28714
+ var path37 = __toESM(require("path"));
28692
28715
 
28693
28716
  // src/core/control-config.ts
28694
28717
  var DEFAULT_CONTROL_API_URL = "http://localhost:3847";
@@ -29888,6 +29911,396 @@ function createRemoteExecutor(apiUrl, apiKey) {
29888
29911
  return new RemoteExecutor(url, key);
29889
29912
  }
29890
29913
 
29914
+ // src/core/telemetry-config.ts
29915
+ var fs33 = __toESM(require("fs"));
29916
+ var os9 = __toESM(require("os"));
29917
+ var path33 = __toESM(require("path"));
29918
+ function getPreferencePath() {
29919
+ if (process.env.HAR_TELEMETRY_CONFIG_PATH) {
29920
+ return path33.resolve(process.env.HAR_TELEMETRY_CONFIG_PATH);
29921
+ }
29922
+ return path33.join(os9.homedir(), ".har", "telemetry.json");
29923
+ }
29924
+ function parseEnvOverride(raw) {
29925
+ if (raw === void 0 || raw === "") return void 0;
29926
+ const normalized = raw.trim().toLowerCase();
29927
+ if (["0", "false", "off", "no", "disabled"].includes(normalized)) return false;
29928
+ if (["1", "true", "on", "yes", "enabled"].includes(normalized)) return true;
29929
+ return void 0;
29930
+ }
29931
+ function readTelemetryPreference() {
29932
+ const preferencePath = getPreferencePath();
29933
+ try {
29934
+ if (!fs33.existsSync(preferencePath)) {
29935
+ return { enabled: true };
29936
+ }
29937
+ const parsed = JSON.parse(fs33.readFileSync(preferencePath, "utf8"));
29938
+ return {
29939
+ enabled: parsed.enabled !== false,
29940
+ updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : void 0
29941
+ };
29942
+ } catch {
29943
+ return { enabled: true };
29944
+ }
29945
+ }
29946
+ function writeTelemetryPreference(enabled) {
29947
+ const preference = {
29948
+ enabled,
29949
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
29950
+ };
29951
+ const preferencePath = getPreferencePath();
29952
+ fs33.mkdirSync(path33.dirname(preferencePath), { recursive: true });
29953
+ fs33.writeFileSync(preferencePath, JSON.stringify(preference, null, 2) + "\n");
29954
+ return preference;
29955
+ }
29956
+ function isTelemetryEnabled() {
29957
+ const envOverride = parseEnvOverride(process.env.HAR_TELEMETRY);
29958
+ if (envOverride !== void 0) return envOverride;
29959
+ return readTelemetryPreference().enabled;
29960
+ }
29961
+ function getTelemetryPreferencePath() {
29962
+ return getPreferencePath();
29963
+ }
29964
+ var TELEMETRY_SIGNALS = [
29965
+ "Claude Code: tokens (input/output/cache) and estimated USD cost via OTEL metrics",
29966
+ "Codex CLI: token usage via OTEL metrics (no native USD; harvest fills gaps)",
29967
+ "Fallback: har control sync harvests local Claude/Codex session files when OTEL is missing"
29968
+ ];
29969
+
29970
+ // src/core/usage-harvest/claude.ts
29971
+ var fs35 = __toESM(require("fs"));
29972
+ var os10 = __toESM(require("os"));
29973
+ var path35 = __toESM(require("path"));
29974
+
29975
+ // src/core/telemetry-env.ts
29976
+ var fs34 = __toESM(require("fs"));
29977
+ var path34 = __toESM(require("path"));
29978
+ function escapeAttrValue(value) {
29979
+ return value.replace(/[,=]/g, "_");
29980
+ }
29981
+ function buildOtelResourceAttributes(attrs) {
29982
+ const parts = [
29983
+ `har.session_key=${escapeAttrValue(attrs.sessionKey)}`,
29984
+ `har.agent_id=${attrs.agentId}`,
29985
+ `har.repo_path=${escapeAttrValue(attrs.repoPath)}`,
29986
+ `har.work_dir=${escapeAttrValue(attrs.workDir)}`
29987
+ ];
29988
+ if (attrs.branch) parts.push(`har.branch=${escapeAttrValue(attrs.branch)}`);
29989
+ if (attrs.suffix) parts.push(`har.suffix=${escapeAttrValue(attrs.suffix)}`);
29990
+ if (attrs.purpose) parts.push(`har.purpose=${escapeAttrValue(attrs.purpose)}`);
29991
+ return parts.join(",");
29992
+ }
29993
+ function buildSessionKey(input) {
29994
+ if (input.branch) return input.branch;
29995
+ const stamp = (input.createdAt ?? (/* @__PURE__ */ new Date()).toISOString()).replace(/[:.]/g, "-");
29996
+ return `agent-${input.agentId}-${input.suffix ?? stamp}`;
29997
+ }
29998
+ function buildTelemetryEnvBlock(attrs, options) {
29999
+ const apiUrl = getControlApiUrl().replace(/\/$/, "");
30000
+ const lines = [
30001
+ "",
30002
+ "# HAR session attribution (generated)",
30003
+ `HAR_SESSION_KEY=${attrs.sessionKey}`,
30004
+ `HAR_CONTROL_API_URL=${apiUrl}`,
30005
+ `OTEL_RESOURCE_ATTRIBUTES=${buildOtelResourceAttributes(attrs)}`
30006
+ ];
30007
+ const injectOtel = isTelemetryEnabled() && options?.otelReady !== false;
30008
+ if (injectOtel) {
30009
+ lines.push(
30010
+ "# HAR telemetry \u2192 Mission Control OTLP (disable: har telemetry off)",
30011
+ "CLAUDE_CODE_ENABLE_TELEMETRY=1",
30012
+ "OTEL_METRICS_EXPORTER=otlp",
30013
+ "OTEL_EXPORTER_OTLP_PROTOCOL=http/json",
30014
+ `OTEL_EXPORTER_OTLP_ENDPOINT=${apiUrl}/api/otel`
30015
+ );
30016
+ }
30017
+ return lines.join("\n") + "\n";
30018
+ }
30019
+ var TELEMETRY_MARKER_START = "# HAR session attribution (generated)";
30020
+ var TELEMETRY_MARKER_END = "# end HAR telemetry";
30021
+ function appendTelemetryEnvToFile(envFilePath, attrs, options) {
30022
+ const block = buildTelemetryEnvBlock(attrs, options).trimEnd() + `
30023
+ ${TELEMETRY_MARKER_END}
30024
+ `;
30025
+ let existing = "";
30026
+ if (fs34.existsSync(envFilePath)) {
30027
+ existing = fs34.readFileSync(envFilePath, "utf8");
30028
+ const start = existing.indexOf(TELEMETRY_MARKER_START);
30029
+ if (start >= 0) {
30030
+ const end = existing.indexOf(TELEMETRY_MARKER_END, start);
30031
+ if (end >= 0) {
30032
+ existing = existing.slice(0, start).replace(/\n+$/, "\n") + existing.slice(end + TELEMETRY_MARKER_END.length).replace(/^\n+/, "\n");
30033
+ } else {
30034
+ existing = existing.slice(0, start).replace(/\n+$/, "\n");
30035
+ }
30036
+ }
30037
+ }
30038
+ fs34.mkdirSync(path34.dirname(envFilePath), { recursive: true });
30039
+ const combined = (existing.replace(/\n+$/, "\n") + block).replace(/^\n+/, "");
30040
+ fs34.writeFileSync(envFilePath, combined.endsWith("\n") ? combined : combined + "\n");
30041
+ }
30042
+ function buildCodexOtelSnippet(attrs) {
30043
+ const apiUrl = getControlApiUrl().replace(/\/$/, "");
30044
+ const resource = buildOtelResourceAttributes(attrs);
30045
+ return `# Merge into ~/.codex/config.toml (HAR does not overwrite your config)
30046
+ # Generated for session ${attrs.sessionKey}
30047
+
30048
+ [otel]
30049
+ exporter = "otlp-http"
30050
+ endpoint = "${apiUrl}/api/otel"
30051
+ metrics_exporter = "otlp-http"
30052
+
30053
+ # Optional: set resource attributes via environment when starting Codex:
30054
+ # export OTEL_RESOURCE_ATTRIBUTES="${resource}"
30055
+ # export HAR_SESSION_KEY="${attrs.sessionKey}"
30056
+ `;
30057
+ }
30058
+
30059
+ // src/core/usage-harvest/claude.ts
30060
+ function pathsMatch(candidate, targets) {
30061
+ const norm = path35.resolve(candidate);
30062
+ return targets.some((t2) => {
30063
+ const target = path35.resolve(t2);
30064
+ return norm === target || norm.startsWith(target + path35.sep) || target.startsWith(norm + path35.sep);
30065
+ });
30066
+ }
30067
+ function claudeProjectsRoot() {
30068
+ return process.env.HAR_CLAUDE_PROJECTS_DIR ? path35.resolve(process.env.HAR_CLAUDE_PROJECTS_DIR) : path35.join(os10.homedir(), ".claude", "projects");
30069
+ }
30070
+ function encodeClaudeProjectDir(cwd) {
30071
+ return cwd.replace(/[^a-zA-Z0-9]/g, "-");
30072
+ }
30073
+ function readJsonlRecords(filePath) {
30074
+ if (!fs35.existsSync(filePath)) return [];
30075
+ const lines = fs35.readFileSync(filePath, "utf8").split(/\r?\n/);
30076
+ const out = [];
30077
+ for (const line of lines) {
30078
+ const trimmed = line.trim();
30079
+ if (!trimmed.startsWith("{")) continue;
30080
+ try {
30081
+ out.push(JSON.parse(trimmed));
30082
+ } catch {
30083
+ }
30084
+ }
30085
+ return out;
30086
+ }
30087
+ function extractClaudeUsageFromRecords(records) {
30088
+ let tokensInput = 0;
30089
+ let tokensOutput = 0;
30090
+ let tokensCacheRead = 0;
30091
+ let tokensCacheCreation = 0;
30092
+ let costUsd = null;
30093
+ for (const record2 of records) {
30094
+ if (!record2 || typeof record2 !== "object") continue;
30095
+ const payload = record2;
30096
+ if (payload.type === "result") {
30097
+ const usage2 = payload.usage ?? {};
30098
+ tokensInput = Number(usage2.input_tokens ?? 0) + Number(usage2.cache_read_input_tokens ?? 0);
30099
+ tokensOutput = Number(usage2.output_tokens ?? 0);
30100
+ tokensCacheRead = Number(usage2.cache_read_input_tokens ?? 0);
30101
+ tokensCacheCreation = Number(usage2.cache_creation_input_tokens ?? 0);
30102
+ if (payload.total_cost_usd != null) costUsd = Number(payload.total_cost_usd);
30103
+ }
30104
+ const nestedUsage = payload.usage ?? payload.message?.usage;
30105
+ if (nestedUsage && payload.type !== "result") {
30106
+ tokensInput += Number(nestedUsage.input_tokens ?? 0);
30107
+ tokensOutput += Number(nestedUsage.output_tokens ?? 0);
30108
+ tokensCacheRead += Number(nestedUsage.cache_read_input_tokens ?? 0);
30109
+ tokensCacheCreation += Number(nestedUsage.cache_creation_input_tokens ?? 0);
30110
+ }
30111
+ }
30112
+ return { tokensInput, tokensOutput, tokensCacheRead, tokensCacheCreation, costUsd };
30113
+ }
30114
+ function harvestClaudeUsage(slot) {
30115
+ const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
30116
+ if (targets.length === 0) return null;
30117
+ const root = claudeProjectsRoot();
30118
+ if (!fs35.existsSync(root)) return null;
30119
+ const sessionKey = buildSessionKey({
30120
+ branch: slot.branch,
30121
+ agentId: slot.agentId,
30122
+ suffix: slot.suffix,
30123
+ createdAt: slot.sessionCreatedAt
30124
+ });
30125
+ let best = null;
30126
+ let bestMtime = 0;
30127
+ for (const entry of fs35.readdirSync(root, { withFileTypes: true })) {
30128
+ if (!entry.isDirectory()) continue;
30129
+ const projectDir = path35.join(root, entry.name);
30130
+ const encodedHit = targets.some((t2) => entry.name.includes(encodeClaudeProjectDir(t2).slice(0, 40)));
30131
+ for (const file of fs35.readdirSync(projectDir)) {
30132
+ if (!file.endsWith(".jsonl")) continue;
30133
+ const filePath = path35.join(projectDir, file);
30134
+ const stat = fs35.statSync(filePath);
30135
+ const records = readJsonlRecords(filePath);
30136
+ let cwdHit = encodedHit;
30137
+ for (const record2 of records) {
30138
+ if (!record2 || typeof record2 !== "object") continue;
30139
+ const cwd = String(record2.cwd ?? "");
30140
+ if (cwd && pathsMatch(cwd, targets)) {
30141
+ cwdHit = true;
30142
+ break;
30143
+ }
30144
+ }
30145
+ if (!cwdHit) continue;
30146
+ const usage2 = extractClaudeUsageFromRecords(records);
30147
+ if (usage2.tokensInput + usage2.tokensOutput + usage2.tokensCacheRead === 0 && (usage2.costUsd == null || usage2.costUsd === 0)) {
30148
+ continue;
30149
+ }
30150
+ if (stat.mtimeMs >= bestMtime) {
30151
+ bestMtime = stat.mtimeMs;
30152
+ best = usage2;
30153
+ }
30154
+ }
30155
+ }
30156
+ if (!best) return null;
30157
+ const now = (/* @__PURE__ */ new Date()).toISOString();
30158
+ const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead + best.tokensCacheCreation;
30159
+ return {
30160
+ sessionKey,
30161
+ agentId: slot.agentId,
30162
+ agentTool: "claude_code",
30163
+ workDir: slot.workDir,
30164
+ branch: slot.branch,
30165
+ suffix: slot.suffix,
30166
+ tokensInput: best.tokensInput,
30167
+ tokensOutput: best.tokensOutput,
30168
+ tokensCacheRead: best.tokensCacheRead,
30169
+ tokensCacheCreation: best.tokensCacheCreation,
30170
+ tokensTotal,
30171
+ costUsd: best.costUsd,
30172
+ sources: ["harvest"],
30173
+ firstSeenAt: slot.sessionCreatedAt ?? now,
30174
+ lastSeenAt: now
30175
+ };
30176
+ }
30177
+
30178
+ // src/core/usage-harvest/codex.ts
30179
+ var fs36 = __toESM(require("fs"));
30180
+ var os11 = __toESM(require("os"));
30181
+ var path36 = __toESM(require("path"));
30182
+ function pathsMatch2(candidate, targets) {
30183
+ const norm = path36.resolve(candidate);
30184
+ return targets.some((t2) => {
30185
+ const target = path36.resolve(t2);
30186
+ return norm === target || norm.startsWith(target + path36.sep) || target.startsWith(norm + path36.sep);
30187
+ });
30188
+ }
30189
+ function codexSessionsRoot() {
30190
+ return process.env.HAR_CODEX_SESSIONS_DIR ? path36.resolve(process.env.HAR_CODEX_SESSIONS_DIR) : path36.join(os11.homedir(), ".codex", "sessions");
30191
+ }
30192
+ function walkJsonlFiles(dir, out = []) {
30193
+ if (!fs36.existsSync(dir)) return out;
30194
+ for (const entry of fs36.readdirSync(dir, { withFileTypes: true })) {
30195
+ const full = path36.join(dir, entry.name);
30196
+ if (entry.isDirectory()) walkJsonlFiles(full, out);
30197
+ else if (entry.isFile() && entry.name.endsWith(".jsonl")) out.push(full);
30198
+ }
30199
+ return out;
30200
+ }
30201
+ function readJsonl(filePath) {
30202
+ const lines = fs36.readFileSync(filePath, "utf8").split(/\r?\n/);
30203
+ const out = [];
30204
+ for (const line of lines) {
30205
+ const trimmed = line.trim();
30206
+ if (!trimmed.startsWith("{")) continue;
30207
+ try {
30208
+ out.push(JSON.parse(trimmed));
30209
+ } catch {
30210
+ }
30211
+ }
30212
+ return out;
30213
+ }
30214
+ function extractCodexTokens(records) {
30215
+ let tokensInput = 0;
30216
+ let tokensOutput = 0;
30217
+ let tokensCacheRead = 0;
30218
+ let cwd;
30219
+ for (const record2 of records) {
30220
+ if (!record2 || typeof record2 !== "object") continue;
30221
+ const payload = record2;
30222
+ if (typeof payload.cwd === "string") cwd = payload.cwd;
30223
+ if (typeof payload.working_directory === "string") {
30224
+ cwd = payload.working_directory;
30225
+ }
30226
+ const usage2 = payload.usage ?? payload.token_usage ?? payload.event?.usage;
30227
+ if (usage2) {
30228
+ tokensInput += Number(
30229
+ usage2.input_tokens ?? usage2.input_token_count ?? usage2.input ?? 0
30230
+ );
30231
+ tokensOutput += Number(
30232
+ usage2.output_tokens ?? usage2.output_token_count ?? usage2.output ?? 0
30233
+ );
30234
+ tokensCacheRead += Number(
30235
+ usage2.cached_input_tokens ?? usage2.cached_token_count ?? usage2.cache_read_input_tokens ?? 0
30236
+ );
30237
+ tokensOutput += Number(usage2.reasoning_tokens ?? usage2.reasoning_token_count ?? 0);
30238
+ }
30239
+ if (payload.type === "response.completed" || payload["event.kind"] === "response.completed") {
30240
+ tokensInput = Math.max(tokensInput, Number(payload.input_token_count ?? 0));
30241
+ tokensOutput = Math.max(tokensOutput, Number(payload.output_token_count ?? 0));
30242
+ tokensCacheRead = Math.max(tokensCacheRead, Number(payload.cached_token_count ?? 0));
30243
+ }
30244
+ }
30245
+ return { tokensInput, tokensOutput, tokensCacheRead, cwd };
30246
+ }
30247
+ function harvestCodexUsage(slot) {
30248
+ const targets = [slot.workDir, slot.worktreePath].filter(Boolean);
30249
+ if (targets.length === 0) return null;
30250
+ const root = codexSessionsRoot();
30251
+ const files = walkJsonlFiles(root);
30252
+ if (files.length === 0) return null;
30253
+ const sessionKey = buildSessionKey({
30254
+ branch: slot.branch,
30255
+ agentId: slot.agentId,
30256
+ suffix: slot.suffix,
30257
+ createdAt: slot.sessionCreatedAt
30258
+ });
30259
+ let best = null;
30260
+ let bestMtime = 0;
30261
+ for (const file of files) {
30262
+ const records = readJsonl(file);
30263
+ const extracted = extractCodexTokens(records);
30264
+ if (!extracted.cwd || !pathsMatch2(extracted.cwd, targets)) continue;
30265
+ if (extracted.tokensInput + extracted.tokensOutput + extracted.tokensCacheRead === 0) continue;
30266
+ const mtime = fs36.statSync(file).mtimeMs;
30267
+ if (mtime >= bestMtime) {
30268
+ bestMtime = mtime;
30269
+ best = extracted;
30270
+ }
30271
+ }
30272
+ if (!best) return null;
30273
+ const now = (/* @__PURE__ */ new Date()).toISOString();
30274
+ const tokensTotal = best.tokensInput + best.tokensOutput + best.tokensCacheRead;
30275
+ return {
30276
+ sessionKey,
30277
+ agentId: slot.agentId,
30278
+ agentTool: "codex",
30279
+ workDir: slot.workDir,
30280
+ branch: slot.branch,
30281
+ suffix: slot.suffix,
30282
+ tokensInput: best.tokensInput,
30283
+ tokensOutput: best.tokensOutput,
30284
+ tokensCacheRead: best.tokensCacheRead,
30285
+ tokensCacheCreation: 0,
30286
+ tokensTotal,
30287
+ costUsd: null,
30288
+ sources: ["harvest"],
30289
+ firstSeenAt: slot.sessionCreatedAt ?? now,
30290
+ lastSeenAt: now
30291
+ };
30292
+ }
30293
+
30294
+ // src/core/usage-harvest/index.ts
30295
+ function harvestUsageForSlot(slot) {
30296
+ const out = [];
30297
+ const claude = harvestClaudeUsage(slot);
30298
+ if (claude) out.push(claude);
30299
+ const codex = harvestCodexUsage(slot);
30300
+ if (codex) out.push(codex);
30301
+ return out;
30302
+ }
30303
+
29891
30304
  // src/core/control-sync.ts
29892
30305
  async function postJson(url, body, dryRun) {
29893
30306
  if (dryRun) return null;
@@ -29917,7 +30330,7 @@ async function waitForControlApi(apiUrl = getControlApiUrl(), timeoutMs = 6e4, i
29917
30330
  const deadline = Date.now() + timeoutMs;
29918
30331
  while (Date.now() < deadline) {
29919
30332
  if (await isControlApiReachable(apiUrl)) return true;
29920
- await new Promise((resolve28) => setTimeout(resolve28, intervalMs));
30333
+ await new Promise((resolve33) => setTimeout(resolve33, intervalMs));
29921
30334
  }
29922
30335
  return false;
29923
30336
  }
@@ -29929,7 +30342,7 @@ async function syncAllKnownReposWithControl(options) {
29929
30342
  }
29930
30343
  const repoPaths = new Set(listRegisteredRepos());
29931
30344
  if (options?.cwd) {
29932
- const cwd = path33.resolve(options.cwd);
30345
+ const cwd = path37.resolve(options.cwd);
29933
30346
  if (readManifest(cwd)) repoPaths.add(cwd);
29934
30347
  }
29935
30348
  try {
@@ -29937,7 +30350,7 @@ async function syncAllKnownReposWithControl(options) {
29937
30350
  if (listResponse.ok) {
29938
30351
  const repos = await listResponse.json();
29939
30352
  for (const repo of repos) {
29940
- const resolved = path33.resolve(repo.path);
30353
+ const resolved = path37.resolve(repo.path);
29941
30354
  if (readManifest(resolved)) repoPaths.add(resolved);
29942
30355
  }
29943
30356
  }
@@ -29956,7 +30369,7 @@ async function syncAllKnownReposWithControl(options) {
29956
30369
  return { synced, failed };
29957
30370
  }
29958
30371
  async function registerRepoWithControl(options) {
29959
- const repoPath = path33.resolve(options.repoPath);
30372
+ const repoPath = path37.resolve(options.repoPath);
29960
30373
  const apiUrl = options.apiUrl ?? getControlApiUrl();
29961
30374
  const manifest = readManifest(repoPath);
29962
30375
  const stagesRegistry = readStageRegistry(repoPath);
@@ -29969,7 +30382,7 @@ async function registerRepoWithControl(options) {
29969
30382
  return postJson(`${apiUrl}/api/repos`, body, options.dryRun);
29970
30383
  }
29971
30384
  async function syncRepoWithControl(options) {
29972
- const repoPath = path33.resolve(options.repoPath);
30385
+ const repoPath = path37.resolve(options.repoPath);
29973
30386
  const apiUrl = options.apiUrl ?? getControlApiUrl();
29974
30387
  if (options.cloud) {
29975
30388
  const remote = createRemoteExecutor();
@@ -29997,7 +30410,7 @@ async function syncRepoWithControl(options) {
29997
30410
  const listResponse = await fetch(`${apiUrl}/api/repos`);
29998
30411
  if (!listResponse.ok) throw new Error("Failed to list repos from Control API");
29999
30412
  const repos = await listResponse.json();
30000
- const existing = repos.find((r2) => path33.resolve(r2.path) === repoPath);
30413
+ const existing = repos.find((r2) => path37.resolve(r2.path) === repoPath);
30001
30414
  if (!existing) throw new Error(`Repo not registered: ${repoPath}`);
30002
30415
  await syncRepoRunsAndSlots(apiUrl, existing.id, repoPath, options.dryRun);
30003
30416
  return;
@@ -30016,6 +30429,37 @@ async function syncRepoRunsAndSlots(apiUrl, repoId, repoPath, dryRun) {
30016
30429
  generatedAt: status.generatedAt
30017
30430
  });
30018
30431
  await postJson(`${apiUrl}/api/repos/${repoId}/slots`, slotsBody, dryRun);
30432
+ if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
30433
+ try {
30434
+ const usage2 = status.slots.flatMap(
30435
+ (slot) => harvestUsageForSlot({
30436
+ agentId: slot.agentId,
30437
+ workDir: slot.workDir,
30438
+ worktreePath: slot.worktreePath,
30439
+ branch: slot.branch,
30440
+ suffix: slot.suffix,
30441
+ sessionCreatedAt: slot.sessionCreatedAt,
30442
+ repoPath
30443
+ }).map((row) => ({
30444
+ ...row,
30445
+ sessionKey: row.sessionKey || buildSessionKey({
30446
+ branch: slot.branch,
30447
+ agentId: slot.agentId,
30448
+ suffix: slot.suffix,
30449
+ createdAt: slot.sessionCreatedAt
30450
+ })
30451
+ }))
30452
+ );
30453
+ if (usage2.length > 0) {
30454
+ const usageBody = SyncUsageInputSchema.parse({ usage: usage2 });
30455
+ await postJson(`${apiUrl}/api/repos/${repoId}/usage`, usageBody, dryRun);
30456
+ }
30457
+ } catch (err) {
30458
+ const message = err instanceof Error ? err.message : String(err);
30459
+ process.stderr.write(`[har control] usage harvest skipped: ${message}
30460
+ `);
30461
+ }
30462
+ }
30019
30463
  const validations = listValidations(resolveHarnessRoot(repoPath));
30020
30464
  if (validations.length > 0) {
30021
30465
  const validationsBody = SyncValidationsInputSchema.parse({ validations });
@@ -30068,7 +30512,199 @@ function checkLaunchGuard2(repoPath, agentId, options = {}) {
30068
30512
  return { allowed: true, slot: occupied.slot, readiness };
30069
30513
  }
30070
30514
 
30515
+ // src/core/control-lifecycle.ts
30516
+ var import_child_process8 = require("child_process");
30517
+ var path38 = __toESM(require("path"));
30518
+
30519
+ // src/core/control-image.ts
30520
+ var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
30521
+ function getControlImageName() {
30522
+ return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
30523
+ }
30524
+ function getControlImageTag() {
30525
+ return process.env.HAR_CONTROL_IMAGE_TAG ?? getHarPackageVersion();
30526
+ }
30527
+ function getControlImageRef() {
30528
+ return `${getControlImageName()}:${getControlImageTag()}`;
30529
+ }
30530
+ function shouldBuildControlLocally() {
30531
+ return process.env.HAR_CONTROL_BUILD === "true";
30532
+ }
30533
+
30534
+ // src/core/control-lifecycle.ts
30535
+ function resolveControlDir() {
30536
+ return path38.resolve(__dirname, "..", "control");
30537
+ }
30538
+ function resolveControlComposeFiles(options) {
30539
+ const controlDir = resolveControlDir();
30540
+ const files = [path38.join(controlDir, "docker-compose.yml")];
30541
+ if (options?.build ?? shouldBuildControlLocally()) {
30542
+ files.push(path38.join(controlDir, "docker-compose.build.yml"));
30543
+ }
30544
+ return files;
30545
+ }
30546
+ function buildDockerComposeEnv() {
30547
+ return {
30548
+ ...process.env,
30549
+ HAR_CONTROL_IMAGE: getControlImageName(),
30550
+ HAR_CONTROL_IMAGE_TAG: getControlImageTag()
30551
+ };
30552
+ }
30553
+ function runDockerCompose(args, options) {
30554
+ const controlDir = resolveControlDir();
30555
+ const composeFiles = resolveControlComposeFiles(options);
30556
+ const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
30557
+ const result = (0, import_child_process8.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
30558
+ cwd: controlDir,
30559
+ stdio: "inherit",
30560
+ env: buildDockerComposeEnv()
30561
+ });
30562
+ if (result.error) {
30563
+ throw result.error;
30564
+ }
30565
+ return result.status ?? 1;
30566
+ }
30567
+ async function startMissionControl(options) {
30568
+ const build = options.build ?? shouldBuildControlLocally();
30569
+ const imageRef = getControlImageRef();
30570
+ if (!build) {
30571
+ const pullCode = runDockerCompose(["pull"], { build: false });
30572
+ if (pullCode !== 0) {
30573
+ return { code: pullCode, apiUrl: getControlApiUrl(), imageRef };
30574
+ }
30575
+ }
30576
+ const detach = options.detach !== false;
30577
+ const upArgs = detach ? ["up", "-d"] : ["up"];
30578
+ if (build) {
30579
+ upArgs.push("--build");
30580
+ }
30581
+ const code = runDockerCompose(upArgs, { build });
30582
+ return { code, apiUrl: getControlApiUrl(), imageRef };
30583
+ }
30584
+ async function syncReposAfterControlStart(cwd) {
30585
+ if (!isControlEnabled()) {
30586
+ return { synced: 0, failed: 0, apiReady: false };
30587
+ }
30588
+ const apiUrl = getControlApiUrl();
30589
+ const apiReady = await waitForControlApi(apiUrl);
30590
+ if (!apiReady) {
30591
+ return { synced: 0, failed: 0, apiReady: false };
30592
+ }
30593
+ const { synced, failed } = await syncAllKnownReposWithControl({ apiUrl, cwd });
30594
+ return { synced, failed, apiReady: true };
30595
+ }
30596
+ async function startControlAndSync(options) {
30597
+ const { code, apiUrl, imageRef } = await startMissionControl({
30598
+ detach: options?.detach,
30599
+ build: options?.build
30600
+ });
30601
+ if (code !== 0) {
30602
+ return { code, apiUrl, imageRef, synced: 0, failed: 0, apiReady: false };
30603
+ }
30604
+ const { synced, failed, apiReady } = await syncReposAfterControlStart(options?.cwd ?? process.cwd());
30605
+ return { code, apiUrl, imageRef, synced, failed, apiReady };
30606
+ }
30607
+
30608
+ // src/core/telemetry-ensure.ts
30609
+ async function ensureTelemetryInfrastructure(options) {
30610
+ const apiUrl = getControlApiUrl();
30611
+ const telemetryEnabled = isTelemetryEnabled();
30612
+ const controlEnabled = isControlEnabled();
30613
+ const startIfNeeded = options?.startIfNeeded !== false;
30614
+ if (!telemetryEnabled) {
30615
+ return {
30616
+ telemetryEnabled: false,
30617
+ controlEnabled,
30618
+ apiUrl,
30619
+ reachable: false,
30620
+ started: false,
30621
+ otelReady: false,
30622
+ message: "Telemetry is off \u2014 Mission Control will not be auto-started. Enable with: har telemetry on"
30623
+ };
30624
+ }
30625
+ if (!controlEnabled) {
30626
+ return {
30627
+ telemetryEnabled: true,
30628
+ controlEnabled: false,
30629
+ apiUrl,
30630
+ reachable: false,
30631
+ started: false,
30632
+ otelReady: false,
30633
+ warning: "Telemetry is on but HAR_CONTROL_DISABLED=true \u2014 cannot start Mission Control. Unset it or run har control up manually."
30634
+ };
30635
+ }
30636
+ if (await isControlApiReachable(apiUrl)) {
30637
+ return {
30638
+ telemetryEnabled: true,
30639
+ controlEnabled: true,
30640
+ apiUrl,
30641
+ reachable: true,
30642
+ started: false,
30643
+ otelReady: true,
30644
+ message: `Telemetry on \u2014 Mission Control reachable at ${apiUrl}`
30645
+ };
30646
+ }
30647
+ if (!startIfNeeded || process.env.NODE_ENV === "test") {
30648
+ return {
30649
+ telemetryEnabled: true,
30650
+ controlEnabled: true,
30651
+ apiUrl,
30652
+ reachable: false,
30653
+ started: false,
30654
+ otelReady: false,
30655
+ warning: `Telemetry on but Mission Control is not reachable at ${apiUrl}. Run: har control up`
30656
+ };
30657
+ }
30658
+ try {
30659
+ const { code } = await startMissionControl({ detach: true });
30660
+ if (code !== 0) {
30661
+ return {
30662
+ telemetryEnabled: true,
30663
+ controlEnabled: true,
30664
+ apiUrl,
30665
+ reachable: false,
30666
+ started: false,
30667
+ otelReady: false,
30668
+ warning: `Telemetry on \u2014 failed to start Mission Control (exit ${code}). OTEL export skipped; harvest may fill gaps later. Fix Docker / run: har control up`
30669
+ };
30670
+ }
30671
+ } catch (err) {
30672
+ const detail = err instanceof Error ? err.message : String(err);
30673
+ return {
30674
+ telemetryEnabled: true,
30675
+ controlEnabled: true,
30676
+ apiUrl,
30677
+ reachable: false,
30678
+ started: false,
30679
+ otelReady: false,
30680
+ warning: `Telemetry on \u2014 could not start Mission Control (${detail}). OTEL export skipped. Run: har control up`
30681
+ };
30682
+ }
30683
+ const ready = await waitForControlApi(apiUrl, 9e4);
30684
+ if (!ready) {
30685
+ return {
30686
+ telemetryEnabled: true,
30687
+ controlEnabled: true,
30688
+ apiUrl,
30689
+ reachable: false,
30690
+ started: true,
30691
+ otelReady: false,
30692
+ warning: `Telemetry on \u2014 started Mission Control but API did not become ready at ${apiUrl}. OTEL export skipped for now.`
30693
+ };
30694
+ }
30695
+ return {
30696
+ telemetryEnabled: true,
30697
+ controlEnabled: true,
30698
+ apiUrl,
30699
+ reachable: true,
30700
+ started: true,
30701
+ otelReady: true,
30702
+ message: `Telemetry on \u2014 started Mission Control at ${apiUrl}`
30703
+ };
30704
+ }
30705
+
30071
30706
  // src/core/run-service.ts
30707
+ var path39 = __toESM(require("path"));
30072
30708
  function extractShellOutput(result) {
30073
30709
  const stdout = result.logs?.find((log) => log.stream === "stdout")?.content ?? result.logs?.find((log) => !log.stream)?.content ?? "";
30074
30710
  const stderr = result.logs?.find((log) => log.stream === "stderr")?.content ?? "";
@@ -30188,6 +30824,21 @@ var RunService = class {
30188
30824
  } : void 0
30189
30825
  };
30190
30826
  }
30827
+ let telemetryBanner = "";
30828
+ let otelReady = false;
30829
+ if (isTelemetryEnabled() && process.env.NODE_ENV !== "test") {
30830
+ const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: true });
30831
+ otelReady = ensured.otelReady;
30832
+ if (ensured.message) {
30833
+ telemetryBanner += `${ensured.message}
30834
+ Usage from Claude Code / Codex will appear under Worktrees. Disable: har telemetry off
30835
+ `;
30836
+ }
30837
+ if (ensured.warning) {
30838
+ telemetryBanner += `${ensured.warning}
30839
+ `;
30840
+ }
30841
+ }
30191
30842
  const result = await this.runStage({
30192
30843
  repoPath: options.repoPath,
30193
30844
  kind: "launch",
@@ -30211,6 +30862,32 @@ var RunService = class {
30211
30862
  envResult.worktreePath = session.worktreePath;
30212
30863
  envResult.branch = session.branch;
30213
30864
  envResult.previewUrls = envResult.previewUrls ?? session.previewUrls;
30865
+ const sessionKey = buildSessionKey({
30866
+ branch: session.branch,
30867
+ agentId: options.agentId,
30868
+ suffix: session.suffix,
30869
+ createdAt: session.createdAt
30870
+ });
30871
+ const envFile = path39.join(session.workDir, `.env.agent.${options.agentId}`);
30872
+ try {
30873
+ appendTelemetryEnvToFile(
30874
+ envFile,
30875
+ {
30876
+ sessionKey,
30877
+ agentId: options.agentId,
30878
+ repoPath: path39.resolve(options.repoPath),
30879
+ workDir: session.workDir,
30880
+ branch: session.branch,
30881
+ suffix: session.suffix,
30882
+ purpose: session.purpose
30883
+ },
30884
+ { otelReady: otelReady && isTelemetryEnabled() }
30885
+ );
30886
+ } catch {
30887
+ }
30888
+ }
30889
+ if (telemetryBanner) {
30890
+ envResult.stderr = `${telemetryBanner}${envResult.stderr ?? ""}`;
30214
30891
  }
30215
30892
  }
30216
30893
  return envResult;
@@ -30577,7 +31254,7 @@ var envCommand = {
30577
31254
  }
30578
31255
  };
30579
31256
  async function handleInit(argv) {
30580
- const repoPath = path34.resolve(argv.repo);
31257
+ const repoPath = path40.resolve(argv.repo);
30581
31258
  header("har env init");
30582
31259
  info(`Repository: ${repoPath}`);
30583
31260
  try {
@@ -30636,7 +31313,7 @@ async function handleInit(argv) {
30636
31313
  }
30637
31314
  }
30638
31315
  async function handleMaintain(argv) {
30639
- const repoPath = path34.resolve(argv.repo);
31316
+ const repoPath = path40.resolve(argv.repo);
30640
31317
  header("har env maintain");
30641
31318
  info(`Repository: ${repoPath}`);
30642
31319
  try {
@@ -30732,7 +31409,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
30732
31409
  if (autoYes) {
30733
31410
  const proposal = readAgentMdProposal(repoPath);
30734
31411
  if (proposal) {
30735
- writeFileSafe(path34.join(repoPath, "AGENT.md"), proposal.content);
31412
+ writeFileSafe(path40.join(repoPath, "AGENT.md"), proposal.content);
30736
31413
  clearAgentMdProposal(repoPath);
30737
31414
  info("Applied AGENT.md proposal (--yes)");
30738
31415
  }
@@ -30741,7 +31418,7 @@ async function handleAgentMdProposal(repoPath, autoYes) {
30741
31418
  await promptApplyAgentMdProposal(repoPath);
30742
31419
  }
30743
31420
  async function handleAddStage(argv) {
30744
- const repoPath = path34.resolve(argv.repo);
31421
+ const repoPath = path40.resolve(argv.repo);
30745
31422
  const available = listStageTemplateIds();
30746
31423
  if (argv.list) {
30747
31424
  for (const id of available) {
@@ -30818,7 +31495,7 @@ async function handleAddStage(argv) {
30818
31495
  }
30819
31496
  }
30820
31497
  async function handlePreflight(argv) {
30821
- const repo = path34.resolve(argv.repo);
31498
+ const repo = path40.resolve(argv.repo);
30822
31499
  const agentId = validateAgentId(argv.id, repo);
30823
31500
  const result = await preflightEnvironment({
30824
31501
  repoPath: repo,
@@ -30836,7 +31513,7 @@ async function handlePreflight(argv) {
30836
31513
  process.exit(result.code);
30837
31514
  }
30838
31515
  async function handleLaunch(argv) {
30839
- const repo = path34.resolve(argv.repo);
31516
+ const repo = path40.resolve(argv.repo);
30840
31517
  const agentId = validateAgentId(argv.id, repo);
30841
31518
  let confirmReplace = argv.replace;
30842
31519
  let force = argv.force;
@@ -30848,8 +31525,8 @@ async function handleLaunch(argv) {
30848
31525
  console.error(guard.reason ?? "");
30849
31526
  const readline5 = await import("readline");
30850
31527
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
30851
- const answer = await new Promise((resolve28) => {
30852
- rl.question("Replace this slot? [y/N] ", resolve28);
31528
+ const answer = await new Promise((resolve33) => {
31529
+ rl.question("Replace this slot? [y/N] ", resolve33);
30853
31530
  });
30854
31531
  rl.close();
30855
31532
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -30865,8 +31542,8 @@ async function handleLaunch(argv) {
30865
31542
  warn("Worktree has uncommitted changes.");
30866
31543
  const readline5 = await import("readline");
30867
31544
  const rl = readline5.createInterface({ input: process.stdin, output: process.stderr });
30868
- const answer = await new Promise((resolve28) => {
30869
- rl.question("Discard uncommitted changes? [y/N] ", resolve28);
31545
+ const answer = await new Promise((resolve33) => {
31546
+ rl.question("Discard uncommitted changes? [y/N] ", resolve33);
30870
31547
  });
30871
31548
  rl.close();
30872
31549
  if (!/^[Yy]$/.test(answer.trim())) {
@@ -30895,6 +31572,13 @@ async function handleLaunch(argv) {
30895
31572
  process.exit(result.code || 2);
30896
31573
  }
30897
31574
  if (result.code === 0 && result.workDir) {
31575
+ if (result.stderr) {
31576
+ for (const line of result.stderr.split("\n").filter(Boolean)) {
31577
+ if (/telemetry|mission control|otel/i.test(line)) {
31578
+ info(line);
31579
+ }
31580
+ }
31581
+ }
30898
31582
  divider();
30899
31583
  success(`Session ready \u2014 make ALL file edits under: ${result.workDir}`);
30900
31584
  if (result.branch) info(`Branch: ${result.branch}`);
@@ -30913,7 +31597,7 @@ async function handleRecover(argv) {
30913
31597
  });
30914
31598
  }
30915
31599
  async function handleVerify(argv) {
30916
- const repo = path34.resolve(argv.repo);
31600
+ const repo = path40.resolve(argv.repo);
30917
31601
  const agentId = validateAgentId(argv.id, repo);
30918
31602
  const result = await runVerification({
30919
31603
  repoPath: repo,
@@ -30925,7 +31609,7 @@ async function handleVerify(argv) {
30925
31609
  process.exit(result.code);
30926
31610
  }
30927
31611
  async function handleTeardown(argv) {
30928
- const repo = path34.resolve(argv.repo);
31612
+ const repo = path40.resolve(argv.repo);
30929
31613
  const agentId = validateAgentId(argv.id, repo);
30930
31614
  const result = await teardownEnvironment({
30931
31615
  repoPath: repo,
@@ -30936,7 +31620,7 @@ async function handleTeardown(argv) {
30936
31620
  process.exit(result.code);
30937
31621
  }
30938
31622
  async function handleComplete(argv) {
30939
- const repo = path34.resolve(argv.repo);
31623
+ const repo = path40.resolve(argv.repo);
30940
31624
  const agentId = validateAgentId(argv.id, repo);
30941
31625
  const result = await completeEnvironment({
30942
31626
  repoPath: repo,
@@ -30957,7 +31641,7 @@ async function handleComplete(argv) {
30957
31641
  process.exit(result.code);
30958
31642
  }
30959
31643
  async function handleStatus(argv) {
30960
- const repoPath = path34.resolve(argv.repo);
31644
+ const repoPath = path40.resolve(argv.repo);
30961
31645
  if (argv.json) {
30962
31646
  const status = collectEnvironmentStatus(repoPath);
30963
31647
  const output = EnvironmentStatusSchema.parse(status);
@@ -30970,7 +31654,7 @@ async function handleStatus(argv) {
30970
31654
  });
30971
31655
  }
30972
31656
  async function handleRunsList(argv) {
30973
- const repoPath = path34.resolve(argv.repo);
31657
+ const repoPath = path40.resolve(argv.repo);
30974
31658
  const runs = listRuns(repoPath, { stageId: argv.stage, limit: argv.limit });
30975
31659
  if (argv.json) {
30976
31660
  process.stdout.write(JSON.stringify({ runs }, null, 2) + "\n");
@@ -30987,7 +31671,7 @@ async function handleRunsList(argv) {
30987
31671
  }
30988
31672
  }
30989
31673
  async function handleRunsGet(argv) {
30990
- const run2 = getRun(path34.resolve(argv.repo), argv.runId);
31674
+ const run2 = getRun(path40.resolve(argv.repo), argv.runId);
30991
31675
  if (!run2) {
30992
31676
  error(`Run not found: ${argv.runId}`);
30993
31677
  process.exit(1);
@@ -31060,102 +31744,7 @@ function printNextSteps(auto2) {
31060
31744
  }
31061
31745
 
31062
31746
  // src/cli/commands/control.ts
31063
- var path36 = __toESM(require("path"));
31064
-
31065
- // src/core/control-lifecycle.ts
31066
- var import_child_process8 = require("child_process");
31067
- var path35 = __toESM(require("path"));
31068
-
31069
- // src/core/control-image.ts
31070
- var DEFAULT_CONTROL_IMAGE = "theosfactory/har-control";
31071
- function getControlImageName() {
31072
- return process.env.HAR_CONTROL_IMAGE ?? DEFAULT_CONTROL_IMAGE;
31073
- }
31074
- function getControlImageTag() {
31075
- return process.env.HAR_CONTROL_IMAGE_TAG ?? getHarPackageVersion();
31076
- }
31077
- function getControlImageRef() {
31078
- return `${getControlImageName()}:${getControlImageTag()}`;
31079
- }
31080
- function shouldBuildControlLocally() {
31081
- return process.env.HAR_CONTROL_BUILD === "true";
31082
- }
31083
-
31084
- // src/core/control-lifecycle.ts
31085
- function resolveControlDir() {
31086
- return path35.resolve(__dirname, "..", "control");
31087
- }
31088
- function resolveControlComposeFiles(options) {
31089
- const controlDir = resolveControlDir();
31090
- const files = [path35.join(controlDir, "docker-compose.yml")];
31091
- if (options?.build ?? shouldBuildControlLocally()) {
31092
- files.push(path35.join(controlDir, "docker-compose.build.yml"));
31093
- }
31094
- return files;
31095
- }
31096
- function buildDockerComposeEnv() {
31097
- return {
31098
- ...process.env,
31099
- HAR_CONTROL_IMAGE: getControlImageName(),
31100
- HAR_CONTROL_IMAGE_TAG: getControlImageTag()
31101
- };
31102
- }
31103
- function runDockerCompose(args, options) {
31104
- const controlDir = resolveControlDir();
31105
- const composeFiles = resolveControlComposeFiles(options);
31106
- const composeArgs = composeFiles.flatMap((file) => ["-f", file]);
31107
- const result = (0, import_child_process8.spawnSync)("docker", ["compose", ...composeArgs, ...args], {
31108
- cwd: controlDir,
31109
- stdio: "inherit",
31110
- env: buildDockerComposeEnv()
31111
- });
31112
- if (result.error) {
31113
- throw result.error;
31114
- }
31115
- return result.status ?? 1;
31116
- }
31117
- async function startMissionControl(options) {
31118
- const build = options.build ?? shouldBuildControlLocally();
31119
- const imageRef = getControlImageRef();
31120
- if (!build) {
31121
- const pullCode = runDockerCompose(["pull"], { build: false });
31122
- if (pullCode !== 0) {
31123
- return { code: pullCode, apiUrl: getControlApiUrl(), imageRef };
31124
- }
31125
- }
31126
- const detach = options.detach !== false;
31127
- const upArgs = detach ? ["up", "-d"] : ["up"];
31128
- if (build) {
31129
- upArgs.push("--build");
31130
- }
31131
- const code = runDockerCompose(upArgs, { build });
31132
- return { code, apiUrl: getControlApiUrl(), imageRef };
31133
- }
31134
- async function syncReposAfterControlStart(cwd) {
31135
- if (!isControlEnabled()) {
31136
- return { synced: 0, failed: 0, apiReady: false };
31137
- }
31138
- const apiUrl = getControlApiUrl();
31139
- const apiReady = await waitForControlApi(apiUrl);
31140
- if (!apiReady) {
31141
- return { synced: 0, failed: 0, apiReady: false };
31142
- }
31143
- const { synced, failed } = await syncAllKnownReposWithControl({ apiUrl, cwd });
31144
- return { synced, failed, apiReady: true };
31145
- }
31146
- async function startControlAndSync(options) {
31147
- const { code, apiUrl, imageRef } = await startMissionControl({
31148
- detach: options?.detach,
31149
- build: options?.build
31150
- });
31151
- if (code !== 0) {
31152
- return { code, apiUrl, imageRef, synced: 0, failed: 0, apiReady: false };
31153
- }
31154
- const { synced, failed, apiReady } = await syncReposAfterControlStart(options?.cwd ?? process.cwd());
31155
- return { code, apiUrl, imageRef, synced, failed, apiReady };
31156
- }
31157
-
31158
- // src/cli/commands/control.ts
31747
+ var path41 = __toESM(require("path"));
31159
31748
  var controlCommand = {
31160
31749
  command: "control <subcommand>",
31161
31750
  describe: "Mission Control dashboard (local)",
@@ -31258,7 +31847,7 @@ async function handleDown() {
31258
31847
  process.exit(code);
31259
31848
  }
31260
31849
  async function handleRegister(argv) {
31261
- const repoPath = path36.resolve(argv.repo);
31850
+ const repoPath = path41.resolve(argv.repo);
31262
31851
  header("har control register");
31263
31852
  info(`Repository: ${repoPath}`);
31264
31853
  try {
@@ -31278,7 +31867,7 @@ async function handleRegister(argv) {
31278
31867
  }
31279
31868
  }
31280
31869
  async function handleSync(argv) {
31281
- const repoPath = path36.resolve(argv.repo);
31870
+ const repoPath = path41.resolve(argv.repo);
31282
31871
  try {
31283
31872
  await syncRepoWithControl({
31284
31873
  repoPath,
@@ -31317,7 +31906,7 @@ async function handleWatch(argv) {
31317
31906
  };
31318
31907
  const tick = async () => {
31319
31908
  if (argv.repo) {
31320
- await syncOne(path36.resolve(argv.repo));
31909
+ await syncOne(path41.resolve(argv.repo));
31321
31910
  return;
31322
31911
  }
31323
31912
  try {
@@ -31348,12 +31937,12 @@ async function handleLogin(argv) {
31348
31937
  }
31349
31938
 
31350
31939
  // src/cli/commands/hooks.ts
31351
- var path39 = __toESM(require("path"));
31940
+ var path44 = __toESM(require("path"));
31352
31941
 
31353
31942
  // src/core/hooks.ts
31354
- var fs33 = __toESM(require("fs"));
31355
- var os9 = __toESM(require("os"));
31356
- var path37 = __toESM(require("path"));
31943
+ var fs37 = __toESM(require("fs"));
31944
+ var os12 = __toESM(require("os"));
31945
+ var path42 = __toESM(require("path"));
31357
31946
  var MARKER_START = "# >>> har commit gate (managed by `har hooks`) >>>";
31358
31947
  var MARKER_END = "# <<< har commit gate <<<";
31359
31948
  var PRE_COMMIT_BLOCK = [
@@ -31378,7 +31967,7 @@ function resolveCheckoutRoot(cwd) {
31378
31967
  function resolveHooksDir(checkoutDir) {
31379
31968
  const hooksPath = tryGit2(checkoutDir, "rev-parse --git-path hooks");
31380
31969
  if (!hooksPath) throw new Error(`Not a git checkout: ${checkoutDir}`);
31381
- return path37.resolve(checkoutDir, hooksPath);
31970
+ return path42.resolve(checkoutDir, hooksPath);
31382
31971
  }
31383
31972
  function getConfiguredHooksPath(checkoutDir) {
31384
31973
  return tryGit2(checkoutDir, "config core.hooksPath") || void 0;
@@ -31386,9 +31975,9 @@ function getConfiguredHooksPath(checkoutDir) {
31386
31975
  function defaultHarInvocation() {
31387
31976
  const entry = process.argv[1];
31388
31977
  if (entry && entry.endsWith(".js")) {
31389
- return `"${process.execPath}" "${path37.resolve(entry)}"`;
31978
+ return `"${process.execPath}" "${path42.resolve(entry)}"`;
31390
31979
  }
31391
- if (entry) return `"${path37.resolve(entry)}"`;
31980
+ if (entry) return `"${path42.resolve(entry)}"`;
31392
31981
  return "har";
31393
31982
  }
31394
31983
  function buildHookScript(harInvocation, subcommand, failOpenNotice) {
@@ -31409,40 +31998,40 @@ exit 0
31409
31998
  `;
31410
31999
  }
31411
32000
  function upsertMarkedBlock(filePath, block) {
31412
- if (!fs33.existsSync(filePath)) {
31413
- fs33.writeFileSync(filePath, `#!/bin/sh
32001
+ if (!fs37.existsSync(filePath)) {
32002
+ fs37.writeFileSync(filePath, `#!/bin/sh
31414
32003
  ${block}
31415
32004
  `, { mode: 493 });
31416
32005
  return "created";
31417
32006
  }
31418
- const content = fs33.readFileSync(filePath, "utf8");
32007
+ const content = fs37.readFileSync(filePath, "utf8");
31419
32008
  if (content.includes(MARKER_START)) {
31420
32009
  const pattern = new RegExp(
31421
32010
  `${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}`
31422
32011
  );
31423
- fs33.writeFileSync(filePath, content.replace(pattern, block));
31424
- fs33.chmodSync(filePath, 493);
32012
+ fs37.writeFileSync(filePath, content.replace(pattern, block));
32013
+ fs37.chmodSync(filePath, 493);
31425
32014
  return "updated";
31426
32015
  }
31427
32016
  const suffix = content.endsWith("\n") ? "" : "\n";
31428
- fs33.writeFileSync(filePath, `${content}${suffix}
32017
+ fs37.writeFileSync(filePath, `${content}${suffix}
31429
32018
  ${block}
31430
32019
  `);
31431
- fs33.chmodSync(filePath, 493);
32020
+ fs37.chmodSync(filePath, 493);
31432
32021
  return "appended";
31433
32022
  }
31434
32023
  function removeMarkedBlock(filePath) {
31435
- if (!fs33.existsSync(filePath)) return false;
31436
- const content = fs33.readFileSync(filePath, "utf8");
32024
+ if (!fs37.existsSync(filePath)) return false;
32025
+ const content = fs37.readFileSync(filePath, "utf8");
31437
32026
  if (!content.includes(MARKER_START)) return false;
31438
32027
  const pattern = new RegExp(
31439
32028
  `\\n?${escapeRegExp(MARKER_START)}[\\s\\S]*?${escapeRegExp(MARKER_END)}\\n?`
31440
32029
  );
31441
32030
  const stripped = content.replace(pattern, "\n");
31442
32031
  if (stripped.replace(/^#!\/bin\/sh\n?/, "").trim() === "") {
31443
- fs33.rmSync(filePath);
32032
+ fs37.rmSync(filePath);
31444
32033
  } else {
31445
- fs33.writeFileSync(filePath, stripped);
32034
+ fs37.writeFileSync(filePath, stripped);
31446
32035
  }
31447
32036
  return true;
31448
32037
  }
@@ -31464,20 +32053,20 @@ Or re-run with --force to write into that directory anyway.`
31464
32053
  );
31465
32054
  }
31466
32055
  const hooksDir = resolveHooksDir(checkout);
31467
- fs33.mkdirSync(hooksDir, { recursive: true });
32056
+ fs37.mkdirSync(hooksDir, { recursive: true });
31468
32057
  const invocation = options.harInvocation ?? defaultHarInvocation();
31469
- fs33.writeFileSync(
31470
- path37.join(hooksDir, "har-pre-commit"),
32058
+ fs37.writeFileSync(
32059
+ path42.join(hooksDir, "har-pre-commit"),
31471
32060
  buildHookScript(invocation, "check", "har: binary not found; skipping commit gate (reinstall with har hooks install)"),
31472
32061
  { mode: 493 }
31473
32062
  );
31474
- fs33.writeFileSync(
31475
- path37.join(hooksDir, "har-post-commit"),
32063
+ fs37.writeFileSync(
32064
+ path42.join(hooksDir, "har-post-commit"),
31476
32065
  buildHookScript(invocation, "record-commit", "har: binary not found; skipping commit association"),
31477
32066
  { mode: 493 }
31478
32067
  );
31479
- const preCommit = upsertMarkedBlock(path37.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
31480
- const postCommit = upsertMarkedBlock(path37.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
32068
+ const preCommit = upsertMarkedBlock(path42.join(hooksDir, "pre-commit"), PRE_COMMIT_BLOCK);
32069
+ const postCommit = upsertMarkedBlock(path42.join(hooksDir, "post-commit"), POST_COMMIT_BLOCK);
31481
32070
  ensureValidationsIgnored(checkout);
31482
32071
  return { hooksDir, preCommit, postCommit };
31483
32072
  }
@@ -31486,12 +32075,12 @@ function uninstallHooks(repoPath) {
31486
32075
  if (!checkout) throw new Error(`Not a git repository: ${repoPath}`);
31487
32076
  const hooksDir = resolveHooksDir(checkout);
31488
32077
  let removed = false;
31489
- removed = removeMarkedBlock(path37.join(hooksDir, "pre-commit")) || removed;
31490
- removed = removeMarkedBlock(path37.join(hooksDir, "post-commit")) || removed;
32078
+ removed = removeMarkedBlock(path42.join(hooksDir, "pre-commit")) || removed;
32079
+ removed = removeMarkedBlock(path42.join(hooksDir, "post-commit")) || removed;
31491
32080
  for (const file of ["har-pre-commit", "har-post-commit"]) {
31492
- const full = path37.join(hooksDir, file);
31493
- if (fs33.existsSync(full)) {
31494
- fs33.rmSync(full);
32081
+ const full = path42.join(hooksDir, file);
32082
+ if (fs37.existsSync(full)) {
32083
+ fs37.rmSync(full);
31495
32084
  removed = true;
31496
32085
  }
31497
32086
  }
@@ -31508,9 +32097,9 @@ function getCommitGateConfig(checkoutDir) {
31508
32097
  function isAgentWorktree(checkoutDir) {
31509
32098
  const branch = getCurrentBranch(checkoutDir);
31510
32099
  if (branch && /^har-agent-\d+$/.test(branch)) return true;
31511
- const worktreesRoot = path37.join(os9.homedir(), "worktrees");
31512
- const resolved = path37.resolve(checkoutDir);
31513
- return resolved.startsWith(`${worktreesRoot}${path37.sep}`) && /-agent-\d+$/.test(path37.basename(resolved));
32100
+ const worktreesRoot = path42.join(os12.homedir(), "worktrees");
32101
+ const resolved = path42.resolve(checkoutDir);
32102
+ return resolved.startsWith(`${worktreesRoot}${path42.sep}`) && /-agent-\d+$/.test(path42.basename(resolved));
31514
32103
  }
31515
32104
  function resolveEffectiveMode(checkoutDir, gate) {
31516
32105
  if (!gate.enabled) return "off";
@@ -31523,15 +32112,15 @@ function getHooksStatus(repoPath) {
31523
32112
  const hooksDir = resolveHooksDir(checkout);
31524
32113
  const gate = getCommitGateConfig(checkout);
31525
32114
  const hasBlock = (name) => {
31526
- const file = path37.join(hooksDir, name);
31527
- return fs33.existsSync(file) && fs33.readFileSync(file, "utf8").includes(MARKER_START);
32115
+ const file = path42.join(hooksDir, name);
32116
+ return fs37.existsSync(file) && fs37.readFileSync(file, "utf8").includes(MARKER_START);
31528
32117
  };
31529
32118
  return {
31530
32119
  checkout,
31531
32120
  hooksDir,
31532
32121
  configuredHooksPath: getConfiguredHooksPath(checkout),
31533
- preCommitInstalled: hasBlock("pre-commit") && fs33.existsSync(path37.join(hooksDir, "har-pre-commit")),
31534
- postCommitInstalled: hasBlock("post-commit") && fs33.existsSync(path37.join(hooksDir, "har-post-commit")),
32122
+ preCommitInstalled: hasBlock("pre-commit") && fs37.existsSync(path42.join(hooksDir, "har-pre-commit")),
32123
+ postCommitInstalled: hasBlock("post-commit") && fs37.existsSync(path42.join(hooksDir, "har-post-commit")),
31535
32124
  gate,
31536
32125
  effectiveMode: resolveEffectiveMode(checkout, gate)
31537
32126
  };
@@ -31542,7 +32131,7 @@ function checkCommitGate(cwd) {
31542
32131
  }
31543
32132
  const checkout = resolveCheckoutRoot(cwd);
31544
32133
  if (!checkout) return { exitCode: 0, messages: [] };
31545
- if (!fs33.existsSync(path37.join(checkout, ".har", "stages.json"))) {
32134
+ if (!fs37.existsSync(path42.join(checkout, ".har", "stages.json"))) {
31546
32135
  return { exitCode: 0, messages: [] };
31547
32136
  }
31548
32137
  const gate = getCommitGateConfig(checkout);
@@ -31613,8 +32202,8 @@ async function recordCommitAssociation(cwd) {
31613
32202
  }
31614
32203
 
31615
32204
  // src/core/claude-hooks.ts
31616
- var fs34 = __toESM(require("fs"));
31617
- var path38 = __toESM(require("path"));
32205
+ var fs38 = __toESM(require("fs"));
32206
+ var path43 = __toESM(require("path"));
31618
32207
  var CLAUDE_GUARD_RELATIVE_PATH = ".har/hooks/claude-worktree-guard.sh";
31619
32208
  var CLAUDE_SETTINGS_RELATIVE_PATH = ".claude/settings.json";
31620
32209
  var GUARD_MATCHER = "Edit|Write|MultiEdit|NotebookEdit";
@@ -31625,8 +32214,8 @@ function isHarGuardEntry(entry) {
31625
32214
  );
31626
32215
  }
31627
32216
  function readSettings(settingsPath) {
31628
- if (!fs34.existsSync(settingsPath)) return {};
31629
- const raw = fs34.readFileSync(settingsPath, "utf8").trim();
32217
+ if (!fs38.existsSync(settingsPath)) return {};
32218
+ const raw = fs38.readFileSync(settingsPath, "utf8").trim();
31630
32219
  if (raw === "") return {};
31631
32220
  const parsed = JSON.parse(raw);
31632
32221
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -31635,19 +32224,19 @@ function readSettings(settingsPath) {
31635
32224
  return parsed;
31636
32225
  }
31637
32226
  function writeSettings(settingsPath, settings) {
31638
- fs34.mkdirSync(path38.dirname(settingsPath), { recursive: true });
31639
- fs34.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
32227
+ fs38.mkdirSync(path43.dirname(settingsPath), { recursive: true });
32228
+ fs38.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
31640
32229
  }
31641
32230
  function installClaudeGuard(repoPath) {
31642
32231
  const templatePath = resolveTemplateFile("claude-worktree-guard.sh.template");
31643
32232
  if (!templatePath) {
31644
32233
  throw new Error("Guard template not found (claude-worktree-guard.sh.template). Run npm run build.");
31645
32234
  }
31646
- const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
31647
- fs34.mkdirSync(path38.dirname(guardScript), { recursive: true });
31648
- fs34.copyFileSync(templatePath, guardScript);
31649
- fs34.chmodSync(guardScript, 493);
31650
- const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32235
+ const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32236
+ fs38.mkdirSync(path43.dirname(guardScript), { recursive: true });
32237
+ fs38.copyFileSync(templatePath, guardScript);
32238
+ fs38.chmodSync(guardScript, 493);
32239
+ const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31651
32240
  const settings = readSettings(settingsPath);
31652
32241
  const hooks = settings.hooks ?? {};
31653
32242
  const preToolUse = (hooks.PreToolUse ?? []).filter((entry) => !isHarGuardEntry(entry));
@@ -31661,15 +32250,15 @@ function installClaudeGuard(repoPath) {
31661
32250
  }
31662
32251
  function uninstallClaudeGuard(repoPath) {
31663
32252
  let removed = false;
31664
- const guardScript = path38.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
31665
- if (fs34.existsSync(guardScript)) {
31666
- fs34.rmSync(guardScript);
32253
+ const guardScript = path43.join(repoPath, CLAUDE_GUARD_RELATIVE_PATH);
32254
+ if (fs38.existsSync(guardScript)) {
32255
+ fs38.rmSync(guardScript);
31667
32256
  removed = true;
31668
- const parent = path38.dirname(guardScript);
31669
- if (fs34.existsSync(parent) && fs34.readdirSync(parent).length === 0) fs34.rmdirSync(parent);
32257
+ const parent = path43.dirname(guardScript);
32258
+ if (fs38.existsSync(parent) && fs38.readdirSync(parent).length === 0) fs38.rmdirSync(parent);
31670
32259
  }
31671
- const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31672
- if (fs34.existsSync(settingsPath)) {
32260
+ const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32261
+ if (fs38.existsSync(settingsPath)) {
31673
32262
  const settings = readSettings(settingsPath);
31674
32263
  const preToolUse = settings.hooks?.PreToolUse;
31675
32264
  if (preToolUse) {
@@ -31694,8 +32283,8 @@ function uninstallClaudeGuard(repoPath) {
31694
32283
  return { removed };
31695
32284
  }
31696
32285
  function claudeGuardInstalled(repoPath) {
31697
- const settingsPath = path38.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
31698
- if (!fs34.existsSync(settingsPath)) return false;
32286
+ const settingsPath = path43.join(repoPath, CLAUDE_SETTINGS_RELATIVE_PATH);
32287
+ if (!fs38.existsSync(settingsPath)) return false;
31699
32288
  try {
31700
32289
  const settings = readSettings(settingsPath);
31701
32290
  return (settings.hooks?.PreToolUse ?? []).some(isHarGuardEntry);
@@ -31711,7 +32300,7 @@ function repoOption(y2) {
31711
32300
  function handleInstall2(argv) {
31712
32301
  try {
31713
32302
  if (argv.claude) {
31714
- const result2 = installClaudeGuard(path39.resolve(argv.repo));
32303
+ const result2 = installClaudeGuard(path44.resolve(argv.repo));
31715
32304
  success("Claude Code worktree guard installed.");
31716
32305
  info(`Guard script: ${result2.guardScript}`);
31717
32306
  info(`Settings: ${result2.settingsPath} (PreToolUse: Edit|Write|MultiEdit|NotebookEdit)`);
@@ -31719,7 +32308,7 @@ function handleInstall2(argv) {
31719
32308
  info("Commit both files so the guard travels with the repo.");
31720
32309
  return;
31721
32310
  }
31722
- const result = installHooks({ repoPath: path39.resolve(argv.repo), force: argv.force });
32311
+ const result = installHooks({ repoPath: path44.resolve(argv.repo), force: argv.force });
31723
32312
  success(`Commit gate installed in ${result.hooksDir}`);
31724
32313
  info(`pre-commit: ${result.preCommit}, post-commit: ${result.postCommit}`);
31725
32314
  info("Commits of unverified change batches will be blocked in agent worktrees.");
@@ -31732,7 +32321,7 @@ function handleInstall2(argv) {
31732
32321
  function handleUninstall(argv) {
31733
32322
  try {
31734
32323
  if (argv.claude) {
31735
- const result2 = uninstallClaudeGuard(path39.resolve(argv.repo));
32324
+ const result2 = uninstallClaudeGuard(path44.resolve(argv.repo));
31736
32325
  if (result2.removed) {
31737
32326
  success("Claude Code worktree guard removed.");
31738
32327
  } else {
@@ -31740,7 +32329,7 @@ function handleUninstall(argv) {
31740
32329
  }
31741
32330
  return;
31742
32331
  }
31743
- const result = uninstallHooks(path39.resolve(argv.repo));
32332
+ const result = uninstallHooks(path44.resolve(argv.repo));
31744
32333
  if (result.removed) {
31745
32334
  success(`Commit gate removed from ${result.hooksDir}`);
31746
32335
  } else {
@@ -31753,7 +32342,7 @@ function handleUninstall(argv) {
31753
32342
  }
31754
32343
  function handleStatus2(argv) {
31755
32344
  try {
31756
- const status = getHooksStatus(path39.resolve(argv.repo));
32345
+ const status = getHooksStatus(path44.resolve(argv.repo));
31757
32346
  if (argv.json) {
31758
32347
  process.stdout.write(`${JSON.stringify(status, null, 2)}
31759
32348
  `);
@@ -31771,7 +32360,7 @@ function handleStatus2(argv) {
31771
32360
  );
31772
32361
  info(`Effective: ${status.effectiveMode} (in this checkout)`);
31773
32362
  info(
31774
- `Claude guard: ${claudeGuardInstalled(path39.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
32363
+ `Claude guard: ${claudeGuardInstalled(path44.resolve(argv.repo)) ? `installed (${CLAUDE_GUARD_RELATIVE_PATH})` : "not installed"}`
31775
32364
  );
31776
32365
  } catch (err) {
31777
32366
  error(err instanceof Error ? err.message : String(err));
@@ -32032,10 +32621,10 @@ function assignProp(target, prop, value) {
32032
32621
  configurable: true
32033
32622
  });
32034
32623
  }
32035
- function getElementAtPath(obj, path41) {
32036
- if (!path41)
32624
+ function getElementAtPath(obj, path47) {
32625
+ if (!path47)
32037
32626
  return obj;
32038
- return path41.reduce((acc, key) => acc?.[key], obj);
32627
+ return path47.reduce((acc, key) => acc?.[key], obj);
32039
32628
  }
32040
32629
  function promiseAllObject(promisesObj) {
32041
32630
  const keys = Object.keys(promisesObj);
@@ -32355,11 +32944,11 @@ function aborted(x2, startIndex = 0) {
32355
32944
  }
32356
32945
  return false;
32357
32946
  }
32358
- function prefixIssues(path41, issues) {
32947
+ function prefixIssues(path47, issues) {
32359
32948
  return issues.map((iss) => {
32360
32949
  var _a3;
32361
32950
  (_a3 = iss).path ?? (_a3.path = []);
32362
- iss.path.unshift(path41);
32951
+ iss.path.unshift(path47);
32363
32952
  return iss;
32364
32953
  });
32365
32954
  }
@@ -37682,7 +38271,7 @@ var Protocol = class {
37682
38271
  return;
37683
38272
  }
37684
38273
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
37685
- await new Promise((resolve28) => setTimeout(resolve28, pollInterval));
38274
+ await new Promise((resolve33) => setTimeout(resolve33, pollInterval));
37686
38275
  options?.signal?.throwIfAborted();
37687
38276
  }
37688
38277
  } catch (error3) {
@@ -37699,7 +38288,7 @@ var Protocol = class {
37699
38288
  */
37700
38289
  request(request, resultSchema, options) {
37701
38290
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
37702
- return new Promise((resolve28, reject) => {
38291
+ return new Promise((resolve33, reject) => {
37703
38292
  const earlyReject = (error3) => {
37704
38293
  reject(error3);
37705
38294
  };
@@ -37777,7 +38366,7 @@ var Protocol = class {
37777
38366
  if (!parseResult.success) {
37778
38367
  reject(parseResult.error);
37779
38368
  } else {
37780
- resolve28(parseResult.data);
38369
+ resolve33(parseResult.data);
37781
38370
  }
37782
38371
  } catch (error3) {
37783
38372
  reject(error3);
@@ -38038,12 +38627,12 @@ var Protocol = class {
38038
38627
  }
38039
38628
  } catch {
38040
38629
  }
38041
- return new Promise((resolve28, reject) => {
38630
+ return new Promise((resolve33, reject) => {
38042
38631
  if (signal.aborted) {
38043
38632
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
38044
38633
  return;
38045
38634
  }
38046
- const timeoutId = setTimeout(resolve28, interval);
38635
+ const timeoutId = setTimeout(resolve33, interval);
38047
38636
  signal.addEventListener("abort", () => {
38048
38637
  clearTimeout(timeoutId);
38049
38638
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -38833,7 +39422,7 @@ var Server = class extends Protocol {
38833
39422
  };
38834
39423
 
38835
39424
  // src/mcp/server.ts
38836
- var path40 = __toESM(require("path"));
39425
+ var path45 = __toESM(require("path"));
38837
39426
 
38838
39427
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/stdio.js
38839
39428
  var import_node_process2 = __toESM(require("node:process"), 1);
@@ -38916,12 +39505,12 @@ var StdioServerTransport = class {
38916
39505
  this.onclose?.();
38917
39506
  }
38918
39507
  send(message) {
38919
- return new Promise((resolve28) => {
39508
+ return new Promise((resolve33) => {
38920
39509
  const json = serializeMessage(message);
38921
39510
  if (this._stdout.write(json)) {
38922
- resolve28();
39511
+ resolve33();
38923
39512
  } else {
38924
- this._stdout.once("drain", resolve28);
39513
+ this._stdout.once("drain", resolve33);
38925
39514
  }
38926
39515
  });
38927
39516
  }
@@ -39465,7 +40054,7 @@ async function handleMcpToolCall(toolName, args, defaultRepo = ".") {
39465
40054
  const input = ControlUpInputSchema.parse({ ...args, repo });
39466
40055
  const result = await startControlAndSync({
39467
40056
  detach: input.detach,
39468
- cwd: path40.resolve(input.repo)
40057
+ cwd: path45.resolve(input.repo)
39469
40058
  });
39470
40059
  if (result.code !== 0) {
39471
40060
  return {
@@ -39527,9 +40116,231 @@ var mcpCommand = {
39527
40116
  }
39528
40117
  };
39529
40118
 
40119
+ // src/cli/commands/telemetry.ts
40120
+ var fs39 = __toESM(require("fs"));
40121
+ var path46 = __toESM(require("path"));
40122
+ function printSignals() {
40123
+ info("Collected when enabled (local Mission Control only):");
40124
+ for (const signal of TELEMETRY_SIGNALS) {
40125
+ info(` \u2022 ${signal}`);
40126
+ }
40127
+ info("Disable anytime: har telemetry off");
40128
+ }
40129
+ async function handleStatus3(argv) {
40130
+ const preference = readTelemetryPreference();
40131
+ const enabled = isTelemetryEnabled();
40132
+ const apiUrl = getControlApiUrl();
40133
+ const reachable = enabled ? await isControlApiReachable(apiUrl) : false;
40134
+ const payload = {
40135
+ enabled,
40136
+ preferenceEnabled: preference.enabled,
40137
+ preferencePath: getTelemetryPreferencePath(),
40138
+ updatedAt: preference.updatedAt ?? null,
40139
+ envOverride: process.env.HAR_TELEMETRY ?? null,
40140
+ controlApiUrl: apiUrl,
40141
+ controlReachable: reachable,
40142
+ otelEndpoint: `${apiUrl.replace(/\/$/, "")}/api/otel`,
40143
+ signals: [...TELEMETRY_SIGNALS]
40144
+ };
40145
+ if (argv.json) {
40146
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}
40147
+ `);
40148
+ return;
40149
+ }
40150
+ header("har telemetry status");
40151
+ info(`Enabled: ${enabled ? "yes" : "no"}${process.env.HAR_TELEMETRY ? " (HAR_TELEMETRY override)" : ""}`);
40152
+ info(`Preference: ${getTelemetryPreferencePath()}${preference.updatedAt ? ` (updated ${preference.updatedAt})` : " (default on)"}`);
40153
+ info(`Mission Control: ${apiUrl} (${reachable ? "reachable" : "not reachable"})`);
40154
+ info(`OTLP endpoint: ${payload.otelEndpoint}`);
40155
+ printSignals();
40156
+ }
40157
+ async function handleOn() {
40158
+ header("har telemetry on");
40159
+ writeTelemetryPreference(true);
40160
+ success("Telemetry enabled (opt-out). Agent usage will be stored in local Mission Control.");
40161
+ printSignals();
40162
+ const result = await ensureTelemetryInfrastructure({ startIfNeeded: true });
40163
+ if (result.message) success(result.message);
40164
+ if (result.warning) warn(result.warning);
40165
+ if (result.otelReady) {
40166
+ info(`OTLP ingest: ${result.apiUrl.replace(/\/$/, "")}/api/otel`);
40167
+ info("Usage appears under Mission Control \u2192 Worktrees. Disable: har telemetry off");
40168
+ }
40169
+ }
40170
+ async function handleOff() {
40171
+ header("har telemetry off");
40172
+ writeTelemetryPreference(false);
40173
+ success("Telemetry disabled.");
40174
+ info("Future launches will not inject OTEL exporters or auto-start Mission Control.");
40175
+ info("Existing usage rows in Mission Control are kept. Historical data is not deleted.");
40176
+ }
40177
+ async function handleWriteEnv(argv) {
40178
+ const repoPath = path46.resolve(argv.repo);
40179
+ const harnessRoot = resolveHarnessRoot(repoPath);
40180
+ const session = readSlotRegistry(harnessRoot, argv.agentId);
40181
+ const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40182
+ const branch = argv.branch ?? session?.branch;
40183
+ const suffix = argv.suffix ?? session?.suffix;
40184
+ const purpose = argv.purpose ?? session?.purpose;
40185
+ const sessionKey = argv.sessionKey ?? buildSessionKey({
40186
+ branch,
40187
+ agentId: argv.agentId,
40188
+ suffix,
40189
+ createdAt: session?.createdAt
40190
+ });
40191
+ const envFile = argv.envFile ?? path46.join(workDir, `.env.agent.${argv.agentId}`);
40192
+ let otelReady = argv.otelReady;
40193
+ if (otelReady === void 0 && isTelemetryEnabled()) {
40194
+ const ensured = await ensureTelemetryInfrastructure({ startIfNeeded: false });
40195
+ otelReady = ensured.otelReady;
40196
+ }
40197
+ const attrs = {
40198
+ sessionKey,
40199
+ agentId: argv.agentId,
40200
+ repoPath,
40201
+ workDir,
40202
+ branch,
40203
+ suffix,
40204
+ purpose
40205
+ };
40206
+ appendTelemetryEnvToFile(envFile, attrs, { otelReady: otelReady !== false && isTelemetryEnabled() });
40207
+ success(`Wrote telemetry env to ${envFile}`);
40208
+ }
40209
+ function handlePrintEnv(argv) {
40210
+ const repoPath = path46.resolve(argv.repo);
40211
+ const workDir = argv.workDir ?? repoPath;
40212
+ const sessionKey = buildSessionKey({
40213
+ branch: argv.branch,
40214
+ agentId: argv.agentId,
40215
+ suffix: argv.suffix
40216
+ });
40217
+ process.stdout.write(
40218
+ buildTelemetryEnvBlock(
40219
+ {
40220
+ sessionKey,
40221
+ agentId: argv.agentId,
40222
+ repoPath,
40223
+ workDir,
40224
+ branch: argv.branch,
40225
+ suffix: argv.suffix
40226
+ },
40227
+ { otelReady: argv.otelReady && isTelemetryEnabled() }
40228
+ )
40229
+ );
40230
+ }
40231
+ function handleCodexSnippet(argv) {
40232
+ const repoPath = path46.resolve(argv.repo);
40233
+ const harnessRoot = resolveHarnessRoot(repoPath);
40234
+ const session = readSlotRegistry(harnessRoot, argv.agentId);
40235
+ const workDir = argv.workDir ?? session?.workDir ?? repoPath;
40236
+ const branch = argv.branch ?? session?.branch;
40237
+ const suffix = argv.suffix ?? session?.suffix;
40238
+ const sessionKey = buildSessionKey({
40239
+ branch,
40240
+ agentId: argv.agentId,
40241
+ suffix,
40242
+ createdAt: session?.createdAt
40243
+ });
40244
+ const snippet = buildCodexOtelSnippet({
40245
+ sessionKey,
40246
+ agentId: argv.agentId,
40247
+ repoPath,
40248
+ workDir,
40249
+ branch,
40250
+ suffix,
40251
+ purpose: session?.purpose
40252
+ });
40253
+ if (argv.write) {
40254
+ const outDir = path46.join(harnessRoot, ".har", "telemetry");
40255
+ fs39.mkdirSync(outDir, { recursive: true });
40256
+ const outPath = path46.join(outDir, "codex.otel.toml.snippet");
40257
+ fs39.writeFileSync(outPath, snippet);
40258
+ success(`Wrote ${outPath}`);
40259
+ info("Merge the [otel] table into ~/.codex/config.toml manually.");
40260
+ return;
40261
+ }
40262
+ process.stdout.write(snippet);
40263
+ }
40264
+ var telemetryCommand = {
40265
+ command: "telemetry <subcommand>",
40266
+ describe: "Agent usage telemetry (Claude Code / Codex \u2192 Mission Control)",
40267
+ builder: (yargs) => yargs.command(
40268
+ "status",
40269
+ "Show telemetry preference and Mission Control reachability",
40270
+ (y2) => y2.option("json", { type: "boolean", default: false }),
40271
+ (argv) => {
40272
+ void handleStatus3({ json: argv.json });
40273
+ }
40274
+ ).command(
40275
+ "on",
40276
+ "Enable telemetry (default) and ensure Mission Control is running",
40277
+ () => {
40278
+ },
40279
+ () => {
40280
+ void handleOn();
40281
+ }
40282
+ ).command(
40283
+ "off",
40284
+ "Disable telemetry (no OTEL injection, no MC auto-start)",
40285
+ () => {
40286
+ },
40287
+ () => {
40288
+ void handleOff();
40289
+ }
40290
+ ).command(
40291
+ "write-env",
40292
+ "Append session + OTEL env vars to .env.agent.<id>",
40293
+ (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" }),
40294
+ (argv) => handleWriteEnv({
40295
+ agentId: argv["agent-id"],
40296
+ repo: argv.repo,
40297
+ envFile: argv["env-file"],
40298
+ workDir: argv["work-dir"],
40299
+ branch: argv.branch,
40300
+ suffix: argv.suffix,
40301
+ purpose: argv.purpose,
40302
+ sessionKey: argv["session-key"],
40303
+ otelReady: argv["otel-ready"]
40304
+ }).catch((err) => {
40305
+ error(err instanceof Error ? err.message : String(err));
40306
+ process.exitCode = 1;
40307
+ })
40308
+ ).command(
40309
+ "print-env",
40310
+ "Print telemetry env block to stdout",
40311
+ (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 }),
40312
+ (argv) => handlePrintEnv({
40313
+ agentId: argv["agent-id"],
40314
+ repo: argv.repo,
40315
+ workDir: argv["work-dir"],
40316
+ branch: argv.branch,
40317
+ suffix: argv.suffix,
40318
+ otelReady: argv["otel-ready"]
40319
+ })
40320
+ ).command(
40321
+ "codex-snippet",
40322
+ "Print or write a Codex [otel] config snippet",
40323
+ (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", {
40324
+ type: "boolean",
40325
+ default: false,
40326
+ describe: "Write to .har/telemetry/codex.otel.toml.snippet"
40327
+ }),
40328
+ (argv) => handleCodexSnippet({
40329
+ agentId: argv["agent-id"],
40330
+ repo: argv.repo,
40331
+ workDir: argv["work-dir"],
40332
+ branch: argv.branch,
40333
+ suffix: argv.suffix,
40334
+ write: argv.write
40335
+ })
40336
+ ).demandCommand(1, "Please specify a subcommand: status, on, off, write-env, print-env, codex-snippet"),
40337
+ handler: () => {
40338
+ }
40339
+ };
40340
+
39530
40341
  // src/cli/index.ts
39531
40342
  async function runCli() {
39532
- await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
40343
+ await yargs_default(hideBin(process.argv)).scriptName("har").usage("$0 <command> [options]").command(envCommand).command(agentsCommand).command(controlCommand).command(hooksCommand).command(mcpCommand).command(telemetryCommand).demandCommand(1, "Please specify a command. Try: har env init").strict().help().version(getHarPackageVersion()).parse();
39533
40344
  }
39534
40345
 
39535
40346
  // src/index.ts