@harnessforce/claude-code-plugin 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1895 @@
1
+ let node_os = require("node:os");
2
+ let node_path = require("node:path");
3
+ let node_perf_hooks = require("node:perf_hooks");
4
+ let node_crypto = require("node:crypto");
5
+ let node_fs_promises = require("node:fs/promises");
6
+ let node_child_process = require("node:child_process");
7
+ let node_fs = require("node:fs");
8
+ const NO_WHITE_SPACE = new RegExp(`^[^\\t-\\r \\u0085\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000]+$`);
9
+ const isToken = (value, maxLength = 256) => {
10
+ const length = [...value].length;
11
+ return length >= 1 && length <= maxLength && NO_WHITE_SPACE.test(value);
12
+ };
13
+ const INSTANT_PATTERN = `^(?:1(?:6(?:7[7-9]|[89]\\d)|[7-9]\\d\\d)|2(?:[01]\\d\\d|2(?:[0-5]\\d|6[0-2])))-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$`;
14
+ const INSTANT_PARTS = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?(?:Z|([+-])(\d{2}):(\d{2}))$/;
15
+ const INSTANT = new RegExp(INSTANT_PATTERN);
16
+ const MIN_INSTANT_MS = Date.UTC(1677, 8, 21, 0, 12, 44);
17
+ const MAX_INSTANT_MS = Date.UTC(2262, 3, 11, 23, 47, 16);
18
+ function isInstant(value) {
19
+ const parts = INSTANT.test(value) ? INSTANT_PARTS.exec(value) : null;
20
+ if (!parts) return false;
21
+ const [, ...fields] = parts;
22
+ const [year, month, day, hour, minute, second] = fields.slice(0, 6).map(Number);
23
+ const [fraction = "", sign, offsetHours = "0", offsetMinutes = "0"] = fields.slice(6);
24
+ const localMs = Date.UTC(year, month - 1, day, hour, minute, second);
25
+ const local = new Date(localMs);
26
+ if (!(local.getUTCFullYear() === year && local.getUTCMonth() === month - 1 && local.getUTCDate() === day && local.getUTCHours() === hour && local.getUTCMinutes() === minute && local.getUTCSeconds() === second)) return false;
27
+ const secondMs = localMs - (sign === "-" ? -1 : 1) * (Number(offsetHours) * 60 + Number(offsetMinutes)) * 6e4;
28
+ const nanos = Number(fraction.slice(0, 9) || "0");
29
+ return secondMs >= MIN_INSTANT_MS && !(secondMs > MAX_INSTANT_MS || secondMs === MAX_INSTANT_MS && nanos > 0);
30
+ }
31
+ //#endregion
32
+ //#region ../../packages/semconv/dist/repository.js
33
+ const SCHEME_URL = /^[a-z][a-z0-9+.-]*:\/\//i;
34
+ const SCP_LIKE = /^(?:[^@/\s]+@)?([^:/\s]+):(?!\/)(.+)$/;
35
+ const HOST = /^[a-z0-9.-]+$/;
36
+ const SEGMENT = /^[A-Za-z0-9_.-]+$/;
37
+ function splitRemote(remote) {
38
+ if (SCHEME_URL.test(remote)) {
39
+ let url;
40
+ try {
41
+ url = new URL(remote);
42
+ } catch {
43
+ return;
44
+ }
45
+ return url.protocol === "file:" ? void 0 : {
46
+ host: url.hostname,
47
+ path: url.pathname
48
+ };
49
+ }
50
+ const scp = SCP_LIKE.exec(remote);
51
+ return scp?.[1] && scp[2] ? {
52
+ host: scp[1],
53
+ path: scp[2]
54
+ } : void 0;
55
+ }
56
+ function normalizeRepository(remoteUrl) {
57
+ const location = splitRemote(remoteUrl.trim());
58
+ if (!location) return void 0;
59
+ const lowerHost = location.host.toLowerCase();
60
+ const host = lowerHost === "ssh.github.com" ? "github.com" : lowerHost;
61
+ const segments = location.path.replace(/\/+$/, "").replace(/\.git$/, "").replace(/^\/+/, "").split("/");
62
+ if (segments.length !== 2 || !HOST.test(host) || !segments.every((segment) => SEGMENT.test(segment))) return void 0;
63
+ const [owner, name] = host === "github.com" ? segments.map((segment) => segment.toLowerCase()) : segments;
64
+ return `${host}/${owner}/${name}`;
65
+ }
66
+ //#endregion
67
+ //#region ../../packages/agent-core/src/object.ts
68
+ const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
69
+ //#endregion
70
+ //#region ../../packages/agent-core/src/config/canonical.ts
71
+ const byCodeUnit$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
72
+ function canonicalJson(value) {
73
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
74
+ if (typeof value === "object" && value !== null) return `{${Object.keys(value).sort(byCodeUnit$1).map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
75
+ return JSON.stringify(value);
76
+ }
77
+ const sha256 = (data) => (0, node_crypto.createHash)("sha256").update(data).digest("hex");
78
+ const hashValue = (value) => sha256(Buffer.from(canonicalJson(value), "utf8"));
79
+ function hashFileContent(content) {
80
+ const normalized = content.includes("\r\n") ? Buffer.from(content.toString("latin1").replace(/\r\n/g, "\n"), "latin1") : content;
81
+ return sha256(normalized);
82
+ }
83
+ function sortComponents(components) {
84
+ return [...components].sort((a, b) => byCodeUnit$1(a.kind, b.kind) || byCodeUnit$1(a.source, b.source) || byCodeUnit$1(a.id, b.id));
85
+ }
86
+ //#endregion
87
+ //#region ../../packages/agent-core/src/config/files.ts
88
+ const MAX_DEPTH = 8;
89
+ const MAX_FILE_BYTES = 1048576;
90
+ var CollectionExpired = class extends Error {};
91
+ async function readFileIfExists(path, guard) {
92
+ guard();
93
+ const info = await (0, node_fs_promises.stat)(path).catch(() => void 0);
94
+ if (!info?.isFile() || info.size > MAX_FILE_BYTES) return void 0;
95
+ return (0, node_fs_promises.readFile)(path).catch(() => void 0);
96
+ }
97
+ async function exists(path, guard) {
98
+ guard();
99
+ return (0, node_fs_promises.stat)(path).then(() => true, () => false);
100
+ }
101
+ async function readJsonObjectFile(path, guard) {
102
+ const content = await readFileIfExists(path, guard);
103
+ if (!content) return void 0;
104
+ try {
105
+ const value = JSON.parse(content.toString("utf8"));
106
+ return isObject(value) ? value : void 0;
107
+ } catch {
108
+ return;
109
+ }
110
+ }
111
+ async function entries(dir, guard) {
112
+ guard();
113
+ return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch(() => [])).filter((entry) => !entry.name.startsWith("."));
114
+ }
115
+ async function kindOf(dir, entry, guard) {
116
+ if (entry.isFile()) return "file";
117
+ if (entry.isDirectory()) return "directory";
118
+ if (!entry.isSymbolicLink()) return void 0;
119
+ guard();
120
+ const target = await (0, node_fs_promises.stat)((0, node_path.join)(dir, entry.name)).catch(() => void 0);
121
+ if (target?.isFile()) return "file";
122
+ return target?.isDirectory() ? "directory" : void 0;
123
+ }
124
+ async function listFiles(dir, ext, recursive, guard) {
125
+ const found = [];
126
+ const visited = /* @__PURE__ */ new Set();
127
+ const walk = async (relative, depth) => {
128
+ const current = relative ? (0, node_path.join)(dir, relative) : dir;
129
+ guard();
130
+ const real = await (0, node_fs_promises.realpath)(current).catch(() => void 0);
131
+ if (real === void 0 || visited.has(real)) return;
132
+ visited.add(real);
133
+ for (const entry of await entries(current, guard)) {
134
+ const path = relative ? `${relative}/${entry.name}` : entry.name;
135
+ const kind = await kindOf(current, entry, guard);
136
+ if (kind === "file" && entry.name.endsWith(ext)) found.push(path);
137
+ if (kind === "directory" && recursive && depth < MAX_DEPTH) await walk(path, depth + 1);
138
+ }
139
+ };
140
+ await walk("", 0);
141
+ return found;
142
+ }
143
+ async function listDirectories(dir, guard) {
144
+ const names = [];
145
+ for (const entry of await entries(dir, guard)) if (await kindOf(dir, entry, guard) === "directory") names.push(entry.name);
146
+ return names;
147
+ }
148
+ //#endregion
149
+ //#region ../../packages/agent-core/src/config/hash-input.ts
150
+ function withoutSecrets(entry) {
151
+ const { env, headers, url, args: _args, ...rest } = entry;
152
+ const result = rest;
153
+ if (isObject(env)) result.env = Object.keys(env).sort(byCodeUnit$1);
154
+ if (isObject(headers)) result.headers = Object.keys(headers).sort(byCodeUnit$1);
155
+ const strippedUrl = typeof url === "string" ? withoutCredentials(url) : void 0;
156
+ if (strippedUrl !== void 0) result.url = strippedUrl;
157
+ return result;
158
+ }
159
+ function withoutCredentials(url) {
160
+ let parsed;
161
+ try {
162
+ parsed = new URL(url);
163
+ } catch {
164
+ return;
165
+ }
166
+ parsed.username = "";
167
+ parsed.password = "";
168
+ parsed.search = "";
169
+ parsed.hash = "";
170
+ return parsed.href;
171
+ }
172
+ const reduceHookGroup = (group) => isObject(group) && Array.isArray(group.hooks) ? {
173
+ ...group,
174
+ hooks: group.hooks.map((handler) => isObject(handler) ? withoutSecrets(handler) : handler)
175
+ } : group;
176
+ function hashInput(kind, value) {
177
+ if (kind === "mcp_server" && isObject(value)) return withoutSecrets(value);
178
+ if (kind === "hook" && Array.isArray(value)) return value.map(reduceHookGroup);
179
+ return value;
180
+ }
181
+ //#endregion
182
+ //#region ../../packages/agent-core/src/config/scope.ts
183
+ const MAX_COMPONENTS = 1e3;
184
+ var TooManyComponents = class extends Error {};
185
+ var Sink = class {
186
+ guard;
187
+ components = [];
188
+ constructor(guard) {
189
+ this.guard = guard;
190
+ }
191
+ add(component) {
192
+ if (!isToken(component.id)) return;
193
+ if (this.components.length >= MAX_COMPONENTS) throw new TooManyComponents();
194
+ this.components.push(component);
195
+ }
196
+ hasDuplicates() {
197
+ return new Set(this.components.map((c) => `${c.kind}\0${c.source}\0${c.id}`)).size !== this.components.length;
198
+ }
199
+ scope = (source, idPrefix = "", version) => {
200
+ const add = (kind, id, hash) => this.add({
201
+ kind,
202
+ source,
203
+ id: `${idPrefix}${id}`,
204
+ ...version === void 0 ? {} : { version },
205
+ hash
206
+ });
207
+ return {
208
+ guard: this.guard,
209
+ addValue: (kind, id, value) => add(kind, id, hashValue(hashInput(kind, value))),
210
+ addFile: async (kind, id, path) => {
211
+ const content = await readFileIfExists(path, this.guard);
212
+ if (content) add(kind, id, hashFileContent(content));
213
+ }
214
+ };
215
+ };
216
+ };
217
+ const absoluteEnv = (value) => value && (0, node_path.isAbsolute)(value) ? value : void 0;
218
+ const withoutExt = (path, ext) => path.slice(0, -ext.length);
219
+ const colonName = (path) => withoutExt(path, ".md").replace(/\//g, ":");
220
+ async function collectLayout(s, layout) {
221
+ if (layout.skills) for (const name of await listDirectories(layout.skills, s.guard)) await s.addFile("skill", name, (0, node_path.join)(layout.skills, name, "SKILL.md"));
222
+ if (layout.agents) for (const path of await listFiles(layout.agents, ".md", true, s.guard)) await s.addFile("agent", colonName(path), (0, node_path.join)(layout.agents, path));
223
+ if (layout.commands) for (const path of await listFiles(layout.commands, ".md", true, s.guard)) await s.addFile("command", colonName(path), (0, node_path.join)(layout.commands, path));
224
+ if (layout.workflows) for (const path of await listFiles(layout.workflows, ".js", false, s.guard)) await s.addFile("workflow", withoutExt(path, ".js"), (0, node_path.join)(layout.workflows, path));
225
+ }
226
+ async function collectRules(s, base, names, rulesDir) {
227
+ for (const name of names) await s.addFile("rule", name, (0, node_path.join)(base, name));
228
+ if (rulesDir) for (const path of await listFiles(rulesDir, ".md", true, s.guard)) await s.addFile("rule", `rules/${path}`, (0, node_path.join)(rulesDir, path));
229
+ }
230
+ function collectHooks(s, hooks) {
231
+ if (!isObject(hooks)) return;
232
+ for (const [event, value] of Object.entries(hooks)) s.addValue("hook", event, value);
233
+ }
234
+ function collectMcpServers(s, servers) {
235
+ if (!isObject(servers)) return;
236
+ for (const [name, value] of Object.entries(servers)) if (isObject(value)) s.addValue("mcp_server", name, value);
237
+ }
238
+ //#endregion
239
+ //#region ../../packages/agent-core/src/hook-warning.ts
240
+ const RESTART_FAILED_WARNING = "Could not restart the Harnessforce hook";
241
+ const INVALID_ENDPOINT_WARNING = "The Harnessforce destination is invalid. Run `harnessforce init` again";
242
+ const REGISTRATION_FAILED_WARNING = "Harnessforce session registration failed. Check your network connection";
243
+ const notConfiguredWarning = (agent) => agent === "claude_code" ? "Harnessforce sending is not configured. Run `/harnessforce:setup`" : "Harnessforce sending is not configured. Run `harnessforce init`";
244
+ const stopped = (agent) => agent === "claude_code" ? "telemetry is not being sent" : "sessions are not being registered";
245
+ const keyMissingWarning = (agent) => `Harnessforce cannot find your ingest key, so ${stopped(agent)}. Run \`harnessforce init\``;
246
+ const keyUnreadableWarning = (agent) => `Harnessforce could not read your ingest key from the OS keychain, so ${agent === "claude_code" ? "telemetry may not be sent" : "sessions may not be registered"}. Unlock the keychain or run \`harnessforce init\``;
247
+ const DESTINATION_MISMATCH_WARNING = {
248
+ claude_code: "Telemetry is not being sent because the Harnessforce destination differs from the one saved by `harnessforce init`. Check this repository's settings or run `harnessforce init` again",
249
+ codex: "Sessions are not being registered because the Harnessforce destination differs from the one saved by `harnessforce init`. Run `harnessforce init` again"
250
+ };
251
+ const TELEMETRY_NOT_CONFIGURED_WARNING = "Claude Code telemetry for Harnessforce is not configured. Run `harnessforce init`";
252
+ const warningOnlyOutput = (message) => `${JSON.stringify({ systemMessage: message })}\n`;
253
+ //#endregion
254
+ //#region ../../packages/agent-core/src/managed.ts
255
+ const MANAGED_DIRS = {
256
+ darwin: "/Library/Application Support/ClaudeCode",
257
+ win32: "C:\\Program Files\\ClaudeCode"
258
+ };
259
+ const managedDirFor = (platform) => MANAGED_DIRS[platform] ?? "/etc/claude-code";
260
+ const noGuard$2 = () => {};
261
+ async function readManagedEnv(managedDir, names) {
262
+ const dropInDir = (0, node_path.join)(managedDir, "managed-settings.d");
263
+ const dropIns = (await listFiles(dropInDir, ".json", false, noGuard$2)).sort(byCodeUnit$1);
264
+ const files = [(0, node_path.join)(managedDir, "managed-settings.json"), ...dropIns.map((name) => (0, node_path.join)(dropInDir, name))];
265
+ const values = Object.fromEntries(names.map((name) => [name, void 0]));
266
+ for (const file of files) {
267
+ const env = (await readJsonObjectFile(file, noGuard$2))?.env;
268
+ if (!isObject(env)) continue;
269
+ for (const name of names) if (typeof env[name] === "string") values[name] = env[name];
270
+ }
271
+ return values;
272
+ }
273
+ //#endregion
274
+ //#region ../../packages/agent-core/src/plugin-auto-update.ts
275
+ const MARKETPLACE_NAME = "harnessforce-agent";
276
+ const MARKETPLACE_KEYS = ["extraKnownMarketplaces", "additionalMarketplaces"];
277
+ const DISABLING_VARIABLES = [
278
+ "DISABLE_UPDATES",
279
+ "DISABLE_AUTOUPDATER",
280
+ "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC"
281
+ ];
282
+ const noGuard$1 = () => {};
283
+ function marketplacesOf(settings) {
284
+ const key = MARKETPLACE_KEYS.find((name) => settings?.[name] !== void 0);
285
+ const value = key === void 0 ? void 0 : settings?.[key];
286
+ return isObject(value) ? value : void 0;
287
+ }
288
+ const declaresEntry = (settings) => MARKETPLACE_KEYS.some((key) => {
289
+ const value = settings?.[key];
290
+ return isObject(value) && value["harnessforce-agent"] !== void 0;
291
+ });
292
+ async function managedFiles(managedDir) {
293
+ const dropInDir = (0, node_path.join)(managedDir, "managed-settings.d");
294
+ const dropIns = (await listFiles(dropInDir, ".json", false, noGuard$1)).sort(byCodeUnit$1);
295
+ return [(0, node_path.join)(managedDir, "managed-settings.json"), ...dropIns.map((name) => (0, node_path.join)(dropInDir, name))];
296
+ }
297
+ /**
298
+ * Claude Codeがpluginを自動更新するとみなせるか(client-updates.md「表示」)。確かめられない場合は偽とし、outdatedを表示する。
299
+ * user settingsのentryが真で、優先度の高いfileが同じ名前のentryを持たず、自動更新を止める変数が無い場合だけ真とする。
300
+ */
301
+ async function isPluginAutoUpdateEffective(options) {
302
+ const { env } = options;
303
+ if (DISABLING_VARIABLES.some((name) => env[name] === "1") && env.FORCE_AUTOUPDATE_PLUGINS !== "1") return false;
304
+ const higher = [
305
+ ...await managedFiles(options.managedDir),
306
+ (0, node_path.join)(options.projectDir, ".claude", "settings.json"),
307
+ (0, node_path.join)(options.projectDir, ".claude", "settings.local.json")
308
+ ];
309
+ for (const file of higher) {
310
+ if (!await (0, node_fs_promises.access)(file).then(() => true, () => false)) continue;
311
+ const settings = await readJsonObjectFile(file, noGuard$1);
312
+ if (!settings || declaresEntry(settings)) return false;
313
+ }
314
+ const entry = marketplacesOf(await readJsonObjectFile(options.userSettingsPath, noGuard$1))?.[MARKETPLACE_NAME];
315
+ return isObject(entry) && entry.autoUpdate === true;
316
+ }
317
+ //#endregion
318
+ //#region ../../packages/agent-core/src/process/lookup.ts
319
+ const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
320
+ const NPM_SHIM_MAX_BYTES = 65536;
321
+ const SHEBANG_MAX_BYTES = 256;
322
+ const CMD_UNSAFE = /["%!\r\n]/;
323
+ const NPM_SHIM_PROG_LINE = "SET \"_prog=%dp0%\\node.exe\"";
324
+ const NPM_SHIM_LAUNCH_LINE = /"%_prog%"[ \t]+"%dp0%\\([^"]+)"[ \t]+%\*$/;
325
+ const pathFor = (platform) => platform === "win32" ? node_path.win32 : node_path.posix;
326
+ const isBatch = (file) => /\.(cmd|bat)$/i.test(file);
327
+ function envValue(env, name, platform) {
328
+ if (platform !== "win32") return env[name];
329
+ const upper = name.toUpperCase();
330
+ const key = Object.keys(env).find((k) => k.toUpperCase() === upper);
331
+ return key === void 0 ? void 0 : env[key];
332
+ }
333
+ async function readBytes(path, length) {
334
+ try {
335
+ const handle = await (0, node_fs_promises.open)(path, "r");
336
+ try {
337
+ const buffer = Buffer.alloc(length);
338
+ const { bytesRead } = await handle.read(buffer, 0, length, 0);
339
+ return buffer.subarray(0, bytesRead);
340
+ } finally {
341
+ await handle.close();
342
+ }
343
+ } catch {
344
+ return;
345
+ }
346
+ }
347
+ const defaultFs = {
348
+ isFile: async (path) => {
349
+ try {
350
+ return (await (0, node_fs_promises.stat)(path)).isFile();
351
+ } catch {
352
+ return false;
353
+ }
354
+ },
355
+ isExecutable: async (path) => {
356
+ try {
357
+ await (0, node_fs_promises.access)(path, node_fs.constants.X_OK);
358
+ return true;
359
+ } catch {
360
+ return false;
361
+ }
362
+ },
363
+ exists: async (path) => {
364
+ try {
365
+ await (0, node_fs_promises.stat)(path);
366
+ return true;
367
+ } catch {
368
+ return false;
369
+ }
370
+ },
371
+ readSmallText: async (path, maxBytes) => {
372
+ const bytes = await readBytes(path, maxBytes + 1);
373
+ return bytes === void 0 || bytes.length > maxBytes ? void 0 : bytes.toString("utf8");
374
+ },
375
+ readHead: async (path, maxBytes) => (await readBytes(path, maxBytes))?.toString("utf8")
376
+ };
377
+ async function excludedDirectories(context) {
378
+ const path = pathFor(context.platform);
379
+ const fs = context.fs ?? defaultFs;
380
+ const key = (dir) => {
381
+ const resolved = path.resolve(dir);
382
+ return context.platform === "win32" ? resolved.toLowerCase() : resolved;
383
+ };
384
+ const exact = new Set(context.bases.map(key));
385
+ const roots = [];
386
+ for (const base of context.bases) {
387
+ let dir = path.resolve(base);
388
+ for (;;) {
389
+ if (await fs.exists(path.join(dir, ".git"))) {
390
+ roots.push(key(dir));
391
+ break;
392
+ }
393
+ const parent = path.dirname(dir);
394
+ if (parent === dir) break;
395
+ dir = parent;
396
+ }
397
+ }
398
+ return (dir) => {
399
+ const candidate = key(dir);
400
+ if (exact.has(candidate)) return true;
401
+ return roots.some((root) => {
402
+ const prefix = root.endsWith(path.sep) ? root : `${root}${path.sep}`;
403
+ return candidate === root || candidate.startsWith(prefix);
404
+ });
405
+ };
406
+ }
407
+ function candidateNames(name, context) {
408
+ if (context.platform !== "win32") return [name];
409
+ const extensions = (envValue(context.env, "PATHEXT", "win32") ?? DEFAULT_PATHEXT).split(";").filter(Boolean);
410
+ const extension = node_path.win32.extname(name).toLowerCase();
411
+ if (extension && extensions.some((e) => e.toLowerCase() === extension)) return [name];
412
+ return extensions.map((e) => `${name}${e}`);
413
+ }
414
+ async function isRunnable(file, context) {
415
+ const fs = context.fs ?? defaultFs;
416
+ if (!await fs.isFile(file)) return false;
417
+ return context.platform === "win32" || fs.isExecutable(file);
418
+ }
419
+ async function findIn(name, context, excluded) {
420
+ const path = pathFor(context.platform);
421
+ const dirs = (envValue(context.env, "PATH", context.platform) ?? "").split(path.delimiter).filter((dir) => dir !== "" && path.isAbsolute(dir) && !excluded(dir));
422
+ for (const dir of dirs) for (const candidate of candidateNames(name, context)) {
423
+ const file = path.join(dir, candidate);
424
+ if (await isRunnable(file, context)) return file;
425
+ }
426
+ }
427
+ async function findCommand(name, context) {
428
+ return findIn(name, context, await excludedDirectories(context));
429
+ }
430
+ async function findProgram(name, context) {
431
+ const file = await findCommand(name, context);
432
+ return file === void 0 || context.platform === "win32" && isBatch(file) ? void 0 : file;
433
+ }
434
+ async function npmShimScript(file, fs) {
435
+ if (!/\.cmd$/i.test(file)) return void 0;
436
+ const text = await fs.readSmallText(file, NPM_SHIM_MAX_BYTES);
437
+ if (text === void 0) return void 0;
438
+ const lines = text.split(/\r?\n/);
439
+ if (!lines.some((line) => line.trim() === NPM_SHIM_PROG_LINE)) return void 0;
440
+ const launches = lines.flatMap((line) => {
441
+ const match = NPM_SHIM_LAUNCH_LINE.exec(line);
442
+ return match?.[1] === void 0 ? [] : [match[1]];
443
+ });
444
+ const [script] = launches;
445
+ if (launches.length !== 1 || script === void 0 || CMD_UNSAFE.test(script)) return void 0;
446
+ return node_path.win32.join(node_path.win32.dirname(file), script);
447
+ }
448
+ async function isEnvNodeScript(file, fs) {
449
+ const head = await fs.readHead(file, SHEBANG_MAX_BYTES);
450
+ const end = head?.indexOf("\n") ?? -1;
451
+ if (head === void 0 || end < 0) return false;
452
+ const line = head.slice(0, end).replace(/\r$/, "");
453
+ if (!line.startsWith("#!")) return false;
454
+ const words = line.slice(2).split(/[ \t]+/).filter(Boolean);
455
+ const [interpreter, program] = words;
456
+ return words.length === 2 && interpreter !== void 0 && node_path.posix.isAbsolute(interpreter) && node_path.posix.basename(interpreter) === "env" && program === "node";
457
+ }
458
+ async function nodeScriptFor(file, context) {
459
+ const fs = context.fs ?? defaultFs;
460
+ if (context.platform === "win32") return npmShimScript(file, fs);
461
+ return await isEnvNodeScript(file, fs) ? file : void 0;
462
+ }
463
+ function cmdExe(context, excluded) {
464
+ const usable = (file) => node_path.win32.isAbsolute(file) && !excluded(node_path.win32.dirname(file));
465
+ const comSpec = envValue(context.env, "ComSpec", "win32");
466
+ if (comSpec !== void 0 && usable(comSpec)) return comSpec;
467
+ const systemRoot = envValue(context.env, "SystemRoot", "win32");
468
+ if (systemRoot === void 0 || !node_path.win32.isAbsolute(systemRoot)) return void 0;
469
+ if (excluded(systemRoot)) return void 0;
470
+ const file = node_path.win32.join(systemRoot, "System32", "cmd.exe");
471
+ return usable(file) ? file : void 0;
472
+ }
473
+ async function commandLineFor(file, args, context, { quoteArgs = true } = {}) {
474
+ const script = await nodeScriptFor(file, context);
475
+ if (script !== void 0) return {
476
+ file: context.execPath ?? process.execPath,
477
+ args: [script, ...args],
478
+ verbatim: false
479
+ };
480
+ if (context.platform !== "win32" || !isBatch(file)) return {
481
+ file,
482
+ args: [...args],
483
+ verbatim: false
484
+ };
485
+ if ([file, ...args].some((word) => CMD_UNSAFE.test(word))) return void 0;
486
+ const shell = cmdExe(context, await excludedDirectories(context));
487
+ if (shell === void 0) return void 0;
488
+ return {
489
+ file: shell,
490
+ args: [
491
+ "/d",
492
+ "/s",
493
+ "/c",
494
+ `"${quoteArgs ? [file, ...args].map((word) => `"${word}"`).join(" ") : [`"${file}"`, ...args].join(" ")}"`
495
+ ],
496
+ verbatim: true
497
+ };
498
+ }
499
+ //#endregion
500
+ //#region ../../packages/agent-core/src/process/git.ts
501
+ const GIT_TIMEOUT_MS = 1e3;
502
+ const execGit = (file, args, env, timeoutMs) => new Promise((resolve) => {
503
+ (0, node_child_process.execFile)(file, args, {
504
+ encoding: "utf8",
505
+ env,
506
+ timeout: timeoutMs,
507
+ windowsHide: true
508
+ }, (error, stdout) => resolve(error ? void 0 : stdout.trim() || void 0));
509
+ });
510
+ function createGitRunner({ platform, env, processCwd, excludeTarget, timeoutMs = GIT_TIMEOUT_MS, fs, exec = execGit }) {
511
+ const isGitVariable = (name) => (platform === "win32" ? name.toUpperCase() : name).startsWith("GIT_");
512
+ const gitEnv = Object.fromEntries(Object.entries(env).filter((entry) => entry[1] !== void 0 && !isGitVariable(entry[0])));
513
+ const resolved = /* @__PURE__ */ new Map();
514
+ const gitFor = (cwd) => {
515
+ const bases = excludeTarget ? [processCwd, cwd] : [processCwd];
516
+ const key = bases.join("\0");
517
+ let file = resolved.get(key);
518
+ if (!file) {
519
+ file = findProgram("git", {
520
+ platform,
521
+ env,
522
+ bases,
523
+ ...fs ? { fs } : {}
524
+ });
525
+ resolved.set(key, file);
526
+ }
527
+ return file;
528
+ };
529
+ return async (cwd, args) => {
530
+ const file = await gitFor(cwd);
531
+ return file === void 0 ? void 0 : exec(file, [
532
+ "-C",
533
+ cwd,
534
+ ...args
535
+ ], gitEnv, timeoutMs);
536
+ };
537
+ }
538
+ //#endregion
539
+ //#region ../../packages/agent-core/src/process/runtime-env.ts
540
+ const RUNTIME_VARIABLES = [
541
+ "NODE_TLS_REJECT_UNAUTHORIZED",
542
+ "NODE_EXTRA_CA_CERTS",
543
+ "NODE_OPTIONS",
544
+ "HTTP_PROXY",
545
+ "HTTPS_PROXY",
546
+ "NO_PROXY",
547
+ "http_proxy",
548
+ "https_proxy",
549
+ "no_proxy",
550
+ "OPENSSL_CONF",
551
+ "SSL_CERT_FILE",
552
+ "SSL_CERT_DIR"
553
+ ];
554
+ const STASH = "HARNESSFORCE_RUNTIME_ENV";
555
+ const IGNORED_WHILE_WAITING = ["SIGINT", "SIGQUIT"];
556
+ const FORWARDED = ["SIGTERM", "SIGHUP"];
557
+ const isRuntimeVariable = (name, platform) => platform === "win32" ? RUNTIME_VARIABLES.some((v) => v.toUpperCase() === name.toUpperCase()) : RUNTIME_VARIABLES.includes(name);
558
+ function splitRuntimeEnv(env, platform) {
559
+ const clean = {};
560
+ const removed = {};
561
+ for (const [name, value] of Object.entries(env)) {
562
+ if (value === void 0) continue;
563
+ if (isRuntimeVariable(name, platform)) removed[name] = value;
564
+ else clean[name] = value;
565
+ }
566
+ return {
567
+ clean,
568
+ removed
569
+ };
570
+ }
571
+ const spawnSelf = (env) => (0, node_child_process.spawn)(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
572
+ env,
573
+ stdio: "inherit"
574
+ });
575
+ function relaunchWithoutRuntimeVariables({ platform, env, stash, spawnSelf: start }) {
576
+ const { clean, removed } = splitRuntimeEnv(env, platform);
577
+ if (Object.keys(removed).length === 0) return Promise.resolve({ kind: "not-needed" });
578
+ const childEnv = Object.fromEntries(Object.entries(clean).filter(([name]) => name !== STASH));
579
+ if (stash) childEnv[STASH] = JSON.stringify(removed);
580
+ return new Promise((resolve) => {
581
+ let child;
582
+ try {
583
+ child = start(childEnv);
584
+ } catch {
585
+ return resolve({ kind: "failed" });
586
+ }
587
+ const ignore = () => {};
588
+ const forward = (signal) => child.kill(signal);
589
+ for (const signal of IGNORED_WHILE_WAITING) process.on(signal, ignore);
590
+ for (const signal of FORWARDED) process.on(signal, forward);
591
+ const finish = (outcome) => {
592
+ for (const signal of IGNORED_WHILE_WAITING) process.off(signal, ignore);
593
+ for (const signal of FORWARDED) process.off(signal, forward);
594
+ resolve(outcome);
595
+ };
596
+ child.once("error", () => finish({ kind: "failed" }));
597
+ child.once("exit", (code, signal) => finish({
598
+ kind: "exited",
599
+ code: code ?? 128 + (signal ? node_os.constants.signals[signal] : 0)
600
+ }));
601
+ });
602
+ }
603
+ //#endregion
604
+ //#region ../../packages/agent-core/src/telemetry-settings.ts
605
+ const EXPECTED = {
606
+ CLAUDE_CODE_ENABLE_TELEMETRY: (value) => value === "1",
607
+ OTEL_LOGS_EXPORTER: (value) => includesOtlp(value),
608
+ OTEL_METRICS_EXPORTER: (value) => includesOtlp(value),
609
+ OTEL_TRACES_EXPORTER: (value) => includesOtlp(value),
610
+ OTEL_EXPORTER_OTLP_PROTOCOL: (value) => value === "http/protobuf"
611
+ };
612
+ const NAMES = Object.keys(EXPECTED);
613
+ const includesOtlp = (value) => value.split(",").some((exporter) => exporter.trim() === "otlp");
614
+ const noGuard = () => {};
615
+ async function isTelemetryConfigured(options) {
616
+ const managed = await readManagedEnv(options.managedDir, NAMES);
617
+ const settings = await readJsonObjectFile(options.userSettingsPath, noGuard).catch(() => void 0);
618
+ const userEnv = isObject(settings?.env) ? settings.env : {};
619
+ return NAMES.every((name) => {
620
+ const value = managed[name] ?? userEnv[name];
621
+ return typeof value === "string" && EXPECTED[name](value);
622
+ }) && typeof settings?.otelHeadersHelper === "string";
623
+ }
624
+ //#endregion
625
+ //#region ../../packages/agent-core/src/url.ts
626
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
627
+ "localhost",
628
+ "127.0.0.1",
629
+ "[::1]"
630
+ ]);
631
+ function parseAllowedUrl(value) {
632
+ if (!value) return void 0;
633
+ let url;
634
+ try {
635
+ url = new URL(value);
636
+ } catch {
637
+ return;
638
+ }
639
+ return url.protocol === "https:" || url.protocol === "http:" && LOOPBACK_HOSTS.has(url.hostname) ? url : void 0;
640
+ }
641
+ function underBase(base, path) {
642
+ const url = new URL(base.origin);
643
+ url.pathname = `${base.pathname.replace(/\/+$/, "")}/${path}`;
644
+ return url;
645
+ }
646
+ //#endregion
647
+ //#region ../../packages/agent-core/src/client-update.ts
648
+ const CLIENT_UPDATE_HEADER = "harnessforce-client-update";
649
+ const UPDATE_COMMANDS = {
650
+ cli: "npm install -g @harnessforce/cli@latest",
651
+ "claude-code-plugin": "claude plugin update harnessforce@harnessforce-agent"
652
+ };
653
+ const DISPLAY_NAMES = {
654
+ cli: "CLI",
655
+ "claude-code-plugin": "plugin"
656
+ };
657
+ const DAY_MS = 864e5;
658
+ const RECHECK_TIMEOUT_MS = 2e3;
659
+ const userAgentOf = (client) => `harnessforce-${client.component}${client.dev ? "-dev" : ""}/${client.version}`;
660
+ const STATUSES = /* @__PURE__ */ new Set([
661
+ "current",
662
+ "outdated",
663
+ "deprecated",
664
+ "unsupported"
665
+ ]);
666
+ const REASONS = /* @__PURE__ */ new Set(["security", "compatibility"]);
667
+ function parseClientUpdateHeader(value) {
668
+ if (!value) return void 0;
669
+ const members = /* @__PURE__ */ new Map();
670
+ for (const member of value.split(",")) {
671
+ const [key, raw] = member.trim().split("=", 2);
672
+ if (key && raw !== void 0) members.set(key, raw);
673
+ }
674
+ const status = members.get("status");
675
+ if (!status || !STATUSES.has(status)) return void 0;
676
+ const update = { status };
677
+ const latest = /^"([^"\\]*)"$/.exec(members.get("latest") ?? "")?.[1];
678
+ if (latest) update.latest = latest;
679
+ const reason = members.get("reason");
680
+ if (reason && REASONS.has(reason)) update.reason = reason;
681
+ const seconds = /^@(\d+)$/.exec(members.get("deadline") ?? "")?.[1];
682
+ if (seconds) update.deadline = (/* @__PURE__ */ new Date(Number(seconds) * 1e3)).toISOString();
683
+ return isComplete(update) ? update : void 0;
684
+ }
685
+ function isComplete(update) {
686
+ switch (update.status) {
687
+ case "outdated": return Boolean(update.latest);
688
+ case "deprecated": return Boolean(update.reason) && !Number.isNaN(Date.parse(update.deadline ?? ""));
689
+ case "unsupported": return Boolean(update.reason);
690
+ default: return true;
691
+ }
692
+ }
693
+ function stateFrom(value, version) {
694
+ if (!isObject(value) || value.version !== version) return void 0;
695
+ if (typeof value.receivedAt !== "string") return void 0;
696
+ if (typeof value.status !== "string" || !STATUSES.has(value.status)) return void 0;
697
+ const state = value;
698
+ return isComplete(state) ? state : void 0;
699
+ }
700
+ async function readUpdateState(path, version) {
701
+ const saved = await readSavedUpdateState(path);
702
+ return saved?.version === version ? saved : void 0;
703
+ }
704
+ async function readSavedUpdateState(path) {
705
+ try {
706
+ const value = JSON.parse(await (0, node_fs_promises.readFile)(path, "utf8"));
707
+ return isObject(value) && typeof value.version === "string" ? stateFrom(value, value.version) : void 0;
708
+ } catch {
709
+ return;
710
+ }
711
+ }
712
+ async function writeUpdateState(path, state) {
713
+ await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), {
714
+ recursive: true,
715
+ mode: 448
716
+ });
717
+ const temporary = `${path}.${(0, node_crypto.randomUUID)()}.tmp`;
718
+ await (0, node_fs_promises.writeFile)(temporary, `${JSON.stringify(state)}\n`, { mode: 384 });
719
+ await (0, node_fs_promises.rename)(temporary, path);
720
+ }
721
+ /**
722
+ * Harnessforceへの要求に`User-Agent`を付け、応答の`Harnessforce-Client-Update`を状態のfileへ保存するfetch。
723
+ * 保存に失敗しても要求の結果は変えない。statePathが無い(pluginのdata directoryが無い)場合は版の申告だけを行う。
724
+ */
725
+ function recordingFetch(fetchImpl, client, statePath, now) {
726
+ return async (url, init) => {
727
+ const headers = {
728
+ ...Object.fromEntries(new Headers(init.headers)),
729
+ "user-agent": userAgentOf(client)
730
+ };
731
+ const response = await fetchImpl(url, {
732
+ ...init,
733
+ headers
734
+ });
735
+ const update = parseClientUpdateHeader(response.headers.get(CLIENT_UPDATE_HEADER));
736
+ if (update && statePath) {
737
+ const previous = await readUpdateState(statePath, client.version);
738
+ await writeUpdateState(statePath, {
739
+ ...update,
740
+ version: client.version,
741
+ receivedAt: now().toISOString(),
742
+ ...previous?.outdatedShownAt ? { outdatedShownAt: previous.outdatedShownAt } : {}
743
+ }).catch(() => {});
744
+ }
745
+ return response;
746
+ };
747
+ }
748
+ const utcDate = (instant) => new Intl.DateTimeFormat("en-CA", {
749
+ timeZone: "UTC",
750
+ year: "numeric",
751
+ month: "2-digit",
752
+ day: "2-digit"
753
+ }).format(new Date(instant));
754
+ const unsupportedLine = (component) => `This Harnessforce ${DISPLAY_NAMES[component]} version is no longer supported, so nothing is being sent to Harnessforce. Update it: \`${UPDATE_COMMANDS[component]}\``;
755
+ function updateLine(component, update) {
756
+ const name = DISPLAY_NAMES[component];
757
+ const command = UPDATE_COMMANDS[component];
758
+ switch (update.status) {
759
+ case "unsupported": return unsupportedLine(component);
760
+ case "deprecated": {
761
+ const stops = `stops sending on ${utcDate(update.deadline ?? "")} (00:00 UTC)`;
762
+ return update.reason === "security" ? `A security update for Harnessforce ${name} is available. This version ${stops}. Update it: \`${command}\`` : `This Harnessforce ${name} version ${stops}. Update it: \`${command}\``;
763
+ }
764
+ case "outdated": return `Harnessforce ${name} ${update.latest} is available. Update it: \`${command}\``;
765
+ default: return;
766
+ }
767
+ }
768
+ function shouldShowOutdated(state, now, env) {
769
+ if (state.status !== "outdated" || env.NO_UPDATE_NOTIFIER || env.CI) return false;
770
+ const shownAt = state.outdatedShownAt ? Date.parse(state.outdatedShownAt) : NaN;
771
+ return Number.isNaN(shownAt) || now.getTime() - shownAt >= DAY_MS;
772
+ }
773
+ const markOutdatedShown = (path, state, now) => writeUpdateState(path, {
774
+ ...state,
775
+ outdatedShownAt: now.toISOString()
776
+ });
777
+ /**
778
+ * 表示する1行。outdatedは24時間に1回までとし、表示する場合はその時刻を書く。isSuppressedは、pluginを
779
+ * Claude Codeが自動更新する間など、呼び出し側がoutdatedを示さない条件。
780
+ */
781
+ async function noticeLine(component, statePath, state, options) {
782
+ if (state.status === "outdated") {
783
+ if (!shouldShowOutdated(state, options.now, options.env) || await options.isSuppressed?.()) return void 0;
784
+ await markOutdatedShown(statePath, state, options.now).catch(() => {});
785
+ }
786
+ return updateLine(component, state);
787
+ }
788
+ /**
789
+ * unsupportedを受けてから24時間以上経っていれば、資格情報を送らずに`GET /v1/client-status`で確かめ直す
790
+ * (client-updates.md「送信の停止」)。失敗した場合は状態を変えない。
791
+ */
792
+ async function recheckClientStatus(options) {
793
+ const { state } = options;
794
+ if (state?.status !== "unsupported" || options.now().getTime() - Date.parse(state.receivedAt) < DAY_MS) return state;
795
+ try {
796
+ await (await options.fetch(underBase(options.ingestBase, "v1/client-status"), {
797
+ method: "GET",
798
+ redirect: "error",
799
+ signal: AbortSignal.timeout(RECHECK_TIMEOUT_MS)
800
+ })).body?.cancel();
801
+ } catch {
802
+ return state;
803
+ }
804
+ return await readUpdateState(options.statePath, options.version) ?? state;
805
+ }
806
+ //#endregion
807
+ //#region ../../packages/agent-core/src/config/plugins.ts
808
+ const NON_MARKETPLACE_ORIGINS = /* @__PURE__ */ new Set([
809
+ "inline",
810
+ "skills-dir",
811
+ "synced"
812
+ ]);
813
+ const PLUGIN_ID = /^([A-Za-z0-9._-]+)@([A-Za-z0-9._-]+)$/;
814
+ const isPathSegment = (name) => name !== "." && name !== "..";
815
+ const ORPHANED_MARK = ".orphaned_at";
816
+ function enabledPluginIds(sources) {
817
+ const merged = /* @__PURE__ */ new Map();
818
+ for (const settings of sources) {
819
+ const enabled = settings?.enabledPlugins;
820
+ if (isObject(enabled)) for (const [id, value] of Object.entries(enabled)) merged.set(id, value);
821
+ }
822
+ return [...merged].filter(([, value]) => value === true).map(([id]) => id);
823
+ }
824
+ async function liveVersion(pluginDir, guard) {
825
+ const live = [];
826
+ for (const version of await listDirectories(pluginDir, guard)) if (!await exists((0, node_path.join)(pluginDir, version, ORPHANED_MARK), guard)) live.push(version);
827
+ return live.length === 1 ? live[0] : void 0;
828
+ }
829
+ async function collectPlugins(pluginsRoot, settingsByPrecedence, scope, guard) {
830
+ for (const id of enabledPluginIds(settingsByPrecedence)) {
831
+ const [, name, marketplace] = PLUGIN_ID.exec(id) ?? [];
832
+ if (!name || !marketplace || !isPathSegment(name) || !isPathSegment(marketplace) || NON_MARKETPLACE_ORIGINS.has(marketplace)) continue;
833
+ const pluginDir = (0, node_path.join)(pluginsRoot, "cache", marketplace, name);
834
+ const version = await liveVersion(pluginDir, guard);
835
+ if (version === void 0) continue;
836
+ const root = (0, node_path.join)(pluginDir, version);
837
+ const s = scope("plugin", `${id}:`, isToken(version, 64) ? version : void 0);
838
+ await collectLayout(s, {
839
+ skills: (0, node_path.join)(root, "skills"),
840
+ agents: (0, node_path.join)(root, "agents"),
841
+ commands: (0, node_path.join)(root, "commands"),
842
+ workflows: (0, node_path.join)(root, "workflows")
843
+ });
844
+ collectHooks(s, (await readJsonObjectFile((0, node_path.join)(root, "hooks/hooks.json"), guard))?.hooks);
845
+ collectMcpServers(s, (await readJsonObjectFile((0, node_path.join)(root, ".mcp.json"), guard))?.mcpServers);
846
+ }
847
+ }
848
+ //#endregion
849
+ //#region ../../packages/agent-core/src/config/collect.ts
850
+ const COLLECT_BUDGET_MS = 1e3;
851
+ async function readManagedSettings(managedDir, guard) {
852
+ let merged = await readJsonObjectFile((0, node_path.join)(managedDir, "managed-settings.json"), guard);
853
+ const dropInDir = (0, node_path.join)(managedDir, "managed-settings.d");
854
+ const dropIns = (await listFiles(dropInDir, ".json", false, guard)).sort(byCodeUnit$1);
855
+ for (const name of dropIns) {
856
+ const dropIn = await readJsonObjectFile((0, node_path.join)(dropInDir, name), guard);
857
+ if (dropIn) merged = {
858
+ ...merged,
859
+ ...dropIn
860
+ };
861
+ }
862
+ return merged;
863
+ }
864
+ function collectSettings(s, settings) {
865
+ if (!settings) return;
866
+ const { hooks, permissions, model } = settings;
867
+ collectHooks(s, hooks);
868
+ if (isObject(permissions)) s.addValue("permissions", "permissions", permissions);
869
+ if (typeof model === "string") s.addValue("model", model, model);
870
+ }
871
+ const objectAt = (value, key) => {
872
+ const found = value?.[key];
873
+ return isObject(found) ? found : {};
874
+ };
875
+ function locationsOf(options) {
876
+ const { projectRoot, managedDir, homeDir } = options;
877
+ const user = (0, node_path.join)(homeDir, ".claude");
878
+ const configDir = absoluteEnv(options.env.CLAUDE_CONFIG_DIR);
879
+ return {
880
+ projectRoot,
881
+ managedDir,
882
+ homeDir,
883
+ user,
884
+ configDir,
885
+ config: configDir ?? user,
886
+ project: (0, node_path.join)(projectRoot, ".claude")
887
+ };
888
+ }
889
+ async function collectFiles(at, s) {
890
+ const { projectRoot, managedDir, user, project } = at;
891
+ await collectRules(s.managed, managedDir, ["CLAUDE.md"]);
892
+ await collectLayout(s.managed, { skills: (0, node_path.join)(managedDir, ".claude/skills") });
893
+ if (!at.configDir) {
894
+ await collectRules(s.user, user, ["CLAUDE.md"], (0, node_path.join)(user, "rules"));
895
+ await collectLayout(s.user, {
896
+ skills: (0, node_path.join)(user, "skills"),
897
+ agents: (0, node_path.join)(user, "agents"),
898
+ commands: (0, node_path.join)(user, "commands")
899
+ });
900
+ }
901
+ await collectLayout(s.user, { workflows: (0, node_path.join)(at.config, "workflows") });
902
+ await collectRules(s.repository, projectRoot, ["CLAUDE.md", ".claude/CLAUDE.md"], (0, node_path.join)(project, "rules"));
903
+ await collectLayout(s.repository, {
904
+ skills: (0, node_path.join)(project, "skills"),
905
+ agents: (0, node_path.join)(project, "agents"),
906
+ commands: (0, node_path.join)(project, "commands"),
907
+ workflows: (0, node_path.join)(project, "workflows")
908
+ });
909
+ await collectRules(s.local, projectRoot, ["CLAUDE.local.md"]);
910
+ }
911
+ async function readAllSettings(at, guard) {
912
+ return {
913
+ managed: await readManagedSettings(at.managedDir, guard),
914
+ user: await readJsonObjectFile((0, node_path.join)(at.config, "settings.json"), guard),
915
+ repository: await readJsonObjectFile((0, node_path.join)(at.project, "settings.json"), guard),
916
+ local: await readJsonObjectFile((0, node_path.join)(at.project, "settings.local.json"), guard)
917
+ };
918
+ }
919
+ async function collectAllMcpServers(at, s, settings, guard) {
920
+ const managedMcp = await readJsonObjectFile((0, node_path.join)(at.managedDir, "managed-mcp.json"), guard);
921
+ collectMcpServers(s.managed, {
922
+ ...objectAt(settings.managed, "managedMcpServers"),
923
+ ...objectAt(managedMcp, "mcpServers")
924
+ });
925
+ const projectMcp = await readJsonObjectFile((0, node_path.join)(at.projectRoot, ".mcp.json"), guard);
926
+ collectMcpServers(s.repository, projectMcp?.mcpServers);
927
+ if (!at.configDir) {
928
+ const global = await readJsonObjectFile((0, node_path.join)(at.homeDir, ".claude.json"), guard);
929
+ collectMcpServers(s.user, global?.mcpServers);
930
+ collectMcpServers(s.local, objectAt(objectAt(global, "projects"), at.projectRoot).mcpServers);
931
+ }
932
+ }
933
+ async function collectAll(options, scope, guard) {
934
+ const at = locationsOf(options);
935
+ const scopes = {
936
+ managed: scope("managed"),
937
+ user: scope("user"),
938
+ repository: scope("repository"),
939
+ local: scope("local")
940
+ };
941
+ await collectFiles(at, scopes);
942
+ const settings = await readAllSettings(at, guard);
943
+ collectSettings(scopes.managed, settings.managed);
944
+ collectSettings(scopes.user, settings.user);
945
+ collectSettings(scopes.repository, settings.repository);
946
+ collectSettings(scopes.local, settings.local);
947
+ await collectAllMcpServers(at, scopes, settings, guard);
948
+ await collectPlugins(absoluteEnv(options.env.CLAUDE_CODE_PLUGIN_CACHE_DIR) ?? (0, node_path.join)(at.config, "plugins"), [
949
+ settings.user,
950
+ settings.repository,
951
+ settings.local,
952
+ settings.managed
953
+ ], scope, guard);
954
+ }
955
+ async function collectConfig(options) {
956
+ const guard = () => {
957
+ if (options.isExpired()) throw new CollectionExpired();
958
+ };
959
+ const sink = new Sink(guard);
960
+ try {
961
+ await collectAll(options, sink.scope, guard);
962
+ guard();
963
+ } catch (error) {
964
+ if (error instanceof CollectionExpired) return {
965
+ kind: "skipped",
966
+ reason: "timeout"
967
+ };
968
+ if (error instanceof TooManyComponents) return {
969
+ kind: "skipped",
970
+ reason: "too many components"
971
+ };
972
+ throw error;
973
+ }
974
+ if (sink.hasDuplicates()) return {
975
+ kind: "skipped",
976
+ reason: "duplicate identifier"
977
+ };
978
+ return {
979
+ kind: "collected",
980
+ components: sortComponents(sink.components)
981
+ };
982
+ }
983
+ //#endregion
984
+ //#region ../../packages/agent-core/src/ingest.ts
985
+ const SEND_TIMEOUT_MS = 2e3;
986
+ const INGEST_KEY_WORKSPACE = /^hf_ik_([A-Za-z0-9-]{1,128})_/;
987
+ const workspaceIdOf = (key) => INGEST_KEY_WORKSPACE.exec(key)?.[1];
988
+ function ingestBaseFrom(endpoint) {
989
+ const base = parseAllowedUrl(endpoint);
990
+ if (!base) return void 0;
991
+ const ingestBase = new URL(base.origin);
992
+ ingestBase.pathname = base.pathname.replace(/\/+$/, "");
993
+ return ingestBase;
994
+ }
995
+ async function postItems(destination, path, items, fetchImpl, timeoutMs = SEND_TIMEOUT_MS) {
996
+ try {
997
+ const response = await fetchImpl(underBase(destination.ingestBase, path), {
998
+ method: "POST",
999
+ headers: {
1000
+ authorization: `Bearer ${destination.key}`,
1001
+ "content-type": "application/json"
1002
+ },
1003
+ body: JSON.stringify(items),
1004
+ redirect: "error",
1005
+ signal: AbortSignal.timeout(timeoutMs)
1006
+ });
1007
+ await response.body?.cancel();
1008
+ if (response.status === 401) return { kind: "unauthorized" };
1009
+ return response.ok ? { kind: "accepted" } : {
1010
+ kind: "failed",
1011
+ reason: `HTTP ${response.status}`
1012
+ };
1013
+ } catch (error) {
1014
+ return {
1015
+ kind: "failed",
1016
+ reason: error instanceof Error ? error.name : "unknown"
1017
+ };
1018
+ }
1019
+ }
1020
+ //#endregion
1021
+ //#region ../../packages/agent-core/src/issue.ts
1022
+ const isIssueIdentifier = (value) => isToken(value, 300);
1023
+ //#endregion
1024
+ //#region ../../packages/agent-core/src/using-harnessforce.ts
1025
+ const usingHarnessforceBody = "---\nname: using-harnessforce\ndescription: Use when starting a session in an environment with the Harnessforce hook, when starting work on an Issue, or when asked what is recorded or collected. The hook injects this text into the context at session start.\n---\n\n# Using Harnessforce\n\nThis session has the Harnessforce hook installed. Harnessforce links the work of people and AI to Issues and records it. Follow these rules.\n\n## What is recorded\n\n- Session registration: agent, session ID, repository, branch, commit, start time, and the first prompt ID.\n- Config snapshot: the structure of the configuration in use (Claude Code only).\n- OTel telemetry: what the user chooses to send.\n- For work on an Issue, `record-run` registers the session as a Run and closes it. It records no Plan, Decision, or Definition of Done.\n\n## What is not collected\n\n- Prompts, responses, tool input and output, and the body of code diffs arrive only when the user opts in with `--send-content` and the Workspace has opted in. They are not collected by default.\n- Never put these contents, secrets, or environment variable values in MCP tool input or in a record.\n\n## Which hook sends what\n\n- SessionStart: session registration and the config snapshot (Codex sends session registration only).\n- UserPromptSubmit: resends on the first prompt (Claude Code only).\n- A failing hook never stops the session. When the hook cannot send, it shows a one-line warning. Tell the user when a warning appears.\n\n## Which skill and tool to use, and when\n\n- For work on a specified Issue ID, or when the environment variable `HARNESSFORCE_ISSUE` is set, follow `record-run` and use `start_run`, `get_issue`, and `complete_run`. An agent that cannot load the skill calls the tools in the same order.\n- Pass the value of the `harnessforce session_id:` line in the context to `start_run`. If there are several, use the last one. If the line is missing, do not guess the session ID, and do not call `start_run` or `complete_run`.\n- Use `propose-improvements` for the analysis of `/harnessforce:tune`.\n- When no Issue is specified, do not start recording a Run.\n\n## Red Flags\n\nStop when one of these thoughts comes up.\n\n| Thought | Reality |\n| --- | --- |\n| An Issue was specified, but the work is small, so recording is unnecessary | Work on a specified Issue is recorded regardless of its size |\n| The session ID line is missing, so I will guess it and call `start_run` | Do not guess. The Run would no longer be linked to the session |\n| Summarizing the content before recording would help | Do not pass content to the tool. It goes beyond what is collected |\n| A warning appeared, but I can ignore it | Sending is not working. Tell the user |\n\n## Priority\n\n1. The user's instructions (CLAUDE.md, AGENTS.md, direct requests). If the user tells you to stop recording, stop recording the Run.\n2. This introduction.\n3. Default behavior.\n\n## Subagents\n\nA subagent that was delegated only a specific task by its parent does not register a Run by following this introduction. The parent registers the Run.\n".replace(/\r\n/g, "\n").replace(/^---\n[\s\S]*?\n---\n+/, "").trimEnd();
1026
+ //#endregion
1027
+ //#region ../../packages/agent-core/src/session-context.ts
1028
+ const sessionContextLine = (sessionId) => `harnessforce session_id: ${sessionId}`;
1029
+ const sessionContext = (sessionId) => ({ hookSpecificOutput: {
1030
+ hookEventName: "SessionStart",
1031
+ additionalContext: `${sessionContextLine(sessionId)}\n\n${usingHarnessforceBody}`
1032
+ } });
1033
+ //#endregion
1034
+ //#region ../../packages/agent-core/src/vcs.ts
1035
+ async function selectRemote(cwd, git) {
1036
+ if (await git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") return void 0;
1037
+ const remotes = (await git(cwd, ["remote"]))?.split("\n") ?? [];
1038
+ return remotes.includes("origin") ? "origin" : remotes[0];
1039
+ }
1040
+ async function resolveVcs(cwd, git) {
1041
+ const remote = await selectRemote(cwd, git);
1042
+ if (!remote) return void 0;
1043
+ const [url, branch, commit] = await Promise.all([
1044
+ git(cwd, [
1045
+ "remote",
1046
+ "get-url",
1047
+ remote
1048
+ ]),
1049
+ git(cwd, [
1050
+ "symbolic-ref",
1051
+ "--quiet",
1052
+ "--short",
1053
+ "HEAD"
1054
+ ]),
1055
+ git(cwd, [
1056
+ "rev-parse",
1057
+ "--verify",
1058
+ "--quiet",
1059
+ "HEAD"
1060
+ ])
1061
+ ]);
1062
+ const repository = url === void 0 ? void 0 : normalizeRepository(url);
1063
+ return {
1064
+ ...repository === void 0 ? {} : { repository },
1065
+ ...branch === void 0 ? {} : { branch },
1066
+ ...commit === void 0 ? {} : { commit }
1067
+ };
1068
+ }
1069
+ async function resolveProjectRoot(cwd, git) {
1070
+ const topLevel = await git(cwd, ["rev-parse", "--show-toplevel"]);
1071
+ return topLevel && (0, node_path.isAbsolute)(topLevel) ? topLevel : cwd;
1072
+ }
1073
+ //#endregion
1074
+ //#region src/input.ts
1075
+ const FILE_SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
1076
+ const isFileSafeSessionId = (sessionId) => FILE_SAFE_SESSION_ID.test(sessionId);
1077
+ const token = (value) => typeof value === "string" && isToken(value) ? value : void 0;
1078
+ const absolutePath = (value) => typeof value === "string" && (0, node_path.isAbsolute)(value) ? value : void 0;
1079
+ function parseJsonObject(raw) {
1080
+ try {
1081
+ const value = JSON.parse(raw);
1082
+ return isObject(value) ? value : void 0;
1083
+ } catch {
1084
+ return;
1085
+ }
1086
+ }
1087
+ function parseHookInput(raw) {
1088
+ const fields = parseJsonObject(raw);
1089
+ const sessionId = token(fields?.session_id);
1090
+ const cwd = absolutePath(fields?.cwd);
1091
+ if (!fields || !sessionId || !cwd) return void 0;
1092
+ const scratchpadDir = absolutePath(fields.scratchpad_dir);
1093
+ return {
1094
+ sessionId,
1095
+ cwd,
1096
+ promptId: token(fields.prompt_id),
1097
+ scratchpad: scratchpadDir && isFileSafeSessionId(sessionId) ? {
1098
+ dir: scratchpadDir,
1099
+ sessionId
1100
+ } : void 0,
1101
+ source: typeof fields.source === "string" ? fields.source : void 0,
1102
+ fields
1103
+ };
1104
+ }
1105
+ //#endregion
1106
+ //#region src/scratchpad.ts
1107
+ const registrationFile = (pad) => (0, node_path.join)(pad.dir, `registration-${pad.sessionId}.json`);
1108
+ const unauthorizedMark = (pad) => (0, node_path.join)(pad.dir, `unauthorized-${pad.sessionId}`);
1109
+ const firstPromptSentMark = (pad) => (0, node_path.join)(pad.dir, `first-prompt-sent-${pad.sessionId}`);
1110
+ async function saveRegistration(pad, registration) {
1111
+ await (0, node_fs_promises.writeFile)(registrationFile(pad), JSON.stringify(registration));
1112
+ }
1113
+ async function markUnauthorized(pad) {
1114
+ await (0, node_fs_promises.writeFile)(unauthorizedMark(pad), "");
1115
+ }
1116
+ async function isMarkedUnauthorized(pad) {
1117
+ return (0, node_fs_promises.access)(unauthorizedMark(pad)).then(() => true, () => false);
1118
+ }
1119
+ async function loadRegistration(pad) {
1120
+ try {
1121
+ const saved = JSON.parse(await (0, node_fs_promises.readFile)(registrationFile(pad), "utf8"));
1122
+ return typeof saved === "object" && saved !== null && saved.session_id === pad.sessionId ? saved : void 0;
1123
+ } catch {
1124
+ return;
1125
+ }
1126
+ }
1127
+ async function isFirstPromptSent(pad) {
1128
+ return (0, node_fs_promises.access)(firstPromptSentMark(pad)).then(() => true, () => false);
1129
+ }
1130
+ async function claimFirstPrompt(pad) {
1131
+ return (0, node_fs_promises.writeFile)(firstPromptSentMark(pad), "", { flag: "wx" }).then(() => true, () => false);
1132
+ }
1133
+ //#endregion
1134
+ //#region src/usage/names.ts
1135
+ const PLUGIN_COMPONENT = /^([A-Za-z0-9._-]+)@[A-Za-z0-9._-]+:(.+)$/;
1136
+ function pluginPartsOf(identifier) {
1137
+ const parts = identifier.plugin ? PLUGIN_COMPONENT.exec(identifier.id) : null;
1138
+ return parts ? {
1139
+ plugin: parts[1],
1140
+ name: parts[2]
1141
+ } : void 0;
1142
+ }
1143
+ function onlyOne(candidates) {
1144
+ const ids = new Set(candidates.map((c) => c.id));
1145
+ return ids.size === 1 ? [...ids][0] : void 0;
1146
+ }
1147
+ function identifierOfName(name, candidates) {
1148
+ const colon = name.indexOf(":");
1149
+ if (colon >= 0) {
1150
+ const plugin = name.slice(0, colon);
1151
+ const rest = name.slice(colon + 1);
1152
+ return onlyOne(candidates.filter((c) => {
1153
+ const parts = pluginPartsOf(c);
1154
+ return parts?.plugin === plugin && parts.name === rest;
1155
+ }));
1156
+ }
1157
+ const direct = candidates.filter((c) => !c.plugin && c.id === name);
1158
+ return onlyOne(direct.length > 0 ? direct : candidates.filter((c) => pluginPartsOf(c)?.name === name));
1159
+ }
1160
+ const toToolName = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
1161
+ function toolPrefixOf(server) {
1162
+ if (!server.plugin) return `mcp__${toToolName(server.id)}__`;
1163
+ const parts = pluginPartsOf(server);
1164
+ return parts && `mcp__plugin_${toToolName(parts.plugin)}_${toToolName(parts.name)}__`;
1165
+ }
1166
+ const mcpServerOfTool = (toolName, servers) => onlyOne(servers.filter((server) => {
1167
+ const prefix = toolPrefixOf(server);
1168
+ return prefix !== void 0 && toolName.startsWith(prefix);
1169
+ }));
1170
+ //#endregion
1171
+ //#region src/usage/count.ts
1172
+ const COUNTING_EVENTS = [
1173
+ "post-tool-use",
1174
+ "post-tool-use-failure",
1175
+ "user-prompt-expansion",
1176
+ "permission-request",
1177
+ "post-compact"
1178
+ ];
1179
+ const named = (kind, id) => id === void 0 ? { kind } : {
1180
+ kind,
1181
+ id
1182
+ };
1183
+ const stringOf = (value) => typeof value === "string" ? value : void 0;
1184
+ function toolCall(fields, identifiers) {
1185
+ const tool = stringOf(fields.tool_name);
1186
+ const input = isObject(fields.tool_input) ? fields.tool_input : {};
1187
+ if (tool === "Skill") {
1188
+ const skill = stringOf(input.skill)?.replace(/^\//, "");
1189
+ return named("skill", skill === void 0 ? void 0 : identifierOfName(skill, identifiers.skill));
1190
+ }
1191
+ if (tool === "Agent" || tool === "Task") {
1192
+ const type = stringOf(input.subagent_type);
1193
+ return named("subagent", type === void 0 ? void 0 : identifierOfName(type, identifiers.agent));
1194
+ }
1195
+ if (tool?.startsWith("mcp__")) return named("mcp", mcpServerOfTool(tool, identifiers.mcp_server));
1196
+ }
1197
+ function commandCall(fields, identifiers) {
1198
+ if (fields.expansion_type === "mcp_prompt") return named("command", void 0);
1199
+ if (fields.expansion_type !== "slash_command") return void 0;
1200
+ const name = stringOf(fields.command_name);
1201
+ return named("command", name === void 0 ? void 0 : identifierOfName(name, [...identifiers.skill, ...identifiers.command]));
1202
+ }
1203
+ function countedOf(event, fields, identifiers) {
1204
+ switch (event) {
1205
+ case "post-tool-use": return toolCall(fields, identifiers);
1206
+ case "post-tool-use-failure": {
1207
+ const call = toolCall(fields, identifiers);
1208
+ return call && {
1209
+ ...call,
1210
+ failed: true
1211
+ };
1212
+ }
1213
+ case "user-prompt-expansion": return commandCall(fields, identifiers);
1214
+ case "permission-request": return { kind: "permission" };
1215
+ case "post-compact": return fields.trigger === "auto" || fields.trigger === "manual" ? {
1216
+ kind: "compaction",
1217
+ trigger: fields.trigger
1218
+ } : void 0;
1219
+ }
1220
+ }
1221
+ //#endregion
1222
+ //#region src/usage/store.ts
1223
+ function usageStoreOf(sessionId, env) {
1224
+ const data = env.CLAUDE_PLUGIN_DATA;
1225
+ if (!data || !(0, node_path.isAbsolute)(data) || !isFileSafeSessionId(sessionId)) return void 0;
1226
+ return {
1227
+ dir: (0, node_path.join)(data, "usage"),
1228
+ sessionId
1229
+ };
1230
+ }
1231
+ const recordFile = (store, sessionId) => (0, node_path.join)(store.dir, `${sessionId}.jsonl`);
1232
+ const stateFile = (store, sessionId) => (0, node_path.join)(store.dir, `${sessionId}.json`);
1233
+ const IDENTIFIER_KINDS = [
1234
+ "skill",
1235
+ "command",
1236
+ "agent",
1237
+ "mcp_server"
1238
+ ];
1239
+ function identifiersOf(components) {
1240
+ const of = (kind) => {
1241
+ const unique = /* @__PURE__ */ new Map();
1242
+ for (const c of components) if (c.kind === kind) {
1243
+ const plugin = c.source === "plugin";
1244
+ unique.set(`${plugin}:${c.id}`, {
1245
+ id: c.id,
1246
+ plugin
1247
+ });
1248
+ }
1249
+ return [...unique.values()];
1250
+ };
1251
+ return {
1252
+ skill: of("skill"),
1253
+ command: of("command"),
1254
+ agent: of("agent"),
1255
+ mcp_server: of("mcp_server")
1256
+ };
1257
+ }
1258
+ async function createState(store, state) {
1259
+ await (0, node_fs_promises.mkdir)(store.dir, { recursive: true });
1260
+ await (0, node_fs_promises.writeFile)(stateFile(store, store.sessionId), JSON.stringify(state), { flag: "wx" }).catch((error) => {
1261
+ if (error.code !== "EEXIST") throw error;
1262
+ });
1263
+ }
1264
+ const isIdentifier = (value) => isObject(value) && typeof value.id === "string" && isToken(value.id) && typeof value.plugin === "boolean";
1265
+ function parseState(value) {
1266
+ if (!isObject(value) || !isObject(value.identifiers)) return void 0;
1267
+ const { workspaceId, identifiers, sentLength } = value;
1268
+ return typeof workspaceId === "string" && typeof sentLength === "number" && Number.isSafeInteger(sentLength) && sentLength >= 0 && IDENTIFIER_KINDS.every((kind) => {
1269
+ const list = identifiers[kind];
1270
+ return Array.isArray(list) && list.every(isIdentifier);
1271
+ }) ? value : void 0;
1272
+ }
1273
+ async function readState(store, sessionId = store.sessionId) {
1274
+ try {
1275
+ return parseState(JSON.parse(await (0, node_fs_promises.readFile)(stateFile(store, sessionId), "utf8")));
1276
+ } catch {
1277
+ return;
1278
+ }
1279
+ }
1280
+ async function writeSentLength(store, sessionId, state, sentLength) {
1281
+ const file = stateFile(store, sessionId);
1282
+ const partial = `${file}.${process.pid}.tmp`;
1283
+ await (0, node_fs_promises.writeFile)(partial, JSON.stringify({
1284
+ ...state,
1285
+ sentLength
1286
+ }));
1287
+ await (0, node_fs_promises.rename)(partial, file);
1288
+ }
1289
+ const RECORD_KINDS = [
1290
+ "start",
1291
+ "prompt",
1292
+ "skill",
1293
+ "command",
1294
+ "subagent",
1295
+ "mcp",
1296
+ "permission",
1297
+ "compaction"
1298
+ ];
1299
+ const appendRecord = (store, line) => (0, node_fs_promises.appendFile)(recordFile(store, store.sessionId), `${JSON.stringify(line)}\n`);
1300
+ const isOptional = (value, check) => value === void 0 || check(value);
1301
+ const isTokenValue = (value) => typeof value === "string" && isToken(value);
1302
+ function parseLine(text) {
1303
+ let value;
1304
+ try {
1305
+ value = JSON.parse(text);
1306
+ } catch {
1307
+ return [];
1308
+ }
1309
+ if (!isObject(value)) return [];
1310
+ const { at, kind, promptId, id, failed, trigger } = value;
1311
+ return typeof at === "string" && isInstant(at) && RECORD_KINDS.some((k) => k === kind) && isOptional(promptId, isTokenValue) && isOptional(id, isTokenValue) && isOptional(failed, (v) => v === true) && isOptional(trigger, (v) => v === "auto" || v === "manual") ? [value] : [];
1312
+ }
1313
+ async function readRecord(store, sessionId = store.sessionId) {
1314
+ const bytes = await (0, node_fs_promises.readFile)(recordFile(store, sessionId)).catch(() => void 0);
1315
+ if (!bytes) return void 0;
1316
+ const length = bytes.lastIndexOf(10) + 1;
1317
+ return {
1318
+ lines: bytes.subarray(0, length).toString("utf8").split("\n").flatMap(parseLine),
1319
+ length
1320
+ };
1321
+ }
1322
+ async function listSessions(store) {
1323
+ return (await (0, node_fs_promises.readdir)(store.dir).catch(() => [])).filter((name) => name.endsWith(".json")).map((name) => name.slice(0, -5)).filter(isFileSafeSessionId);
1324
+ }
1325
+ const recordChangeOf = (store, sessionId) => (0, node_fs_promises.stat)(recordFile(store, sessionId)).then(({ size, mtimeMs }) => ({
1326
+ size,
1327
+ mtimeMs
1328
+ }), () => void 0);
1329
+ const RETENTION_MS = 2592e6;
1330
+ const REMOVALS_PER_START = 100;
1331
+ async function removeExpiredFiles(store, now) {
1332
+ let removed = 0;
1333
+ for (const name of await (0, node_fs_promises.readdir)(store.dir).catch(() => [])) {
1334
+ if (removed === REMOVALS_PER_START) return;
1335
+ const file = (0, node_path.join)(store.dir, name);
1336
+ const info = await (0, node_fs_promises.lstat)(file).catch(() => void 0);
1337
+ if (!info?.isFile() || now.getTime() - info.mtimeMs <= RETENTION_MS) continue;
1338
+ if (await (0, node_fs_promises.unlink)(file).then(() => true, () => false)) removed += 1;
1339
+ }
1340
+ }
1341
+ //#endregion
1342
+ //#region src/usage/summary.ts
1343
+ const COLLECTOR_VERSION = "0.1.0";
1344
+ const MAX_ITEMS = 200;
1345
+ const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
1346
+ function callListOf(lines, kind) {
1347
+ const byName = /* @__PURE__ */ new Map();
1348
+ const other = {
1349
+ calls: 0,
1350
+ failures: 0
1351
+ };
1352
+ for (const line of lines) {
1353
+ if (line.kind !== kind) continue;
1354
+ let calls = other;
1355
+ if (line.id !== void 0) {
1356
+ calls = byName.get(line.id) ?? {
1357
+ calls: 0,
1358
+ failures: 0
1359
+ };
1360
+ byName.set(line.id, calls);
1361
+ }
1362
+ calls.calls += 1;
1363
+ if (line.failed) calls.failures += 1;
1364
+ }
1365
+ const ranked = [...byName].map(([name, calls]) => ({
1366
+ name,
1367
+ ...calls
1368
+ })).sort((a, b) => b.calls - a.calls || byCodeUnit(a.name, b.name));
1369
+ for (const rest of ranked.slice(MAX_ITEMS)) {
1370
+ other.calls += rest.calls;
1371
+ other.failures += rest.failures;
1372
+ }
1373
+ return {
1374
+ items: ranked.slice(0, MAX_ITEMS),
1375
+ other
1376
+ };
1377
+ }
1378
+ function commandListOf(lines) {
1379
+ const { items, other } = callListOf(lines, "command");
1380
+ return {
1381
+ items: items.map(({ name, calls }) => ({
1382
+ name,
1383
+ calls
1384
+ })),
1385
+ other: { calls: other.calls }
1386
+ };
1387
+ }
1388
+ const countOf = (lines, kind) => lines.filter((line) => line.kind === kind).length;
1389
+ const compactionsOf = (lines, trigger) => lines.filter((line) => line.kind === "compaction" && line.trigger === trigger).length;
1390
+ const byTime = (lines) => lines.map((line, order) => ({
1391
+ line,
1392
+ ms: Date.parse(line.at),
1393
+ order
1394
+ })).sort((a, b) => a.ms - b.ms || a.order - b.order).map(({ line }) => line);
1395
+ function buildSummary(sessionId, lines) {
1396
+ const ordered = byTime(lines);
1397
+ const first = ordered[0];
1398
+ const last = ordered.at(-1);
1399
+ if (!first || !last) return void 0;
1400
+ const firstPromptId = ordered.find((line) => line.promptId)?.promptId;
1401
+ return {
1402
+ agent: "claude_code",
1403
+ session_id: sessionId,
1404
+ ...firstPromptId === void 0 ? {} : { first_prompt_id: firstPromptId },
1405
+ started_at: first.at,
1406
+ last_event_at: last.at,
1407
+ collector_version: COLLECTOR_VERSION,
1408
+ skills: callListOf(lines, "skill"),
1409
+ commands: commandListOf(lines),
1410
+ subagents: callListOf(lines, "subagent"),
1411
+ mcp_servers: callListOf(lines, "mcp"),
1412
+ permission_requests: countOf(lines, "permission"),
1413
+ compactions: {
1414
+ auto: compactionsOf(lines, "auto"),
1415
+ manual: compactionsOf(lines, "manual")
1416
+ }
1417
+ };
1418
+ }
1419
+ //#endregion
1420
+ //#region src/usage/send.ts
1421
+ async function sendSessionUsage(store, destination, deadlineMs, deps) {
1422
+ const state = await readState(store);
1423
+ if (!state || state.workspaceId !== workspaceIdOf(destination.key)) return void 0;
1424
+ const record = await readRecord(store);
1425
+ const summary = record ? buildSummary(store.sessionId, record.lines) : void 0;
1426
+ if (!record || !summary) return void 0;
1427
+ const remainingMs = deadlineMs - deps.now().getTime();
1428
+ if (remainingMs <= 0) return {
1429
+ kind: "failed",
1430
+ reason: "TimeoutError"
1431
+ };
1432
+ const outcome = await postItems(destination, "v1/session-usage", [summary], deps.fetch, Math.ceil(remainingMs));
1433
+ if (outcome.kind === "accepted") await writeSentLength(store, store.sessionId, state, record.length);
1434
+ return outcome;
1435
+ }
1436
+ const PENDING_IDLE_MS = 6e5;
1437
+ const PENDING_PER_REQUEST = 20;
1438
+ async function pendingSessions(store, workspaceId, nowMs) {
1439
+ const pending = [];
1440
+ for (const sessionId of await listSessions(store)) {
1441
+ if (sessionId === store.sessionId) continue;
1442
+ const change = await recordChangeOf(store, sessionId);
1443
+ if (!change || nowMs - change.mtimeMs < PENDING_IDLE_MS) continue;
1444
+ const state = await readState(store, sessionId);
1445
+ if (state?.workspaceId === workspaceId && change.size > state.sentLength) pending.push({
1446
+ sessionId,
1447
+ state,
1448
+ mtimeMs: change.mtimeMs
1449
+ });
1450
+ }
1451
+ return pending.sort((a, b) => a.mtimeMs - b.mtimeMs);
1452
+ }
1453
+ async function sendUnsentUsage(store, destination, deps) {
1454
+ const workspaceId = workspaceIdOf(destination.key);
1455
+ if (workspaceId === void 0) return void 0;
1456
+ const batch = [];
1457
+ for (const pending of await pendingSessions(store, workspaceId, deps.now().getTime())) {
1458
+ if (batch.length === PENDING_PER_REQUEST) break;
1459
+ const record = await readRecord(store, pending.sessionId);
1460
+ if (!record || record.length <= pending.state.sentLength) continue;
1461
+ const summary = buildSummary(pending.sessionId, record.lines);
1462
+ if (summary) batch.push({
1463
+ ...pending,
1464
+ record,
1465
+ summary
1466
+ });
1467
+ }
1468
+ if (batch.length === 0) return void 0;
1469
+ const outcome = await postItems(destination, "v1/session-usage", batch.map((sent) => sent.summary), deps.fetch);
1470
+ if (outcome.kind === "accepted") await Promise.all(batch.map((sent) => writeSentLength(store, sent.sessionId, sent.state, sent.record.length)));
1471
+ return outcome;
1472
+ }
1473
+ //#endregion
1474
+ //#region src/hook.ts
1475
+ const pluginStatePath = (env) => env.CLAUDE_PLUGIN_DATA && (0, node_path.isAbsolute)(env.CLAUDE_PLUGIN_DATA) ? (0, node_path.join)(env.CLAUDE_PLUGIN_DATA, "update-status.json") : void 0;
1476
+ const cliStatePath = (homeDir) => (0, node_path.join)(homeDir, ".harnessforce", "update-status.json");
1477
+ const CLI_UNSUPPORTED_WARNING = unsupportedLine("cli");
1478
+ const REVOKED_KEY_MESSAGES = {
1479
+ user: "Your ingest key has been revoked. Run `harnessforce init`",
1480
+ workspace: "The organization's ingest key has been revoked. Contact a Workspace administrator"
1481
+ };
1482
+ const REGISTERING_SOURCES = /* @__PURE__ */ new Set([
1483
+ "startup",
1484
+ "clear",
1485
+ "fork"
1486
+ ]);
1487
+ const logError = (deps) => (error) => deps.stderr(`harnessforce: ${error instanceof Error ? error.message : String(error)}\n`);
1488
+ const report = (deps, subject, detail) => deps.stderr(`harnessforce: ${subject} ${detail}\n`);
1489
+ const KEY_WARNINGS = {
1490
+ absent: keyMissingWarning("claude_code"),
1491
+ withheld: DESTINATION_MISMATCH_WARNING.claude_code,
1492
+ unreadable: keyUnreadableWarning("claude_code")
1493
+ };
1494
+ async function selectUserKey(deps, cwd) {
1495
+ if (!deps.env.HARNESSFORCE_WORKSPACE_ID) return {};
1496
+ const read = await deps.readUserKey(cwd);
1497
+ if (read.kind === "found") return { key: read.key };
1498
+ if (read.kind !== "missing") deps.stderr("harnessforce: no user key in keychain or read failed\n");
1499
+ return {
1500
+ warning: KEY_WARNINGS[read.kind],
1501
+ isCliUnsupported: read.kind === "cliUnsupported"
1502
+ };
1503
+ }
1504
+ const sendingNotice = (message) => message === void 0 ? void 0 : {
1505
+ kind: "sending",
1506
+ message
1507
+ };
1508
+ async function resolveDestination(deps, cwd, withUserKey = true) {
1509
+ const managed = await readManagedEnv(deps.managedDir, ["HARNESSFORCE_INGEST_KEY", "HARNESSFORCE_ENDPOINT"]);
1510
+ const workspaceKey = managed.HARNESSFORCE_INGEST_KEY;
1511
+ if (!workspaceKey && !withUserKey) return {};
1512
+ const endpoint = workspaceKey ? managed.HARNESSFORCE_ENDPOINT : deps.env.HARNESSFORCE_ENDPOINT;
1513
+ const notConfigured = !endpoint || !(workspaceKey || deps.env.HARNESSFORCE_WORKSPACE_ID);
1514
+ const ingestBase = ingestBaseFrom(endpoint);
1515
+ if (!ingestBase) {
1516
+ report(deps, "session registration", "skipped (invalid endpoint)");
1517
+ return { warning: sendingNotice(notConfigured ? notConfiguredWarning("claude_code") : INVALID_ENDPOINT_WARNING) };
1518
+ }
1519
+ if (workspaceKey) return { destination: {
1520
+ ingestBase,
1521
+ key: workspaceKey,
1522
+ keyKind: "workspace"
1523
+ } };
1524
+ const { key: userKey, warning, isCliUnsupported } = await selectUserKey(deps, cwd);
1525
+ if (!userKey) {
1526
+ report(deps, "session registration", "skipped (no ingest key)");
1527
+ if (isCliUnsupported) {
1528
+ await recheckCliStatus(deps, ingestBase);
1529
+ return { warning: {
1530
+ kind: "clientUnsupported",
1531
+ message: CLI_UNSUPPORTED_WARNING
1532
+ } };
1533
+ }
1534
+ return { warning: sendingNotice(notConfigured ? notConfiguredWarning("claude_code") : warning) };
1535
+ }
1536
+ return { destination: {
1537
+ ingestBase,
1538
+ key: userKey,
1539
+ keyKind: "user"
1540
+ } };
1541
+ }
1542
+ async function recheckCliStatus(deps, ingestBase) {
1543
+ const statePath = cliStatePath(deps.homeDir);
1544
+ const state = await readSavedUpdateState(statePath);
1545
+ if (!state) return;
1546
+ const cli = {
1547
+ component: "cli",
1548
+ version: state.version,
1549
+ dev: false
1550
+ };
1551
+ await recheckClientStatus({
1552
+ state,
1553
+ ingestBase,
1554
+ fetch: recordingFetch(deps.baseFetch, cli, statePath, deps.now),
1555
+ statePath,
1556
+ version: state.version,
1557
+ now: deps.now
1558
+ });
1559
+ }
1560
+ function registrationSource(destination, env) {
1561
+ const issue = env.HARNESSFORCE_ISSUE;
1562
+ return destination.keyKind === "user" && issue && isIssueIdentifier(issue) ? {
1563
+ source: "cli",
1564
+ issue_identifier: issue
1565
+ } : { source: "hook" };
1566
+ }
1567
+ async function send(destination, path, item, subject, deps) {
1568
+ const outcome = await postItems(destination, path, [item], deps.fetch);
1569
+ if (outcome.kind === "failed") report(deps, subject, `failed (${outcome.reason})`);
1570
+ return outcome;
1571
+ }
1572
+ async function reportRevokedKey(destination, pad, deps) {
1573
+ const message = REVOKED_KEY_MESSAGES[destination.keyKind];
1574
+ deps.stderr(`${message}\n`);
1575
+ if (pad) await markUnauthorized(pad).catch(logError(deps));
1576
+ return message;
1577
+ }
1578
+ const writeJson = (deps, output) => deps.stdout(`${JSON.stringify(output)}\n`);
1579
+ async function registerSession(input, destination, deps) {
1580
+ const vcs = await resolveVcs(input.cwd, deps.git);
1581
+ if (!vcs) return void 0;
1582
+ const registration = {
1583
+ agent: "claude_code",
1584
+ session_id: input.sessionId,
1585
+ ...vcs,
1586
+ ...registrationSource(destination, deps.env),
1587
+ started_at: deps.now().toISOString()
1588
+ };
1589
+ if (input.scratchpad) await saveRegistration(input.scratchpad, registration).catch(logError(deps));
1590
+ return send(destination, "v1/sessions", registration, "session registration", deps);
1591
+ }
1592
+ async function collectComponents(input, deps) {
1593
+ const startedMs = deps.now().getTime();
1594
+ try {
1595
+ const result = await collectConfig({
1596
+ projectRoot: await resolveProjectRoot(input.cwd, deps.git),
1597
+ homeDir: deps.homeDir,
1598
+ managedDir: deps.managedDir,
1599
+ env: deps.env,
1600
+ isExpired: () => deps.now().getTime() - startedMs > COLLECT_BUDGET_MS
1601
+ });
1602
+ if (result.kind === "collected") return result.components;
1603
+ report(deps, "config snapshot", `skipped (${result.reason})`);
1604
+ } catch (error) {
1605
+ logError(deps)(error);
1606
+ }
1607
+ }
1608
+ async function sendConfigSnapshot(input, components, destination, deps) {
1609
+ if (components.length === 0) return void 0;
1610
+ return send(destination, "v1/config-snapshots", {
1611
+ agent: "claude_code",
1612
+ session_id: input.sessionId,
1613
+ components
1614
+ }, "config snapshot", deps);
1615
+ }
1616
+ async function createUsageState(usage, destination, components) {
1617
+ const workspaceId = workspaceIdOf(destination.key);
1618
+ if (workspaceId === void 0) return;
1619
+ await createState(usage, {
1620
+ workspaceId,
1621
+ identifiers: identifiersOf(components),
1622
+ sentLength: 0
1623
+ });
1624
+ }
1625
+ async function appendIfRecording(usage, line) {
1626
+ if (await readState(usage)) await appendRecord(usage, line);
1627
+ }
1628
+ const promptIdOf = (input) => input.promptId === void 0 ? {} : { promptId: input.promptId };
1629
+ async function startUsage(usage, destination, collected, deps) {
1630
+ const [, unsent] = await Promise.all([collected.then((components) => createUsageState(usage, destination, components ?? [])).catch(logError(deps)), sendUnsentUsage(usage, destination, deps).catch(logError(deps))]);
1631
+ if (unsent?.kind === "failed") report(deps, "session usage", `failed (${unsent.reason})`);
1632
+ return unsent ?? void 0;
1633
+ }
1634
+ async function sendSessionStart(input, usage, deps) {
1635
+ const registers = input.source === void 0 || REGISTERING_SOURCES.has(input.source);
1636
+ if (!registers && !usage) return void 0;
1637
+ if (input.scratchpad && await isMarkedUnauthorized(input.scratchpad)) return void 0;
1638
+ const { destination, warning } = await resolveDestination(deps, input.cwd);
1639
+ if (!destination) return registers ? warning : void 0;
1640
+ const collected = registers || usage && !await readState(usage) ? collectComponents(input, deps) : Promise.resolve(void 0);
1641
+ const isConfigured = registers && destination.keyKind === "user" ? deps.isTelemetryConfigured() : Promise.resolve(true);
1642
+ const [registration, snapshot, unsent, configured] = await Promise.all([
1643
+ registers ? registerSession(input, destination, deps).catch(logError(deps)) : void 0,
1644
+ registers ? collected.then((components) => components ? sendConfigSnapshot(input, components, destination, deps) : void 0).catch(logError(deps)) : void 0,
1645
+ usage ? startUsage(usage, destination, collected, deps) : void 0,
1646
+ isConfigured
1647
+ ]);
1648
+ if ([
1649
+ registration,
1650
+ snapshot,
1651
+ unsent
1652
+ ].some((outcome) => outcome?.kind === "unauthorized")) return {
1653
+ kind: "revoked",
1654
+ message: await reportRevokedKey(destination, input.scratchpad, deps)
1655
+ };
1656
+ if (!configured) return sendingNotice(TELEMETRY_NOT_CONFIGURED_WARNING);
1657
+ return sendingNotice(registration?.kind === "failed" ? REGISTRATION_FAILED_WARNING : void 0);
1658
+ }
1659
+ async function statusCheckBase(deps) {
1660
+ const managed = await readManagedEnv(deps.managedDir, ["HARNESSFORCE_INGEST_KEY", "HARNESSFORCE_ENDPOINT"]);
1661
+ return ingestBaseFrom(managed.HARNESSFORCE_INGEST_KEY ? managed.HARNESSFORCE_ENDPOINT : deps.env.HARNESSFORCE_ENDPOINT);
1662
+ }
1663
+ /**
1664
+ * pluginの状態のfileがunsupportedなら送信を止める(client-updates.md「送信の停止」)。recheckなら、受けてから24時間以上
1665
+ * 経った状態を資格情報を送らずに確かめ直す。SessionEndは1秒の予算に収めるため確かめ直さない。
1666
+ */
1667
+ async function isPluginBlocked(deps, recheck) {
1668
+ const statePath = pluginStatePath(deps.env);
1669
+ if (!statePath) return false;
1670
+ const state = await readUpdateState(statePath, deps.client.version);
1671
+ if (state?.status !== "unsupported") return false;
1672
+ const ingestBase = recheck ? await statusCheckBase(deps) : void 0;
1673
+ return (ingestBase ? await recheckClientStatus({
1674
+ state,
1675
+ ingestBase,
1676
+ fetch: deps.fetch,
1677
+ statePath,
1678
+ version: deps.client.version,
1679
+ now: deps.now
1680
+ }) : state)?.status === "unsupported";
1681
+ }
1682
+ async function withUpdateLine(notice, input, deps) {
1683
+ if (notice && notice.kind !== "sending") return notice.message;
1684
+ const statePath = pluginStatePath(deps.env);
1685
+ const state = statePath ? await readUpdateState(statePath, deps.client.version) : void 0;
1686
+ if (state?.status === "unsupported") return updateLine("claude-code-plugin", state);
1687
+ if (notice || !state || !statePath) return notice?.message;
1688
+ return noticeLine("claude-code-plugin", statePath, state, {
1689
+ now: deps.now(),
1690
+ env: deps.env,
1691
+ isSuppressed: () => deps.isPluginAutoUpdateEffective(input.cwd)
1692
+ });
1693
+ }
1694
+ async function onSessionStart(input, deps) {
1695
+ const usage = usageStoreOf(input.sessionId, deps.env);
1696
+ const startedAt = deps.now();
1697
+ if (usage) await removeExpiredFiles(usage, startedAt).catch(logError(deps));
1698
+ const registers = input.source === void 0 || REGISTERING_SOURCES.has(input.source);
1699
+ const sent = await isPluginBlocked(deps, true).catch(() => false) ? void 0 : await sendSessionStart(input, usage, deps).catch((error) => {
1700
+ logError(deps)(error);
1701
+ });
1702
+ const message = registers ? await withUpdateLine(sent, input, deps).catch(() => sent?.message) : sent?.message;
1703
+ if (usage) await appendIfRecording(usage, {
1704
+ at: startedAt.toISOString(),
1705
+ kind: "start",
1706
+ ...promptIdOf(input)
1707
+ }).catch(logError(deps));
1708
+ writeJson(deps, {
1709
+ ...sessionContext(input.sessionId),
1710
+ ...message === void 0 ? {} : { systemMessage: message }
1711
+ });
1712
+ }
1713
+ async function onUserPromptSubmit(input, deps) {
1714
+ const usage = usageStoreOf(input.sessionId, deps.env);
1715
+ if (usage) await appendIfRecording(usage, {
1716
+ at: deps.now().toISOString(),
1717
+ kind: "prompt",
1718
+ ...promptIdOf(input)
1719
+ }).catch(logError(deps));
1720
+ const pad = input.scratchpad;
1721
+ if (!input.promptId || !pad || await isMarkedUnauthorized(pad)) return;
1722
+ if (await isPluginBlocked(deps, false)) return;
1723
+ const saved = await loadRegistration(pad);
1724
+ if (!saved || await isFirstPromptSent(pad)) return;
1725
+ const { destination } = await resolveDestination(deps, input.cwd);
1726
+ if (!destination || !await claimFirstPrompt(pad)) return;
1727
+ if ((await send(destination, "v1/sessions", {
1728
+ ...saved,
1729
+ first_prompt_id: input.promptId
1730
+ }, "session registration", deps)).kind === "unauthorized") writeJson(deps, { systemMessage: await reportRevokedKey(destination, pad, deps) });
1731
+ }
1732
+ const SESSION_END_BUDGET_MS = 1e3;
1733
+ async function onSessionEnd(input, deps) {
1734
+ const usage = usageStoreOf(input.sessionId, deps.env);
1735
+ if (!usage) return;
1736
+ if (input.scratchpad && await isMarkedUnauthorized(input.scratchpad)) return;
1737
+ if (await isPluginBlocked(deps, false)) return;
1738
+ const { destination } = await resolveDestination(deps, input.cwd, false);
1739
+ if (!destination) return;
1740
+ const outcome = await sendSessionUsage(usage, destination, deps.processStartMs + SESSION_END_BUDGET_MS, deps);
1741
+ if (outcome && outcome.kind !== "accepted") report(deps, "session usage", `failed (${outcome.kind === "failed" ? outcome.reason : "HTTP 401"})`);
1742
+ }
1743
+ const onCountingEvent = (event) => async (input, deps) => {
1744
+ const usage = usageStoreOf(input.sessionId, deps.env);
1745
+ const state = usage && await readState(usage);
1746
+ const counted = state && countedOf(event, input.fields, state.identifiers);
1747
+ if (usage && counted) await appendRecord(usage, {
1748
+ at: deps.now().toISOString(),
1749
+ ...promptIdOf(input),
1750
+ ...counted
1751
+ });
1752
+ };
1753
+ const HANDLERS = new Map([
1754
+ ["session-start", onSessionStart],
1755
+ ["user-prompt-submit", onUserPromptSubmit],
1756
+ ["session-end", onSessionEnd],
1757
+ ...COUNTING_EVENTS.map((event) => [event, onCountingEvent(event)])
1758
+ ]);
1759
+ async function runHook(event, rawInput, hookDeps) {
1760
+ const handler = HANDLERS.get(event);
1761
+ const input = parseHookInput(rawInput);
1762
+ if (!handler || !input) return;
1763
+ const deps = {
1764
+ ...hookDeps,
1765
+ baseFetch: hookDeps.fetch,
1766
+ fetch: recordingFetch(hookDeps.fetch, hookDeps.client, pluginStatePath(hookDeps.env), hookDeps.now)
1767
+ };
1768
+ await handler(input, deps).catch(logError(deps));
1769
+ }
1770
+ //#endregion
1771
+ //#region src/user-key.ts
1772
+ const HF_TIMEOUT_MS = 1e3;
1773
+ const HF_OUTPUT_LIMIT_BYTES = 65536;
1774
+ const BEARER = /^Bearer (\S+)$/;
1775
+ const FAILURES = {
1776
+ 3: { kind: "absent" },
1777
+ 4: { kind: "withheld" },
1778
+ 5: { kind: "cliUnsupported" }
1779
+ };
1780
+ function parseKey(stdout) {
1781
+ try {
1782
+ const value = JSON.parse(stdout);
1783
+ if (!isObject(value)) return void 0;
1784
+ const authorization = value.Authorization;
1785
+ return typeof authorization === "string" ? BEARER.exec(authorization)?.[1] : void 0;
1786
+ } catch {
1787
+ return;
1788
+ }
1789
+ }
1790
+ function createUserKeyReader({ platform, env, exec, processCwd, fs }) {
1791
+ return async (cwd) => {
1792
+ const context = {
1793
+ platform,
1794
+ env,
1795
+ bases: [processCwd, cwd],
1796
+ ...fs ? { fs } : {}
1797
+ };
1798
+ const file = await findCommand("harnessforce", context);
1799
+ const command = file === void 0 ? void 0 : await commandLineFor(file, ["otel-headers"], context, { quoteArgs: false });
1800
+ if (!command) return { kind: "missing" };
1801
+ const result = await exec(command.file, command.args, {
1802
+ timeoutMs: HF_TIMEOUT_MS,
1803
+ windowsVerbatimArguments: command.verbatim,
1804
+ env: {
1805
+ ...env,
1806
+ HARNESSFORCE_NO_MIGRATE: "1"
1807
+ }
1808
+ });
1809
+ const key = result.ok ? parseKey(result.stdout) : void 0;
1810
+ if (key) return {
1811
+ kind: "found",
1812
+ key
1813
+ };
1814
+ return result.exitCode !== void 0 && FAILURES[result.exitCode] || { kind: "unreadable" };
1815
+ };
1816
+ }
1817
+ const execHf = (file, args, options) => new Promise((resolve) => {
1818
+ (0, node_child_process.execFile)(file, args, {
1819
+ encoding: "utf8",
1820
+ env: options.env,
1821
+ timeout: options.timeoutMs,
1822
+ killSignal: "SIGKILL",
1823
+ maxBuffer: HF_OUTPUT_LIMIT_BYTES,
1824
+ windowsHide: true,
1825
+ windowsVerbatimArguments: options.windowsVerbatimArguments
1826
+ }, (error, stdout) => resolve({
1827
+ ok: !error,
1828
+ stdout,
1829
+ ...error && !error.killed && typeof error.code === "number" ? { exitCode: error.code } : {}
1830
+ }));
1831
+ });
1832
+ //#endregion
1833
+ //#region src/main.ts
1834
+ delete process.env.HARNESSFORCE_RUNTIME_ENV;
1835
+ async function readStdin() {
1836
+ const chunks = [];
1837
+ for await (const chunk of process.stdin) chunks.push(chunk);
1838
+ return Buffer.concat(chunks).toString("utf8");
1839
+ }
1840
+ for (const stream of [process.stdout, process.stderr]) stream.on("error", () => {});
1841
+ async function main() {
1842
+ const relaunch = await relaunchWithoutRuntimeVariables({
1843
+ platform: process.platform,
1844
+ env: process.env,
1845
+ stash: false,
1846
+ spawnSelf
1847
+ });
1848
+ if (relaunch.kind === "failed") {
1849
+ process.stderr.write("harnessforce: session registration skipped (restart failed)\n");
1850
+ process.stdout.write(warningOnlyOutput(RESTART_FAILED_WARNING));
1851
+ }
1852
+ if (relaunch.kind !== "not-needed") return;
1853
+ const raw = await readStdin().catch(() => "");
1854
+ const managedDir = managedDirFor(process.platform);
1855
+ const userSettingsPath = (0, node_path.join)(absoluteEnv(process.env.CLAUDE_CONFIG_DIR) ?? (0, node_path.join)((0, node_os.homedir)(), ".claude"), "settings.json");
1856
+ await runHook(process.argv[2] ?? "", raw, {
1857
+ env: process.env,
1858
+ now: () => /* @__PURE__ */ new Date(),
1859
+ processStartMs: node_perf_hooks.performance.timeOrigin,
1860
+ git: createGitRunner({
1861
+ platform: process.platform,
1862
+ env: process.env,
1863
+ processCwd: process.cwd(),
1864
+ excludeTarget: true
1865
+ }),
1866
+ fetch: (url, init) => fetch(url, init),
1867
+ stdout: (text) => process.stdout.write(text),
1868
+ stderr: (text) => process.stderr.write(text),
1869
+ homeDir: (0, node_os.homedir)(),
1870
+ managedDir,
1871
+ readUserKey: createUserKeyReader({
1872
+ platform: process.platform,
1873
+ env: process.env,
1874
+ exec: execHf,
1875
+ processCwd: process.cwd()
1876
+ }),
1877
+ isTelemetryConfigured: () => isTelemetryConfigured({
1878
+ userSettingsPath,
1879
+ managedDir
1880
+ }),
1881
+ client: {
1882
+ component: "claude-code-plugin",
1883
+ version: "0.2.0",
1884
+ dev: false
1885
+ },
1886
+ isPluginAutoUpdateEffective: (cwd) => isPluginAutoUpdateEffective({
1887
+ userSettingsPath,
1888
+ managedDir,
1889
+ projectDir: absoluteEnv(process.env.CLAUDE_PROJECT_DIR) ?? cwd,
1890
+ env: process.env
1891
+ })
1892
+ });
1893
+ }
1894
+ main();
1895
+ //#endregion