@herdingchats/friday 0.1.613 → 0.1.623

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 (2) hide show
  1. package/herd.mjs +527 -284
  2. package/package.json +2 -2
package/herd.mjs CHANGED
@@ -18,7 +18,7 @@ var __export = (target, all) => {
18
18
  var define_HERD_PACKAGE_default;
19
19
  var init_define_HERD_PACKAGE = __esm({
20
20
  "<define:HERD_PACKAGE>"() {
21
- define_HERD_PACKAGE_default = { version: "0.1.613", gitHead: "81ca2a791596886d867b9d1da1ba8e0430bed3c4" };
21
+ define_HERD_PACKAGE_default = { version: "0.1.623", gitHead: "0c391f8523159d26c6e23d46eb803c56a4e7a9ec" };
22
22
  }
23
23
  });
24
24
 
@@ -1496,8 +1496,6 @@ var init_conversation_state = __esm({
1496
1496
  waiting: "eyes",
1497
1497
  working: "hourglass_flowing_sand",
1498
1498
  handled: "white_check_mark",
1499
- // Seen, and not meant for the bot, so it left the message alone.
1500
- passed: "ok_hand",
1501
1499
  stuck: "warning"
1502
1500
  };
1503
1501
  conversationStateSchema = z13.enum([
@@ -3206,9 +3204,9 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
3206
3204
  init_define_HERD_PACKAGE();
3207
3205
  init_src();
3208
3206
  import { spawn as spawn7 } from "node:child_process";
3209
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
3210
- import { homedir as homedir4 } from "node:os";
3211
- import { dirname as dirname10, join as join14 } from "node:path";
3207
+ import { existsSync as existsSync12, readFileSync as readFileSync10 } from "node:fs";
3208
+ import { homedir as homedir5 } from "node:os";
3209
+ import { dirname as dirname10, join as join15 } from "node:path";
3212
3210
  import { createInterface } from "node:readline/promises";
3213
3211
  import { z as z22 } from "zod";
3214
3212
 
@@ -5662,6 +5660,9 @@ StandardError=append:${context2.paths.supervisorLog}
5662
5660
  WantedBy=default.target
5663
5661
  `;
5664
5662
  }
5663
+ function makeLogFolder(context2) {
5664
+ mkdirSync8(dirname8(context2.paths.supervisorLog), { recursive: true });
5665
+ }
5665
5666
  function writeIfChanged(path, text4) {
5666
5667
  if (existsSync7(path) && readFileSync7(path, "utf8") === text4) return false;
5667
5668
  mkdirSync8(dirname8(path), { recursive: true });
@@ -5675,7 +5676,10 @@ function launchd(context2) {
5675
5676
  const loaded = async () => (await context2.run("launchctl", ["print", target])).status === 0;
5676
5677
  return {
5677
5678
  kind: "launchd",
5678
- install: async () => writeIfChanged(plist, launchAgentPlist(context2)),
5679
+ install: async () => {
5680
+ makeLogFolder(context2);
5681
+ return writeIfChanged(plist, launchAgentPlist(context2));
5682
+ },
5679
5683
  async start(changed) {
5680
5684
  if (await loaded()) {
5681
5685
  if (!changed) return;
@@ -5703,7 +5707,10 @@ function systemd(context2) {
5703
5707
  };
5704
5708
  return {
5705
5709
  kind: "systemd",
5706
- install: async () => writeIfChanged(unit, systemdUnit(context2)),
5710
+ install: async () => {
5711
+ makeLogFolder(context2);
5712
+ return writeIfChanged(unit, systemdUnit(context2));
5713
+ },
5707
5714
  async start(changed) {
5708
5715
  if (changed) await systemctl("daemon-reload");
5709
5716
  await systemctl("enable", SYSTEMD_UNIT);
@@ -5775,87 +5782,30 @@ function serviceFor(kind, context2) {
5775
5782
  init_define_HERD_PACKAGE();
5776
5783
  init_src();
5777
5784
  import { mkdirSync as mkdirSync10, realpathSync as realpathSync4 } from "node:fs";
5778
- import { join as join13, resolve as resolve3 } from "node:path";
5779
-
5780
- // src/friday/legacy.ts
5781
- init_define_HERD_PACKAGE();
5782
- import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync8 } from "node:fs";
5783
- import { userInfo as userInfo2 } from "node:os";
5784
- import { join as join11 } from "node:path";
5785
- var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
5786
- var OLD_APPS = ["herd-daemon", "herd-updater"];
5787
- function oldPm2s(paths) {
5788
- const dir = join11(paths.herdHome, "daemon");
5789
- if (!existsSync8(dir)) return [];
5790
- return readdirSync3(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join11(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync8(pm2));
5791
- }
5792
- async function retireOldDaemon(paths, { home, run: run4 }) {
5793
- const removed = [];
5794
- const plist = join11(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
5795
- if (existsSync8(plist)) {
5796
- await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
5797
- rmSync8(plist, { force: true });
5798
- removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
5799
- }
5800
- for (const pm2 of oldPm2s(paths)) {
5801
- const listed = await run4(process.execPath, [pm2, "jlist"]);
5802
- let names = [];
5803
- try {
5804
- names = JSON.parse(listed.stdout).map((app) => app.name ?? "");
5805
- } catch {
5806
- names = [];
5807
- }
5808
- const ours = OLD_APPS.filter((name) => names.includes(name));
5809
- if (ours.length === 0) continue;
5810
- for (const name of ours) await run4(process.execPath, [pm2, "delete", name]);
5811
- await run4(process.execPath, [pm2, "save", "--force"]);
5812
- removed.push(`pm2's ${ours.join(" and ")}`);
5813
- }
5814
- return removed;
5815
- }
5785
+ import { join as join14, resolve as resolve3 } from "node:path";
5816
5786
 
5817
- // src/friday/verbose.ts
5787
+ // src/package-manager.ts
5818
5788
  init_define_HERD_PACKAGE();
5819
- var VERBOSE_ENV = "HERD_VERBOSE";
5820
- var began = Date.now();
5821
- function verbose2(line2) {
5822
- if (!process.env[VERBOSE_ENV]) return;
5823
- const seconds = ((Date.now() - began) / 1e3).toFixed(1);
5824
- process.stderr.write(`[friday +${seconds}s] ${line2}
5825
- `);
5826
- }
5827
- var tail = (text4) => text4.trim().split("\n").slice(-3).join(" | ");
5828
- function verboseExec(run4) {
5829
- return async (command, args, options) => {
5830
- if (!process.env[VERBOSE_ENV]) return run4(command, args, options);
5831
- const shown = [command, ...args].join(" ");
5832
- verbose2(`running ${shown}`);
5833
- const started = Date.now();
5834
- const ran = await run4(command, args, options);
5835
- const took = `${Date.now() - started}ms`;
5836
- verbose2(
5837
- ran.status === 0 ? `ran ${shown} (${took})` : `${shown} exited ${ran.status} (${took})${ran.stderr.trim() ? `: ${tail(ran.stderr)}` : ""}`
5838
- );
5839
- return ran;
5840
- };
5841
- }
5789
+ import { existsSync as existsSync9 } from "node:fs";
5790
+ import { homedir as homedir4 } from "node:os";
5791
+ import { basename as basename3, delimiter, dirname as dirname9, isAbsolute as isAbsolute2, join as join12 } from "node:path";
5842
5792
 
5843
5793
  // src/friday/versions.ts
5844
5794
  init_define_HERD_PACKAGE();
5845
5795
  import { spawn as spawn5 } from "node:child_process";
5846
5796
  import {
5847
- existsSync as existsSync9,
5797
+ existsSync as existsSync8,
5848
5798
  mkdirSync as mkdirSync9,
5849
- readdirSync as readdirSync4,
5799
+ readdirSync as readdirSync3,
5850
5800
  readFileSync as readFileSync8,
5851
5801
  readlinkSync,
5852
5802
  realpathSync as realpathSync3,
5853
5803
  renameSync as renameSync3,
5854
- rmSync as rmSync9,
5804
+ rmSync as rmSync8,
5855
5805
  symlinkSync,
5856
5806
  writeFileSync as writeFileSync8
5857
5807
  } from "node:fs";
5858
- import { basename as basename2, dirname as dirname9, join as join12 } from "node:path";
5808
+ import { basename as basename2, join as join11 } from "node:path";
5859
5809
  var exec = (command, args, options = {}) => new Promise((resolve7) => {
5860
5810
  let stdout = "";
5861
5811
  let stderr = "";
@@ -5876,10 +5826,6 @@ var exec = (command, args, options = {}) => new Promise((resolve7) => {
5876
5826
  resolve7({ status: status2 ?? 1, stdout, stderr });
5877
5827
  });
5878
5828
  });
5879
- function npmCommand() {
5880
- const beside = join12(dirname9(process.execPath), "npm");
5881
- return existsSync9(beside) ? beside : "npm";
5882
- }
5883
5829
  function currentVersion(paths) {
5884
5830
  try {
5885
5831
  return basename2(readlinkSync(paths.current));
@@ -5888,10 +5834,10 @@ function currentVersion(paths) {
5888
5834
  }
5889
5835
  }
5890
5836
  function installedVersions(paths) {
5891
- if (!existsSync9(paths.versions)) return [];
5892
- return readdirSync4(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
5837
+ if (!existsSync8(paths.versions)) return [];
5838
+ return readdirSync3(paths.versions).filter((name) => VERSION.test(name)).sort(compareVersions);
5893
5839
  }
5894
- var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
5840
+ var binOf = (versionDir, bin) => join11(packageDir(versionDir), `${bin}.mjs`);
5895
5841
  async function versionRuns(versionDir, version, run4 = exec) {
5896
5842
  const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
5897
5843
  timeoutMs: 3e4
@@ -5903,12 +5849,19 @@ async function versionRuns(versionDir, version, run4 = exec) {
5903
5849
  return null;
5904
5850
  }
5905
5851
  function recordedIntegrity(dir) {
5906
- try {
5907
- const lock = JSON.parse(readFileSync8(join12(dir, "package-lock.json"), "utf8"));
5908
- return lock.packages?.[`node_modules/${PACKAGE_NAME}`]?.integrity ?? null;
5909
- } catch {
5910
- return null;
5852
+ for (const manager of INSTALLERS) {
5853
+ for (const file of LOCKFILES[manager]) {
5854
+ let lock;
5855
+ try {
5856
+ lock = readFileSync8(join11(dir, file), "utf8");
5857
+ } catch {
5858
+ continue;
5859
+ }
5860
+ const found = lockedIntegrity(manager, lock, PACKAGE_NAME);
5861
+ if (found) return found;
5862
+ }
5911
5863
  }
5864
+ return null;
5912
5865
  }
5913
5866
  function npmFreeEnv() {
5914
5867
  return Object.fromEntries(
@@ -5916,10 +5869,10 @@ function npmFreeEnv() {
5916
5869
  );
5917
5870
  }
5918
5871
  async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
5919
- } } = {}) {
5872
+ }, manager } = {}) {
5920
5873
  if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
5921
- const dir = join12(paths.versions, version);
5922
- if (existsSync9(dir)) {
5874
+ const dir = join11(paths.versions, version);
5875
+ if (existsSync8(dir)) {
5923
5876
  const installed2 = recordedIntegrity(dir);
5924
5877
  if (integrity && installed2 !== integrity)
5925
5878
  return { ok: false, reason: `the installed ${version} doesn't have the integrity named.` };
@@ -5927,62 +5880,63 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
5927
5880
  if (!broken2) return { ok: true, dir };
5928
5881
  return { ok: false, reason: `the installed ${version} doesn't run: ${broken2}.` };
5929
5882
  }
5883
+ const using = manager ?? await findPackageManager({ env: { ...process.env, ...env }, run: run4 });
5884
+ if (!using)
5885
+ return {
5886
+ ok: false,
5887
+ reason: "there's no package manager to install it with: herd looked for npm, pnpm and Yarn 1 beside Node, on the PATH and in their usual folders."
5888
+ };
5930
5889
  sweepStaging(paths);
5931
- const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
5932
- rmSync9(named, { recursive: true, force: true });
5890
+ const named = join11(paths.versions, `.staging-${version}-${process.pid}`);
5891
+ rmSync8(named, { recursive: true, force: true });
5933
5892
  mkdirSync9(named, { recursive: true });
5934
5893
  const staging = realpathSync3(named);
5935
5894
  writeFileSync8(
5936
- join12(staging, "package.json"),
5937
- `${JSON.stringify({ private: true, allowScripts: { "node-pty": true } }, null, 2)}
5895
+ join11(staging, "package.json"),
5896
+ `${JSON.stringify(allowScriptsManifest(["node-pty"]), null, 2)}
5938
5897
  `
5939
5898
  );
5899
+ if (using.name === "pnpm")
5900
+ writeFileSync8(join11(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
5940
5901
  const fail = (reason) => {
5941
- rmSync9(staging, { recursive: true, force: true });
5902
+ rmSync8(staging, { recursive: true, force: true });
5942
5903
  return { ok: false, reason };
5943
5904
  };
5944
- log(`Installing ${PACKAGE_NAME}@${version}.`);
5905
+ log(`Installing ${PACKAGE_NAME}@${version} with ${using.name} ${using.version}.`);
5945
5906
  const installed = await run4(
5946
- npmCommand(),
5947
- [
5948
- "install",
5949
- "--prefix",
5950
- staging,
5951
- spec ?? `${PACKAGE_NAME}@${version}`,
5952
- "--no-audit",
5953
- "--no-fund",
5954
- "--no-update-notifier",
5955
- "--save-exact"
5956
- ],
5907
+ using.command,
5908
+ addArgs(using.name, staging, spec ?? `${PACKAGE_NAME}@${version}`),
5957
5909
  // From inside the folder, so a workspace around wherever this ran isn't taken for its project.
5958
5910
  { cwd: staging, env: { ...npmFreeEnv(), ...env }, timeoutMs: 10 * 6e4 }
5959
5911
  );
5960
5912
  if (installed.status !== 0)
5961
- return fail(`npm couldn't install it: ${installed.stderr.trim().slice(-500)}`);
5913
+ return fail(
5914
+ `${using.name} couldn't install it: ${(installed.stderr || installed.stdout).trim().slice(-500)}`
5915
+ );
5962
5916
  if (integrity) {
5963
5917
  const got = recordedIntegrity(staging);
5964
5918
  if (got !== integrity)
5965
5919
  return fail(
5966
- `its integrity doesn't match the release: npm served ${got ?? "nothing"}, and herd named ${integrity}`
5920
+ `its integrity doesn't match the release: ${using.name} recorded ${got ?? "nothing"}, and herd named ${integrity}`
5967
5921
  );
5968
5922
  }
5969
5923
  const broken = await versionRuns(staging, version, run4);
5970
5924
  if (broken) return fail(broken);
5971
- renameSync3(staging, join12(realpathSync3(paths.versions), version));
5925
+ renameSync3(staging, join11(realpathSync3(paths.versions), version));
5972
5926
  return { ok: true, dir };
5973
5927
  }
5974
5928
  function sweepStaging(paths) {
5975
- if (!existsSync9(paths.versions)) return;
5976
- for (const name of readdirSync4(paths.versions)) {
5929
+ if (!existsSync8(paths.versions)) return;
5930
+ for (const name of readdirSync3(paths.versions)) {
5977
5931
  const match = /^\.staging-.+-(\d+)$/.exec(name);
5978
5932
  if (match && !alive2(Number(match[1])))
5979
- rmSync9(join12(paths.versions, name), { recursive: true, force: true });
5933
+ rmSync8(join11(paths.versions, name), { recursive: true, force: true });
5980
5934
  }
5981
5935
  }
5982
5936
  function activate(paths, version) {
5983
5937
  const next = `${paths.current}.next-${process.pid}`;
5984
- rmSync9(next, { force: true });
5985
- symlinkSync(join12("versions", version), next);
5938
+ rmSync8(next, { force: true });
5939
+ symlinkSync(join11("versions", version), next);
5986
5940
  renameSync3(next, paths.current);
5987
5941
  }
5988
5942
  function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
@@ -5997,7 +5951,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
5997
5951
  const removed = [];
5998
5952
  for (const version of versions) {
5999
5953
  if (kept.has(version)) continue;
6000
- rmSync9(join12(paths.versions, version), { recursive: true, force: true });
5954
+ rmSync8(join11(paths.versions, version), { recursive: true, force: true });
6001
5955
  removed.push(version);
6002
5956
  }
6003
5957
  return removed;
@@ -6021,6 +5975,271 @@ async function hostVersions(paths, run4 = exec) {
6021
5975
  return [...found].filter(Boolean);
6022
5976
  }
6023
5977
 
5978
+ // src/package-manager.ts
5979
+ var MANAGERS = ["npm", "pnpm", "yarn", "bun"];
5980
+ var INSTALLERS = ["npm", "pnpm", "yarn"];
5981
+ var MANAGER_ENV = "HERD_PACKAGE_MANAGER";
5982
+ var nameOf = (command) => {
5983
+ const name = basename3(command).replace(/\.(cmd|exe|ps1)$/i, "");
5984
+ return MANAGERS.includes(name) ? name : null;
5985
+ };
5986
+ function homesOf(name, env, home) {
5987
+ const common = [join12(home, ".volta", "bin"), join12(home, ".local", "bin")];
5988
+ switch (name) {
5989
+ case "pnpm":
5990
+ return [
5991
+ ...env.PNPM_HOME ? [env.PNPM_HOME] : [],
5992
+ join12(home, "Library", "pnpm"),
5993
+ join12(home, ".local", "share", "pnpm"),
5994
+ ...common
5995
+ ];
5996
+ case "bun":
5997
+ return [join12(env.BUN_INSTALL || join12(home, ".bun"), "bin"), ...common];
5998
+ case "yarn":
5999
+ return [join12(home, ".yarn", "bin"), ...common];
6000
+ case "npm":
6001
+ return common;
6002
+ }
6003
+ }
6004
+ function candidatePaths(name, {
6005
+ env = process.env,
6006
+ execPath = process.execPath,
6007
+ home = homedir4(),
6008
+ platform: platform2 = process.platform
6009
+ } = {}) {
6010
+ const path = (env.PATH ?? env.Path ?? "").split(platform2 === "win32" ? ";" : delimiter);
6011
+ const dirs = [
6012
+ dirname9(execPath),
6013
+ ...path,
6014
+ ...homesOf(name, env, home),
6015
+ "/opt/homebrew/bin",
6016
+ "/usr/local/bin",
6017
+ "/usr/bin"
6018
+ ];
6019
+ const files = platform2 === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
6020
+ const seen = /* @__PURE__ */ new Set();
6021
+ for (const dir of dirs.filter(Boolean)) {
6022
+ for (const file of files) seen.add(join12(dir, file));
6023
+ }
6024
+ return [...seen];
6025
+ }
6026
+ async function versionOf(command, run4, env) {
6027
+ const ran = await run4(command, ["--version"], {
6028
+ // A corepack shim would otherwise ask before downloading the manager it stands in for.
6029
+ env: { ...env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
6030
+ timeoutMs: 2e4
6031
+ });
6032
+ const version = ran.stdout.trim().split("\n").at(-1)?.trim() ?? "";
6033
+ return ran.status === 0 && /^\d+\.\d+/.test(version) ? version : null;
6034
+ }
6035
+ var installs = (name, version) => name !== "yarn" || Number(version.split(".")[0]) < 2;
6036
+ async function findPackageManager(options = {}) {
6037
+ const env = options.env ?? process.env;
6038
+ const exists = options.exists ?? existsSync9;
6039
+ const run4 = options.run ?? exec;
6040
+ const only = options.only ?? INSTALLERS;
6041
+ const accept = options.accept ?? installs;
6042
+ const tried = /* @__PURE__ */ new Set();
6043
+ const attempt = async (name, command) => {
6044
+ if (tried.has(command)) return null;
6045
+ tried.add(command);
6046
+ if (isAbsolute2(command) && !exists(command)) return null;
6047
+ const version = await versionOf(command, run4, env);
6048
+ return version && accept(name, version) ? { name, command, version } : null;
6049
+ };
6050
+ const override = env[MANAGER_ENV]?.trim();
6051
+ if (override) {
6052
+ const name = nameOf(override);
6053
+ if (name && only.includes(name)) {
6054
+ const paths = isAbsolute2(override) ? [override] : candidatePaths(name, options);
6055
+ for (const path of paths) {
6056
+ const found = await attempt(name, path);
6057
+ if (found) return found;
6058
+ }
6059
+ }
6060
+ }
6061
+ for (const name of only) {
6062
+ for (const path of candidatePaths(name, options)) {
6063
+ const found = await attempt(name, path);
6064
+ if (found) return found;
6065
+ }
6066
+ }
6067
+ return null;
6068
+ }
6069
+ function addArgs(manager, dir, spec) {
6070
+ if (manager === "bun") throw new Error("Bun doesn't install Friday's versions.");
6071
+ switch (manager) {
6072
+ case "npm":
6073
+ return [
6074
+ "install",
6075
+ "--prefix",
6076
+ dir,
6077
+ spec,
6078
+ "--no-audit",
6079
+ "--no-fund",
6080
+ "--no-update-notifier",
6081
+ "--save-exact"
6082
+ ];
6083
+ case "pnpm":
6084
+ return ["add", spec, "--dir", dir, "--save-exact", "--config.update-notifier=false"];
6085
+ case "yarn":
6086
+ return ["add", spec, "--cwd", dir, "--exact", "--no-progress", "--non-interactive"];
6087
+ }
6088
+ }
6089
+ var LOCKFILES = {
6090
+ npm: ["package-lock.json", "npm-shrinkwrap.json"],
6091
+ pnpm: ["pnpm-lock.yaml"],
6092
+ bun: ["bun.lock", "bun.lockb"],
6093
+ yarn: ["yarn.lock"]
6094
+ };
6095
+ var escapeRegExp = (text4) => text4.replace(/[.*+?^${}()|[\]\\/]/g, "\\$&");
6096
+ function lockedIntegrity(manager, lock, name) {
6097
+ const integrity = /sha512-[A-Za-z0-9+/]+=*/;
6098
+ switch (manager) {
6099
+ case "npm": {
6100
+ try {
6101
+ const parsed = JSON.parse(lock);
6102
+ return parsed.packages?.[`node_modules/${name}`]?.integrity ?? null;
6103
+ } catch {
6104
+ return null;
6105
+ }
6106
+ }
6107
+ case "bun":
6108
+ return null;
6109
+ case "pnpm":
6110
+ case "yarn": {
6111
+ const lines = lock.split("\n");
6112
+ const key = new RegExp(`^(\\s*)["']?${escapeRegExp(name)}@[^\\n]*:\\s*$`);
6113
+ for (let i = 0; i < lines.length; i++) {
6114
+ const match = key.exec(lines[i] ?? "");
6115
+ if (!match) continue;
6116
+ const indent = match[1]?.length ?? 0;
6117
+ for (let j = i + 1; j < lines.length; j++) {
6118
+ const line2 = lines[j] ?? "";
6119
+ if (line2.trim() === "") continue;
6120
+ if (line2.length - line2.trimStart().length <= indent) break;
6121
+ const found = integrity.exec(line2);
6122
+ if (found) return found[0];
6123
+ }
6124
+ }
6125
+ return null;
6126
+ }
6127
+ }
6128
+ }
6129
+ function allowScriptsManifest(packages) {
6130
+ return {
6131
+ private: true,
6132
+ // npm's.
6133
+ allowScripts: Object.fromEntries(packages.map((name) => [name, true])),
6134
+ // pnpm's, before pnpm-workspace.yaml held them.
6135
+ pnpm: { onlyBuiltDependencies: packages }
6136
+ };
6137
+ }
6138
+ var allowScriptsWorkspace = (packages) => `onlyBuiltDependencies:
6139
+ ${packages.map((name) => ` - ${name}`).join("\n")}
6140
+ allowBuilds:
6141
+ ${packages.map((name) => ` ${name}: true`).join("\n")}
6142
+ `;
6143
+ function managerOfCheckout(packageManagerField, hasFile) {
6144
+ const declared = packageManagerField ? /^(npm|pnpm|bun|yarn)@(\d+)/.exec(packageManagerField) : null;
6145
+ const lockOf = (name) => LOCKFILES[name].find(hasFile) ?? null;
6146
+ if (declared) {
6147
+ const name = declared[1];
6148
+ return { name, lockfile: lockOf(name), major: Number(declared[2]) };
6149
+ }
6150
+ for (const name of MANAGERS) {
6151
+ const lockfile = lockOf(name);
6152
+ if (lockfile) return { name, lockfile, major: null };
6153
+ }
6154
+ return null;
6155
+ }
6156
+ function frozenInstallArgs(name, { offline, yarnMajor }) {
6157
+ switch (name) {
6158
+ case "npm":
6159
+ return [
6160
+ "ci",
6161
+ offline ? "--offline" : "--prefer-offline",
6162
+ "--no-audit",
6163
+ "--no-fund",
6164
+ "--no-update-notifier"
6165
+ ];
6166
+ case "pnpm":
6167
+ return ["install", "--frozen-lockfile", offline ? "--offline" : "--prefer-offline"];
6168
+ case "bun":
6169
+ return ["install", "--frozen-lockfile", "--no-progress"];
6170
+ case "yarn":
6171
+ return (yarnMajor ?? 1) >= 2 ? ["install", "--immutable"] : [
6172
+ "install",
6173
+ "--frozen-lockfile",
6174
+ "--non-interactive",
6175
+ ...offline ? ["--offline"] : ["--prefer-offline"]
6176
+ ];
6177
+ }
6178
+ }
6179
+
6180
+ // src/friday/legacy.ts
6181
+ init_define_HERD_PACKAGE();
6182
+ import { existsSync as existsSync10, readdirSync as readdirSync4, rmSync as rmSync9 } from "node:fs";
6183
+ import { userInfo as userInfo2 } from "node:os";
6184
+ import { join as join13 } from "node:path";
6185
+ var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
6186
+ var OLD_APPS = ["herd-daemon", "herd-updater"];
6187
+ function oldPm2s(paths) {
6188
+ const dir = join13(paths.herdHome, "daemon");
6189
+ if (!existsSync10(dir)) return [];
6190
+ return readdirSync4(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join13(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync10(pm2));
6191
+ }
6192
+ async function retireOldDaemon(paths, { home, run: run4 }) {
6193
+ const removed = [];
6194
+ const plist = join13(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
6195
+ if (existsSync10(plist)) {
6196
+ await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
6197
+ rmSync9(plist, { force: true });
6198
+ removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
6199
+ }
6200
+ for (const pm2 of oldPm2s(paths)) {
6201
+ const listed = await run4(process.execPath, [pm2, "jlist"]);
6202
+ let names = [];
6203
+ try {
6204
+ names = JSON.parse(listed.stdout).map((app) => app.name ?? "");
6205
+ } catch {
6206
+ names = [];
6207
+ }
6208
+ const ours = OLD_APPS.filter((name) => names.includes(name));
6209
+ if (ours.length === 0) continue;
6210
+ for (const name of ours) await run4(process.execPath, [pm2, "delete", name]);
6211
+ await run4(process.execPath, [pm2, "save", "--force"]);
6212
+ removed.push(`pm2's ${ours.join(" and ")}`);
6213
+ }
6214
+ return removed;
6215
+ }
6216
+
6217
+ // src/friday/verbose.ts
6218
+ init_define_HERD_PACKAGE();
6219
+ var VERBOSE_ENV = "HERD_VERBOSE";
6220
+ var began = Date.now();
6221
+ function verbose2(line2) {
6222
+ if (!process.env[VERBOSE_ENV]) return;
6223
+ const seconds = ((Date.now() - began) / 1e3).toFixed(1);
6224
+ process.stderr.write(`[friday +${seconds}s] ${line2}
6225
+ `);
6226
+ }
6227
+ var tail = (text4) => text4.trim().split("\n").slice(-3).join(" | ");
6228
+ function verboseExec(run4) {
6229
+ return async (command, args, options) => {
6230
+ if (!process.env[VERBOSE_ENV]) return run4(command, args, options);
6231
+ const shown = [command, ...args].join(" ");
6232
+ verbose2(`running ${shown}`);
6233
+ const started = Date.now();
6234
+ const ran = await run4(command, args, options);
6235
+ const took = `${Date.now() - started}ms`;
6236
+ verbose2(
6237
+ ran.status === 0 ? `ran ${shown} (${took})` : `${shown} exited ${ran.status} (${took})${ran.stderr.trim() ? `: ${tail(ran.stderr)}` : ""}`
6238
+ );
6239
+ return ran;
6240
+ };
6241
+ }
6242
+
6024
6243
  // src/friday/start.ts
6025
6244
  var MIN_NODE = [22, 18];
6026
6245
  var SETUP_WAIT_MS = 2e4;
@@ -6051,7 +6270,7 @@ var real = (path) => {
6051
6270
  }
6052
6271
  };
6053
6272
  function chooseRoot(cwd, home) {
6054
- return real(cwd) === real(home) ? join13(home, "friday") : resolve3(cwd);
6273
+ return real(cwd) === real(home) ? join14(home, "friday") : resolve3(cwd);
6055
6274
  }
6056
6275
  async function runStart(options) {
6057
6276
  const { paths, run: run4 } = options;
@@ -6116,9 +6335,15 @@ async function runStart(options) {
6116
6335
  print(
6117
6336
  `Stopped the old daemon: removed ${retired.join(" and ")}. Its checkout is left as it was.`
6118
6337
  );
6119
- await ensureInstalled(options, print);
6338
+ const manager = options.install?.manager ?? await findPackageManager({ run: run4 });
6339
+ if (!manager)
6340
+ throw new StartError(
6341
+ "Friday installs and updates itself with npm, pnpm or Yarn 1, and herd found none of them that runs, beside Node, on your PATH or in their usual folders. Install one, or set HERD_PACKAGE_MANAGER to the path of one, then run this again."
6342
+ );
6343
+ verbose2(`installing with ${manager.name} ${manager.version}, at ${manager.command}`);
6344
+ await ensureInstalled({ ...options, install: { ...options.install, manager } }, print);
6120
6345
  const command = [process.execPath, binOf(paths.current, "friday"), "supervise"];
6121
- const env = { HERD_HOME: paths.herdHome, ...options.env };
6346
+ const env = { HERD_HOME: paths.herdHome, [MANAGER_ENV]: manager.command, ...options.env };
6122
6347
  const service = (options.service ?? defaultService(options))(command, env);
6123
6348
  if (moving && running) {
6124
6349
  print(`Stopping Friday at ${existing} to start it again from ${root}.`);
@@ -6224,7 +6449,7 @@ init_src();
6224
6449
  import { spawn as spawn6 } from "node:child_process";
6225
6450
  import {
6226
6451
  closeSync as closeSync3,
6227
- existsSync as existsSync10,
6452
+ existsSync as existsSync11,
6228
6453
  mkdirSync as mkdirSync11,
6229
6454
  openSync as openSync4,
6230
6455
  readFileSync as readFileSync9,
@@ -6309,7 +6534,7 @@ var Supervisor = class {
6309
6534
  const { daemonLog, logs } = this.options.paths;
6310
6535
  mkdirSync11(logs, { recursive: true });
6311
6536
  try {
6312
- if (existsSync10(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
6537
+ if (existsSync11(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
6313
6538
  renameSync4(daemonLog, `${daemonLog}.1`);
6314
6539
  } catch {
6315
6540
  }
@@ -6462,9 +6687,9 @@ Usage: ${FRIDAY_COMMAND} <command>
6462
6687
 
6463
6688
  Agents use the herd command, which comes with this one. herd setup shows what's left to set up.`;
6464
6689
  function defaults() {
6465
- const home = homedir4();
6690
+ const home = homedir5();
6466
6691
  return {
6467
- paths: fridayPaths(process.env.HERD_HOME ?? join14(home, ".herd")),
6692
+ paths: fridayPaths(process.env.HERD_HOME ?? join15(home, ".herd")),
6468
6693
  home,
6469
6694
  cwd: process.cwd(),
6470
6695
  run: exec,
@@ -6536,7 +6761,7 @@ async function friday(args, deps = defaults()) {
6536
6761
  agents: {
6537
6762
  // The choice lives in herd.config.ts, so it's asked for only before there is one, or
6538
6763
  // with --config.
6539
- ask: rest.includes("--config") || !existsSync11(herdConfigIn(deps.paths.herdHome)),
6764
+ ask: rest.includes("--config") || !existsSync12(herdConfigIn(deps.paths.herdHome)),
6540
6765
  installed: async () => {
6541
6766
  verbose2("looking for installed agents");
6542
6767
  const found = installedAgents();
@@ -6595,7 +6820,7 @@ function agentsFlag(args) {
6595
6820
  return parsed.shares;
6596
6821
  }
6597
6822
  function attachTo(paths, terminal) {
6598
- const herd = existsSync11(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join14(dirname10(process.argv[1] ?? ""), "herd.mjs");
6823
+ const herd = existsSync12(binOf(paths.current, "herd")) ? binOf(paths.current, "herd") : join15(dirname10(process.argv[1] ?? ""), "herd.mjs");
6599
6824
  const child = spawn7(process.execPath, [herd, "attach", terminal], {
6600
6825
  stdio: "inherit",
6601
6826
  env: { ...process.env, HERD_HOME: paths.herdHome }
@@ -6659,14 +6884,14 @@ function runStatus(deps) {
6659
6884
  function readMissingFolder(herdHome2) {
6660
6885
  try {
6661
6886
  return folderUnavailableSchema.parse(
6662
- JSON.parse(readFileSync10(join14(herdHome2, "folder-unavailable.json"), "utf8"))
6887
+ JSON.parse(readFileSync10(join15(herdHome2, "folder-unavailable.json"), "utf8"))
6663
6888
  );
6664
6889
  } catch {
6665
6890
  return null;
6666
6891
  }
6667
6892
  }
6668
6893
  function tail2(path, count) {
6669
- if (!existsSync11(path)) return [];
6894
+ if (!existsSync12(path)) return [];
6670
6895
  const lines = readFileSync10(path, "utf8").split("\n");
6671
6896
  if (lines.at(-1) === "") lines.pop();
6672
6897
  return lines.slice(-count);
@@ -6674,7 +6899,7 @@ function tail2(path, count) {
6674
6899
  async function runLogs(args, deps) {
6675
6900
  const { paths, print } = deps;
6676
6901
  const follow = args.includes("-f") || args.includes("--follow");
6677
- const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync11(file));
6902
+ const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync12(file));
6678
6903
  if (files.length === 0) {
6679
6904
  print(`There are no logs in ${paths.logs} yet.`);
6680
6905
  return 0;
@@ -6724,7 +6949,7 @@ init_define_HERD_PACKAGE();
6724
6949
  init_define_HERD_PACKAGE();
6725
6950
  import { spawn as spawn8 } from "node:child_process";
6726
6951
  import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
6727
- import { dirname as dirname11, join as join15 } from "node:path";
6952
+ import { dirname as dirname11, join as join16 } from "node:path";
6728
6953
  var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
6729
6954
  function buildPolicy(input) {
6730
6955
  const platform2 = input.platform ?? process.platform;
@@ -6771,7 +6996,7 @@ async function runSandboxed(args) {
6771
6996
  return 3;
6772
6997
  }
6773
6998
  await SandboxManager.initialize(policy, void 0, true);
6774
- const blocked = join15(process.env.HOME ?? ".", BLOCKED_LOG);
6999
+ const blocked = join16(process.env.HOME ?? ".", BLOCKED_LOG);
6775
7000
  let written = 0;
6776
7001
  const unsubscribe = SandboxManager.getSandboxViolationStore().subscribe((violations) => {
6777
7002
  const fresh = violations.slice(written).map((violation) => describeRefusal(violation.line)).filter((line2) => line2 !== null);
@@ -7272,8 +7497,8 @@ init_define_HERD_PACKAGE();
7272
7497
  // src/build-runner-key.ts
7273
7498
  init_define_HERD_PACKAGE();
7274
7499
  init_src();
7275
- import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
7276
- import { join as join16 } from "node:path";
7500
+ import { chmodSync as chmodSync3, existsSync as existsSync13, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
7501
+ import { join as join17 } from "node:path";
7277
7502
  import { z as z24 } from "zod";
7278
7503
  var BUILDS_URL = "https://builds.herdingchats.com";
7279
7504
  var runnerKeySchema = z24.object({
@@ -7281,10 +7506,10 @@ var runnerKeySchema = z24.object({
7281
7506
  key: z24.string().min(20),
7282
7507
  url: z24.string().url()
7283
7508
  });
7284
- var runnerKeyPath = () => join16(herdHome(), "build-runner.json");
7509
+ var runnerKeyPath = () => join17(herdHome(), "build-runner.json");
7285
7510
  function readRunnerKey() {
7286
7511
  const path = runnerKeyPath();
7287
- if (!existsSync12(path)) return null;
7512
+ if (!existsSync13(path)) return null;
7288
7513
  try {
7289
7514
  return runnerKeySchema.parse(JSON.parse(readFileSync13(path, "utf8")));
7290
7515
  } catch {
@@ -7463,7 +7688,7 @@ function formatSpeed(report) {
7463
7688
  // src/builds-admin.ts
7464
7689
  init_define_HERD_PACKAGE();
7465
7690
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
7466
- import { join as join17 } from "node:path";
7691
+ import { join as join18 } from "node:path";
7467
7692
  var NOT_ADMIN = "builds are only for the admin's machines";
7468
7693
  async function isAdmin(fetch2) {
7469
7694
  const response = await fetch2("/api/people/me").catch(() => null);
@@ -7471,13 +7696,13 @@ async function isAdmin(fetch2) {
7471
7696
  const body = await response.json().catch(() => ({}));
7472
7697
  return body.admin === true;
7473
7698
  }
7474
- var refusalPath = () => join17(herdHome(), "builds", "refused.json");
7699
+ var refusalPath = () => join18(herdHome(), "builds", "refused.json");
7475
7700
  function writeBuildsRefusal(reason) {
7476
7701
  if (!reason) {
7477
7702
  rmSync10(refusalPath(), { force: true });
7478
7703
  return;
7479
7704
  }
7480
- mkdirSync13(join17(herdHome(), "builds"), { recursive: true });
7705
+ mkdirSync13(join18(herdHome(), "builds"), { recursive: true });
7481
7706
  writeFileSync10(refusalPath(), JSON.stringify({ reason }));
7482
7707
  }
7483
7708
  function readBuildsRefusal() {
@@ -7641,7 +7866,7 @@ async function runConversations(rest) {
7641
7866
  init_define_HERD_PACKAGE();
7642
7867
  init_src();
7643
7868
  import { availableParallelism, totalmem } from "node:os";
7644
- import { join as join33 } from "node:path";
7869
+ import { join as join34 } from "node:path";
7645
7870
 
7646
7871
  // src/agent.ts
7647
7872
  init_define_HERD_PACKAGE();
@@ -7750,20 +7975,20 @@ function readClaudeScreen(raw) {
7750
7975
 
7751
7976
  // src/worktrees.ts
7752
7977
  init_define_HERD_PACKAGE();
7753
- import { existsSync as existsSync13, realpathSync as realpathSync6 } from "node:fs";
7754
- import { isAbsolute as isAbsolute2, join as join18, relative } from "node:path";
7755
- var worktreesRoot = () => join18(herdHome(), "worktrees");
7978
+ import { existsSync as existsSync14, realpathSync as realpathSync6 } from "node:fs";
7979
+ import { isAbsolute as isAbsolute3, join as join19, relative } from "node:path";
7980
+ var worktreesRoot = () => join19(herdHome(), "worktrees");
7756
7981
  var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
7757
7982
  var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
7758
7983
  var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
7759
- var conversationFolder = (convos, conversationId) => join18(convos, conversationPrefix(conversationId));
7984
+ var conversationFolder = (convos, conversationId) => join19(convos, conversationPrefix(conversationId));
7760
7985
  function folderForConversation(root, conversationId) {
7761
7986
  const earlier = conversationFolder(legacyConvosDir(root), conversationId);
7762
- return existsSync13(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
7987
+ return existsSync14(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
7763
7988
  }
7764
7989
  var within = (dir, path) => {
7765
7990
  const between = relative(dir, path);
7766
- return between !== "" && !between.startsWith("..") && !isAbsolute2(between);
7991
+ return between !== "" && !between.startsWith("..") && !isAbsolute3(between);
7767
7992
  };
7768
7993
  var real2 = (path) => {
7769
7994
  try {
@@ -8062,8 +8287,8 @@ function readAgyScreen(raw) {
8062
8287
  // src/agy-settings.ts
8063
8288
  init_define_HERD_PACKAGE();
8064
8289
  import { lstatSync as lstatSync2, mkdirSync as mkdirSync14, realpathSync as realpathSync7, writeFileSync as writeFileSync11 } from "node:fs";
8065
- import { join as join19, sep } from "node:path";
8066
- var AGY_SETTINGS_FILE = join19(".herd", "agy-settings.json");
8290
+ import { join as join20, sep } from "node:path";
8291
+ var AGY_SETTINGS_FILE = join20(".herd", "agy-settings.json");
8067
8292
  var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
8068
8293
  var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
8069
8294
  function agyHooks(herd) {
@@ -8080,7 +8305,7 @@ function agySettings(herd) {
8080
8305
  return { statusLine: { type: "command", command: `${herd} statusline agy` } };
8081
8306
  }
8082
8307
  function ownFolder(worktree, name) {
8083
- const folder = join19(worktree, name);
8308
+ const folder = join20(worktree, name);
8084
8309
  if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
8085
8310
  throw new Error(`${name} in the worktree is a link`);
8086
8311
  }
@@ -8092,13 +8317,13 @@ function ownFolder(worktree, name) {
8092
8317
  }
8093
8318
  function writeAgyFiles(worktree, herd) {
8094
8319
  const agents = ownFolder(worktree, ".agents");
8095
- writeFileSync11(join19(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8320
+ writeFileSync11(join20(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8096
8321
  `);
8097
- writeFileSync11(join19(agents, ".gitignore"), "*\n");
8322
+ writeFileSync11(join20(agents, ".gitignore"), "*\n");
8098
8323
  const herdFolder = ownFolder(worktree, ".herd");
8099
- writeFileSync11(join19(herdFolder, ".gitignore"), "*\n");
8324
+ writeFileSync11(join20(herdFolder, ".gitignore"), "*\n");
8100
8325
  writeFileSync11(
8101
- join19(worktree, AGY_SETTINGS_FILE),
8326
+ join20(worktree, AGY_SETTINGS_FILE),
8102
8327
  `${JSON.stringify(agySettings(herd), null, 2)}
8103
8328
  `
8104
8329
  );
@@ -8253,15 +8478,15 @@ var AgyDriver = class {
8253
8478
  // src/bare-clones.ts
8254
8479
  init_define_HERD_PACKAGE();
8255
8480
  import { randomBytes as randomBytes5 } from "node:crypto";
8256
- import { existsSync as existsSync14, mkdirSync as mkdirSync15, readdirSync as readdirSync5, renameSync as renameSync5, rmSync as rmSync11, statSync as statSync7 } from "node:fs";
8257
- import { dirname as dirname12, join as join20 } from "node:path";
8481
+ import { existsSync as existsSync15, mkdirSync as mkdirSync15, readdirSync as readdirSync5, renameSync as renameSync5, rmSync as rmSync11, statSync as statSync7 } from "node:fs";
8482
+ import { dirname as dirname12, join as join21 } from "node:path";
8258
8483
  function bareClonePath(dir, remote) {
8259
8484
  const key = repoKey(remote);
8260
8485
  if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
8261
- return join20(dir, `${key}.git`);
8486
+ return join21(dir, `${key}.git`);
8262
8487
  }
8263
8488
  async function ensureBareClone(store, remote, env) {
8264
- if (existsSync14(store)) {
8489
+ if (existsSync15(store)) {
8265
8490
  let fetchFailed = null;
8266
8491
  try {
8267
8492
  await git(env, store, "fetch", "--quiet", "--prune", "origin");
@@ -8302,7 +8527,7 @@ function bareClones(dir) {
8302
8527
  return;
8303
8528
  }
8304
8529
  for (const name of entries.sort()) {
8305
- const path = join20(at, name);
8530
+ const path = join21(at, name);
8306
8531
  if (name.endsWith(".git")) {
8307
8532
  try {
8308
8533
  const remote = gitSync(path, "config", "--get", "remote.origin.url");
@@ -8483,7 +8708,7 @@ init_define_HERD_PACKAGE();
8483
8708
  import { execFile as execFile2, execFileSync as execFileSync5, spawn as spawn9 } from "node:child_process";
8484
8709
  import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
8485
8710
  import {
8486
- existsSync as existsSync15,
8711
+ existsSync as existsSync16,
8487
8712
  mkdirSync as mkdirSync17,
8488
8713
  mkdtempSync,
8489
8714
  readdirSync as readdirSync7,
@@ -8492,16 +8717,16 @@ import {
8492
8717
  rmSync as rmSync13,
8493
8718
  writeFileSync as writeFileSync13
8494
8719
  } from "node:fs";
8495
- import { homedir as homedir6, tmpdir } from "node:os";
8496
- import { join as join22 } from "node:path";
8720
+ import { homedir as homedir7, tmpdir } from "node:os";
8721
+ import { join as join23 } from "node:path";
8497
8722
  import { promisify as promisify2 } from "node:util";
8498
8723
 
8499
8724
  // src/check-registry.ts
8500
8725
  init_define_HERD_PACKAGE();
8501
8726
  import { mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync12, writeFileSync as writeFileSync12 } from "node:fs";
8502
- import { homedir as homedir5 } from "node:os";
8503
- import { join as join21 } from "node:path";
8504
- var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join21(homedir5(), ".cache", "herdingchats", "check", "running");
8727
+ import { homedir as homedir6 } from "node:os";
8728
+ import { join as join22 } from "node:path";
8729
+ var checkRegistryDir = () => process.env.HERDINGCHATS_CHECK_RUNNING ?? join22(homedir6(), ".cache", "herdingchats", "check", "running");
8505
8730
  var RESERVED_MB = 6 * 1024;
8506
8731
  var MIN_MEMORY_MB = 1024;
8507
8732
  var alive3 = (pid) => {
@@ -8514,7 +8739,7 @@ var alive3 = (pid) => {
8514
8739
  };
8515
8740
  function registerCheck(dir, pid, startedAt = Date.now()) {
8516
8741
  mkdirSync16(dir, { recursive: true });
8517
- const file = join21(dir, `${pid}.json`);
8742
+ const file = join22(dir, `${pid}.json`);
8518
8743
  writeFileSync12(file, JSON.stringify({ pid, startedAt }));
8519
8744
  return () => rmSync12(file, { force: true });
8520
8745
  }
@@ -8530,12 +8755,12 @@ function runningChecks(dir, isAlive = alive3) {
8530
8755
  const pid = Number(name.slice(0, -".json".length));
8531
8756
  let entry = null;
8532
8757
  try {
8533
- entry = JSON.parse(readFileSync15(join21(dir, name), "utf8"));
8758
+ entry = JSON.parse(readFileSync15(join22(dir, name), "utf8"));
8534
8759
  } catch {
8535
8760
  continue;
8536
8761
  }
8537
8762
  if (entry?.pid === pid && isAlive(pid)) count += 1;
8538
- else rmSync12(join21(dir, name), { force: true });
8763
+ else rmSync12(join22(dir, name), { force: true });
8539
8764
  }
8540
8765
  return count;
8541
8766
  }
@@ -8543,9 +8768,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
8543
8768
 
8544
8769
  // src/builds.ts
8545
8770
  init_self();
8546
- var sandboxStatusPath = () => join22(herdHome(), "builds", "sandbox.json");
8771
+ var sandboxStatusPath = () => join23(herdHome(), "builds", "sandbox.json");
8547
8772
  function writeSandboxStatus(status2, now = Date.now()) {
8548
- mkdirSync17(join22(herdHome(), "builds"), { recursive: true });
8773
+ mkdirSync17(join23(herdHome(), "builds"), { recursive: true });
8549
8774
  writeFileSync13(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
8550
8775
  }
8551
8776
  function readSandboxStatus() {
@@ -8577,7 +8802,7 @@ var real3 = (path) => {
8577
8802
  }
8578
8803
  };
8579
8804
  function worktreeOf(dir, store) {
8580
- if (!existsSync15(join22(dir, ".git"))) return false;
8805
+ if (!existsSync16(join23(dir, ".git"))) return false;
8581
8806
  try {
8582
8807
  return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
8583
8808
  } catch {
@@ -8589,36 +8814,54 @@ function cloneFolder(from, to) {
8589
8814
  execFileSync5("cp", [...args, from, to]);
8590
8815
  }
8591
8816
  function moduleFolders(dir) {
8592
- const found = existsSync15(join22(dir, "node_modules")) ? ["node_modules"] : [];
8817
+ const found = existsSync16(join23(dir, "node_modules")) ? ["node_modules"] : [];
8593
8818
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
8594
8819
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
8595
- if (existsSync15(join22(dir, entry.name, "node_modules")))
8596
- found.push(join22(entry.name, "node_modules"));
8820
+ if (existsSync16(join23(dir, entry.name, "node_modules")))
8821
+ found.push(join23(entry.name, "node_modules"));
8597
8822
  }
8598
8823
  }
8599
8824
  return found;
8600
8825
  }
8826
+ function checkoutManager(dir) {
8827
+ let field;
8828
+ try {
8829
+ const pkg = JSON.parse(readFileSync16(join23(dir, "package.json"), "utf8"));
8830
+ if (typeof pkg.packageManager === "string") field = pkg.packageManager;
8831
+ } catch {
8832
+ }
8833
+ const found = managerOfCheckout(field, (name) => existsSync16(join23(dir, name)));
8834
+ if (!found?.lockfile)
8835
+ throw new Error(
8836
+ `${dir} has no lockfile to install node_modules from: herd looked for ${Object.values(LOCKFILES).flat().join(", ")}.`
8837
+ );
8838
+ return { ...found, lockfile: found.lockfile };
8839
+ }
8601
8840
  function lockHash(dir) {
8602
- const lock = readFileSync16(join22(dir, "package-lock.json"));
8841
+ const lock = readFileSync16(join23(dir, checkoutManager(dir).lockfile));
8603
8842
  const major = process.versions.node.split(".")[0];
8604
8843
  return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
8605
8844
  }
8606
- var npmCi = (dir) => new Promise((resolve7, reject) => {
8607
- const network = insideBuildSandbox() ? ["--offline"] : ["--prefer-offline"];
8608
- const child = spawn9(
8609
- "npm",
8610
- ["ci", ...network, "--no-audit", "--no-fund", "--no-update-notifier"],
8611
- {
8845
+ var frozenInstall = (gitEnv) => async (dir) => {
8846
+ const { name, major } = checkoutManager(dir);
8847
+ const env = gitEnv?.() ?? process.env;
8848
+ const manager = await findPackageManager({ env, only: [name], accept: () => true });
8849
+ if (!manager)
8850
+ throw new Error(`${dir} installs with ${name}, which isn't on this machine, or doesn't run.`);
8851
+ const args = frozenInstallArgs(name, { offline: insideBuildSandbox(), yarnMajor: major });
8852
+ await new Promise((resolve7, reject) => {
8853
+ const child = spawn9(manager.command, args, {
8612
8854
  cwd: dir,
8855
+ env: { ...env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
8613
8856
  stdio: "ignore"
8614
- }
8615
- );
8616
- child.on("error", reject);
8617
- child.on(
8618
- "close",
8619
- (code) => code === 0 ? resolve7() : reject(new Error(`npm ci exited with ${code}`))
8620
- );
8621
- });
8857
+ });
8858
+ child.on("error", reject);
8859
+ child.on(
8860
+ "close",
8861
+ (code) => code === 0 ? resolve7() : reject(new Error(`${name} ${args[0]} exited with ${code}`))
8862
+ );
8863
+ });
8864
+ };
8622
8865
  var BuildRunner = class _BuildRunner {
8623
8866
  constructor(options) {
8624
8867
  this.options = options;
@@ -8626,11 +8869,11 @@ var BuildRunner = class _BuildRunner {
8626
8869
  options;
8627
8870
  running = /* @__PURE__ */ new Map();
8628
8871
  home() {
8629
- return this.options.home ?? join22(herdHome(), "builds");
8872
+ return this.options.home ?? join23(herdHome(), "builds");
8630
8873
  }
8631
8874
  /** Where builds' own bare clones are, apart from the root's checkouts. */
8632
8875
  clonesDir() {
8633
- return join22(this.home(), "clones");
8876
+ return join23(this.home(), "clones");
8634
8877
  }
8635
8878
  /** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
8636
8879
  storeFor(build) {
@@ -8651,8 +8894,8 @@ var BuildRunner = class _BuildRunner {
8651
8894
  const store = bareClonePath(this.clonesDir(), remote);
8652
8895
  const env = this.options.gitEnv?.();
8653
8896
  const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
8654
- const dir = join22(this.home(), name, String(slot));
8655
- mkdirSync17(join22(this.home(), name), { recursive: true });
8897
+ const dir = join23(this.home(), name, String(slot));
8898
+ mkdirSync17(join23(this.home(), name), { recursive: true });
8656
8899
  await withStoreLock(
8657
8900
  store,
8658
8901
  async () => {
@@ -8691,30 +8934,30 @@ var BuildRunner = class _BuildRunner {
8691
8934
  /** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
8692
8935
  async ensureModules(dir) {
8693
8936
  const wanted = lockHash(dir);
8694
- const marker = join22(dir, "node_modules", ".herd-lock");
8695
- if (existsSync15(marker) && readFileSync16(marker, "utf8") === wanted) return;
8696
- const cache = join22(this.home(), "node_modules", wanted);
8937
+ const marker = join23(dir, "node_modules", ".herd-lock");
8938
+ if (existsSync16(marker) && readFileSync16(marker, "utf8") === wanted) return;
8939
+ const cache = join23(this.home(), "node_modules", wanted);
8697
8940
  for (const folder of moduleFolders(dir))
8698
- rmSync13(join22(dir, folder), { recursive: true, force: true });
8699
- if (existsSync15(join22(cache, ".complete"))) {
8700
- for (const folder of readFileSync16(join22(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
8701
- cloneFolder(join22(cache, folder), join22(dir, folder));
8941
+ rmSync13(join23(dir, folder), { recursive: true, force: true });
8942
+ if (existsSync16(join23(cache, ".complete"))) {
8943
+ for (const folder of readFileSync16(join23(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
8944
+ cloneFolder(join23(cache, folder), join23(dir, folder));
8702
8945
  }
8703
8946
  } else {
8704
8947
  this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
8705
- await (this.options.install ?? npmCi)(dir);
8706
- const staging = mkdtempSync(join22(this.home(), "node_modules-"));
8948
+ await (this.options.install ?? frozenInstall(this.options.gitEnv))(dir);
8949
+ const staging = mkdtempSync(join23(this.home(), "node_modules-"));
8707
8950
  const folders = moduleFolders(dir);
8708
8951
  for (const folder of folders) {
8709
- mkdirSync17(join22(staging, folder, ".."), { recursive: true });
8710
- cloneFolder(join22(dir, folder), join22(staging, folder));
8952
+ mkdirSync17(join23(staging, folder, ".."), { recursive: true });
8953
+ cloneFolder(join23(dir, folder), join23(staging, folder));
8711
8954
  }
8712
- writeFileSync13(join22(staging, ".complete"), folders.join("\n"));
8713
- mkdirSync17(join22(this.home(), "node_modules"), { recursive: true });
8955
+ writeFileSync13(join23(staging, ".complete"), folders.join("\n"));
8956
+ mkdirSync17(join23(this.home(), "node_modules"), { recursive: true });
8714
8957
  rmSync13(cache, { recursive: true, force: true });
8715
8958
  execFileSync5("mv", [staging, cache]);
8716
8959
  }
8717
- mkdirSync17(join22(dir, "node_modules"), { recursive: true });
8960
+ mkdirSync17(join23(dir, "node_modules"), { recursive: true });
8718
8961
  writeFileSync13(marker, wanted);
8719
8962
  }
8720
8963
  /**
@@ -8725,7 +8968,7 @@ var BuildRunner = class _BuildRunner {
8725
8968
  const env = {
8726
8969
  PATH: process.env.PATH ?? "/usr/bin:/bin",
8727
8970
  HOME: home,
8728
- TMPDIR: join22(home, "tmp"),
8971
+ TMPDIR: join23(home, "tmp"),
8729
8972
  LANG: process.env.LANG ?? "en_US.UTF-8",
8730
8973
  HERD_BUILD_ID: build.id,
8731
8974
  HERD_BUILD_SECRET: build.secret,
@@ -8737,7 +8980,7 @@ var BuildRunner = class _BuildRunner {
8737
8980
  // The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
8738
8981
  NODE_USE_ENV_PROXY: "1",
8739
8982
  // Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
8740
- CLAUDE_CODE_TMPDIR: join22(home, "tmp")
8983
+ CLAUDE_CODE_TMPDIR: join23(home, "tmp")
8741
8984
  };
8742
8985
  return env;
8743
8986
  }
@@ -8748,8 +8991,8 @@ var BuildRunner = class _BuildRunner {
8748
8991
  /** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
8749
8992
  async run(build, slot) {
8750
8993
  const dir = await this.prepare(build, slot);
8751
- const home = mkdtempSync(join22(this.options.scratch ?? scratchRoot(), "hb-"));
8752
- mkdirSync17(join22(home, "tmp"), { recursive: true });
8994
+ const home = mkdtempSync(join23(this.options.scratch ?? scratchRoot(), "hb-"));
8995
+ mkdirSync17(join23(home, "tmp"), { recursive: true });
8753
8996
  const run4 = randomUUID4();
8754
8997
  const env = {
8755
8998
  ..._BuildRunner.environment(build, home),
@@ -8759,11 +9002,11 @@ var BuildRunner = class _BuildRunner {
8759
9002
  env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
8760
9003
  if (this.options.memoryMb)
8761
9004
  env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
8762
- let command = [process.execPath, join22(dir, "scripts", "build.mjs")];
9005
+ let command = [process.execPath, join23(dir, "scripts", "build.mjs")];
8763
9006
  let policyPath = null;
8764
9007
  if (this.sandboxed()) {
8765
9008
  const policy = buildPolicy({
8766
- ownerHome: this.options.ownerHome ?? homedir6(),
9009
+ ownerHome: this.options.ownerHome ?? homedir7(),
8767
9010
  root: this.options.root(),
8768
9011
  convos: convosHome(this.options.root()),
8769
9012
  worktree: dir,
@@ -8774,8 +9017,8 @@ var BuildRunner = class _BuildRunner {
8774
9017
  main: build.main,
8775
9018
  buildsUrl: build.url
8776
9019
  });
8777
- mkdirSync17(join22(this.home(), "policies"), { recursive: true });
8778
- policyPath = join22(this.home(), "policies", `${build.id}.json`);
9020
+ mkdirSync17(join23(this.home(), "policies"), { recursive: true });
9021
+ policyPath = join23(this.home(), "policies", `${build.id}.json`);
8779
9022
  writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
8780
9023
  env.HERD_BUILD_SANDBOX = describePolicy(policy);
8781
9024
  command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
@@ -8824,7 +9067,7 @@ var BuildRunner = class _BuildRunner {
8824
9067
  * It goes only by where they run from, never by a build ID, since the daemon a build's own tests
8825
9068
  * start must never stop the build it runs in.
8826
9069
  */
8827
- static async sweep(home = join22(herdHome(), "builds"), list = listProcesses) {
9070
+ static async sweep(home = join23(herdHome(), "builds"), list = listProcesses) {
8828
9071
  const left = buildProcesses(list(), null, home);
8829
9072
  await stopPids(left);
8830
9073
  return left.length;
@@ -8836,37 +9079,37 @@ var BuildRunner = class _BuildRunner {
8836
9079
  */
8837
9080
  static async probe(options = { buildsUrl: "" }) {
8838
9081
  if (insideBuildSandbox()) return { ok: true };
8839
- const herd = options.home ?? join22(herdHome(), "builds");
8840
- const dir = mkdtempSync(join22(scratchRoot(), "hp-"));
8841
- const secret = join22(dir, "owner", "secret");
9082
+ const herd = options.home ?? join23(herdHome(), "builds");
9083
+ const dir = mkdtempSync(join23(scratchRoot(), "hp-"));
9084
+ const secret = join23(dir, "owner", "secret");
8842
9085
  try {
8843
9086
  mkdirSync17(herd, { recursive: true });
8844
- for (const sub of ["work", "git", "home", "owner"]) mkdirSync17(join22(dir, sub));
9087
+ for (const sub of ["work", "git", "home", "owner"]) mkdirSync17(join23(dir, sub));
8845
9088
  writeFileSync13(secret, "probe");
8846
9089
  const policy = buildPolicy({
8847
- ownerHome: join22(dir, "owner"),
8848
- root: join22(dir, "owner"),
8849
- convos: join22(dir, "owner"),
8850
- worktree: join22(dir, "work"),
8851
- store: join22(dir, "git"),
8852
- worktreeGitDir: join22(dir, "git"),
8853
- home: join22(dir, "home"),
9090
+ ownerHome: join23(dir, "owner"),
9091
+ root: join23(dir, "owner"),
9092
+ convos: join23(dir, "owner"),
9093
+ worktree: join23(dir, "work"),
9094
+ store: join23(dir, "git"),
9095
+ worktreeGitDir: join23(dir, "git"),
9096
+ home: join23(dir, "home"),
8854
9097
  nodePrefix: nodePrefix(),
8855
9098
  main: false,
8856
9099
  buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
8857
9100
  });
8858
- const policyPath = join22(herd, "probe-policy.json");
9101
+ const policyPath = join23(herd, "probe-policy.json");
8859
9102
  writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
8860
9103
  const script = [
8861
9104
  'const fs = require("node:fs");',
8862
9105
  `try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
8863
- `fs.writeFileSync(${JSON.stringify(join22(dir, "home", "ok"))}, "ok");`
9106
+ `fs.writeFileSync(${JSON.stringify(join23(dir, "home", "ok"))}, "ok");`
8864
9107
  ].join(" ");
8865
9108
  const child = spawn9(
8866
9109
  process.execPath,
8867
9110
  [HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
8868
9111
  {
8869
- env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join22(dir, "home") },
9112
+ env: { PATH: process.env.PATH ?? "/usr/bin:/bin", HOME: join23(dir, "home") },
8870
9113
  stdio: ["ignore", "ignore", "pipe"]
8871
9114
  }
8872
9115
  );
@@ -8880,7 +9123,7 @@ var BuildRunner = class _BuildRunner {
8880
9123
  clearTimeout(timer);
8881
9124
  if (status2 === 10)
8882
9125
  return { ok: false, reason: "the sandbox let a build read the owner's files" };
8883
- if (status2 !== 0 || !existsSync15(join22(dir, "home", "ok"))) {
9126
+ if (status2 !== 0 || !existsSync16(join23(dir, "home", "ok"))) {
8884
9127
  const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
8885
9128
  return { ok: false, reason: why.slice(0, 300) };
8886
9129
  }
@@ -9235,7 +9478,7 @@ var CodexDriver = class {
9235
9478
 
9236
9479
  // src/daemon.ts
9237
9480
  init_define_HERD_PACKAGE();
9238
- import { join as join23 } from "node:path";
9481
+ import { join as join24 } from "node:path";
9239
9482
 
9240
9483
  // src/dispatch-connection.ts
9241
9484
  init_define_HERD_PACKAGE();
@@ -9339,7 +9582,7 @@ var DispatchConnection = class {
9339
9582
 
9340
9583
  // src/daemon.ts
9341
9584
  var CONFIG_POLL_MS = 2e3;
9342
- var daemonSocketPath = () => join23(herdHome(), "daemon.sock");
9585
+ var daemonSocketPath = () => join24(herdHome(), "daemon.sock");
9343
9586
  var Daemon = class {
9344
9587
  constructor(options) {
9345
9588
  this.options = options;
@@ -9551,7 +9794,7 @@ var Daemon = class {
9551
9794
 
9552
9795
  // src/folders.ts
9553
9796
  init_define_HERD_PACKAGE();
9554
- import { existsSync as existsSync16, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync14, writeFileSync as writeFileSync14 } from "node:fs";
9797
+ import { existsSync as existsSync17, lstatSync as lstatSync3, readlinkSync as readlinkSync2, rmSync as rmSync14, writeFileSync as writeFileSync14 } from "node:fs";
9555
9798
  import { dirname as dirname13, resolve as resolve4 } from "node:path";
9556
9799
  var FOLDER_WAIT_MS = 2 * 60 * 1e3;
9557
9800
  var FOLDER_CHECK_MS = 30 * 1e3;
@@ -9563,7 +9806,7 @@ function linkOf(path) {
9563
9806
  return null;
9564
9807
  }
9565
9808
  }
9566
- function missingFolder(path, required, { exists = existsSync16, linkTarget = linkOf } = {}) {
9809
+ function missingFolder(path, required, { exists = existsSync17, linkTarget = linkOf } = {}) {
9567
9810
  if (exists(path)) return null;
9568
9811
  const target = linkTarget(path);
9569
9812
  if (target) return { path, target };
@@ -9766,23 +10009,23 @@ init_define_HERD_PACKAGE();
9766
10009
  import { execFileSync as execFileSync6 } from "node:child_process";
9767
10010
  import {
9768
10011
  appendFileSync as appendFileSync2,
9769
- existsSync as existsSync17,
10012
+ existsSync as existsSync18,
9770
10013
  mkdirSync as mkdirSync18,
9771
10014
  readFileSync as readFileSync17,
9772
10015
  renameSync as renameSync6,
9773
10016
  writeFileSync as writeFileSync15
9774
10017
  } from "node:fs";
9775
- import { isAbsolute as isAbsolute3, join as join24 } from "node:path";
9776
- var LOCAL_SETTINGS = join24(".claude", "settings.local.json");
10018
+ import { isAbsolute as isAbsolute4, join as join25 } from "node:path";
10019
+ var LOCAL_SETTINGS = join25(".claude", "settings.local.json");
9777
10020
  var git3 = (cwd, ...args) => execFileSync6("git", ["-C", cwd, ...args], {
9778
10021
  encoding: "utf8",
9779
10022
  stdio: ["ignore", "pipe", "pipe"]
9780
10023
  }).trim();
9781
10024
  var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
9782
10025
  function writeWorktreeSettings(worktree, settings, ownCommand) {
9783
- const path = join24(worktree, LOCAL_SETTINGS);
10026
+ const path = join25(worktree, LOCAL_SETTINGS);
9784
10027
  let current = {};
9785
- if (existsSync17(path)) {
10028
+ if (existsSync18(path)) {
9786
10029
  try {
9787
10030
  const parsed = JSON.parse(readFileSync17(path, "utf8"));
9788
10031
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
@@ -9804,7 +10047,7 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
9804
10047
  }
9805
10048
  next.hooks = hooks;
9806
10049
  }
9807
- mkdirSync18(join24(worktree, ".claude"), { recursive: true });
10050
+ mkdirSync18(join25(worktree, ".claude"), { recursive: true });
9808
10051
  const temporary = `${path}.herd-${process.pid}`;
9809
10052
  writeFileSync15(temporary, `${JSON.stringify(next, null, 2)}
9810
10053
  `);
@@ -9824,10 +10067,10 @@ function ignoreLocalSettings(worktree) {
9824
10067
  } catch {
9825
10068
  return;
9826
10069
  }
9827
- const info = join24(isAbsolute3(common) ? common : join24(worktree, common), "info");
9828
- const exclude = join24(info, "exclude");
10070
+ const info = join25(isAbsolute4(common) ? common : join25(worktree, common), "info");
10071
+ const exclude = join25(info, "exclude");
9829
10072
  const line2 = "/.claude/settings.local.json";
9830
- const existing = existsSync17(exclude) ? readFileSync17(exclude, "utf8") : "";
10073
+ const existing = existsSync18(exclude) ? readFileSync17(exclude, "utf8") : "";
9831
10074
  if (existing.split("\n").includes(line2)) return;
9832
10075
  mkdirSync18(info, { recursive: true });
9833
10076
  appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
@@ -9872,7 +10115,7 @@ function loginShellEnv() {
9872
10115
 
9873
10116
  // src/machine-checks.ts
9874
10117
  init_define_HERD_PACKAGE();
9875
- import { existsSync as existsSync18, rmSync as rmSync15 } from "node:fs";
10118
+ import { existsSync as existsSync19, rmSync as rmSync15 } from "node:fs";
9876
10119
  var REQUEST_POLL_MS = 2e3;
9877
10120
  var MachineChecks = class {
9878
10121
  constructor(options) {
@@ -9918,7 +10161,7 @@ var MachineChecks = class {
9918
10161
  /** `friday start` asked for every agent to be activated again. */
9919
10162
  takeRequest() {
9920
10163
  const path = activationRequestPath(this.options.herdHome);
9921
- if (!existsSync18(path)) return;
10164
+ if (!existsSync19(path)) return;
9922
10165
  rmSync15(path, { force: true });
9923
10166
  this.activateChosen(true);
9924
10167
  }
@@ -9970,15 +10213,15 @@ init_define_HERD_PACKAGE();
9970
10213
  import { spawn as spawn11 } from "node:child_process";
9971
10214
  import { mkdirSync as mkdirSync19, readdirSync as readdirSync8, rmSync as rmSync16 } from "node:fs";
9972
10215
  import { connect as connect2 } from "node:net";
9973
- import { join as join25 } from "node:path";
10216
+ import { join as join26 } from "node:path";
9974
10217
  init_self();
9975
10218
  import {
9976
10219
  HOST_PROTOCOL,
9977
10220
  hostVersion
9978
10221
  } from "./terminal-host.mjs";
9979
- var hostsDir = () => join25(herdHome(), "terminals");
9980
- var legacyHostSocketPath = () => join25(herdHome(), "terminals.sock");
9981
- var currentHostSocketPath = (version = hostVersion()) => join25(hostsDir(), `${version}.sock`);
10222
+ var hostsDir = () => join26(herdHome(), "terminals");
10223
+ var legacyHostSocketPath = () => join26(herdHome(), "terminals.sock");
10224
+ var currentHostSocketPath = (version = hostVersion()) => join26(hostsDir(), `${version}.sock`);
9982
10225
  var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
9983
10226
  var sleep6 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
9984
10227
  function askHost(path, request) {
@@ -10029,7 +10272,7 @@ var HostTerminals = class {
10029
10272
  names = readdirSync8(dir).filter((name) => name.endsWith(".sock"));
10030
10273
  } catch {
10031
10274
  }
10032
- const paths = [legacyHostSocketPath(), ...names.map((name) => join25(dir, name))];
10275
+ const paths = [legacyHostSocketPath(), ...names.map((name) => join26(dir, name))];
10033
10276
  const found = [];
10034
10277
  for (const path of paths) {
10035
10278
  try {
@@ -10268,7 +10511,7 @@ var UsageReporter = class {
10268
10511
  init_define_HERD_PACKAGE();
10269
10512
  init_src();
10270
10513
  import { mkdirSync as mkdirSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "node:fs";
10271
- import { join as join32 } from "node:path";
10514
+ import { join as join33 } from "node:path";
10272
10515
 
10273
10516
  // src/workspaces/codex.ts
10274
10517
  init_define_HERD_PACKAGE();
@@ -10280,7 +10523,7 @@ init_define_HERD_PACKAGE();
10280
10523
  init_define_HERD_PACKAGE();
10281
10524
  init_src();
10282
10525
  init_self();
10283
- import { join as join26 } from "node:path";
10526
+ import { join as join27 } from "node:path";
10284
10527
  import { z as z26 } from "zod";
10285
10528
  function hasThread(events) {
10286
10529
  return events.some(
@@ -10315,7 +10558,7 @@ function filesByMessage(events) {
10315
10558
  }
10316
10559
  return files;
10317
10560
  }
10318
- var FILES_FOLDER = join26(".herd", "files");
10561
+ var FILES_FOLDER = join27(".herd", "files");
10319
10562
  function safeFileName(name) {
10320
10563
  const chars = Array.from(name.replace(/[/\\\p{Cc}]/gu, "_").trim());
10321
10564
  while (Buffer.byteLength(chars.join("")) > 200) chars.shift();
@@ -10548,11 +10791,11 @@ async function claimChange(ws, workspace, change) {
10548
10791
  // src/workspaces/delivery.ts
10549
10792
  init_define_HERD_PACKAGE();
10550
10793
  import { randomBytes as randomBytes6, randomUUID as randomUUID6 } from "node:crypto";
10551
- import { join as join30 } from "node:path";
10794
+ import { join as join31 } from "node:path";
10552
10795
 
10553
10796
  // src/workspaces/repos.ts
10554
10797
  init_define_HERD_PACKAGE();
10555
- import { join as join27 } from "node:path";
10798
+ import { join as join28 } from "node:path";
10556
10799
  var rootOf = (ws) => ws.options.root?.() ?? ws.options.config().root ?? defaultRoot();
10557
10800
  var gitEnvOf = (ws) => ws.options.gitEnv?.() ?? loginShellEnv();
10558
10801
  function knownRemotes(ws) {
@@ -10664,7 +10907,7 @@ async function addHints(ws, workspace, hints, handedOff) {
10664
10907
  const added = await addWorktree({
10665
10908
  root,
10666
10909
  remote: cloneSource(hint.remote, knownRemotes(ws)),
10667
- path: join27(workspace.folder, place.name),
10910
+ path: join28(workspace.folder, place.name),
10668
10911
  branch,
10669
10912
  ...handedOff ? { continueFrom: hint.branch ?? branch } : {},
10670
10913
  env: gitEnvOf(ws),
@@ -10716,14 +10959,14 @@ function handoffRepos(workspace) {
10716
10959
  return workspace.worktrees.map((repo) => ({
10717
10960
  folder: repo.folder,
10718
10961
  branch: repo.branch,
10719
- unpushed: hasUnpushed(join27(workspace.folder, repo.folder))
10962
+ unpushed: hasUnpushed(join28(workspace.folder, repo.folder))
10720
10963
  }));
10721
10964
  }
10722
10965
 
10723
10966
  // src/workspaces/setup.ts
10724
10967
  init_define_HERD_PACKAGE();
10725
10968
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync20, writeFileSync as writeFileSync16 } from "node:fs";
10726
- import { join as join28 } from "node:path";
10969
+ import { join as join29 } from "node:path";
10727
10970
 
10728
10971
  // src/hooks.ts
10729
10972
  init_define_HERD_PACKAGE();
@@ -10756,7 +10999,7 @@ function configureClaude(ws, workspace) {
10756
10999
  configureClaudeIn(workspace.folder, (line2) => ws.log(`${workspace.conversationId}: ${line2}`));
10757
11000
  }
10758
11001
  function configureClaudeIn(folder, log) {
10759
- const herd = shellQuote2(join28(herdPathDir(), "herd"));
11002
+ const herd = shellQuote2(join29(herdPathDir(), "herd"));
10760
11003
  try {
10761
11004
  const written = writeWorktreeSettings(
10762
11005
  folder,
@@ -10781,9 +11024,9 @@ function configureClaudeIn(folder, log) {
10781
11024
  }
10782
11025
  }
10783
11026
  function herdPathDir() {
10784
- const dir = join28(herdHome(), "bin");
11027
+ const dir = join29(herdHome(), "bin");
10785
11028
  mkdirSync20(dir, { recursive: true, mode: 448 });
10786
- const shim = join28(dir, "herd");
11029
+ const shim = join29(dir, "herd");
10787
11030
  writeFileSync16(shim, `#!/bin/sh
10788
11031
  exec "${process.execPath}" "${herdBin}" "$@"
10789
11032
  `);
@@ -10969,7 +11212,7 @@ async function followCheckIn(ws, workspace, reading) {
10969
11212
  init_define_HERD_PACKAGE();
10970
11213
  import { randomUUID as randomUUID5 } from "node:crypto";
10971
11214
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync21 } from "node:fs";
10972
- import { join as join29 } from "node:path";
11215
+ import { join as join30 } from "node:path";
10973
11216
  function needsInvestigator(ws, workspace, reading) {
10974
11217
  const allowed = ws.options.canInvestigate ? ws.options.canInvestigate() : workspace.agent === "claude-code";
10975
11218
  if (!allowed || !reading || workspace.investigation) return false;
@@ -11134,9 +11377,9 @@ async function followInvestigation(ws, workspace) {
11134
11377
  }
11135
11378
  function saveScreen(ws, conversationId, screen, key) {
11136
11379
  try {
11137
- const dir = join29(herdHome(), "dialogs");
11380
+ const dir = join30(herdHome(), "dialogs");
11138
11381
  mkdirSync21(dir, { recursive: true });
11139
- const file = join29(dir, `${conversationId}-${Date.now()}.txt`);
11382
+ const file = join30(dir, `${conversationId}-${Date.now()}.txt`);
11140
11383
  appendFileSync3(file, `${screen.join("\n")}
11141
11384
 
11142
11385
  Way out: ${key}
@@ -11341,7 +11584,7 @@ async function deliver(ws, conversationId, delivery) {
11341
11584
  await ensureLeadTerminal(ws, workspace);
11342
11585
  const codex = workspace.agent === "codex";
11343
11586
  if (workspace.agent === "agy") {
11344
- writeAgyFiles(workspace.folder, shellQuote2(join30(herdPathDir(), "herd")));
11587
+ writeAgyFiles(workspace.folder, shellQuote2(join31(herdPathDir(), "herd")));
11345
11588
  } else if (!codex) configureClaude(ws, workspace);
11346
11589
  const ownId = codex || workspace.agent === "agy";
11347
11590
  if (!workspace.started) {
@@ -11489,7 +11732,7 @@ import {
11489
11732
  statSync as statSync9,
11490
11733
  writeFileSync as writeFileSync17
11491
11734
  } from "node:fs";
11492
- import { dirname as dirname14, join as join31, sep as sep2 } from "node:path";
11735
+ import { dirname as dirname14, join as join32, sep as sep2 } from "node:path";
11493
11736
  import { Readable } from "node:stream";
11494
11737
  import { pipeline } from "node:stream/promises";
11495
11738
 
@@ -11503,7 +11746,7 @@ import { readFile } from "node:fs/promises";
11503
11746
  init_define_HERD_PACKAGE();
11504
11747
  init_src();
11505
11748
  import { realpathSync as realpathSync9, statSync as statSync8 } from "node:fs";
11506
- import { basename as basename3, extname, isAbsolute as isAbsolute4, relative as relative2, resolve as resolve5 } from "node:path";
11749
+ import { basename as basename4, extname, isAbsolute as isAbsolute5, relative as relative2, resolve as resolve5 } from "node:path";
11507
11750
  var TYPES = {
11508
11751
  ".png": "image/png",
11509
11752
  ".jpg": "image/jpeg",
@@ -11528,7 +11771,7 @@ var TYPES = {
11528
11771
  var mediaTypeFor = (path) => TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
11529
11772
  function isInside(root, path) {
11530
11773
  const between = relative2(root, path);
11531
- return between === "" || !between.startsWith("..") && !isAbsolute4(between);
11774
+ return between === "" || !between.startsWith("..") && !isAbsolute5(between);
11532
11775
  }
11533
11776
  function resolveAttachments(worktree, files) {
11534
11777
  let root;
@@ -11558,7 +11801,7 @@ function resolveAttachments(worktree, files) {
11558
11801
  }
11559
11802
  resolved.push({
11560
11803
  path: real4,
11561
- name: basename3(file.path),
11804
+ name: basename4(file.path),
11562
11805
  type: mediaTypeFor(real4),
11563
11806
  size: stats.size,
11564
11807
  ...file.title ? { title: file.title } : {},
@@ -12491,10 +12734,10 @@ async function bringIn(ws, workspace, files) {
12491
12734
  return listed;
12492
12735
  }
12493
12736
  async function download(ws, workspace, file) {
12494
- const path = join31(FILES_FOLDER, safeFileName(file.id), safeFileName(file.name));
12495
- const target = join31(workspace.folder, path);
12737
+ const path = join32(FILES_FOLDER, safeFileName(file.id), safeFileName(file.name));
12738
+ const target = join32(workspace.folder, path);
12496
12739
  if (statSync9(target, { throwIfNoEntry: false })?.size === file.size) return path;
12497
- const herd = join31(workspace.folder, ".herd");
12740
+ const herd = join32(workspace.folder, ".herd");
12498
12741
  if (lstatSync4(herd, { throwIfNoEntry: false })?.isSymbolicLink()) {
12499
12742
  throw new Error("its folder leads outside the worktree");
12500
12743
  }
@@ -12502,7 +12745,7 @@ async function download(ws, workspace, file) {
12502
12745
  if (!realpathSync10(dirname14(target)).startsWith(realpathSync10(workspace.folder) + sep2)) {
12503
12746
  throw new Error("its folder leads outside the worktree");
12504
12747
  }
12505
- writeFileSync17(join31(herd, ".gitignore"), "*\n");
12748
+ writeFileSync17(join32(herd, ".gitignore"), "*\n");
12506
12749
  const response = await ws.api(
12507
12750
  `${ws.conversationPath(workspace.conversationId)}/files/${encodeURIComponent(file.id)}`
12508
12751
  );
@@ -12792,7 +13035,7 @@ var Workspaces = class {
12792
13035
  return this.driver;
12793
13036
  }
12794
13037
  // ---- State, kept on disk so a restarted daemon picks up where it was.
12795
- statePath = () => join32(herdHome(), "state.json");
13038
+ statePath = () => join33(herdHome(), "state.json");
12796
13039
  load() {
12797
13040
  try {
12798
13041
  return stateSchema.parse(JSON.parse(readFileSync18(this.statePath(), "utf8"))).workspaces;
@@ -13021,7 +13264,7 @@ async function runDaemon(args) {
13021
13264
  const folders = new FolderWatch({
13022
13265
  check: () => machineFolderProblem(machineRoot(), convosPath(machineRoot())),
13023
13266
  initial: missing,
13024
- statusPath: join33(herdHome(), "folder-unavailable.json"),
13267
+ statusPath: join34(herdHome(), "folder-unavailable.json"),
13025
13268
  ...process.env.HERD_FOLDER_CHECK_MS ? { everyMs: Number(process.env.HERD_FOLDER_CHECK_MS) } : {},
13026
13269
  onChange: () => daemon?.foldersChanged()
13027
13270
  });
@@ -13121,7 +13364,7 @@ async function runDaemon(args) {
13121
13364
  env: (secret) => envFor(workspaces, secret),
13122
13365
  // As for a conversation: Claude Code's hooks and status line, or agy's. Codex has none.
13123
13366
  configure: (agent, folder) => {
13124
- if (agent === "agy") writeAgyFiles(folder, shellQuote2(join33(herdPathDir(), "herd")));
13367
+ if (agent === "agy") writeAgyFiles(folder, shellQuote2(join34(herdPathDir(), "herd")));
13125
13368
  else if (agent === "claude-code") configureClaudeIn(folder, log);
13126
13369
  },
13127
13370
  ...shellCommand ? { shellCommand } : {},
@@ -13209,7 +13452,7 @@ async function runDaemon(args) {
13209
13452
  capacity: () => sandbox.ok && !folders.problem ? runnerCapacity(daemon?.configuration().config?.builds) : 0,
13210
13453
  // The repos builds have cloned here, as many as the service's hello takes, to put this runner
13211
13454
  // first for their builds.
13212
- repos: () => bareClones(join33(herdHome(), "builds", "clones")).map((repo) => repo.key).filter((key) => key.length <= 300).slice(0, 100),
13455
+ repos: () => bareClones(join34(herdHome(), "builds", "clones")).map((repo) => repo.key).filter((key) => key.length <= 300).slice(0, 100),
13213
13456
  runner: new BuildRunner({
13214
13457
  root: () => machineRoot(),
13215
13458
  gitEnv,
@@ -13267,10 +13510,10 @@ init_define_HERD_PACKAGE();
13267
13510
  // src/agy-hooks.ts
13268
13511
  init_define_HERD_PACKAGE();
13269
13512
  import { readFileSync as readFileSync20 } from "node:fs";
13270
- import { basename as basename4 } from "node:path";
13513
+ import { basename as basename5 } from "node:path";
13271
13514
  var text3 = (value) => typeof value === "string" ? value : "";
13272
13515
  function lastResponse(transcriptPath) {
13273
- if (!/^transcript.*\.jsonl$/.test(basename4(transcriptPath))) return "";
13516
+ if (!/^transcript.*\.jsonl$/.test(basename5(transcriptPath))) return "";
13274
13517
  let lines;
13275
13518
  try {
13276
13519
  lines = readFileSync20(transcriptPath, "utf8").split("\n");
@@ -13396,8 +13639,8 @@ init_define_HERD_PACKAGE();
13396
13639
  import { spawn as spawn12 } from "node:child_process";
13397
13640
  import { readFileSync as readFileSync21 } from "node:fs";
13398
13641
  import { connect as connect3 } from "node:net";
13399
- import { homedir as homedir7 } from "node:os";
13400
- import { join as join34 } from "node:path";
13642
+ import { homedir as homedir8 } from "node:os";
13643
+ import { join as join35 } from "node:path";
13401
13644
  var MAX_INPUT = 64 * 1024;
13402
13645
  var REPORT_TIMEOUT_MS = 300;
13403
13646
  var OWN_COMMAND_TIMEOUT_MS = 5e3;
@@ -13431,10 +13674,10 @@ function agyStateFrom(input) {
13431
13674
  const state = input?.agent_state;
13432
13675
  return state === "working" || state === "idle" ? state : null;
13433
13676
  }
13434
- function ownAgyStatusLine(home = homedir7()) {
13677
+ function ownAgyStatusLine(home = homedir8()) {
13435
13678
  try {
13436
13679
  const settings = JSON.parse(
13437
- readFileSync21(join34(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
13680
+ readFileSync21(join35(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
13438
13681
  );
13439
13682
  const command = settings.statusLine?.command;
13440
13683
  if (typeof command !== "string" || !command.trim()) return null;
@@ -13453,8 +13696,8 @@ function commandIn(path) {
13453
13696
  return null;
13454
13697
  }
13455
13698
  }
13456
- function ownStatusLine(projectDir, home = homedir7()) {
13457
- return commandIn(join34(projectDir, ".claude", "settings.json")) ?? commandIn(join34(home, ".claude", "settings.json"));
13699
+ function ownStatusLine(projectDir, home = homedir8()) {
13700
+ return commandIn(join35(projectDir, ".claude", "settings.json")) ?? commandIn(join35(home, ".claude", "settings.json"));
13458
13701
  }
13459
13702
  function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS) {
13460
13703
  return new Promise((resolve7) => {
@@ -13876,7 +14119,7 @@ async function runTerminalHost(rest) {
13876
14119
 
13877
14120
  // src/commands/repo.ts
13878
14121
  init_define_HERD_PACKAGE();
13879
- import { join as join35 } from "node:path";
14122
+ import { join as join36 } from "node:path";
13880
14123
  import { z as z33 } from "zod";
13881
14124
  var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
13882
14125
 
@@ -13964,7 +14207,7 @@ async function runRepo(rest) {
13964
14207
  folderName ? `${folderName} is already taken in this conversation's folder. Give another name.` : `Both ${repoName(key)} and ${repoOwner(key)}-${repoName(key)} are taken in this conversation's folder. Give a folder name: herd repo ${asked} <folder>.`
13965
14208
  );
13966
14209
  }
13967
- const path = join35(here.folder, place.name);
14210
+ const path = join36(here.folder, place.name);
13968
14211
  const recorded = here.recorded.some((repo) => repo.folder === place.name);
13969
14212
  if (place.existing && recorded) {
13970
14213
  const branch = here.recorded.find((repo) => repo.folder === place.name)?.branch ?? here.branch;