@harnessforce/cli 0.2.0 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +33 -11
  2. package/dist/bin.js +618 -443
  3. package/package.json +5 -5
package/dist/bin.js CHANGED
@@ -3,15 +3,15 @@ import { createRequire } from "node:module";
3
3
  import { constants, homedir, tmpdir } from "node:os";
4
4
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
5
5
  import { basename, dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
6
- import { createHash, randomBytes, randomUUID } from "node:crypto";
7
- import { access, chmod, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
6
+ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
7
+ import { access, chmod, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, utimes, writeFile } from "node:fs/promises";
8
8
  import { execFile, spawn } from "node:child_process";
9
9
  import { constants as constants$1, createReadStream } from "node:fs";
10
10
  import { createServer } from "node:http";
11
- import { ATTR, INTERVENTION_KINDS, LOOP_KINDS, PROPOSAL_KINDS, isToken, normalizeRepository } from "@harnessforce/semconv";
11
+ import { ATTR, INTERVENTION_KINDS, LOOP_KINDS, MAX_WORK_LOCATIONS, PROPOSAL_KINDS, isToken, normalizeRepository } from "@harnessforce/semconv";
12
12
  import { createInterface } from "node:readline";
13
13
  import { isDeepStrictEqual } from "node:util";
14
- //#region ../agent-core/src/config/canonical.ts
14
+ // ../agent-core/src/config/canonical.ts
15
15
  const byCodeUnit$1 = (a, b) => a < b ? -1 : a > b ? 1 : 0;
16
16
  function canonicalJson(value) {
17
17
  if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
@@ -28,11 +28,9 @@ function sortComponents(components) {
28
28
  return [...components].sort((a, b) => byCodeUnit$1(a.kind, b.kind) || byCodeUnit$1(a.source, b.source) || byCodeUnit$1(a.id, b.id));
29
29
  }
30
30
  const snapshotId = (components) => hashValue(sortComponents(components));
31
- //#endregion
32
- //#region ../agent-core/src/object.ts
31
+ // ../agent-core/src/object.ts
33
32
  const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
34
- //#endregion
35
- //#region ../agent-core/src/config/files.ts
33
+ // ../agent-core/src/config/files.ts
36
34
  const MAX_DEPTH = 8;
37
35
  const MAX_FILE_BYTES = 1048576;
38
36
  var CollectionExpired = class extends Error {};
@@ -93,8 +91,7 @@ async function listDirectories(dir, guard) {
93
91
  for (const entry of await entries(dir, guard)) if (await kindOf(dir, entry, guard) === "directory") names.push(entry.name);
94
92
  return names;
95
93
  }
96
- //#endregion
97
- //#region ../agent-core/src/managed.ts
94
+ // ../agent-core/src/managed.ts
98
95
  const MANAGED_DIRS = {
99
96
  darwin: "/Library/Application Support/ClaudeCode",
100
97
  win32: "C:\\Program Files\\ClaudeCode"
@@ -113,8 +110,7 @@ async function readManagedEnv(managedDir, names) {
113
110
  }
114
111
  return values;
115
112
  }
116
- //#endregion
117
- //#region ../agent-core/src/process/lookup.ts
113
+ // ../agent-core/src/process/lookup.ts
118
114
  const DEFAULT_PATHEXT = ".COM;.EXE;.BAT;.CMD";
119
115
  const NPM_SHIM_MAX_BYTES = 65536;
120
116
  const SHEBANG_MAX_BYTES = 256;
@@ -295,8 +291,7 @@ async function commandLineFor(file, args, context, { quoteArgs = true } = {}) {
295
291
  verbatim: true
296
292
  };
297
293
  }
298
- //#endregion
299
- //#region ../agent-core/src/process/git.ts
294
+ // ../agent-core/src/process/git.ts
300
295
  const GIT_TIMEOUT_MS$1 = 1e3;
301
296
  const execGit = (file, args, env, timeoutMs) => new Promise((resolve) => {
302
297
  execFile(file, args, {
@@ -334,8 +329,7 @@ function createGitRunner({ platform, env, processCwd, excludeTarget, timeoutMs =
334
329
  ], gitEnv, timeoutMs);
335
330
  };
336
331
  }
337
- //#endregion
338
- //#region ../agent-core/src/process/runtime-env.ts
332
+ // ../agent-core/src/process/runtime-env.ts
339
333
  const RUNTIME_VARIABLES = [
340
334
  "NODE_TLS_REJECT_UNAUTHORIZED",
341
335
  "NODE_EXTRA_CA_CERTS",
@@ -412,8 +406,7 @@ function takeStashedRuntimeEnv(env, platform) {
412
406
  if (!isObject(value)) return {};
413
407
  return Object.fromEntries(Object.entries(value).filter((entry) => typeof entry[1] === "string" && isRuntimeVariable(entry[0], platform)));
414
408
  }
415
- //#endregion
416
- //#region src/shared/messages.ts
409
+ // src/shared/messages.ts
417
410
  const INIT_MESSAGES = {
418
411
  success: "Connected to Harnessforce. Restart Claude Code to apply the settings",
419
412
  pluginAutoUpdateOn: "Automatic updates for the Harnessforce plugin are on. To turn them off, run `harnessforce init --no-plugin-auto-update` or use /plugin",
@@ -432,25 +425,32 @@ const INIT_MESSAGES = {
432
425
  noWorkspace: "You are not a member of any Workspace. Create a Workspace or accept an invitation, then run `harnessforce init` again",
433
426
  loginFailed: "The login failed. Run `harnessforce init` again",
434
427
  viewer: "The view-only role cannot issue an ingest key. Contact a Workspace administrator",
435
- limitOwnerAdmin: "The number of active ingest keys has reached the limit. Revoke unused keys on the ingest keys page",
428
+ limitOwnerAdmin: "The number of active ingest keys has reached the limit. Ask members to revoke unused keys in My ingest keys on their account page",
436
429
  limitMember: "The ingest key limit has been reached. Contact a Workspace administrator",
437
430
  gateUnavailable: "Could not apply the change on the receiving side. Nothing was changed. Try again",
438
431
  ingestUnavailable: "This environment does not receive telemetry, so an ingest key cannot be issued",
439
432
  saveFailed: "Could not save the ingest key. Run `harnessforce init` again",
440
433
  codexSaveFailed: "Could not write the Codex configuration. Run `harnessforce init` again",
441
- contentEnabled: "Sending the prompts you type is now enabled. Values that match known patterns for secrets and personal information are masked, but not everything can be detected. Within the workspace, only you can read your stored content, and it is deleted after the content retention period (at most 30 days)",
442
- contentNotOptedIn: "This workspace has not opted in to prompts you typed, so sending content was not enabled. Opt in under Data retention in the workspace settings, then run `harnessforce init --send-content` again"
434
+ contentNotStored: "This workspace does not store content, so sending content was not enabled. An owner or admin can turn it on under Collection & retention in the workspace settings, then run `harnessforce init --send-content` again",
435
+ contentNotSent: "Content is not sent from this machine. Run `harnessforce init --send-content` to send it"
443
436
  };
444
437
  const codexNotes = (configPath) => [
445
438
  `Wrote the Codex configuration (${configPath}). Restart Codex to apply it`,
446
- "Codex hooks do not run until you trust them in Codex's /hooks. Trust the harnessforce SessionStart hook in /hooks",
439
+ "Codex hooks do not run until you trust them in Codex's /hooks. Trust the harnessforce SessionStart, PostToolUse, and Stop hooks in /hooks",
447
440
  "To use the Harnessforce MCP server, log in with `codex mcp login harnessforce`"
448
441
  ].join("\n");
449
442
  const nonDefaultConnectionMessage = (url) => `Connecting to ${url} (not the default production endpoint)`;
450
443
  const listenFailedMessage = (port) => `${INIT_MESSAGES.listenFailed}. Stop the process using port ${port}, or specify another port with \`harnessforce init --port\``;
451
444
  const keychainUnavailableMessage = (keychain, message = INIT_MESSAGES.keychainUnavailable) => keychain.unavailableHint ? `${message}. ${keychain.unavailableHint}` : message;
452
- //#endregion
453
- //#region src/credentials/keychain.ts
445
+ const CONTENT_READERS = {
446
+ all_members: "Everyone in the workspace can read it",
447
+ owners_admins_and_sender: "Owners, admins and you can read it",
448
+ sender_only: "Within the workspace, only you can read it"
449
+ };
450
+ function contentEnabledMessage(content) {
451
+ return `Sending ${[...content.isPromptsStored ? ["the prompts you type"] : [], ...content.isModelAndToolsStored ? ["model responses and tool input and output"] : []].join(", ")} is now enabled. Values that match known patterns for secrets and personal information are masked, but not everything can be detected. ${CONTENT_READERS[content.visibility]}. It is deleted after ${content.retentionDays} days`;
452
+ }
453
+ // src/credentials/keychain.ts
454
454
  async function openSingleStore(store) {
455
455
  if (!await store.isAvailable()) return void 0;
456
456
  return {
@@ -476,8 +476,7 @@ const workspaceAccounts = (workspaceId) => [
476
476
  ];
477
477
  const isIngestKeyAccount = (account) => account.endsWith(INGEST_KEY_SUFFIX);
478
478
  const apiTokenWorkspaceId = (account) => account.endsWith(API_TOKEN_SUFFIX) ? account.slice(0, -10) : void 0;
479
- //#endregion
480
- //#region src/credentials/linux-keychain.ts
479
+ // src/credentials/linux-keychain.ts
481
480
  const SECRET_SERVICE_TIMEOUT_MS = 6e4;
482
481
  const WAITING_NOTICE_DELAY_MS = 2e3;
483
482
  const PROBE_ACCOUNT$1 = "availability-probe";
@@ -631,8 +630,7 @@ async function migrate({ kernel, secretService }, workspaceId) {
631
630
  clearTimeout(timer);
632
631
  }
633
632
  }
634
- //#endregion
635
- //#region src/credentials/os-keychain.ts
633
+ // src/credentials/os-keychain.ts
636
634
  const PROBE_ACCOUNT = "availability-probe";
637
635
  function createOsKeychain({ platform, load, secretService }) {
638
636
  let loaded;
@@ -714,8 +712,7 @@ const unsupported = {
714
712
  withTimeout: () => unsupported,
715
713
  openForInit: async () => void 0
716
714
  };
717
- //#endregion
718
- //#region src/credentials/secret-service-process.ts
715
+ // src/credentials/secret-service-process.ts
719
716
  const CHILD_SCRIPT = `
720
717
  const [op, service, account, timeoutMs, modulePath] = process.argv.slice(1);
721
718
  setTimeout(() => process.kill(process.pid, "SIGKILL"), Number(timeoutMs));
@@ -790,8 +787,7 @@ function createSecretServiceProcess({ execPath, resolveKeyring }) {
790
787
  }
791
788
  };
792
789
  }
793
- //#endregion
794
- //#region src/import/repository.ts
790
+ // src/import/repository.ts
795
791
  const GIT_TIMEOUT_MS = 5e3;
796
792
  const createImportGit = (platform, env, processCwd) => createGitRunner({
797
793
  platform,
@@ -800,8 +796,7 @@ const createImportGit = (platform, env, processCwd) => createGitRunner({
800
796
  excludeTarget: false,
801
797
  timeoutMs: GIT_TIMEOUT_MS
802
798
  });
803
- //#endregion
804
- //#region src/init/browser.ts
799
+ // src/init/browser.ts
805
800
  const STILL_RUNNING_MS = 3e3;
806
801
  function openerFor(platform, url) {
807
802
  if (platform === "darwin") return ["open", [url]];
@@ -838,35 +833,63 @@ async function openBrowser(url, { platform, env, cwd, restoredEnv, spawn: spawnI
838
833
  child.once("exit", (code) => finish(code === 0));
839
834
  });
840
835
  }
841
- //#endregion
842
- //#region src/init/loopback.ts
836
+ // src/init/loopback.ts
843
837
  const HOST = "127.0.0.1";
844
838
  const CALLBACK_PATH = "/callback";
845
- const page = (message) => `<!doctype html><html lang="en"><meta charset="utf-8"><title>Harnessforce</title><p>${message}</p></html>`;
846
- const SUCCESS_PAGE = page("Return to the terminal");
847
- const FAILURE_PAGE = page("The login failed. Check the terminal");
839
+ const COMPLETION_PATH = "/cli/connected";
840
+ function completionUrl(webOrigin, completion) {
841
+ const url = new URL(COMPLETION_PATH, webOrigin);
842
+ url.searchParams.set("status", completion.status);
843
+ if (completion.status === "connected" && completion.workspaceId) url.searchParams.set("workspace", completion.workspaceId);
844
+ return url.href;
845
+ }
846
+ function parseRequestUrl(target) {
847
+ try {
848
+ return new URL(target ?? "/", `http://${HOST}`);
849
+ } catch {
850
+ return;
851
+ }
852
+ }
853
+ function isSameState(received, expected) {
854
+ if (received === null) return false;
855
+ const receivedBytes = Buffer.from(received, "utf8");
856
+ const expectedBytes = Buffer.from(expected, "utf8");
857
+ return receivedBytes.length === expectedBytes.length && timingSafeEqual(receivedBytes, expectedBytes);
858
+ }
848
859
  async function startLoopback(options) {
849
860
  let settle = () => {};
850
861
  const callback = new Promise((resolve) => {
851
862
  settle = resolve;
852
863
  });
864
+ let held;
853
865
  const server = createServer((req, res) => {
854
- const url = new URL(req.url ?? "/", `http://${HOST}`);
866
+ const url = parseRequestUrl(req.url);
867
+ if (url === void 0) {
868
+ res.writeHead(400).end();
869
+ return;
870
+ }
855
871
  if (url.pathname !== CALLBACK_PATH) {
856
872
  res.writeHead(404).end();
857
873
  return;
858
874
  }
859
- const received = {
875
+ if (isStopped) {
876
+ res.writeHead(404).end();
877
+ return;
878
+ }
879
+ if (!isSameState(url.searchParams.get("state"), options.state)) {
880
+ res.writeHead(303, {
881
+ location: completionUrl(options.webOrigin, { status: "failed" }),
882
+ "cache-control": "no-store"
883
+ });
884
+ res.end();
885
+ return;
886
+ }
887
+ held = res;
888
+ stop({
860
889
  code: url.searchParams.get("code") || void 0,
861
890
  state: url.searchParams.get("state") ?? void 0,
862
891
  error: url.searchParams.get("error") ?? void 0
863
- };
864
- const isSuccess = !received.error && Boolean(received.code) && received.state === options.state;
865
- res.writeHead(200, {
866
- "content-type": "text/html; charset=utf-8",
867
- "cache-control": "no-store"
868
892
  });
869
- res.end(isSuccess ? SUCCESS_PAGE : FAILURE_PAGE, () => stop(received));
870
893
  });
871
894
  const timer = setTimeout(() => stop(void 0), options.timeoutMs);
872
895
  let isStopped = false;
@@ -875,9 +898,19 @@ async function startLoopback(options) {
875
898
  isStopped = true;
876
899
  clearTimeout(timer);
877
900
  server.close();
878
- server.closeAllConnections();
901
+ if (!held) server.closeAllConnections();
879
902
  settle(received);
880
903
  }
904
+ function finish(completion) {
905
+ const response = held;
906
+ held = void 0;
907
+ if (!response) return;
908
+ response.writeHead(303, {
909
+ location: completionUrl(options.webOrigin, completion),
910
+ "cache-control": "no-store"
911
+ });
912
+ response.end(() => server.closeAllConnections());
913
+ }
881
914
  await new Promise((resolve, reject) => {
882
915
  server.once("error", reject);
883
916
  server.listen(options.port, HOST, resolve);
@@ -888,11 +921,11 @@ async function startLoopback(options) {
888
921
  const { port } = server.address();
889
922
  return {
890
923
  redirectUri: `http://${HOST}:${port}${CALLBACK_PATH}`,
891
- callback
924
+ callback,
925
+ finish
892
926
  };
893
927
  }
894
- //#endregion
895
- //#region ../agent-core/src/url.ts
928
+ // ../agent-core/src/url.ts
896
929
  const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
897
930
  "localhost",
898
931
  "127.0.0.1",
@@ -914,8 +947,7 @@ function underBase(base, path) {
914
947
  return url;
915
948
  }
916
949
  const withoutExtras = (url) => `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
917
- //#endregion
918
- //#region ../agent-core/src/client-update.ts
950
+ // ../agent-core/src/client-update.ts
919
951
  const CLIENT_UPDATE_HEADER = "harnessforce-client-update";
920
952
  const UPDATE_COMMANDS = {
921
953
  cli: "npm install -g @harnessforce/cli@latest",
@@ -1074,8 +1106,7 @@ async function recheckClientStatus(options) {
1074
1106
  }
1075
1107
  return await readUpdateState(options.statePath, options.version) ?? state;
1076
1108
  }
1077
- //#endregion
1078
- //#region ../agent-core/src/ingest.ts
1109
+ // ../agent-core/src/ingest.ts
1079
1110
  const SEND_TIMEOUT_MS = 2e3;
1080
1111
  function ingestBaseFrom(endpoint) {
1081
1112
  const base = parseAllowedUrl(endpoint);
@@ -1110,8 +1141,7 @@ async function postItems(destination, path, items, fetchImpl, timeoutMs = SEND_T
1110
1141
  };
1111
1142
  }
1112
1143
  }
1113
- //#endregion
1114
- //#region ../agent-core/src/config/hash-input.ts
1144
+ // ../agent-core/src/config/hash-input.ts
1115
1145
  function withoutSecrets(entry) {
1116
1146
  const { env, headers, url, args: _args, ...rest } = entry;
1117
1147
  const result = rest;
@@ -1143,8 +1173,7 @@ function hashInput(kind, value) {
1143
1173
  if (kind === "hook" && Array.isArray(value)) return value.map(reduceHookGroup);
1144
1174
  return value;
1145
1175
  }
1146
- //#endregion
1147
- //#region ../agent-core/src/config/scope.ts
1176
+ // ../agent-core/src/config/scope.ts
1148
1177
  const MAX_COMPONENTS = 1e3;
1149
1178
  var TooManyComponents = class extends Error {};
1150
1179
  var Sink = class {
@@ -1200,11 +1229,9 @@ function collectMcpServers$1(s, servers) {
1200
1229
  if (!isObject(servers)) return;
1201
1230
  for (const [name, value] of Object.entries(servers)) if (isObject(value)) s.addValue("mcp_server", name, value);
1202
1231
  }
1203
- //#endregion
1204
- //#region ../agent-core/src/plugin-auto-update.ts
1232
+ // ../agent-core/src/plugin-auto-update.ts
1205
1233
  const MARKETPLACE_NAME = "harnessforce-agent";
1206
- //#endregion
1207
- //#region src/shared/settings.ts
1234
+ // src/shared/settings.ts
1208
1235
  const OTEL_HEADERS_HELPER = "harnessforce otel-headers";
1209
1236
  const PLUGIN_ID$1 = "harnessforce@harnessforce-agent";
1210
1237
  const NEW_FILE_MODE = 384;
@@ -1292,8 +1319,7 @@ async function writeUserSettings(path, settings) {
1292
1319
  await rm(temporary, { force: true });
1293
1320
  }
1294
1321
  }
1295
- //#endregion
1296
- //#region src/destinations.ts
1322
+ // src/destinations.ts
1297
1323
  const NAMES = [
1298
1324
  "HARNESSFORCE_URL",
1299
1325
  "HARNESSFORCE_ENDPOINT",
@@ -1314,8 +1340,7 @@ async function resolveCliDestinations(env, settingsPath, defaultUrl) {
1314
1340
  readApiUrl: pick("HARNESSFORCE_URL") ?? defaultUrl
1315
1341
  };
1316
1342
  }
1317
- //#endregion
1318
- //#region src/client-update.ts
1343
+ // src/client-update.ts
1319
1344
  const updateStatePath = (homeDir) => join(homeDir, ".harnessforce", "update-status.json");
1320
1345
  const readCliUpdateState = (homeDir, version) => readUpdateState(updateStatePath(homeDir), version);
1321
1346
  /**
@@ -1348,8 +1373,7 @@ async function noticeUpdate(deps) {
1348
1373
  });
1349
1374
  if (line) deps.stderr(`${line}\n`);
1350
1375
  }
1351
- //#endregion
1352
- //#region src/help.ts
1376
+ // src/help.ts
1353
1377
  const SENDS_NOTHING_LOCAL = "Nothing. It reads local files only and makes no network request.";
1354
1378
  const COMMANDS$1 = {
1355
1379
  init: {
@@ -1366,8 +1390,9 @@ What it does:
1366
1390
 
1367
1391
  Options:
1368
1392
  --port <port> Loopback port for the login callback, 1-65535 (default 8080).
1369
- --send-content Also send the text of the prompts you type. Takes effect only
1370
- when the Workspace has opted in. Off by default.
1393
+ --send-content Also send the prompts you type, model responses and tool
1394
+ input and output. Takes effect only for the categories the
1395
+ Workspace stores. Without it, init turns all of them off.
1371
1396
  --no-plugin-auto-update
1372
1397
  Turn off automatic updates of the Harnessforce plugin in
1373
1398
  Claude Code. On by default; a value you set is kept.
@@ -1377,9 +1402,9 @@ Sends:
1377
1402
  telemetry (token counts, tool names, timings) with that key.
1378
1403
 
1379
1404
  Does not send:
1380
- Prompt text (unless --send-content and the Workspace opt-in), model responses,
1381
- tool input or output, code diffs, and secrets. The key is never written to a
1382
- plain file.
1405
+ Prompt text, model responses and tool input or output (unless --send-content
1406
+ and the Workspace stores that category), code diffs, and secrets. The key is
1407
+ never written to a plain file.
1383
1408
 
1384
1409
  Exit codes:
1385
1410
  0 Connected and configured.
@@ -1469,8 +1494,8 @@ Sends:
1469
1494
  The same telemetry as an agent started directly, plus the Issue identifier.
1470
1495
 
1471
1496
  Does not send:
1472
- Anything beyond what init configured. Prompt text is sent only if you chose
1473
- --send-content at init.
1497
+ Anything beyond what init configured. Prompt text, responses and tool input
1498
+ and output are sent only if you chose --send-content at init.
1474
1499
 
1475
1500
  Exit codes:
1476
1501
  The agent's own exit code when it ran.
@@ -1561,20 +1586,26 @@ Examples:
1561
1586
  `
1562
1587
  },
1563
1588
  hook: {
1564
- summary: "Session-start hook (used by Codex)",
1565
- text: `Usage: harnessforce hook session-start
1589
+ summary: "Session hooks (used by Codex)",
1590
+ text: `Usage: harnessforce hook session-start|post-tool-use|stop
1566
1591
 
1567
1592
  When to use:
1568
- You normally never run it. Codex runs it when a session starts.
1593
+ You normally never run it. Codex runs session-start when a session starts,
1594
+ post-tool-use after it edits files with apply_patch, and stop at the end of
1595
+ each turn.
1569
1596
 
1570
1597
  What it does:
1571
- Reads the hook input from stdin, registers the session with the Workspace, and
1572
- prints context for the agent (the session ID, plus a one-line warning if
1573
- registration failed).
1598
+ session-start reads the hook input from stdin, registers the session with the
1599
+ Workspace, and prints context for the agent (the session ID, plus a one-line
1600
+ warning if registration failed).
1601
+ post-tool-use and stop record the repository and branch of the Git working
1602
+ trees the session worked in, and stop sends them when they changed.
1574
1603
 
1575
1604
  Sends:
1576
1605
  Session registration: agent, session ID, repository, branch, commit, and
1577
1606
  start time.
1607
+ Work locations: agent, session ID, and the repository and branch of each
1608
+ working tree. Paths, commits, and file names are not sent.
1578
1609
 
1579
1610
  Does not send:
1580
1611
  Prompt text, model responses, tool input or output, and code diffs.
@@ -1631,8 +1662,7 @@ function resolveHelp(argv) {
1631
1662
  const separator = rest.indexOf("--");
1632
1663
  return (separator === -1 ? rest : rest.slice(0, separator)).some(isHelpFlag) ? COMMANDS$1[command].text : void 0;
1633
1664
  }
1634
- //#endregion
1635
- //#region ../agent-core/src/hook-warning.ts
1665
+ // ../agent-core/src/hook-warning.ts
1636
1666
  const RESTART_FAILED_WARNING = "Could not restart the Harnessforce hook";
1637
1667
  const INVALID_ENDPOINT_WARNING = "The Harnessforce destination is invalid. Run `harnessforce init` again";
1638
1668
  const notConfiguredWarning = (agent) => agent === "claude_code" ? "Harnessforce sending is not configured. Run `/harnessforce:setup`" : "Harnessforce sending is not configured. Run `harnessforce init`";
@@ -1643,21 +1673,17 @@ const DESTINATION_MISMATCH_WARNING = {
1643
1673
  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",
1644
1674
  codex: "Sessions are not being registered because the Harnessforce destination differs from the one saved by `harnessforce init`. Run `harnessforce init` again"
1645
1675
  };
1646
- //#endregion
1647
- //#region ../agent-core/src/issue.ts
1676
+ // ../agent-core/src/issue.ts
1648
1677
  const isIssueIdentifier = (value) => isToken(value, 300);
1649
- //#endregion
1650
- //#region ../agent-core/src/using-harnessforce.ts
1651
- 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();
1652
- //#endregion
1653
- //#region ../agent-core/src/session-context.ts
1678
+ // ../agent-core/src/using-harnessforce.ts
1679
+ 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- Work locations: the repository and branch of each Git working tree the session works in. Paths are not sent.\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 sends them (`--send-content` or managed settings) and the Workspace stores that category. The members the Workspace chooses can read them. `harnessforce import` never sends them.\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- Stop: sends the work locations at the end of a turn in which they changed.\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();
1680
+ // ../agent-core/src/session-context.ts
1654
1681
  const sessionContextLine = (sessionId) => `harnessforce session_id: ${sessionId}`;
1655
1682
  const sessionContext = (sessionId) => ({ hookSpecificOutput: {
1656
1683
  hookEventName: "SessionStart",
1657
1684
  additionalContext: `${sessionContextLine(sessionId)}\n\n${usingHarnessforceBody}`
1658
1685
  } });
1659
- //#endregion
1660
- //#region ../agent-core/src/vcs.ts
1686
+ // ../agent-core/src/vcs.ts
1661
1687
  async function selectRemote(cwd, git) {
1662
1688
  if (await git(cwd, ["rev-parse", "--is-inside-work-tree"]) !== "true") return void 0;
1663
1689
  const remotes = (await git(cwd, ["remote"]))?.split("\n") ?? [];
@@ -1707,8 +1733,7 @@ async function resolveProjectRoot(cwd, git) {
1707
1733
  const topLevel = await git(cwd, ["rev-parse", "--show-toplevel"]);
1708
1734
  return topLevel && isAbsolute(topLevel) ? topLevel : cwd;
1709
1735
  }
1710
- //#endregion
1711
- //#region src/credentials/user-destination.ts
1736
+ // src/credentials/user-destination.ts
1712
1737
  async function resolveUserDestination(deps) {
1713
1738
  const { workspaceId, ingestEndpoint } = await resolveCliDestinations(deps.env, userSettingsPath(deps.env, deps.homeDir), "");
1714
1739
  if (!workspaceId && !ingestEndpoint) return { kind: "none" };
@@ -1743,14 +1768,13 @@ async function resolveUserDestination(deps) {
1743
1768
  }
1744
1769
  };
1745
1770
  }
1746
- //#endregion
1747
- //#region src/hook/session-start.ts
1748
- const HOOK_USAGE = "Usage: harnessforce hook session-start\n";
1771
+ // src/hook/session-start.ts
1772
+ const HOOK_USAGE = "Usage: harnessforce hook session-start|post-tool-use|stop\n";
1749
1773
  const MAX_INPUT_BYTES = 1048576;
1750
1774
  const REGISTERING_SOURCES = /* @__PURE__ */ new Set(["startup", "clear"]);
1751
1775
  const KEYCHAIN_READ_TIMEOUT_MS = 1e3;
1752
1776
  const REVOKED_MESSAGE = "Your ingest key has been revoked. Run `harnessforce init`";
1753
- function parseInput(raw) {
1777
+ function parseInput$1(raw) {
1754
1778
  let fields;
1755
1779
  try {
1756
1780
  fields = JSON.parse(raw);
@@ -1766,7 +1790,7 @@ function parseInput(raw) {
1766
1790
  source: typeof source === "string" ? source : void 0
1767
1791
  };
1768
1792
  }
1769
- const report$1 = (deps, detail) => deps.stderr(`harnessforce: session registration ${detail}\n`);
1793
+ const report$2 = (deps, detail) => deps.stderr(`harnessforce: session registration ${detail}\n`);
1770
1794
  const SKIPPED_WARNINGS = {
1771
1795
  "not configured": notConfiguredWarning("codex"),
1772
1796
  "invalid endpoint": INVALID_ENDPOINT_WARNING,
@@ -1794,11 +1818,11 @@ async function resolveDestination(deps) {
1794
1818
  const resolved = await resolveWithinLimit(deps);
1795
1819
  if (resolved.kind === "ok") return { destination: resolved.destination };
1796
1820
  if (resolved.kind === "none") {
1797
- report$1(deps, "skipped (not configured)");
1821
+ report$2(deps, "skipped (not configured)");
1798
1822
  return { warning: notConfiguredWarning("codex") };
1799
1823
  }
1800
1824
  const detail = resolved.reason === "keychain unreadable" ? "no ingest key" : resolved.reason;
1801
- report$1(deps, `skipped (${detail})`);
1825
+ report$2(deps, `skipped (${detail})`);
1802
1826
  return { warning: SKIPPED_WARNINGS[resolved.reason] };
1803
1827
  }
1804
1828
  function registrationSource(env) {
@@ -1821,7 +1845,7 @@ async function register(input, deps) {
1821
1845
  started_at: deps.now().toISOString()
1822
1846
  };
1823
1847
  const outcome = await postItem(destination, "v1/sessions", registration, deps.fetch);
1824
- if (outcome.kind === "failed") report$1(deps, `failed (${outcome.reason})`);
1848
+ if (outcome.kind === "failed") report$2(deps, `failed (${outcome.reason})`);
1825
1849
  return { outcome };
1826
1850
  }
1827
1851
  async function selectWarning(registered, deps) {
@@ -1836,12 +1860,12 @@ async function selectWarning(registered, deps) {
1836
1860
  });
1837
1861
  }
1838
1862
  async function sessionStart(deps) {
1839
- const input = parseInput(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
1863
+ const input = parseInput$1(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
1840
1864
  if (!input) return 0;
1841
1865
  const registers = input.source === void 0 || REGISTERING_SOURCES.has(input.source);
1842
1866
  const blocked = registers && await isSendingBlocked(deps);
1843
1867
  const registered = registers && !blocked ? await register(input, deps).catch((error) => {
1844
- report$1(deps, `failed (${error instanceof Error ? error.name : "unknown"})`);
1868
+ report$2(deps, `failed (${error instanceof Error ? error.name : "unknown"})`);
1845
1869
  return {};
1846
1870
  }) : {};
1847
1871
  const warning = registers ? await selectWarning(registered, deps) : void 0;
@@ -1852,8 +1876,185 @@ async function sessionStart(deps) {
1852
1876
  })}\n`);
1853
1877
  return 0;
1854
1878
  }
1855
- //#endregion
1856
- //#region src/shared/stop.ts
1879
+ // ../agent-core/src/work-location.ts
1880
+ const absolute = (value) => typeof value === "string" && isAbsolute(value) ? value : void 0;
1881
+ const toolInputOf = (fields) => isObject(fields.tool_input) ? fields.tool_input : {};
1882
+ const PATCH_FILE_LINE = /^\*\*\* (?:Add File|Update File|Delete File|Move to): (.+)$/;
1883
+ function patchedDirectories(command, cwd) {
1884
+ if (typeof command !== "string") return [];
1885
+ return command.split(/\r?\n/).flatMap((line) => {
1886
+ const path = PATCH_FILE_LINE.exec(line)?.[1]?.trim();
1887
+ return path ? [dirname(resolve(cwd, path))] : [];
1888
+ });
1889
+ }
1890
+ /**
1891
+ * eventの入力から、作業位置を求めるdirectoryを返す。読むのは`new_cwd`、`file_path`、patchのfileの行のpathだけで、編集の
1892
+ * 本文を読まない。
1893
+ */
1894
+ function observedDirectories(agent, event, fields, cwd) {
1895
+ if (event === "stop") return [cwd];
1896
+ if (agent === "claude_code" && event === "cwd-changed") {
1897
+ const newCwd = absolute(fields.new_cwd);
1898
+ return newCwd ? [newCwd] : [];
1899
+ }
1900
+ if (event !== "post-tool-use") return [];
1901
+ const input = toolInputOf(fields);
1902
+ if (agent === "codex") return fields.tool_name === "apply_patch" ? [...new Set(patchedDirectories(input.command, cwd))] : [];
1903
+ if (fields.tool_name !== "Write" && fields.tool_name !== "Edit") return [];
1904
+ const filePath = absolute(input.file_path);
1905
+ return filePath ? [dirname(filePath)] : [];
1906
+ }
1907
+ const isDirectory = (path) => stat(path).then((found) => found.isDirectory(), () => false);
1908
+ /** directoryのworking treeの、その時点のrepositoryとbranch。repositoryかbranchを求められなければundefined。 */
1909
+ async function observePair(directory, git) {
1910
+ if (!await isDirectory(directory)) return void 0;
1911
+ const vcs = await resolveVcs(directory, git);
1912
+ return vcs?.repository && vcs.branch ? {
1913
+ repository: vcs.repository,
1914
+ branch: vcs.branch
1915
+ } : void 0;
1916
+ }
1917
+ const emptyLocationState = () => ({
1918
+ pairs: [],
1919
+ sent: [],
1920
+ unauthorized: false
1921
+ });
1922
+ const pairKey = (pair) => JSON.stringify([pair.repository, pair.branch]);
1923
+ /** 組を加える。記録済みの組と、10件に達した後の組は加えない。 */
1924
+ function withObservedPair(state, pair) {
1925
+ if (state.pairs.some((saved) => pairKey(saved) === pairKey(pair)) || state.pairs.length >= MAX_WORK_LOCATIONS) return state;
1926
+ return {
1927
+ ...state,
1928
+ pairs: [...state.pairs, pair]
1929
+ };
1930
+ }
1931
+ /** 記録した組の集合が、最後に送った集合と異なる場合だけ送る組。 */
1932
+ function locationsToSend(state) {
1933
+ if (state.unauthorized || state.pairs.length === 0) return void 0;
1934
+ const sent = new Set(state.sent.map(pairKey));
1935
+ return sent.size === state.pairs.length && state.pairs.every((pair) => sent.has(pairKey(pair))) ? void 0 : state.pairs;
1936
+ }
1937
+ const isPair = (value) => isObject(value) && typeof value.repository === "string" && typeof value.branch === "string";
1938
+ async function readLocationState(path) {
1939
+ try {
1940
+ const saved = JSON.parse(await readFile(path, "utf8"));
1941
+ if (!isObject(saved) || !Array.isArray(saved.pairs) || !Array.isArray(saved.sent)) return emptyLocationState();
1942
+ return {
1943
+ pairs: saved.pairs.filter(isPair),
1944
+ sent: saved.sent.filter(isPair),
1945
+ unauthorized: saved.unauthorized === true
1946
+ };
1947
+ } catch {
1948
+ return emptyLocationState();
1949
+ }
1950
+ }
1951
+ async function writeLocationState(path, state) {
1952
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
1953
+ await writeFile(temporary, JSON.stringify(state), { mode: 384 });
1954
+ await rename(temporary, path);
1955
+ }
1956
+ const FILE_SAFE_SESSION_ID = /^[A-Za-z0-9_-]+$/;
1957
+ const LOCATION_DIRECTORY = "harnessforce-locations";
1958
+ async function isPrivateDirectory(path) {
1959
+ const found = await lstat(path).catch(() => void 0);
1960
+ if (!found) return false;
1961
+ const uid = process.getuid?.();
1962
+ return found.isDirectory() && !found.isSymbolicLink() && (uid === void 0 || found.uid === uid) && (found.mode & 511) === 448;
1963
+ }
1964
+ /**
1965
+ * Codexの記録のfile。Codexのhookは`scratchpad_dir`を受け取らないため、OSの一時directoryの下に置く。macOSとLinuxでは
1966
+ * directoryの所有者とmodeを確かめ、満たさなければundefinedを返す(記録も送信もしない)。Windowsの`TEMP`は利用者ごとに分かれる。
1967
+ */
1968
+ async function codexLocationStatePath(temporaryDirectory, sessionId, platform = process.platform) {
1969
+ if (!FILE_SAFE_SESSION_ID.test(sessionId)) return void 0;
1970
+ const directory = join(temporaryDirectory, LOCATION_DIRECTORY);
1971
+ if (platform !== "win32") {
1972
+ await mkdir(directory, { mode: 448 }).catch(() => {});
1973
+ if (!await isPrivateDirectory(directory)) return void 0;
1974
+ } else await mkdir(directory, { recursive: true }).catch(() => {});
1975
+ return join(directory, `codex-${sessionId}.json`);
1976
+ }
1977
+ /** 観測したdirectoryの組を記録のfileへ加える。gitの外のdirectoryは記録しない。 */
1978
+ async function recordObservedPairs(statePath, directories, deps) {
1979
+ const pairs = await Promise.all(directories.map((directory) => observePair(directory, deps.git)));
1980
+ const before = await readLocationState(statePath);
1981
+ const after = pairs.reduce((state, pair) => pair ? withObservedPair(state, pair) : state, before);
1982
+ if (after !== before) await writeLocationState(statePath, after);
1983
+ return after;
1984
+ }
1985
+ /**
1986
+ * 記録した組の集合が最後に送った集合と異なれば`/v1/sessions/locations`へ送る。2xxなら送った集合を記録し、401なら以後送らない
1987
+ * ことを記録する。それ以外の失敗では記録を変えず、次の`Stop`が送り直しを兼ねる。
1988
+ */
1989
+ async function sendLocations(statePath, state, item, destination, fetchImpl) {
1990
+ const locations = locationsToSend(state);
1991
+ if (!locations) return { kind: "unchanged" };
1992
+ const outcome = await postItem(destination, "v1/sessions/locations", {
1993
+ agent: item.agent,
1994
+ session_id: item.sessionId,
1995
+ locations
1996
+ }, fetchImpl);
1997
+ if (outcome.kind === "accepted") await writeLocationState(statePath, {
1998
+ ...state,
1999
+ sent: locations
2000
+ });
2001
+ if (outcome.kind === "unauthorized") await writeLocationState(statePath, {
2002
+ ...state,
2003
+ unauthorized: true
2004
+ });
2005
+ return {
2006
+ kind: "sent",
2007
+ outcome
2008
+ };
2009
+ }
2010
+ // src/hook/work-location.ts
2011
+ function parseInput(raw) {
2012
+ let fields;
2013
+ try {
2014
+ fields = JSON.parse(raw);
2015
+ } catch {
2016
+ return;
2017
+ }
2018
+ if (!isObject(fields)) return void 0;
2019
+ const { session_id: sessionId, cwd } = fields;
2020
+ if (typeof sessionId !== "string" || !isToken(sessionId) || typeof cwd !== "string" || !isAbsolute(cwd)) return void 0;
2021
+ return {
2022
+ sessionId,
2023
+ cwd,
2024
+ fields
2025
+ };
2026
+ }
2027
+ const report$1 = (deps, detail) => deps.stderr(`harnessforce: session location ${detail}\n`);
2028
+ async function observe(event, input, deps) {
2029
+ const statePath = await codexLocationStatePath(deps.hookTmpDir, input.sessionId);
2030
+ if (!statePath) return;
2031
+ const state = await recordObservedPairs(statePath, observedDirectories("codex", event, input.fields, input.cwd), deps);
2032
+ if (event !== "stop") return;
2033
+ if (state.unauthorized) return;
2034
+ if (await isSendingBlocked(deps)) return;
2035
+ const { destination } = await resolveDestination({
2036
+ ...deps,
2037
+ stderr: () => {}
2038
+ });
2039
+ if (!destination) return;
2040
+ const sent = await sendLocations(statePath, state, {
2041
+ agent: "codex",
2042
+ sessionId: input.sessionId
2043
+ }, destination, deps.fetch);
2044
+ if (sent.kind !== "sent" || sent.outcome.kind === "accepted") return;
2045
+ report$1(deps, `failed (${sent.outcome.kind === "unauthorized" ? "unauthorized" : sent.outcome.reason})`);
2046
+ }
2047
+ /**
2048
+ * correlation.md「Codexのhook」「sessionの作業位置」。Codexの`PostToolUse`と`Stop`のhookが起動する。どの失敗でもsessionを
2049
+ * 止めず、常にexit 0で終える。`Stop`はstdoutにJSONを期待するため、空のobjectだけを書く。
2050
+ */
2051
+ async function workLocationHook(event, deps) {
2052
+ const input = parseInput(await deps.readStdin(MAX_INPUT_BYTES).then((buffer) => buffer.toString("utf8")).catch(() => ""));
2053
+ if (input) await observe(event, input, deps).catch((error) => report$1(deps, `failed (${error instanceof Error ? error.name : "unknown"})`));
2054
+ if (event === "stop") deps.stdout("{}\n");
2055
+ return 0;
2056
+ }
2057
+ // src/shared/stop.ts
1857
2058
  var CommandStop = class {
1858
2059
  text;
1859
2060
  constructor(text) {
@@ -1873,8 +2074,7 @@ async function runUntilStop(body, stderr) {
1873
2074
  return 1;
1874
2075
  }
1875
2076
  }
1876
- //#endregion
1877
- //#region src/credentials/api-token.ts
2077
+ // src/credentials/api-token.ts
1878
2078
  const NO_WHITESPACE$1 = /^\S+$/;
1879
2079
  const INSTANT$2 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/;
1880
2080
  const isInstant = (value) => typeof value === "string" && INSTANT$2.test(value) && !Number.isNaN(Date.parse(value));
@@ -1903,8 +2103,7 @@ function parseStoredApiToken(secret, workspaceId) {
1903
2103
  return;
1904
2104
  }
1905
2105
  }
1906
- //#endregion
1907
- //#region src/credentials/access.ts
2106
+ // src/credentials/access.ts
1908
2107
  async function verifyCliAccess(deps, messages) {
1909
2108
  const { keychain } = deps;
1910
2109
  const stop = (message) => stopWith(messages[message]);
@@ -1928,8 +2127,7 @@ async function verifyCliAccess(deps, messages) {
1928
2127
  accessToken: apiToken.accessToken
1929
2128
  };
1930
2129
  }
1931
- //#endregion
1932
- //#region src/shared/http.ts
2130
+ // src/shared/http.ts
1933
2131
  const REQUEST_TIMEOUT_MS = 3e4;
1934
2132
  async function readJsonObjectBody(response) {
1935
2133
  try {
@@ -1939,8 +2137,7 @@ async function readJsonObjectBody(response) {
1939
2137
  return;
1940
2138
  }
1941
2139
  }
1942
- //#endregion
1943
- //#region src/shared/metadata.ts
2140
+ // src/shared/metadata.ts
1944
2141
  const WELL_KNOWN = "/.well-known/oauth-authorization-server";
1945
2142
  const withoutTrailingSlash = (value) => value.replace(/\/+$/, "");
1946
2143
  async function fetchMetadata(base, fetchImpl) {
@@ -1975,8 +2172,7 @@ async function discoverTokenEndpoint(base, fetchImpl) {
1975
2172
  const endpoint = endpointOf(await fetchMetadata(base, fetchImpl), "token_endpoint");
1976
2173
  return endpoint?.origin === base.origin ? endpoint : void 0;
1977
2174
  }
1978
- //#endregion
1979
- //#region src/credentials/token-lock.ts
2175
+ // src/credentials/token-lock.ts
1980
2176
  const tokenLockPath = (homeDir) => join(homeDir, ".harnessforce", "token.lock");
1981
2177
  const LOCK_WAIT_MS$1 = 3e4;
1982
2178
  const STALE_LOCK_MS$1 = 6e4;
@@ -2006,8 +2202,7 @@ async function acquireTokenLock(path, deps) {
2006
2202
  await deps.sleep(RETRY_INTERVAL_MS$1);
2007
2203
  }
2008
2204
  }
2009
- //#endregion
2010
- //#region src/credentials/api-token-session.ts
2205
+ // src/credentials/api-token-session.ts
2011
2206
  const CLIENT_ID$1 = "harnessforce-cli";
2012
2207
  const LOCKED_KEYCHAIN_TIMEOUT_MS = 1e4;
2013
2208
  const isPast = (instant, now) => now >= Date.parse(instant);
@@ -2117,8 +2312,7 @@ function createApiTokenSession(deps) {
2117
2312
  authorizedFetch
2118
2313
  };
2119
2314
  }
2120
- //#endregion
2121
- //#region src/import/read-api.ts
2315
+ // src/import/read-api.ts
2122
2316
  const FAILED$1 = { kind: "failed" };
2123
2317
  async function getJson(url, apiToken, fetchImpl) {
2124
2318
  try {
@@ -2188,8 +2382,7 @@ async function fetchSessionImportDays(base, apiToken, fetchImpl) {
2188
2382
  value: days
2189
2383
  } : FAILED$1;
2190
2384
  }
2191
- //#endregion
2192
- //#region src/import/record-base.ts
2385
+ // src/import/record-base.ts
2193
2386
  async function readSettingsEnv(path) {
2194
2387
  const read = await readUserSettings(path);
2195
2388
  const env = read.kind === "ok" ? read.settings.env : void 0;
@@ -2210,8 +2403,7 @@ async function recordBase(deps) {
2210
2403
  const { CLAUDE_CONFIG_DIR: configDir } = await readTrustedEnv(deps, ["CLAUDE_CONFIG_DIR"]);
2211
2404
  return configDir ?? join(deps.homeDir, ".claude");
2212
2405
  }
2213
- //#endregion
2214
- //#region src/import/state.ts
2406
+ // src/import/state.ts
2215
2407
  const STATE_VERSION = 1;
2216
2408
  const DIR_MODE$1 = 448;
2217
2409
  const FILE_MODE$3 = 384;
@@ -2268,8 +2460,7 @@ async function recordSentSessions(path, destination, sessionIds) {
2268
2460
  await rm(temporary, { force: true });
2269
2461
  }
2270
2462
  }
2271
- //#endregion
2272
- //#region src/import/send.ts
2463
+ // src/import/send.ts
2273
2464
  const BATCH_SIZE$1 = 100;
2274
2465
  const MAX_RESENDS = 3;
2275
2466
  const MAX_RETRY_AFTER_SECONDS = 60;
@@ -2486,8 +2677,7 @@ var SessionAccumulator = class {
2486
2677
  };
2487
2678
  }
2488
2679
  };
2489
- //#endregion
2490
- //#region src/import/rollout.ts
2680
+ // src/import/rollout.ts
2491
2681
  const CODEX_PARSER_VERSION = "1.0.0";
2492
2682
  const DETACHED$2 = "HEAD";
2493
2683
  const COMMIT_SHA = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
@@ -2624,8 +2814,7 @@ async function parseRollout(path) {
2624
2814
  }
2625
2815
  return accumulator.result(skippedLines);
2626
2816
  }
2627
- //#endregion
2628
- //#region src/import/transcript.ts
2817
+ // src/import/transcript.ts
2629
2818
  const PARSER_VERSION = "1.1.0";
2630
2819
  const isToken$1 = (value, maxLength = 256) => typeof value === "string" && value.length >= 1 && value.length <= maxLength && /^\S+$/.test(value);
2631
2820
  const MAX_TOOL_NAME = 128;
@@ -2902,8 +3091,7 @@ async function parseTranscriptUsage(path) {
2902
3091
  const { usage, isReadable } = await readTranscript(path, new Accumulator());
2903
3092
  return isReadable ? usage : void 0;
2904
3093
  }
2905
- //#endregion
2906
- //#region src/import/sessions.ts
3094
+ // src/import/sessions.ts
2907
3095
  const noneNotImported = () => ({
2908
3096
  outOfRange: 0,
2909
3097
  outsideGit: 0,
@@ -3064,8 +3252,7 @@ async function scanCodexSessions(options) {
3064
3252
  compressed
3065
3253
  };
3066
3254
  }
3067
- //#endregion
3068
- //#region src/import/command.ts
3255
+ // src/import/command.ts
3069
3256
  const DAY_MS$1 = 864e5;
3070
3257
  const MESSAGES = {
3071
3258
  runInit: "Run `harnessforce init`",
@@ -3195,9 +3382,8 @@ async function runImport(deps) {
3195
3382
  function importCommand(deps) {
3196
3383
  return runUntilStop(() => runImport(deps), deps.stderr);
3197
3384
  }
3198
- //#endregion
3199
- //#region ../../node_modules/.pnpm/@decimalturn+toml-patch@3.3.0/node_modules/@decimalturn/toml-patch/dist/toml-patch.js
3200
- //! @decimalturn/toml-patch v3.3.0 - https://github.com/DecimalTurn/toml-patch - @license: MIT
3385
+ // ../../node_modules/.pnpm/@decimalturn+toml-patch@3.3.1/node_modules/@decimalturn/toml-patch/dist/toml-patch.js
3386
+ //! @decimalturn/toml-patch v3.3.1 - https://github.com/DecimalTurn/toml-patch - @license: MIT
3201
3387
  function e(e) {
3202
3388
  return e.type === `Document`;
3203
3389
  }
@@ -4573,13 +4759,12 @@ function on(e) {
4573
4759
  if (e[t] !== `\\`) continue;
4574
4760
  let n = e[t + 1];
4575
4761
  if (!n) return null;
4576
- if (n === `\\`) {
4577
- t++;
4578
- continue;
4762
+ if (n === `\\`) t++;
4763
+ else {
4764
+ if (n === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(t + 2, t + 4))) return /[A-F]/.test(e.slice(t + 2, t + 4));
4765
+ if (n === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(t + 2, t + 6))) return /[A-F]/.test(e.slice(t + 2, t + 6));
4766
+ if (n === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(t + 2, t + 10))) return /[A-F]/.test(e.slice(t + 2, t + 10));
4579
4767
  }
4580
- if (n === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(t + 2, t + 4))) return /[A-F]/.test(e.slice(t + 2, t + 4));
4581
- if (n === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(t + 2, t + 6))) return /[A-F]/.test(e.slice(t + 2, t + 6));
4582
- if (n === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(t + 2, t + 10))) return /[A-F]/.test(e.slice(t + 2, t + 10));
4583
4768
  }
4584
4769
  return null;
4585
4770
  }
@@ -4891,14 +5076,11 @@ function qn(e, t) {
4891
5076
  if (e === t) return r;
4892
5077
  if (a(e)) return n(e.value, r);
4893
5078
  if (d(e)) return n(e.item, r);
4894
- if (u(e) || f(e)) {
4895
- for (let t of e.items) {
4896
- let e = n(t, r + 1);
4897
- if (e !== void 0) return e;
4898
- }
4899
- return;
5079
+ if (u(e) || f(e)) for (let t of e.items) {
5080
+ let e = n(t, r + 1);
5081
+ if (e !== void 0) return e;
4900
5082
  }
4901
- if (m(e)) for (let t of e.items) {
5083
+ else if (m(e)) for (let t of e.items) {
4902
5084
  let e = n(t, r);
4903
5085
  if (e !== void 0) return e;
4904
5086
  }
@@ -4910,14 +5092,11 @@ function Jn(e, t) {
4910
5092
  if (e === t) return r;
4911
5093
  if (a(e)) return n(e.value, r);
4912
5094
  if (d(e)) return n(e.item, r);
4913
- if (u(e) || f(e)) {
4914
- for (let t of e.items) {
4915
- let r = n(t, e);
4916
- if (r) return r;
4917
- }
4918
- return;
5095
+ if (u(e) || f(e)) for (let t of e.items) {
5096
+ let r = n(t, e);
5097
+ if (r) return r;
4919
5098
  }
4920
- if (m(e)) for (let t of e.items) {
5099
+ else if (m(e)) for (let t of e.items) {
4921
5100
  let e = n(t, r);
4922
5101
  if (e) return e;
4923
5102
  }
@@ -5617,25 +5796,17 @@ function Nr(e) {
5617
5796
  let r = e[n + 1];
5618
5797
  if (!r) continue;
5619
5798
  let i = kr(r);
5620
- if (i !== null) {
5621
- i !== `
5799
+ if (i !== null) i !== `
5622
5800
  ` && i !== `\r` && !t.has(i) && t.set(i, `\\` + r), n += 1;
5623
- continue;
5624
- }
5625
- if (r === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(n + 2, n + 4))) {
5801
+ else if (r === `x` && /^[0-9A-Fa-f]{2}$/.test(e.slice(n + 2, n + 4))) {
5626
5802
  let r = e.slice(n, n + 4), i = Number.parseInt(e.slice(n + 2, n + 4), 16), a = String.fromCharCode(i);
5627
5803
  t.has(a) || t.set(a, r), n += 3;
5628
- continue;
5629
- }
5630
- if (r === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(n + 2, n + 6))) {
5804
+ } else if (r === `u` && /^[0-9A-Fa-f]{4}$/.test(e.slice(n + 2, n + 6))) {
5631
5805
  let r = e.slice(n, n + 6), i = Number.parseInt(e.slice(n + 2, n + 6), 16), a = String.fromCodePoint(i);
5632
5806
  t.has(a) || t.set(a, r), n += 5;
5633
- continue;
5634
- }
5635
- if (r === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(n + 2, n + 10))) {
5807
+ } else if (r === `U` && /^[0-9A-Fa-f]{8}$/.test(e.slice(n + 2, n + 10))) {
5636
5808
  let r = e.slice(n, n + 10), i = Number.parseInt(e.slice(n + 2, n + 10), 16), a = String.fromCodePoint(i);
5637
5809
  t.has(a) || t.set(a, r), n += 9;
5638
- continue;
5639
5810
  }
5640
5811
  }
5641
5812
  return t;
@@ -5644,11 +5815,7 @@ function Pr(e, t, n, r) {
5644
5815
  let i = ``;
5645
5816
  for (let a of e) {
5646
5817
  let e = t.get(a);
5647
- if (e) {
5648
- i += e;
5649
- continue;
5650
- }
5651
- i += jr(a, n, r);
5818
+ i += e || jr(a, n, r);
5652
5819
  }
5653
5820
  return i;
5654
5821
  }
@@ -6052,7 +6219,7 @@ function oi(e) {
6052
6219
  return !f(e) || e.items.length === 0 ? !1 : e.items[e.items.length - 1].comma === !0;
6053
6220
  }
6054
6221
  function si(e, t) {
6055
- return t.inlineTableStart === 0 ? e : (e.items.filter((e) => {
6222
+ return t.inlineTableStart === 0 || (e.items.filter((e) => {
6056
6223
  if (!a(e)) return !1;
6057
6224
  let n = f(e.value), r = u(e.value) && e.value.items.length > 0 && e.value.items.every((e) => f(e.item));
6058
6225
  if (n || r) {
@@ -6064,7 +6231,7 @@ function si(e, t) {
6064
6231
  V(e, e, n), f(n.value) ? n.value.items.length === 0 ? B(e, e, W(n.key.value)) : B(e, e, ci(n, t.bracketSpacing, t.escapeSequenceUpperCase)) : li(n, t.bracketSpacing, t.escapeSequenceUpperCase).forEach((t) => {
6065
6232
  B(e, e, t);
6066
6233
  });
6067
- }), H(e), e);
6234
+ }), H(e)), e;
6068
6235
  }
6069
6236
  function ci(e, t, n) {
6070
6237
  let r = W(e.key.value, n);
@@ -7101,11 +7268,7 @@ function ia(e, t, n = [], r = {}) {
7101
7268
  let h = 0;
7102
7269
  for (let t = e; t < o.length; t++) o[t] === r && h++;
7103
7270
  let g = p > h;
7104
- if (s && (o.indexOf(a[e]) === -1 || g)) {
7105
- i.push(d(e + l)), a.splice(e, 1), c.splice(e, 1), l++, e--;
7106
- continue;
7107
- }
7108
- if (l > 0 && g) {
7271
+ if (s && (o.indexOf(a[e]) === -1 || g) || l > 0 && g) {
7109
7272
  i.push(d(e + l)), a.splice(e, 1), c.splice(e, 1), l++, e--;
7110
7273
  continue;
7111
7274
  }
@@ -7136,14 +7299,10 @@ function ia(e, t, n = [], r = {}) {
7136
7299
  for (let n = e; n < o.length; n++) o[n] === t && r++;
7137
7300
  h = n > r;
7138
7301
  }
7139
- if (!p && h) {
7140
- Vt(i, $i(c[e], t[e], n.concat(e), r)), a[e] = f, c[e] = t[e];
7141
- continue;
7142
- }
7143
- i.push({
7302
+ !p && h ? (Vt(i, $i(c[e], t[e], n.concat(e), r)), a[e] = f, c[e] = t[e]) : (i.push({
7144
7303
  type: `Add`,
7145
7304
  path: n.concat(e)
7146
- }), u = !0, a.splice(e, 0, f), c.splice(e, 0, t[e]), l > 0 && l--;
7305
+ }), u = !0, a.splice(e, 0, f), c.splice(e, 0, t[e]), l > 0 && l--);
7147
7306
  }
7148
7307
  for (let e = o.length; e < a.length; e++) i.push(d(e + l));
7149
7308
  return i;
@@ -7229,19 +7388,10 @@ function pa(e) {
7229
7388
  let n = !1, r = !1;
7230
7389
  for (let i = t + 1; i < e.raw.length; i++) {
7231
7390
  let t = e.raw[i];
7232
- if (t === `\\`) {
7233
- i++;
7234
- continue;
7235
- }
7236
- if (!r && t === `"`) {
7237
- n = !n;
7238
- continue;
7239
- }
7240
- if (!n && t === `'`) {
7241
- r = !r;
7242
- continue;
7243
- }
7244
- if (!n && !r && t === `#`) return !0;
7391
+ if (t === `\\`) i++;
7392
+ else if (!r && t === `"`) n = !n;
7393
+ else if (!n && t === `'`) r = !r;
7394
+ else if (!n && !r && t === `#`) return !0;
7245
7395
  }
7246
7396
  return !1;
7247
7397
  }
@@ -7509,13 +7659,7 @@ function Ta(t, n, r, i) {
7509
7659
  e && (e.lines = 0, e.columns = 0);
7510
7660
  }
7511
7661
  H(t), p && h && r.loc.end.line > r.loc.start.line && (r.loc.end.column = h.column, d(r) && g && r.item.loc.end.line > r.item.loc.start.line && (r.item.loc.end.column = g.column));
7512
- for (let { node: e, start: t, end: n, endOnly: r, equals: i } of _) {
7513
- if (r) {
7514
- e.loc.end.column = n.column;
7515
- continue;
7516
- }
7517
- e.loc.start.column = t.column, e.loc.end.line === e.loc.start.line && (e.loc.end.column = n.column), i !== void 0 && a(e) && (e.equals = i);
7518
- }
7662
+ for (let { node: e, start: t, end: n, endOnly: r, equals: i } of _) r ? e.loc.end.column = n.column : (e.loc.start.column = t.column, e.loc.end.line === e.loc.start.line && (e.loc.end.column = n.column), i !== void 0 && a(e) && (e.equals = i));
7519
7663
  for (let { owner: t, ownerOriginalStart: n, comments: i } of l) {
7520
7664
  let a;
7521
7665
  t === r ? (a = {
@@ -7640,11 +7784,7 @@ function Ea(e) {
7640
7784
  continue;
7641
7785
  }
7642
7786
  let e = t(i.member) || r(i.member), a = n[n.length - 1];
7643
- if (a && a.kind === `member` && i.key !== void 0 && a.key === i.key) {
7644
- a.groups.push(i), a.endLine = i.endLine, a.items = a.items.concat(i.items);
7645
- continue;
7646
- }
7647
- n.push({
7787
+ a && a.kind === `member` && i.key !== void 0 && a.key === i.key ? (a.groups.push(i), a.endLine = i.endLine, a.items = a.items.concat(i.items)) : n.push({
7648
7788
  kind: `member`,
7649
7789
  key: i.key,
7650
7790
  isSection: e,
@@ -7869,23 +8009,11 @@ function Ua(e, t, n) {
7869
8009
  return r.columns + (i || a ? 2 : 0) + (s ? 1 + (c ? -1 : 0) : 0);
7870
8010
  }
7871
8011
  function Wa(e, n = []) {
7872
- for (let i of e.items) {
7873
- if (p(i)) {
7874
- n.push(i);
7875
- continue;
7876
- }
7877
- (t(i) || r(i)) && Wa(i, n);
7878
- }
8012
+ for (let i of e.items) p(i) ? n.push(i) : (t(i) || r(i)) && Wa(i, n);
7879
8013
  return n;
7880
8014
  }
7881
8015
  function Ga(e, n = /* @__PURE__ */ new Set()) {
7882
- for (let i of e.items) {
7883
- if (a(i) && f(i.value) && i.loc.start.line === i.loc.end.line) {
7884
- n.add(i.loc.end.line);
7885
- continue;
7886
- }
7887
- (t(i) || r(i)) && Ga(i, n);
7888
- }
8016
+ for (let i of e.items) a(i) && f(i.value) && i.loc.start.line === i.loc.end.line ? n.add(i.loc.end.line) : (t(i) || r(i)) && Ga(i, n);
7889
8017
  return n;
7890
8018
  }
7891
8019
  function Ka(e) {
@@ -8257,11 +8385,7 @@ function so(e) {
8257
8385
  let t = [], n = 0, r, i = !1;
8258
8386
  for (let a = 0; a < e.length; a++) {
8259
8387
  let o = e[a];
8260
- if (r) {
8261
- r === `"` && i ? i = !1 : r === `"` && o === `\\` ? i = !0 : o === r && (r = void 0);
8262
- continue;
8263
- }
8264
- o === `"` || o === `'` ? r = o : o === `.` && (t.push(e.slice(n, a)), n = a + 1);
8388
+ r ? r === `"` && i ? i = !1 : r === `"` && o === `\\` ? i = !0 : o === r && (r = void 0) : o === `"` || o === `'` ? r = o : o === `.` && (t.push(e.slice(n, a)), n = a + 1);
8265
8389
  }
8266
8390
  return t.push(e.slice(n)), t;
8267
8391
  }
@@ -9013,11 +9137,20 @@ function yo(n, i, s, c, l = !1, g = /* @__PURE__ */ new WeakSet(), _ = void 0, v
9013
9137
  for (let e of s) Q(n, l, e);
9014
9138
  }
9015
9139
  if ((t(o) || r(o)) && m(l)) {
9016
- let e = o.key.item.value, i = l.items, s = [];
9017
- for (let n of i) {
9018
- if (n === o) continue;
9019
- let i = a(n) ? n.key.value : t(n) || r(n) ? n.key.item.value : void 0;
9020
- i && i.length > e.length && F(i.slice(0, e.length), e) && s.push(n);
9140
+ let e = o.key.item.value, i = l.items, s = [], c = r(o), u = i.indexOf(o), d = i.length;
9141
+ if (c) for (let t = u + 1; t < i.length; t++) {
9142
+ let n = i[t];
9143
+ if (!r(n)) continue;
9144
+ let a = n.key.item.value;
9145
+ if (a.length <= e.length && F(a, e.slice(0, a.length))) {
9146
+ d = t;
9147
+ break;
9148
+ }
9149
+ }
9150
+ for (let [n, l] of i.entries()) {
9151
+ if (l === o || c && (n < u || n >= d)) continue;
9152
+ let i = a(l) ? l.key.value : t(l) || r(l) ? l.key.item.value : void 0;
9153
+ i && i.length > e.length && F(i.slice(0, e.length), e) && s.push(l);
9021
9154
  }
9022
9155
  for (let e of s) Q(n, l, e);
9023
9156
  }
@@ -9403,23 +9536,18 @@ function To(e, n, i, o, s, c, l) {
9403
9536
  let n = s.items.find((e) => a(e) && F(e.key.value, t.key.value));
9404
9537
  n ? pr(e, s, n, t) : B(e, s, t, void 0);
9405
9538
  };
9406
- for (let e of m) {
9407
- if (a(e)) {
9408
- h(e);
9409
- continue;
9410
- }
9411
- if (t(e)) {
9412
- let t = e.key.item.value;
9413
- for (let n of e.items) {
9414
- if (!a(n)) continue;
9415
- let e = n.key.raw, r = t.concat(n.key.value);
9416
- n.key.value = r, n.key.raw = Ur(r, o.escapeSequenceUpperCase).raw;
9417
- let i = n.key.raw.length - e.length;
9418
- n.key.loc.end.column = n.key.loc.start.column + n.key.raw.length, n.equals += i, U(n.value, {
9419
- lines: 0,
9420
- columns: i
9421
- }, { first_line_only: !0 }), n.loc.end.line === n.loc.start.line && (n.loc.end.column += i), h(n);
9422
- }
9539
+ for (let e of m) if (a(e)) h(e);
9540
+ else if (t(e)) {
9541
+ let t = e.key.item.value;
9542
+ for (let n of e.items) {
9543
+ if (!a(n)) continue;
9544
+ let e = n.key.raw, r = t.concat(n.key.value);
9545
+ n.key.value = r, n.key.raw = Ur(r, o.escapeSequenceUpperCase).raw;
9546
+ let i = n.key.raw.length - e.length;
9547
+ n.key.loc.end.column = n.key.loc.start.column + n.key.raw.length, n.equals += i, U(n.value, {
9548
+ lines: 0,
9549
+ columns: i
9550
+ }, { first_line_only: !0 }), n.loc.end.line === n.loc.start.line && (n.loc.end.column += i), h(n);
9423
9551
  }
9424
9552
  }
9425
9553
  return;
@@ -9438,51 +9566,51 @@ function To(e, n, i, o, s, c, l) {
9438
9566
  if (o) {
9439
9567
  for (let t of i.items) B(e, o, t, void 0);
9440
9568
  v ||= o;
9441
- continue;
9442
- }
9443
- if (a(i) && f(i.value)) {
9444
- let o = i.key.value, s = e.items.find((e) => t(e) && F(e.key.item.value, o));
9445
- if (s) {
9446
- for (let t of i.value.items) B(e, s, d(t) ? t.item : t, void 0);
9447
- v ||= s;
9448
- continue;
9449
- }
9450
- if ($(e, o).filter((e) => a(e) || t(e) || r(e)).length > 0) {
9451
- for (let t of i.value.items) {
9452
- let r = d(t) ? t.item : t;
9453
- if (a(r)) {
9454
- let e = r.key.raw, t = o.concat(r.key.value);
9455
- r.key.value = t, r.key.raw = t.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
9456
- let n = r.key.raw.length - e.length;
9457
- r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += n, U(r.value, {
9458
- lines: 0,
9459
- columns: n
9460
- }, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += n);
9569
+ } else {
9570
+ if (a(i) && f(i.value)) {
9571
+ let o = i.key.value, s = e.items.find((e) => t(e) && F(e.key.item.value, o));
9572
+ if (s) {
9573
+ for (let t of i.value.items) B(e, s, d(t) ? t.item : t, void 0);
9574
+ v ||= s;
9575
+ continue;
9576
+ }
9577
+ if ($(e, o).filter((e) => a(e) || t(e) || r(e)).length > 0) {
9578
+ for (let t of i.value.items) {
9579
+ let r = d(t) ? t.item : t;
9580
+ if (a(r)) {
9581
+ let e = r.key.raw, t = o.concat(r.key.value);
9582
+ r.key.value = t, r.key.raw = t.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
9583
+ let n = r.key.raw.length - e.length;
9584
+ r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += n, U(r.value, {
9585
+ lines: 0,
9586
+ columns: n
9587
+ }, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += n);
9588
+ }
9589
+ B(e, e, r, n === 0 ? _ : void 0), v ||= r;
9461
9590
  }
9462
- B(e, e, r, n === 0 ? _ : void 0), v ||= r;
9591
+ continue;
9463
9592
  }
9464
- continue;
9465
9593
  }
9466
- }
9467
- if (t(i)) {
9468
- let t = i.key.item.value;
9469
- if ($(e, t).filter((e) => a(e)).length > 0) {
9470
- for (let r of i.items) {
9471
- if (a(r)) {
9472
- let e = r.key.raw, n = t.concat(r.key.value);
9473
- r.key.value = n, r.key.raw = n.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
9474
- let i = r.key.raw.length - e.length;
9475
- r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += i, U(r.value, {
9476
- lines: 0,
9477
- columns: i
9478
- }, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += i);
9594
+ if (t(i)) {
9595
+ let t = i.key.item.value;
9596
+ if ($(e, t).filter((e) => a(e)).length > 0) {
9597
+ for (let r of i.items) {
9598
+ if (a(r)) {
9599
+ let e = r.key.raw, n = t.concat(r.key.value);
9600
+ r.key.value = n, r.key.raw = n.map((e) => A.test(e) ? e : JSON.stringify(e).replace(/\x7f/g, `\\u007f`)).join(`.`);
9601
+ let i = r.key.raw.length - e.length;
9602
+ r.key.loc.end.column = r.key.loc.start.column + r.key.raw.length, r.equals += i, U(r.value, {
9603
+ lines: 0,
9604
+ columns: i
9605
+ }, { first_line_only: !0 }), r.loc.end.line === r.loc.start.line && (r.loc.end.column += i);
9606
+ }
9607
+ B(e, e, r, n === 0 ? _ : void 0), v ||= r;
9479
9608
  }
9480
- B(e, e, r, n === 0 ? _ : void 0), v ||= r;
9609
+ continue;
9481
9610
  }
9482
- continue;
9483
9611
  }
9612
+ n > 0 && v && t(v) && a(i) ? B(e, v, i, void 0) : B(e, e, i, n === 0 ? _ : void 0), v ||= i;
9484
9613
  }
9485
- n > 0 && v && t(v) && a(i) ? B(e, v, i, void 0) : B(e, e, i, n === 0 ? _ : void 0), v ||= i;
9486
9614
  }
9487
9615
  let y = v;
9488
9616
  t(y) && l.add(y), c.add(y);
@@ -9665,11 +9793,26 @@ function Ko(e, t) {
9665
9793
  temporal: (t == null ? void 0 : t.temporal) ?? !1
9666
9794
  });
9667
9795
  }
9668
- //#endregion
9669
- //#region src/shared/codex-config.ts
9670
- const HOOK_COMMAND = "harnessforce hook session-start";
9671
- const HOOK_COMMAND_WINDOWS = "harnessforce.cmd hook session-start";
9796
+ // src/shared/codex-config.ts
9672
9797
  const HOOK_TIMEOUT_SECONDS = 10;
9798
+ const HOOKS = [
9799
+ {
9800
+ event: "SessionStart",
9801
+ subcommand: "session-start"
9802
+ },
9803
+ {
9804
+ event: "PostToolUse",
9805
+ subcommand: "post-tool-use",
9806
+ matcher: "^apply_patch$",
9807
+ isAsync: true
9808
+ },
9809
+ {
9810
+ event: "Stop",
9811
+ subcommand: "stop",
9812
+ isAsync: true
9813
+ }
9814
+ ];
9815
+ const hookCommand = (spec) => `harnessforce hook ${spec.subcommand}`;
9673
9816
  const FILE_MODE$2 = 384;
9674
9817
  function codexConfigPath(env, homeDir) {
9675
9818
  return join(absoluteEnv(env.CODEX_HOME) ?? join(homeDir, ".codex"), "config.toml");
@@ -9705,11 +9848,14 @@ function withHarnessforce(config, input) {
9705
9848
  headers: { Authorization: `Bearer ${input.ingestKey}` },
9706
9849
  protocol: "binary"
9707
9850
  } };
9708
- if (input.logUserPrompt) otel.log_user_prompt = true;
9851
+ otel.log_user_prompt = input.logUserPrompt;
9852
+ otel.log_agent_responses = input.logAgentResponses;
9709
9853
  const hooks = tableAt(config, "hooks");
9710
- const groups = hooks.SessionStart ?? [];
9711
- if (!Array.isArray(groups)) throw new Error("SessionStart");
9712
- hooks.SessionStart = withHandler(groups);
9854
+ for (const spec of HOOKS) {
9855
+ const groups = hooks[spec.event] ?? [];
9856
+ if (!Array.isArray(groups)) throw new Error(spec.event);
9857
+ hooks[spec.event] = withHandler(groups, spec);
9858
+ }
9713
9859
  const servers = tableAt(config, "mcp_servers");
9714
9860
  const existing = servers.harnessforce;
9715
9861
  if (existing !== void 0 && !isObject(existing)) throw new Error("server");
@@ -9725,16 +9871,21 @@ function tableAt(parent, key) {
9725
9871
  parent[key] = value;
9726
9872
  return value;
9727
9873
  }
9728
- const isOurs = (handler) => isObject(handler) && handler.command === HOOK_COMMAND;
9729
- const hasOurs = (group) => isObject(group) && Array.isArray(group.hooks) && group.hooks.some(isOurs);
9730
- function withHandler(groups) {
9874
+ function withHandler(groups, spec) {
9875
+ const command = hookCommand(spec);
9876
+ const isOurs = (entry) => isObject(entry) && entry.command === command;
9877
+ const hasOurs = (group) => isObject(group) && Array.isArray(group.hooks) && group.hooks.some(isOurs);
9731
9878
  const handler = {
9732
9879
  type: "command",
9733
- command: HOOK_COMMAND,
9734
- commandWindows: HOOK_COMMAND_WINDOWS,
9735
- timeout: HOOK_TIMEOUT_SECONDS
9736
- };
9737
- if (!groups.some(hasOurs)) return [...groups, { hooks: [handler] }];
9880
+ command,
9881
+ commandWindows: `harnessforce.cmd hook ${spec.subcommand}`,
9882
+ timeout: HOOK_TIMEOUT_SECONDS,
9883
+ ...spec.isAsync ? { async: true } : {}
9884
+ };
9885
+ if (!groups.some(hasOurs)) return [...groups, {
9886
+ ...spec.matcher === void 0 ? {} : { matcher: spec.matcher },
9887
+ hooks: [handler]
9888
+ }];
9738
9889
  return groups.map((group) => hasOurs(group) ? {
9739
9890
  ...group,
9740
9891
  hooks: group.hooks.map((entry) => isOurs(entry) ? handler : entry)
@@ -9754,8 +9905,29 @@ async function writeCodexConfig(path, text) {
9754
9905
  await rm(temporary, { force: true });
9755
9906
  }
9756
9907
  }
9757
- //#endregion
9758
- //#region src/init/credentials-api.ts
9908
+ // src/init/credentials-api.ts
9909
+ const VISIBILITIES = /* @__PURE__ */ new Set([
9910
+ "all_members",
9911
+ "owners_admins_and_sender",
9912
+ "sender_only"
9913
+ ]);
9914
+ const RETENTION_DAYS = /* @__PURE__ */ new Set([
9915
+ 7,
9916
+ 14,
9917
+ 30
9918
+ ]);
9919
+ function parseContent(value) {
9920
+ if (!isObject(value)) return void 0;
9921
+ const { prompts, model_and_tools: modelAndTools, visibility } = value;
9922
+ const retentionDays = value.retention_days;
9923
+ if (typeof prompts !== "boolean" || typeof modelAndTools !== "boolean" || typeof visibility !== "string" || !VISIBILITIES.has(visibility) || typeof retentionDays !== "number" || !RETENTION_DAYS.has(retentionDays)) return void 0;
9924
+ return {
9925
+ isPromptsStored: prompts,
9926
+ isModelAndToolsStored: modelAndTools,
9927
+ visibility,
9928
+ retentionDays
9929
+ };
9930
+ }
9759
9931
  const WORKSPACE_ID = /^[A-Za-z0-9-]{1,128}$/;
9760
9932
  const NO_WHITESPACE = /^\S+$/;
9761
9933
  const LIMIT_ROLES = /* @__PURE__ */ new Set([
@@ -9764,15 +9936,16 @@ const LIMIT_ROLES = /* @__PURE__ */ new Set([
9764
9936
  "member"
9765
9937
  ]);
9766
9938
  function parseIssued(body) {
9767
- const { workspace_id: workspaceId, ingest_key: ingestKey, ingest_endpoint: ingestEndpoint, content_opt_in: contentOptIn } = body;
9768
- if (typeof workspaceId !== "string" || typeof ingestKey !== "string" || typeof ingestEndpoint !== "string" || typeof contentOptIn !== "boolean" || !WORKSPACE_ID.test(workspaceId)) return void 0;
9939
+ const { workspace_id: workspaceId, ingest_key: ingestKey, ingest_endpoint: ingestEndpoint, content } = body;
9940
+ if (typeof workspaceId !== "string" || typeof ingestKey !== "string" || typeof ingestEndpoint !== "string" || !WORKSPACE_ID.test(workspaceId)) return void 0;
9769
9941
  const apiToken = parseTokenPair(body, workspaceId);
9770
- return ingestKey.startsWith(`hf_ik_${workspaceId}_`) && NO_WHITESPACE.test(ingestKey) && apiToken !== void 0 && parseAllowedUrl(ingestEndpoint) !== void 0 && apiToken ? {
9942
+ const parsedContent = parseContent(content);
9943
+ return ingestKey.startsWith(`hf_ik_${workspaceId}_`) && NO_WHITESPACE.test(ingestKey) && apiToken !== void 0 && parsedContent !== void 0 && parseAllowedUrl(ingestEndpoint) !== void 0 && apiToken && parsedContent ? {
9771
9944
  workspaceId,
9772
9945
  ingestKey,
9773
9946
  apiToken,
9774
9947
  ingestEndpoint,
9775
- contentOptIn
9948
+ content: parsedContent
9776
9949
  } : void 0;
9777
9950
  }
9778
9951
  function classify(status, body) {
@@ -9812,8 +9985,7 @@ async function requestCredentials(base, request, fetchImpl) {
9812
9985
  return { kind: "unexpected" };
9813
9986
  }
9814
9987
  }
9815
- //#endregion
9816
- //#region src/init/pkce.ts
9988
+ // src/init/pkce.ts
9817
9989
  const RANDOM_BYTES = 32;
9818
9990
  const codeChallenge = (codeVerifier) => createHash("sha256").update(codeVerifier).digest("base64url");
9819
9991
  function createLoginSecrets() {
@@ -9824,8 +9996,7 @@ function createLoginSecrets() {
9824
9996
  state: randomBytes(RANDOM_BYTES).toString("base64url")
9825
9997
  };
9826
9998
  }
9827
- //#endregion
9828
- //#region src/init/init.ts
9999
+ // src/init/init.ts
9829
10000
  const CLIENT_ID = "harnessforce-cli";
9830
10001
  const DEFAULT_PORT = 8080;
9831
10002
  const MAX_REVOKE_HASHES = 100;
@@ -9856,28 +10027,43 @@ async function runInit(args, deps) {
9856
10027
  if (settings.kind === "invalid" || !hasWritableMarketplaces(settings.settings)) stop$1("settingsUnreadable");
9857
10028
  const authorizationEndpoint = await discoverAuthorizationEndpoint(base, deps.fetch) ?? stop$1("network");
9858
10029
  const secrets = createLoginSecrets();
9859
- const { code, redirectUri } = await logIn(authorizationEndpoint, secrets, port, deps);
9860
- const issued = issuedOrStop(await requestCredentials(base, {
9861
- code,
9862
- code_verifier: secrets.codeVerifier,
9863
- client_id: CLIENT_ID,
9864
- redirect_uri: redirectUri,
9865
- revoke_key_hashes: revoke.keyHashes,
9866
- revoke_api_token_hashes: revoke.apiTokenHashes
9867
- }, deps.fetch));
9868
- const isPluginAutoUpdateOn = await save(issued, recordedUrl, settingsPath, args, store).catch((error) => {
9869
- if (isStop(error)) throw error;
9870
- return stop$1("saveFailed");
9871
- });
9872
- const codexPath = codexConfigPath(deps.env, deps.homeDir);
9873
- await saveCodexConfig(issued, recordedUrl, codexPath, args.sendContent).catch(() => stop$1("codexSaveFailed"));
9874
- if (args.sendContent) deps.stdout(`${issued.contentOptIn ? INIT_MESSAGES.contentEnabled : INIT_MESSAGES.contentNotOptedIn}\n`);
9875
- return {
9876
- codexPath,
9877
- connection: recordedUrl,
9878
- isKernelKeyring: store.isKernelKeyring,
9879
- isPluginAutoUpdateOn
9880
- };
10030
+ const { code, redirectUri, loopback } = await logIn(authorizationEndpoint, secrets, port, new URL(recordedUrl).origin, deps);
10031
+ let completion = "failed";
10032
+ try {
10033
+ const outcome = await requestCredentials(base, {
10034
+ code,
10035
+ code_verifier: secrets.codeVerifier,
10036
+ client_id: CLIENT_ID,
10037
+ redirect_uri: redirectUri,
10038
+ revoke_key_hashes: revoke.keyHashes,
10039
+ revoke_api_token_hashes: revoke.apiTokenHashes
10040
+ }, deps.fetch);
10041
+ if (outcome.kind === "viewer") completion = "viewer";
10042
+ const issued = issuedOrStop(outcome);
10043
+ const isPluginAutoUpdateOn = await save(issued, recordedUrl, settingsPath, args, store).catch((error) => {
10044
+ if (isStop(error)) throw error;
10045
+ return stop$1("saveFailed");
10046
+ });
10047
+ const codexPath = codexConfigPath(deps.env, deps.homeDir);
10048
+ await saveCodexConfig(issued, recordedUrl, codexPath, args.sendContent).catch(() => stop$1("codexSaveFailed"));
10049
+ completion = "connected";
10050
+ loopback.finish({
10051
+ status: completion,
10052
+ workspaceId: issued.workspaceId
10053
+ });
10054
+ return finishInit(issued, args, deps, {
10055
+ codexPath,
10056
+ connection: recordedUrl,
10057
+ isKernelKeyring: store.isKernelKeyring,
10058
+ isPluginAutoUpdateOn
10059
+ });
10060
+ } finally {
10061
+ loopback.finish({ status: completion });
10062
+ }
10063
+ }
10064
+ function finishInit(issued, args, deps, result) {
10065
+ deps.stdout(`${contentNotice(issued.content, args.sendContent)}\n`);
10066
+ return result;
9881
10067
  }
9882
10068
  async function openKeychain(deps, previousWorkspaceId) {
9883
10069
  return await deps.keychain.openForInit({
@@ -9899,11 +10085,12 @@ function revokeHashes({ items }) {
9899
10085
  apiTokenHashes
9900
10086
  };
9901
10087
  }
9902
- async function logIn(authorizationEndpoint, secrets, port, deps) {
10088
+ async function logIn(authorizationEndpoint, secrets, port, webOrigin, deps) {
9903
10089
  const loopback = await deps.startLoopback({
9904
10090
  state: secrets.state,
9905
10091
  timeoutMs: deps.callbackTimeoutMs,
9906
- port
10092
+ port,
10093
+ webOrigin
9907
10094
  }).catch(() => stopWith(listenFailedMessage(port)));
9908
10095
  const authorization = new URL(authorizationEndpoint);
9909
10096
  for (const [name, value] of Object.entries({
@@ -9915,10 +10102,23 @@ async function logIn(authorizationEndpoint, secrets, port, deps) {
9915
10102
  state: secrets.state
9916
10103
  })) authorization.searchParams.set(name, value);
9917
10104
  if (!await deps.openBrowser(authorization.href)) deps.stderr(`${authorization.href}\n`);
9918
- return {
9919
- code: codeOrStop(await loopback.callback ?? stop$1("timeout"), secrets.state),
9920
- redirectUri: loopback.redirectUri
9921
- };
10105
+ const callback = await loopback.callback ?? stop$1("timeout");
10106
+ try {
10107
+ return {
10108
+ code: codeOrStop(callback, secrets.state),
10109
+ redirectUri: loopback.redirectUri,
10110
+ loopback
10111
+ };
10112
+ } catch (error) {
10113
+ loopback.finish({ status: callbackFailure(callback, secrets.state) });
10114
+ throw error;
10115
+ }
10116
+ }
10117
+ function callbackFailure(callback, state) {
10118
+ if (callback.state !== state) return "failed";
10119
+ if (callback.error === "access_denied") return "denied";
10120
+ if (callback.error === "no_workspace") return "no_workspace";
10121
+ return "failed";
9922
10122
  }
9923
10123
  function codeOrStop(callback, state) {
9924
10124
  if (callback.state !== state) return stop$1("loginFailed");
@@ -9961,21 +10161,35 @@ async function save(issued, connection, settingsPath, args, store) {
9961
10161
  OTEL_TRACES_EXPORTER: "otlp",
9962
10162
  OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
9963
10163
  };
9964
- if (args.sendContent && issued.contentOptIn) env.OTEL_LOG_USER_PROMPTS = "1";
10164
+ Object.assign(env, contentEnv(issued.content, args.sendContent));
9965
10165
  const { settings, isOn } = withPluginAutoUpdate(mergeUserSettings(current.settings, env), args.noPluginAutoUpdate);
9966
10166
  await writeUserSettings(settingsPath, settings);
9967
10167
  return isOn;
9968
10168
  }
10169
+ function contentEnv(content, sendContent) {
10170
+ const flag = (isStored) => sendContent && isStored ? "1" : "0";
10171
+ const modelAndTools = flag(content.isModelAndToolsStored);
10172
+ return {
10173
+ OTEL_LOG_USER_PROMPTS: flag(content.isPromptsStored),
10174
+ OTEL_LOG_ASSISTANT_RESPONSES: modelAndTools,
10175
+ OTEL_LOG_TOOL_DETAILS: modelAndTools,
10176
+ OTEL_LOG_TOOL_CONTENT: modelAndTools
10177
+ };
10178
+ }
10179
+ function contentNotice(content, sendContent) {
10180
+ if (!sendContent) return INIT_MESSAGES.contentNotSent;
10181
+ return content.isPromptsStored || content.isModelAndToolsStored ? contentEnabledMessage(content) : INIT_MESSAGES.contentNotStored;
10182
+ }
9969
10183
  async function saveCodexConfig(issued, connection, path, sendContent) {
9970
10184
  await writeCodexConfig(path, mergeCodexConfig(await readCodexConfig(path), {
9971
10185
  connection,
9972
10186
  ingestEndpoint: issued.ingestEndpoint,
9973
10187
  ingestKey: issued.ingestKey,
9974
- logUserPrompt: sendContent && issued.contentOptIn
10188
+ logUserPrompt: sendContent && issued.content.isPromptsStored,
10189
+ logAgentResponses: sendContent && issued.content.isModelAndToolsStored
9975
10190
  }));
9976
10191
  }
9977
- //#endregion
9978
- //#region src/otel-headers.ts
10192
+ // src/otel-headers.ts
9979
10193
  const DESTINATION_VARIABLES = [
9980
10194
  "HARNESSFORCE_ENDPOINT",
9981
10195
  "OTEL_EXPORTER_OTLP_ENDPOINT",
@@ -10029,8 +10243,7 @@ async function selectKey(env, managedDir, unlimited) {
10029
10243
  function isPinnedDestination(env, pinnedOrigin) {
10030
10244
  return DESTINATION_VARIABLES.map((name) => env[name]).filter((value) => Boolean(value)).every((value) => parseAllowedUrl(value)?.origin === pinnedOrigin);
10031
10245
  }
10032
- //#endregion
10033
- //#region ../agent-core/src/config/plugins.ts
10246
+ // ../agent-core/src/config/plugins.ts
10034
10247
  const NON_MARKETPLACE_ORIGINS = /* @__PURE__ */ new Set([
10035
10248
  "inline",
10036
10249
  "skills-dir",
@@ -10071,8 +10284,7 @@ async function collectPlugins(pluginsRoot, settingsByPrecedence, scope, guard) {
10071
10284
  collectMcpServers$1(s, (await readJsonObjectFile(join(root, ".mcp.json"), guard))?.mcpServers);
10072
10285
  }
10073
10286
  }
10074
- //#endregion
10075
- //#region ../agent-core/src/config/collect.ts
10287
+ // ../agent-core/src/config/collect.ts
10076
10288
  const COLLECT_BUDGET_MS = 1e3;
10077
10289
  async function readManagedSettings(managedDir, guard) {
10078
10290
  let merged = await readJsonObjectFile(join(managedDir, "managed-settings.json"), guard);
@@ -10206,8 +10418,7 @@ async function collectConfig(options) {
10206
10418
  components: sortComponents(sink.components)
10207
10419
  };
10208
10420
  }
10209
- //#endregion
10210
- //#region src/run/context.ts
10421
+ // src/run/context.ts
10211
10422
  async function resolveConfigVersion(options) {
10212
10423
  const startedMs = options.now().getTime();
10213
10424
  const result = await collectConfig({
@@ -10227,8 +10438,7 @@ async function resolveLaunchContext(options) {
10227
10438
  ...configVersion === void 0 ? {} : { configVersion }
10228
10439
  };
10229
10440
  }
10230
- //#endregion
10231
- //#region src/run/issues.ts
10441
+ // src/run/issues.ts
10232
10442
  const MAX_CANDIDATES = 10;
10233
10443
  const DOT_SEGMENT = /^\.{1,2}$/;
10234
10444
  async function get(url, apiToken, fetchImpl, timeoutMs) {
@@ -10287,8 +10497,7 @@ async function resolveIssue(base, identifier, apiToken, fetchImpl, timeoutMs = R
10287
10497
  }
10288
10498
  return fetchCandidates(base, identifier, apiToken, fetchImpl, timeoutMs);
10289
10499
  }
10290
- //#endregion
10291
- //#region src/run/launch.ts
10500
+ // src/run/launch.ts
10292
10501
  const STRIPPED_FROM_SHELL = /* @__PURE__ */ new Set(["OTEL_EXPORTER_OTLP_HEADERS", ...[
10293
10502
  "TRACES",
10294
10503
  "LOGS",
@@ -10350,8 +10559,7 @@ function buildLaunch(request) {
10350
10559
  ...isClaudeCode(request.agent) ? { settingsEnv: injectedEnv } : {}
10351
10560
  };
10352
10561
  }
10353
- //#endregion
10354
- //#region src/run/messages.ts
10562
+ // src/run/messages.ts
10355
10563
  const RUN_MESSAGES = {
10356
10564
  keychainUnavailable: INIT_MESSAGES.keychainUnavailable,
10357
10565
  initRequired: "Run `harnessforce init`",
@@ -10364,8 +10572,7 @@ const RUN_MESSAGES = {
10364
10572
  candidatesHeader: "Could not resolve the Issue. Candidates:"
10365
10573
  };
10366
10574
  const launchFailedMessage = (agent) => `Could not start ${agent}`;
10367
- //#endregion
10368
- //#region src/run/run.ts
10575
+ // src/run/run.ts
10369
10576
  const stop = (message) => stopWith(RUN_MESSAGES[message]);
10370
10577
  const printable = (text) => text.replace(/\p{Cc}/gu, "");
10371
10578
  function candidateList(candidates) {
@@ -10444,8 +10651,7 @@ async function runWithoutHarnessforce(args, deps) {
10444
10651
  deps.stderr(`${launchFailedMessage(args.agent)}\n`);
10445
10652
  return 1;
10446
10653
  }
10447
- //#endregion
10448
- //#region src/top/args.ts
10654
+ // src/top/args.ts
10449
10655
  const TOP_USAGE = "Usage: harnessforce top [--once] [--json] [--ascii] [--theme <auto|light|dark|ansi>]\n";
10450
10656
  const THEMES = [
10451
10657
  "auto",
@@ -10474,8 +10680,7 @@ function parseTopArgs(args) {
10474
10680
  theme
10475
10681
  };
10476
10682
  }
10477
- //#endregion
10478
- //#region src/top/width.ts
10683
+ // src/top/width.ts
10479
10684
  const SGR = /\x1b\[[0-9;]*m/g;
10480
10685
  const stripSgr = (text) => text.replace(SGR, "");
10481
10686
  const WIDE_RANGES = [
@@ -10540,8 +10745,7 @@ function truncate(text, maxWidth, ambiguousWide, ellipsis) {
10540
10745
  function padEnd(text, width, ambiguousWide) {
10541
10746
  return text + " ".repeat(Math.max(0, width - displayWidth(text, ambiguousWide)));
10542
10747
  }
10543
- //#endregion
10544
- //#region src/top/frame.ts
10748
+ // src/top/frame.ts
10545
10749
  const plainPainter = {
10546
10750
  color: (_token, text) => text,
10547
10751
  bold: (text) => text,
@@ -10741,8 +10945,7 @@ function renderDetail(row, options) {
10741
10945
  ];
10742
10946
  }
10743
10947
  const skippedNotice = (snapshot) => snapshot.skippedLines > 0 || snapshot.skippedFiles > 0 ? [`Skipped ${snapshot.skippedLines} unreadable line(s) and ${snapshot.skippedFiles} unreadable file(s)`] : [];
10744
- //#endregion
10745
- //#region src/top/codex-session.ts
10948
+ // src/top/codex-session.ts
10746
10949
  const DETACHED = "HEAD";
10747
10950
  const SHELL = "shell";
10748
10951
  const COUNTED_CALLS = /* @__PURE__ */ new Set(["function_call", "custom_tool_call"]);
@@ -10860,8 +11063,7 @@ var CodexSessionAccumulator = class {
10860
11063
  };
10861
11064
  }
10862
11065
  };
10863
- //#endregion
10864
- //#region src/top/reader.ts
11066
+ // src/top/reader.ts
10865
11067
  const WINDOW_MS = 864e5;
10866
11068
  const freshState = (newAccumulator) => ({
10867
11069
  offset: 0,
@@ -10986,8 +11188,7 @@ function createTopReader({ projectsDir, codexSessionsDir, git }) {
10986
11188
  };
10987
11189
  } };
10988
11190
  }
10989
- //#endregion
10990
- //#region src/top/color.ts
11191
+ // src/top/color.ts
10991
11192
  const MIN_CONTRAST = 3;
10992
11193
  const TOKEN_VALUES = {
10993
11194
  good: [[
@@ -11126,8 +11327,7 @@ function createPainter(input) {
11126
11327
  dim: (text) => open$1("2", text, "22")
11127
11328
  };
11128
11329
  }
11129
- //#endregion
11130
- //#region src/top/screen.ts
11330
+ // src/top/screen.ts
11131
11331
  const ENTER = "\x1B[?1049h\x1B[?25l";
11132
11332
  const RESTORE = "\x1B[?25h\x1B[?1049l";
11133
11333
  const HOME = "\x1B[H";
@@ -11237,7 +11437,7 @@ async function runScreen(options) {
11237
11437
  };
11238
11438
  const indexOfSelected = (rows) => {
11239
11439
  const index = rows.findIndex((row) => row.sessionId === selectedId);
11240
- return index >= 0 ? index : 0;
11440
+ return Math.max(index, 0);
11241
11441
  };
11242
11442
  const select = (rows, index) => {
11243
11443
  selectedId = rows[Math.max(0, Math.min(index, rows.length - 1))]?.sessionId;
@@ -11347,8 +11547,7 @@ async function runScreen(options) {
11347
11547
  render();
11348
11548
  });
11349
11549
  }
11350
- //#endregion
11351
- //#region src/top/settings.ts
11550
+ // src/top/settings.ts
11352
11551
  const DEFAULT_SETTINGS = {
11353
11552
  ambiguousWide: false,
11354
11553
  braille: false
@@ -11383,8 +11582,7 @@ async function readTopSettings(homeDir) {
11383
11582
  braille: braille === true
11384
11583
  } };
11385
11584
  }
11386
- //#endregion
11387
- //#region src/top/command.ts
11585
+ // src/top/command.ts
11388
11586
  const iso$1 = (ms) => new Date(ms).toISOString();
11389
11587
  function toJson(row, nowMs) {
11390
11588
  return {
@@ -11447,8 +11645,7 @@ async function topCommand(args, deps) {
11447
11645
  deps.stdout(`${lines.join("\n")}\n`);
11448
11646
  return 0;
11449
11647
  }
11450
- //#endregion
11451
- //#region src/tune/mcp-config.ts
11648
+ // src/tune/mcp-config.ts
11452
11649
  const UNLISTED = "unlisted";
11453
11650
  const PLUGIN_SERVER = /^([A-Za-z0-9._-]+)@[A-Za-z0-9._-]+:(.+)$/;
11454
11651
  const toToolName = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
@@ -11468,8 +11665,7 @@ function mcpConfigOf(identifiers) {
11468
11665
  }
11469
11666
  };
11470
11667
  }
11471
- //#endregion
11472
- //#region src/tune/vocabulary.ts
11668
+ // src/tune/vocabulary.ts
11473
11669
  const CONTINUE_PHRASES = /* @__PURE__ */ new Set([
11474
11670
  "continue",
11475
11671
  "go",
@@ -11530,8 +11726,7 @@ const EDIT_TOOLS = /* @__PURE__ */ new Set([
11530
11726
  "NotebookEdit"
11531
11727
  ]);
11532
11728
  const ISSUE_TOOL = /^mcp__.+__get_issue$/;
11533
- //#endregion
11534
- //#region src/tune/analyze.ts
11729
+ // src/tune/analyze.ts
11535
11730
  const ANALYZER_VERSION = "1.1.0";
11536
11731
  const NOT_MEASURED_INTERVENTIONS = /* @__PURE__ */ new Set(["approval", "answer"]);
11537
11732
  const emptyProposalCounts = () => Object.fromEntries(PROPOSAL_KINDS.map((kind) => [kind, {
@@ -11720,8 +11915,7 @@ function analyzeSession(input) {
11720
11915
  records_skipped: input.skippedLines
11721
11916
  };
11722
11917
  }
11723
- //#endregion
11724
- //#region src/tune/config-snapshot.ts
11918
+ // src/tune/config-snapshot.ts
11725
11919
  async function collectAt(projectRoot, deps, startedMs = deps.now()) {
11726
11920
  const result = await collectConfig({
11727
11921
  projectRoot,
@@ -11740,8 +11934,7 @@ async function collectMcpServers(cwd, git, deps) {
11740
11934
  if (collected.kind === "fixed_failure") return [];
11741
11935
  return [...new Set(collected.components.filter((c) => c.kind === "mcp_server").map((c) => c.id))];
11742
11936
  }
11743
- //#endregion
11744
- //#region src/tune/applied.ts
11937
+ // src/tune/applied.ts
11745
11938
  async function detectApplied(proposals, snapshot) {
11746
11939
  const byRoot = /* @__PURE__ */ new Map();
11747
11940
  const collect = (root) => {
@@ -11765,8 +11958,7 @@ async function detectApplied(proposals, snapshot) {
11765
11958
  }
11766
11959
  return detected;
11767
11960
  }
11768
- //#endregion
11769
- //#region src/tune/usage.ts
11961
+ // src/tune/usage.ts
11770
11962
  const USAGE_KINDS = [
11771
11963
  "frequent_compaction",
11772
11964
  "low_cache_reuse",
@@ -11837,8 +12029,7 @@ function modelShares(usages) {
11837
12029
  subagent: shares(subagent)
11838
12030
  };
11839
12031
  }
11840
- //#endregion
11841
- //#region src/tune/display.ts
12032
+ // src/tune/display.ts
11842
12033
  const median = (values) => {
11843
12034
  if (values.length === 0) return null;
11844
12035
  const sorted = [...values].sort((a, b) => a - b);
@@ -11906,8 +12097,7 @@ function localIso(ms) {
11906
12097
  const pad = (n) => String(n).padStart(2, "0");
11907
12098
  return `${new Date(ms + offsetMinutes * 6e4).toISOString().slice(0, 19)}${sign}${pad(Math.floor(absolute / 60))}:${pad(absolute % 60)}`;
11908
12099
  }
11909
- //#endregion
11910
- //#region src/tune/followups.ts
12100
+ // src/tune/followups.ts
11911
12101
  const PERIOD_MS = 12096e5;
11912
12102
  const MIN_SESSIONS = 5;
11913
12103
  const ratio = (numerator, denominator) => denominator === 0 ? null : numerator / denominator;
@@ -12011,8 +12201,7 @@ function computeFollowups(proposals, sessions, nowMs, rangeSinceMs) {
12011
12201
  ended
12012
12202
  };
12013
12203
  }
12014
- //#endregion
12015
- //#region src/tune/lock.ts
12204
+ // src/tune/lock.ts
12016
12205
  const LOCK_WAIT_MS = 3e4;
12017
12206
  const STALE_LOCK_MS = 6e4;
12018
12207
  const HEARTBEAT_MS = 1e4;
@@ -12047,8 +12236,7 @@ async function acquireTuneLock(path, deps) {
12047
12236
  await deps.sleep(RETRY_INTERVAL_MS);
12048
12237
  }
12049
12238
  }
12050
- //#endregion
12051
- //#region src/tune/messages.ts
12239
+ // src/tune/messages.ts
12052
12240
  const NOTICE_ORDER = [
12053
12241
  "insufficient",
12054
12242
  "outOfRange",
@@ -12122,8 +12310,7 @@ var Notices = class {
12122
12310
  return code;
12123
12311
  }
12124
12312
  };
12125
- //#endregion
12126
- //#region src/tune/store.ts
12313
+ // src/tune/store.ts
12127
12314
  const tuneDir = (homeDir) => join(homeDir, ".harnessforce", "tune");
12128
12315
  const tunePaths = (homeDir) => {
12129
12316
  const dir = tuneDir(homeDir);
@@ -12169,8 +12356,7 @@ async function writeFileAtomic(path, content) {
12169
12356
  }
12170
12357
  }
12171
12358
  const writeJsonFile = (path, value) => writeFileAtomic(path, `${JSON.stringify(value)}\n`);
12172
- //#endregion
12173
- //#region src/tune/proposals.ts
12359
+ // src/tune/proposals.ts
12174
12360
  const isRecord = (value) => isObject(value) && typeof value.proposal_id === "string" && typeof value.change_type === "string" && PROPOSAL_KINDS.includes(value.change_type) && Array.isArray(value.evidence_session_ids) && (value.attribution === "pending" || value.attribution === "decided");
12175
12361
  async function readProposals(path) {
12176
12362
  const value = await readJsonFile(path);
@@ -12209,8 +12395,7 @@ function countProposals(proposals, sessionId) {
12209
12395
  }
12210
12396
  return counts;
12211
12397
  }
12212
- //#endregion
12213
- //#region src/tune/saved-state.ts
12398
+ // src/tune/saved-state.ts
12214
12399
  async function readDestinations(path) {
12215
12400
  const value = await readJsonFile(path);
12216
12401
  return isObject(value) && isObject(value.destinations) ? value.destinations : {};
@@ -12269,8 +12454,7 @@ const writeSessionConfigs = (path, configs) => writeJsonFile(path, {
12269
12454
  transcript_path: config.transcriptPath
12270
12455
  }]))
12271
12456
  });
12272
- //#endregion
12273
- //#region src/tune/send.ts
12457
+ // src/tune/send.ts
12274
12458
  const BATCH_SIZE = 100;
12275
12459
  const READ_ONLY = "workspace_read_only";
12276
12460
  function parseReply(body, size) {
@@ -12371,8 +12555,7 @@ async function sendReports(options) {
12371
12555
  result.invalid.reasons = [...reasons];
12372
12556
  return result;
12373
12557
  }
12374
- //#endregion
12375
- //#region src/tune/sessions.ts
12558
+ // src/tune/sessions.ts
12376
12559
  const byCodeUnit = (a, b) => a < b ? -1 : a > b ? 1 : 0;
12377
12560
  async function readSubagentUsages(transcriptPath) {
12378
12561
  const dir = join(dirname(transcriptPath), basename(transcriptPath, ".jsonl"), "subagents");
@@ -12421,8 +12604,7 @@ async function readTuneSessions(options) {
12421
12604
  }
12422
12605
  return [...sessions.values()].sort((a, b) => Date.parse(a.session.startedAt) - Date.parse(b.session.startedAt) || byCodeUnit(a.session.sessionId, b.session.sessionId));
12423
12606
  }
12424
- //#endregion
12425
- //#region src/tune/settings.ts
12607
+ // src/tune/settings.ts
12426
12608
  const tuneConfigPath = (homeDir) => join(homeDir, ".harnessforce", "config.json");
12427
12609
  const isMissing = (error) => error?.code === "ENOENT";
12428
12610
  async function readSendSetting(homeDir) {
@@ -12447,8 +12629,7 @@ async function readSendSetting(homeDir) {
12447
12629
  if (typeof sendReport !== "boolean") return "unreadable";
12448
12630
  return sendReport ? "send" : "no_send";
12449
12631
  }
12450
- //#endregion
12451
- //#region src/tune/command.ts
12632
+ // src/tune/command.ts
12452
12633
  const DAY_MS = 864e5;
12453
12634
  const DEFAULT_RANGE_DAYS = 30;
12454
12635
  const SKIPPED_RATIO = .05;
@@ -12730,8 +12911,7 @@ function analyzeCommand(options, deps) {
12730
12911
  }
12731
12912
  }, deps.stderr);
12732
12913
  }
12733
- //#endregion
12734
- //#region src/tune/purge.ts
12914
+ // src/tune/purge.ts
12735
12915
  async function purgeTuneFiles(paths) {
12736
12916
  const targets = [
12737
12917
  paths.repositories,
@@ -12746,8 +12926,7 @@ async function purgeTuneFiles(paths) {
12746
12926
  force: true
12747
12927
  })))).every((r) => r.status === "fulfilled");
12748
12928
  }
12749
- //#endregion
12750
- //#region src/tune/analysis-file.ts
12929
+ // src/tune/analysis-file.ts
12751
12930
  async function readAnalysisSessions(path) {
12752
12931
  const value = await readJsonFile(path);
12753
12932
  const sessions = isObject(value) && Array.isArray(value.sessions) ? value.sessions : [];
@@ -12762,8 +12941,7 @@ async function readAnalysisSessions(path) {
12762
12941
  }
12763
12942
  return found;
12764
12943
  }
12765
- //#endregion
12766
- //#region src/tune/record.ts
12944
+ // src/tune/record.ts
12767
12945
  const MAX_RECORD_INPUT_BYTES = 1048576;
12768
12946
  const SCOPES = [
12769
12947
  "user",
@@ -12927,8 +13105,7 @@ async function recordProposal(text, paths, now) {
12927
13105
  attributed_session_id: record.attributed_session_id
12928
13106
  };
12929
13107
  }
12930
- //#endregion
12931
- //#region src/tune/tune.ts
13108
+ // src/tune/tune.ts
12932
13109
  const FLAGS = {
12933
13110
  "--all": "all",
12934
13111
  "--no-send": "noSend",
@@ -13010,8 +13187,7 @@ async function tuneCommand(args, deps) {
13010
13187
  });
13011
13188
  return analyzeCommand(parsed.options, deps);
13012
13189
  }
13013
- //#endregion
13014
- //#region src/main.ts
13190
+ // src/main.ts
13015
13191
  const { version } = createRequire(import.meta.url)("../package.json");
13016
13192
  const EXIT_CLIENT_UNSUPPORTED = 5;
13017
13193
  function parseInitArgs(args) {
@@ -13114,11 +13290,8 @@ async function run(argv, deps) {
13114
13290
  now: () => cli.now().getTime()
13115
13291
  }));
13116
13292
  if (command === "hook") {
13117
- if (rest.length !== 1 || rest[0] !== "session-start") {
13118
- cli.stderr(HOOK_USAGE);
13119
- return 1;
13120
- }
13121
- return sessionStart({
13293
+ const [event] = rest;
13294
+ const hookDeps = {
13122
13295
  env: cli.env,
13123
13296
  homeDir: cli.homeDir,
13124
13297
  now: cli.now,
@@ -13130,7 +13303,14 @@ async function run(argv, deps) {
13130
13303
  stderr: cli.stderr,
13131
13304
  client,
13132
13305
  defaultUrl: cli.defaultUrl
13306
+ };
13307
+ if (rest.length === 1 && event === "session-start") return sessionStart(hookDeps);
13308
+ if (rest.length === 1 && (event === "post-tool-use" || event === "stop")) return workLocationHook(event, {
13309
+ ...hookDeps,
13310
+ hookTmpDir: cli.hookTmpDir
13133
13311
  });
13312
+ cli.stderr(HOOK_USAGE);
13313
+ return 1;
13134
13314
  }
13135
13315
  if (command === "top") return topCommand(rest, {
13136
13316
  homeDir: cli.homeDir,
@@ -13156,8 +13336,7 @@ async function run(argv, deps) {
13156
13336
  cli.stderr(UNKNOWN_COMMAND_HINT);
13157
13337
  return 1;
13158
13338
  }
13159
- //#endregion
13160
- //#region src/relaunch.ts
13339
+ // src/relaunch.ts
13161
13340
  const SENDING_COMMANDS = /* @__PURE__ */ new Set([
13162
13341
  "init",
13163
13342
  "run",
@@ -13185,8 +13364,7 @@ async function relaunchHf(argv, deps) {
13185
13364
  deps.stderr(`${RESTART_FAILED}\n`);
13186
13365
  return 1;
13187
13366
  }
13188
- //#endregion
13189
- //#region src/run/command.ts
13367
+ // src/run/command.ts
13190
13368
  const hasSeparator = (command, platform) => platform === "win32" ? /[\\/]/.test(command) : command.includes("/");
13191
13369
  const contextFor = ({ platform, env, cwd, fs }) => ({
13192
13370
  platform,
@@ -13202,8 +13380,7 @@ async function resolveAgentFile(command, options) {
13202
13380
  function commandLine(file, args, options) {
13203
13381
  return commandLineFor(file, args, contextFor(options));
13204
13382
  }
13205
- //#endregion
13206
- //#region src/run/process.ts
13383
+ // src/run/process.ts
13207
13384
  const IGNORED_WHILE_RUNNING = ["SIGINT", "SIGQUIT"];
13208
13385
  const FORWARDED = ["SIGTERM", "SIGHUP"];
13209
13386
  const FAILED = { kind: "failed" };
@@ -13265,8 +13442,7 @@ function spawnAndWait(command, env) {
13265
13442
  }));
13266
13443
  });
13267
13444
  }
13268
- //#endregion
13269
- //#region src/top/process-io.ts
13445
+ // src/top/process-io.ts
13270
13446
  const EXIT_SIGNALS = ["SIGTERM", "SIGHUP"];
13271
13447
  function createProcessIo(proc = process) {
13272
13448
  const { stdin, stdout } = proc;
@@ -13327,8 +13503,7 @@ function createProcessIo(proc = process) {
13327
13503
  }
13328
13504
  };
13329
13505
  }
13330
- //#endregion
13331
- //#region src/bin.ts
13506
+ // src/bin.ts
13332
13507
  const require = createRequire(import.meta.url);
13333
13508
  const { url: defaultUrl, isDefault } = require("./build-config.json");
13334
13509
  const CALLBACK_TIMEOUT_MS = 3e5;
@@ -13390,6 +13565,7 @@ process.exitCode = relaunched ?? await run(argv, {
13390
13565
  processCwd: process.cwd(),
13391
13566
  excludeTarget: true
13392
13567
  }),
13568
+ hookTmpDir: tmpdir(),
13393
13569
  sleep: (ms) => setTimeout$1(ms),
13394
13570
  top: createProcessIo(),
13395
13571
  readStdin: async (maxBytes) => {
@@ -13403,5 +13579,4 @@ process.exitCode = relaunched ?? await run(argv, {
13403
13579
  return Buffer.concat(chunks);
13404
13580
  }
13405
13581
  });
13406
- //#endregion
13407
13582
  export {};