@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
|
|
769
|
-
if (
|
|
768
|
+
const path47 = url.path;
|
|
769
|
+
if (path47.length === 0) {
|
|
770
770
|
return;
|
|
771
771
|
}
|
|
772
|
-
if (url.scheme === "file" &&
|
|
772
|
+
if (url.scheme === "file" && path47.length === 1 && isNormalizedWindowsDriveLetter(path47[0])) {
|
|
773
773
|
return;
|
|
774
774
|
}
|
|
775
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2980
|
+
resolve33(response);
|
|
2981
2981
|
});
|
|
2982
2982
|
raw.on("end", function() {
|
|
2983
2983
|
if (!response) {
|
|
2984
2984
|
response = new Response3(body, response_options);
|
|
2985
|
-
|
|
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
|
-
|
|
2993
|
+
resolve33(response);
|
|
2994
2994
|
return;
|
|
2995
2995
|
}
|
|
2996
2996
|
response = new Response3(body, response_options);
|
|
2997
|
-
|
|
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(
|
|
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:
|
|
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(
|
|
6371
|
-
const stats = (0, import_fs5.statSync)(
|
|
6372
|
-
return createFileFromPath(
|
|
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(
|
|
6375
|
-
const stats = await import_fs5.promises.stat(
|
|
6376
|
-
return createFileFromPath(
|
|
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 =
|
|
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
|
|
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(
|
|
9633
|
-
let input =
|
|
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 [
|
|
9886
|
-
wsComponent.path =
|
|
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
|
|
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:
|
|
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,
|
|
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,
|
|
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: (
|
|
14568
|
+
require: (path47) => {
|
|
14569
14569
|
if (typeof require !== "undefined") {
|
|
14570
|
-
return require(
|
|
14571
|
-
} else if (
|
|
14572
|
-
return JSON.parse((0, import_fs2.readFileSync)(
|
|
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
|
|
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 +=
|
|
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((
|
|
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
|
-
|
|
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:
|
|
19254
|
-
const fullPath = [...
|
|
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,
|
|
19370
|
+
constructor(parent, value, path47, key) {
|
|
19371
19371
|
this._cachedPath = [];
|
|
19372
19372
|
this.parent = parent;
|
|
19373
19373
|
this.data = value;
|
|
19374
|
-
this._path =
|
|
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((
|
|
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
|
-
|
|
23403
|
+
resolve33(true);
|
|
23381
23404
|
return;
|
|
23382
23405
|
}
|
|
23383
|
-
|
|
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
|
|
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(
|
|
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(
|
|
23994
|
+
console.warn(`fileFromPath is deprecated; use fs.createReadStream(${JSON.stringify(path47)}) instead`);
|
|
23972
23995
|
fileFromPathWarned = true;
|
|
23973
23996
|
}
|
|
23974
|
-
return await _fileFromPath(
|
|
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((
|
|
24471
|
-
|
|
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(
|
|
24572
|
-
return this.methodRequest("get",
|
|
24594
|
+
get(path47, opts) {
|
|
24595
|
+
return this.methodRequest("get", path47, opts);
|
|
24573
24596
|
}
|
|
24574
|
-
post(
|
|
24575
|
-
return this.methodRequest("post",
|
|
24597
|
+
post(path47, opts) {
|
|
24598
|
+
return this.methodRequest("post", path47, opts);
|
|
24576
24599
|
}
|
|
24577
|
-
patch(
|
|
24578
|
-
return this.methodRequest("patch",
|
|
24600
|
+
patch(path47, opts) {
|
|
24601
|
+
return this.methodRequest("patch", path47, opts);
|
|
24579
24602
|
}
|
|
24580
|
-
put(
|
|
24581
|
-
return this.methodRequest("put",
|
|
24603
|
+
put(path47, opts) {
|
|
24604
|
+
return this.methodRequest("put", path47, opts);
|
|
24582
24605
|
}
|
|
24583
|
-
delete(
|
|
24584
|
-
return this.methodRequest("delete",
|
|
24606
|
+
delete(path47, opts) {
|
|
24607
|
+
return this.methodRequest("delete", path47, opts);
|
|
24585
24608
|
}
|
|
24586
|
-
methodRequest(method,
|
|
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:
|
|
24612
|
+
return { method, path: path47, ...opts2, body };
|
|
24590
24613
|
}));
|
|
24591
24614
|
}
|
|
24592
|
-
getAPIList(
|
|
24593
|
-
return this.requestAPIList(Page, { method: "get", path:
|
|
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:
|
|
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(
|
|
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(
|
|
24724
|
-
const url = isAbsoluteURL(
|
|
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((
|
|
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((
|
|
25501
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveConnectedPromise,
|
|
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((
|
|
25505
|
-
__classPrivateFieldSet7(this, _PromptCachingBetaMessageStream_resolveEndPromise,
|
|
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((
|
|
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,
|
|
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((
|
|
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((
|
|
26014
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveConnectedPromise,
|
|
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((
|
|
26018
|
-
__classPrivateFieldSet8(this, _MessageStream_resolveEndPromise,
|
|
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((
|
|
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,
|
|
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((
|
|
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((
|
|
26661
|
+
return new Promise((resolve33) => {
|
|
26639
26662
|
process.stderr.write(`${question} `);
|
|
26640
26663
|
rl.once("line", (answer) => {
|
|
26641
26664
|
rl.close();
|
|
26642
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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) =>
|
|
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((
|
|
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) =>
|
|
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((
|
|
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
|
-
|
|
28424
|
+
resolve33(true);
|
|
28402
28425
|
return;
|
|
28403
28426
|
}
|
|
28404
|
-
|
|
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
|
|
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((
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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((
|
|
30852
|
-
rl.question("Replace this slot? [y/N] ",
|
|
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((
|
|
30869
|
-
rl.question("Discard uncommitted changes? [y/N] ",
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
|
31940
|
+
var path44 = __toESM(require("path"));
|
|
31352
31941
|
|
|
31353
31942
|
// src/core/hooks.ts
|
|
31354
|
-
var
|
|
31355
|
-
var
|
|
31356
|
-
var
|
|
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
|
|
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}" "${
|
|
31978
|
+
return `"${process.execPath}" "${path42.resolve(entry)}"`;
|
|
31390
31979
|
}
|
|
31391
|
-
if (entry) return `"${
|
|
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 (!
|
|
31413
|
-
|
|
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 =
|
|
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
|
-
|
|
31424
|
-
|
|
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
|
-
|
|
32017
|
+
fs37.writeFileSync(filePath, `${content}${suffix}
|
|
31429
32018
|
${block}
|
|
31430
32019
|
`);
|
|
31431
|
-
|
|
32020
|
+
fs37.chmodSync(filePath, 493);
|
|
31432
32021
|
return "appended";
|
|
31433
32022
|
}
|
|
31434
32023
|
function removeMarkedBlock(filePath) {
|
|
31435
|
-
if (!
|
|
31436
|
-
const content =
|
|
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
|
-
|
|
32032
|
+
fs37.rmSync(filePath);
|
|
31444
32033
|
} else {
|
|
31445
|
-
|
|
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
|
-
|
|
32056
|
+
fs37.mkdirSync(hooksDir, { recursive: true });
|
|
31468
32057
|
const invocation = options.harInvocation ?? defaultHarInvocation();
|
|
31469
|
-
|
|
31470
|
-
|
|
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
|
-
|
|
31475
|
-
|
|
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(
|
|
31480
|
-
const postCommit = upsertMarkedBlock(
|
|
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(
|
|
31490
|
-
removed = removeMarkedBlock(
|
|
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 =
|
|
31493
|
-
if (
|
|
31494
|
-
|
|
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 =
|
|
31512
|
-
const resolved =
|
|
31513
|
-
return resolved.startsWith(`${worktreesRoot}${
|
|
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 =
|
|
31527
|
-
return
|
|
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") &&
|
|
31534
|
-
postCommitInstalled: hasBlock("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 (!
|
|
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
|
|
31617
|
-
var
|
|
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 (!
|
|
31629
|
-
const raw =
|
|
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
|
-
|
|
31639
|
-
|
|
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 =
|
|
31647
|
-
|
|
31648
|
-
|
|
31649
|
-
|
|
31650
|
-
const settingsPath =
|
|
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 =
|
|
31665
|
-
if (
|
|
31666
|
-
|
|
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 =
|
|
31669
|
-
if (
|
|
32257
|
+
const parent = path43.dirname(guardScript);
|
|
32258
|
+
if (fs38.existsSync(parent) && fs38.readdirSync(parent).length === 0) fs38.rmdirSync(parent);
|
|
31670
32259
|
}
|
|
31671
|
-
const settingsPath =
|
|
31672
|
-
if (
|
|
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 =
|
|
31698
|
-
if (!
|
|
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(
|
|
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:
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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,
|
|
32036
|
-
if (!
|
|
32624
|
+
function getElementAtPath(obj, path47) {
|
|
32625
|
+
if (!path47)
|
|
32037
32626
|
return obj;
|
|
32038
|
-
return
|
|
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(
|
|
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(
|
|
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((
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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(
|
|
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
|
|
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((
|
|
39508
|
+
return new Promise((resolve33) => {
|
|
38920
39509
|
const json = serializeMessage(message);
|
|
38921
39510
|
if (this._stdout.write(json)) {
|
|
38922
|
-
|
|
39511
|
+
resolve33();
|
|
38923
39512
|
} else {
|
|
38924
|
-
this._stdout.once("drain",
|
|
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:
|
|
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
|