@herdingchats/friday 0.1.621 → 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 +516 -280
  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.621", gitHead: "a26572f3278fa3eeb800c1a4269dddcd279cf117" };
21
+ define_HERD_PACKAGE_default = { version: "0.1.623", gitHead: "0c391f8523159d26c6e23d46eb803c56a4e7a9ec" };
22
22
  }
23
23
  });
24
24
 
@@ -3204,9 +3204,9 @@ import { fileURLToPath as fileURLToPath3 } from "node:url";
3204
3204
  init_define_HERD_PACKAGE();
3205
3205
  init_src();
3206
3206
  import { spawn as spawn7 } from "node:child_process";
3207
- import { existsSync as existsSync11, readFileSync as readFileSync10 } from "node:fs";
3208
- import { homedir as homedir4 } from "node:os";
3209
- 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";
3210
3210
  import { createInterface } from "node:readline/promises";
3211
3211
  import { z as z22 } from "zod";
3212
3212
 
@@ -5782,87 +5782,30 @@ function serviceFor(kind, context2) {
5782
5782
  init_define_HERD_PACKAGE();
5783
5783
  init_src();
5784
5784
  import { mkdirSync as mkdirSync10, realpathSync as realpathSync4 } from "node:fs";
5785
- import { join as join13, resolve as resolve3 } from "node:path";
5785
+ import { join as join14, resolve as resolve3 } from "node:path";
5786
5786
 
5787
- // src/friday/legacy.ts
5787
+ // src/package-manager.ts
5788
5788
  init_define_HERD_PACKAGE();
5789
- import { existsSync as existsSync8, readdirSync as readdirSync3, rmSync as rmSync8 } from "node:fs";
5790
- import { userInfo as userInfo2 } from "node:os";
5791
- import { join as join11 } from "node:path";
5792
- var OLD_LAUNCH_AGENT = "com.herdingchats.herd";
5793
- var OLD_APPS = ["herd-daemon", "herd-updater"];
5794
- function oldPm2s(paths) {
5795
- const dir = join11(paths.herdHome, "daemon");
5796
- if (!existsSync8(dir)) return [];
5797
- return readdirSync3(dir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join11(dir, entry.name, "node_modules", "pm2", "bin", "pm2")).filter((pm2) => existsSync8(pm2));
5798
- }
5799
- async function retireOldDaemon(paths, { home, run: run4 }) {
5800
- const removed = [];
5801
- const plist = join11(home, "Library", "LaunchAgents", `${OLD_LAUNCH_AGENT}.plist`);
5802
- if (existsSync8(plist)) {
5803
- await run4("launchctl", ["bootout", `gui/${userInfo2().uid}/${OLD_LAUNCH_AGENT}`]);
5804
- rmSync8(plist, { force: true });
5805
- removed.push(`the login item ${OLD_LAUNCH_AGENT}`);
5806
- }
5807
- for (const pm2 of oldPm2s(paths)) {
5808
- const listed = await run4(process.execPath, [pm2, "jlist"]);
5809
- let names = [];
5810
- try {
5811
- names = JSON.parse(listed.stdout).map((app) => app.name ?? "");
5812
- } catch {
5813
- names = [];
5814
- }
5815
- const ours = OLD_APPS.filter((name) => names.includes(name));
5816
- if (ours.length === 0) continue;
5817
- for (const name of ours) await run4(process.execPath, [pm2, "delete", name]);
5818
- await run4(process.execPath, [pm2, "save", "--force"]);
5819
- removed.push(`pm2's ${ours.join(" and ")}`);
5820
- }
5821
- return removed;
5822
- }
5823
-
5824
- // src/friday/verbose.ts
5825
- init_define_HERD_PACKAGE();
5826
- var VERBOSE_ENV = "HERD_VERBOSE";
5827
- var began = Date.now();
5828
- function verbose2(line2) {
5829
- if (!process.env[VERBOSE_ENV]) return;
5830
- const seconds = ((Date.now() - began) / 1e3).toFixed(1);
5831
- process.stderr.write(`[friday +${seconds}s] ${line2}
5832
- `);
5833
- }
5834
- var tail = (text4) => text4.trim().split("\n").slice(-3).join(" | ");
5835
- function verboseExec(run4) {
5836
- return async (command, args, options) => {
5837
- if (!process.env[VERBOSE_ENV]) return run4(command, args, options);
5838
- const shown = [command, ...args].join(" ");
5839
- verbose2(`running ${shown}`);
5840
- const started = Date.now();
5841
- const ran = await run4(command, args, options);
5842
- const took = `${Date.now() - started}ms`;
5843
- verbose2(
5844
- ran.status === 0 ? `ran ${shown} (${took})` : `${shown} exited ${ran.status} (${took})${ran.stderr.trim() ? `: ${tail(ran.stderr)}` : ""}`
5845
- );
5846
- return ran;
5847
- };
5848
- }
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";
5849
5792
 
5850
5793
  // src/friday/versions.ts
5851
5794
  init_define_HERD_PACKAGE();
5852
5795
  import { spawn as spawn5 } from "node:child_process";
5853
5796
  import {
5854
- existsSync as existsSync9,
5797
+ existsSync as existsSync8,
5855
5798
  mkdirSync as mkdirSync9,
5856
- readdirSync as readdirSync4,
5799
+ readdirSync as readdirSync3,
5857
5800
  readFileSync as readFileSync8,
5858
5801
  readlinkSync,
5859
5802
  realpathSync as realpathSync3,
5860
5803
  renameSync as renameSync3,
5861
- rmSync as rmSync9,
5804
+ rmSync as rmSync8,
5862
5805
  symlinkSync,
5863
5806
  writeFileSync as writeFileSync8
5864
5807
  } from "node:fs";
5865
- import { basename as basename2, dirname as dirname9, join as join12 } from "node:path";
5808
+ import { basename as basename2, join as join11 } from "node:path";
5866
5809
  var exec = (command, args, options = {}) => new Promise((resolve7) => {
5867
5810
  let stdout = "";
5868
5811
  let stderr = "";
@@ -5883,10 +5826,6 @@ var exec = (command, args, options = {}) => new Promise((resolve7) => {
5883
5826
  resolve7({ status: status2 ?? 1, stdout, stderr });
5884
5827
  });
5885
5828
  });
5886
- function npmCommand() {
5887
- const beside = join12(dirname9(process.execPath), "npm");
5888
- return existsSync9(beside) ? beside : "npm";
5889
- }
5890
5829
  function currentVersion(paths) {
5891
5830
  try {
5892
5831
  return basename2(readlinkSync(paths.current));
@@ -5895,10 +5834,10 @@ function currentVersion(paths) {
5895
5834
  }
5896
5835
  }
5897
5836
  function installedVersions(paths) {
5898
- if (!existsSync9(paths.versions)) return [];
5899
- 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);
5900
5839
  }
5901
- var binOf = (versionDir, bin) => join12(packageDir(versionDir), `${bin}.mjs`);
5840
+ var binOf = (versionDir, bin) => join11(packageDir(versionDir), `${bin}.mjs`);
5902
5841
  async function versionRuns(versionDir, version, run4 = exec) {
5903
5842
  const ran = await run4(process.execPath, [binOf(versionDir, "herd"), "--version"], {
5904
5843
  timeoutMs: 3e4
@@ -5910,12 +5849,19 @@ async function versionRuns(versionDir, version, run4 = exec) {
5910
5849
  return null;
5911
5850
  }
5912
5851
  function recordedIntegrity(dir) {
5913
- try {
5914
- const lock = JSON.parse(readFileSync8(join12(dir, "package-lock.json"), "utf8"));
5915
- return lock.packages?.[`node_modules/${PACKAGE_NAME}`]?.integrity ?? null;
5916
- } catch {
5917
- 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
+ }
5918
5863
  }
5864
+ return null;
5919
5865
  }
5920
5866
  function npmFreeEnv() {
5921
5867
  return Object.fromEntries(
@@ -5923,10 +5869,10 @@ function npmFreeEnv() {
5923
5869
  );
5924
5870
  }
5925
5871
  async function installVersion(paths, version, { spec, integrity, run: run4 = exec, env = {}, log = () => {
5926
- } } = {}) {
5872
+ }, manager } = {}) {
5927
5873
  if (!VERSION.test(version)) return { ok: false, reason: `${version} isn't a version.` };
5928
- const dir = join12(paths.versions, version);
5929
- if (existsSync9(dir)) {
5874
+ const dir = join11(paths.versions, version);
5875
+ if (existsSync8(dir)) {
5930
5876
  const installed2 = recordedIntegrity(dir);
5931
5877
  if (integrity && installed2 !== integrity)
5932
5878
  return { ok: false, reason: `the installed ${version} doesn't have the integrity named.` };
@@ -5934,62 +5880,63 @@ async function installVersion(paths, version, { spec, integrity, run: run4 = exe
5934
5880
  if (!broken2) return { ok: true, dir };
5935
5881
  return { ok: false, reason: `the installed ${version} doesn't run: ${broken2}.` };
5936
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
+ };
5937
5889
  sweepStaging(paths);
5938
- const named = join12(paths.versions, `.staging-${version}-${process.pid}`);
5939
- rmSync9(named, { recursive: true, force: true });
5890
+ const named = join11(paths.versions, `.staging-${version}-${process.pid}`);
5891
+ rmSync8(named, { recursive: true, force: true });
5940
5892
  mkdirSync9(named, { recursive: true });
5941
5893
  const staging = realpathSync3(named);
5942
5894
  writeFileSync8(
5943
- join12(staging, "package.json"),
5944
- `${JSON.stringify({ private: true, allowScripts: { "node-pty": true } }, null, 2)}
5895
+ join11(staging, "package.json"),
5896
+ `${JSON.stringify(allowScriptsManifest(["node-pty"]), null, 2)}
5945
5897
  `
5946
5898
  );
5899
+ if (using.name === "pnpm")
5900
+ writeFileSync8(join11(staging, "pnpm-workspace.yaml"), allowScriptsWorkspace(["node-pty"]));
5947
5901
  const fail = (reason) => {
5948
- rmSync9(staging, { recursive: true, force: true });
5902
+ rmSync8(staging, { recursive: true, force: true });
5949
5903
  return { ok: false, reason };
5950
5904
  };
5951
- log(`Installing ${PACKAGE_NAME}@${version}.`);
5905
+ log(`Installing ${PACKAGE_NAME}@${version} with ${using.name} ${using.version}.`);
5952
5906
  const installed = await run4(
5953
- npmCommand(),
5954
- [
5955
- "install",
5956
- "--prefix",
5957
- staging,
5958
- spec ?? `${PACKAGE_NAME}@${version}`,
5959
- "--no-audit",
5960
- "--no-fund",
5961
- "--no-update-notifier",
5962
- "--save-exact"
5963
- ],
5907
+ using.command,
5908
+ addArgs(using.name, staging, spec ?? `${PACKAGE_NAME}@${version}`),
5964
5909
  // From inside the folder, so a workspace around wherever this ran isn't taken for its project.
5965
5910
  { cwd: staging, env: { ...npmFreeEnv(), ...env }, timeoutMs: 10 * 6e4 }
5966
5911
  );
5967
5912
  if (installed.status !== 0)
5968
- 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
+ );
5969
5916
  if (integrity) {
5970
5917
  const got = recordedIntegrity(staging);
5971
5918
  if (got !== integrity)
5972
5919
  return fail(
5973
- `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}`
5974
5921
  );
5975
5922
  }
5976
5923
  const broken = await versionRuns(staging, version, run4);
5977
5924
  if (broken) return fail(broken);
5978
- renameSync3(staging, join12(realpathSync3(paths.versions), version));
5925
+ renameSync3(staging, join11(realpathSync3(paths.versions), version));
5979
5926
  return { ok: true, dir };
5980
5927
  }
5981
5928
  function sweepStaging(paths) {
5982
- if (!existsSync9(paths.versions)) return;
5983
- for (const name of readdirSync4(paths.versions)) {
5929
+ if (!existsSync8(paths.versions)) return;
5930
+ for (const name of readdirSync3(paths.versions)) {
5984
5931
  const match = /^\.staging-.+-(\d+)$/.exec(name);
5985
5932
  if (match && !alive2(Number(match[1])))
5986
- rmSync9(join12(paths.versions, name), { recursive: true, force: true });
5933
+ rmSync8(join11(paths.versions, name), { recursive: true, force: true });
5987
5934
  }
5988
5935
  }
5989
5936
  function activate(paths, version) {
5990
5937
  const next = `${paths.current}.next-${process.pid}`;
5991
- rmSync9(next, { force: true });
5992
- symlinkSync(join12("versions", version), next);
5938
+ rmSync8(next, { force: true });
5939
+ symlinkSync(join11("versions", version), next);
5993
5940
  renameSync3(next, paths.current);
5994
5941
  }
5995
5942
  function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
@@ -6004,7 +5951,7 @@ function pruneVersions(paths, { inUse = [], keep = 3 } = {}) {
6004
5951
  const removed = [];
6005
5952
  for (const version of versions) {
6006
5953
  if (kept.has(version)) continue;
6007
- rmSync9(join12(paths.versions, version), { recursive: true, force: true });
5954
+ rmSync8(join11(paths.versions, version), { recursive: true, force: true });
6008
5955
  removed.push(version);
6009
5956
  }
6010
5957
  return removed;
@@ -6028,6 +5975,271 @@ async function hostVersions(paths, run4 = exec) {
6028
5975
  return [...found].filter(Boolean);
6029
5976
  }
6030
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
+
6031
6243
  // src/friday/start.ts
6032
6244
  var MIN_NODE = [22, 18];
6033
6245
  var SETUP_WAIT_MS = 2e4;
@@ -6058,7 +6270,7 @@ var real = (path) => {
6058
6270
  }
6059
6271
  };
6060
6272
  function chooseRoot(cwd, home) {
6061
- return real(cwd) === real(home) ? join13(home, "friday") : resolve3(cwd);
6273
+ return real(cwd) === real(home) ? join14(home, "friday") : resolve3(cwd);
6062
6274
  }
6063
6275
  async function runStart(options) {
6064
6276
  const { paths, run: run4 } = options;
@@ -6123,9 +6335,15 @@ async function runStart(options) {
6123
6335
  print(
6124
6336
  `Stopped the old daemon: removed ${retired.join(" and ")}. Its checkout is left as it was.`
6125
6337
  );
6126
- 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);
6127
6345
  const command = [process.execPath, binOf(paths.current, "friday"), "supervise"];
6128
- const env = { HERD_HOME: paths.herdHome, ...options.env };
6346
+ const env = { HERD_HOME: paths.herdHome, [MANAGER_ENV]: manager.command, ...options.env };
6129
6347
  const service = (options.service ?? defaultService(options))(command, env);
6130
6348
  if (moving && running) {
6131
6349
  print(`Stopping Friday at ${existing} to start it again from ${root}.`);
@@ -6231,7 +6449,7 @@ init_src();
6231
6449
  import { spawn as spawn6 } from "node:child_process";
6232
6450
  import {
6233
6451
  closeSync as closeSync3,
6234
- existsSync as existsSync10,
6452
+ existsSync as existsSync11,
6235
6453
  mkdirSync as mkdirSync11,
6236
6454
  openSync as openSync4,
6237
6455
  readFileSync as readFileSync9,
@@ -6316,7 +6534,7 @@ var Supervisor = class {
6316
6534
  const { daemonLog, logs } = this.options.paths;
6317
6535
  mkdirSync11(logs, { recursive: true });
6318
6536
  try {
6319
- if (existsSync10(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
6537
+ if (existsSync11(daemonLog) && statSync6(daemonLog).size > MAX_LOG_BYTES2)
6320
6538
  renameSync4(daemonLog, `${daemonLog}.1`);
6321
6539
  } catch {
6322
6540
  }
@@ -6469,9 +6687,9 @@ Usage: ${FRIDAY_COMMAND} <command>
6469
6687
 
6470
6688
  Agents use the herd command, which comes with this one. herd setup shows what's left to set up.`;
6471
6689
  function defaults() {
6472
- const home = homedir4();
6690
+ const home = homedir5();
6473
6691
  return {
6474
- paths: fridayPaths(process.env.HERD_HOME ?? join14(home, ".herd")),
6692
+ paths: fridayPaths(process.env.HERD_HOME ?? join15(home, ".herd")),
6475
6693
  home,
6476
6694
  cwd: process.cwd(),
6477
6695
  run: exec,
@@ -6543,7 +6761,7 @@ async function friday(args, deps = defaults()) {
6543
6761
  agents: {
6544
6762
  // The choice lives in herd.config.ts, so it's asked for only before there is one, or
6545
6763
  // with --config.
6546
- ask: rest.includes("--config") || !existsSync11(herdConfigIn(deps.paths.herdHome)),
6764
+ ask: rest.includes("--config") || !existsSync12(herdConfigIn(deps.paths.herdHome)),
6547
6765
  installed: async () => {
6548
6766
  verbose2("looking for installed agents");
6549
6767
  const found = installedAgents();
@@ -6602,7 +6820,7 @@ function agentsFlag(args) {
6602
6820
  return parsed.shares;
6603
6821
  }
6604
6822
  function attachTo(paths, terminal) {
6605
- 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");
6606
6824
  const child = spawn7(process.execPath, [herd, "attach", terminal], {
6607
6825
  stdio: "inherit",
6608
6826
  env: { ...process.env, HERD_HOME: paths.herdHome }
@@ -6666,14 +6884,14 @@ function runStatus(deps) {
6666
6884
  function readMissingFolder(herdHome2) {
6667
6885
  try {
6668
6886
  return folderUnavailableSchema.parse(
6669
- JSON.parse(readFileSync10(join14(herdHome2, "folder-unavailable.json"), "utf8"))
6887
+ JSON.parse(readFileSync10(join15(herdHome2, "folder-unavailable.json"), "utf8"))
6670
6888
  );
6671
6889
  } catch {
6672
6890
  return null;
6673
6891
  }
6674
6892
  }
6675
6893
  function tail2(path, count) {
6676
- if (!existsSync11(path)) return [];
6894
+ if (!existsSync12(path)) return [];
6677
6895
  const lines = readFileSync10(path, "utf8").split("\n");
6678
6896
  if (lines.at(-1) === "") lines.pop();
6679
6897
  return lines.slice(-count);
@@ -6681,7 +6899,7 @@ function tail2(path, count) {
6681
6899
  async function runLogs(args, deps) {
6682
6900
  const { paths, print } = deps;
6683
6901
  const follow = args.includes("-f") || args.includes("--follow");
6684
- const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync11(file));
6902
+ const files = [paths.supervisorLog, paths.daemonLog].filter((file) => existsSync12(file));
6685
6903
  if (files.length === 0) {
6686
6904
  print(`There are no logs in ${paths.logs} yet.`);
6687
6905
  return 0;
@@ -6731,7 +6949,7 @@ init_define_HERD_PACKAGE();
6731
6949
  init_define_HERD_PACKAGE();
6732
6950
  import { spawn as spawn8 } from "node:child_process";
6733
6951
  import { appendFileSync, readFileSync as readFileSync11 } from "node:fs";
6734
- import { dirname as dirname11, join as join15 } from "node:path";
6952
+ import { dirname as dirname11, join as join16 } from "node:path";
6735
6953
  var DEPLOY_HOSTS = ["api.cloudflare.com", "api.github.com", "registry.npmjs.org"];
6736
6954
  function buildPolicy(input) {
6737
6955
  const platform2 = input.platform ?? process.platform;
@@ -6778,7 +6996,7 @@ async function runSandboxed(args) {
6778
6996
  return 3;
6779
6997
  }
6780
6998
  await SandboxManager.initialize(policy, void 0, true);
6781
- const blocked = join15(process.env.HOME ?? ".", BLOCKED_LOG);
6999
+ const blocked = join16(process.env.HOME ?? ".", BLOCKED_LOG);
6782
7000
  let written = 0;
6783
7001
  const unsubscribe = SandboxManager.getSandboxViolationStore().subscribe((violations) => {
6784
7002
  const fresh = violations.slice(written).map((violation) => describeRefusal(violation.line)).filter((line2) => line2 !== null);
@@ -7279,8 +7497,8 @@ init_define_HERD_PACKAGE();
7279
7497
  // src/build-runner-key.ts
7280
7498
  init_define_HERD_PACKAGE();
7281
7499
  init_src();
7282
- import { chmodSync as chmodSync3, existsSync as existsSync12, mkdirSync as mkdirSync12, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "node:fs";
7283
- 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";
7284
7502
  import { z as z24 } from "zod";
7285
7503
  var BUILDS_URL = "https://builds.herdingchats.com";
7286
7504
  var runnerKeySchema = z24.object({
@@ -7288,10 +7506,10 @@ var runnerKeySchema = z24.object({
7288
7506
  key: z24.string().min(20),
7289
7507
  url: z24.string().url()
7290
7508
  });
7291
- var runnerKeyPath = () => join16(herdHome(), "build-runner.json");
7509
+ var runnerKeyPath = () => join17(herdHome(), "build-runner.json");
7292
7510
  function readRunnerKey() {
7293
7511
  const path = runnerKeyPath();
7294
- if (!existsSync12(path)) return null;
7512
+ if (!existsSync13(path)) return null;
7295
7513
  try {
7296
7514
  return runnerKeySchema.parse(JSON.parse(readFileSync13(path, "utf8")));
7297
7515
  } catch {
@@ -7470,7 +7688,7 @@ function formatSpeed(report) {
7470
7688
  // src/builds-admin.ts
7471
7689
  init_define_HERD_PACKAGE();
7472
7690
  import { mkdirSync as mkdirSync13, readFileSync as readFileSync14, rmSync as rmSync10, writeFileSync as writeFileSync10 } from "node:fs";
7473
- import { join as join17 } from "node:path";
7691
+ import { join as join18 } from "node:path";
7474
7692
  var NOT_ADMIN = "builds are only for the admin's machines";
7475
7693
  async function isAdmin(fetch2) {
7476
7694
  const response = await fetch2("/api/people/me").catch(() => null);
@@ -7478,13 +7696,13 @@ async function isAdmin(fetch2) {
7478
7696
  const body = await response.json().catch(() => ({}));
7479
7697
  return body.admin === true;
7480
7698
  }
7481
- var refusalPath = () => join17(herdHome(), "builds", "refused.json");
7699
+ var refusalPath = () => join18(herdHome(), "builds", "refused.json");
7482
7700
  function writeBuildsRefusal(reason) {
7483
7701
  if (!reason) {
7484
7702
  rmSync10(refusalPath(), { force: true });
7485
7703
  return;
7486
7704
  }
7487
- mkdirSync13(join17(herdHome(), "builds"), { recursive: true });
7705
+ mkdirSync13(join18(herdHome(), "builds"), { recursive: true });
7488
7706
  writeFileSync10(refusalPath(), JSON.stringify({ reason }));
7489
7707
  }
7490
7708
  function readBuildsRefusal() {
@@ -7648,7 +7866,7 @@ async function runConversations(rest) {
7648
7866
  init_define_HERD_PACKAGE();
7649
7867
  init_src();
7650
7868
  import { availableParallelism, totalmem } from "node:os";
7651
- import { join as join33 } from "node:path";
7869
+ import { join as join34 } from "node:path";
7652
7870
 
7653
7871
  // src/agent.ts
7654
7872
  init_define_HERD_PACKAGE();
@@ -7757,20 +7975,20 @@ function readClaudeScreen(raw) {
7757
7975
 
7758
7976
  // src/worktrees.ts
7759
7977
  init_define_HERD_PACKAGE();
7760
- import { existsSync as existsSync13, realpathSync as realpathSync6 } from "node:fs";
7761
- import { isAbsolute as isAbsolute2, join as join18, relative } from "node:path";
7762
- 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");
7763
7981
  var WORD_ID = /^[a-z]+-[a-z]+-[a-z]+$/;
7764
7982
  var conversationPrefix = (conversationId) => WORD_ID.test(conversationId) ? conversationId : conversationId.replace(/[^A-Za-z0-9]/g, "").slice(0, 12).toLowerCase();
7765
7983
  var conversationBranch = (conversationId) => `herd/${conversationPrefix(conversationId)}`;
7766
- var conversationFolder = (convos, conversationId) => join18(convos, conversationPrefix(conversationId));
7984
+ var conversationFolder = (convos, conversationId) => join19(convos, conversationPrefix(conversationId));
7767
7985
  function folderForConversation(root, conversationId) {
7768
7986
  const earlier = conversationFolder(legacyConvosDir(root), conversationId);
7769
- return existsSync13(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
7987
+ return existsSync14(earlier) ? earlier : conversationFolder(convosHome(root), conversationId);
7770
7988
  }
7771
7989
  var within = (dir, path) => {
7772
7990
  const between = relative(dir, path);
7773
- return between !== "" && !between.startsWith("..") && !isAbsolute2(between);
7991
+ return between !== "" && !between.startsWith("..") && !isAbsolute3(between);
7774
7992
  };
7775
7993
  var real2 = (path) => {
7776
7994
  try {
@@ -8069,8 +8287,8 @@ function readAgyScreen(raw) {
8069
8287
  // src/agy-settings.ts
8070
8288
  init_define_HERD_PACKAGE();
8071
8289
  import { lstatSync as lstatSync2, mkdirSync as mkdirSync14, realpathSync as realpathSync7, writeFileSync as writeFileSync11 } from "node:fs";
8072
- import { join as join19, sep } from "node:path";
8073
- 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");
8074
8292
  var AGY_HOOK_EVENTS = ["SessionStart", "PreInvocation", "Stop"];
8075
8293
  var agyHookCommand = (event2, herd) => `${herd} hook agy ${event2} || true`;
8076
8294
  function agyHooks(herd) {
@@ -8087,7 +8305,7 @@ function agySettings(herd) {
8087
8305
  return { statusLine: { type: "command", command: `${herd} statusline agy` } };
8088
8306
  }
8089
8307
  function ownFolder(worktree, name) {
8090
- const folder = join19(worktree, name);
8308
+ const folder = join20(worktree, name);
8091
8309
  if (lstatSync2(folder, { throwIfNoEntry: false })?.isSymbolicLink()) {
8092
8310
  throw new Error(`${name} in the worktree is a link`);
8093
8311
  }
@@ -8099,13 +8317,13 @@ function ownFolder(worktree, name) {
8099
8317
  }
8100
8318
  function writeAgyFiles(worktree, herd) {
8101
8319
  const agents = ownFolder(worktree, ".agents");
8102
- writeFileSync11(join19(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8320
+ writeFileSync11(join20(agents, "hooks.json"), `${JSON.stringify(agyHooks(herd), null, 2)}
8103
8321
  `);
8104
- writeFileSync11(join19(agents, ".gitignore"), "*\n");
8322
+ writeFileSync11(join20(agents, ".gitignore"), "*\n");
8105
8323
  const herdFolder = ownFolder(worktree, ".herd");
8106
- writeFileSync11(join19(herdFolder, ".gitignore"), "*\n");
8324
+ writeFileSync11(join20(herdFolder, ".gitignore"), "*\n");
8107
8325
  writeFileSync11(
8108
- join19(worktree, AGY_SETTINGS_FILE),
8326
+ join20(worktree, AGY_SETTINGS_FILE),
8109
8327
  `${JSON.stringify(agySettings(herd), null, 2)}
8110
8328
  `
8111
8329
  );
@@ -8260,15 +8478,15 @@ var AgyDriver = class {
8260
8478
  // src/bare-clones.ts
8261
8479
  init_define_HERD_PACKAGE();
8262
8480
  import { randomBytes as randomBytes5 } from "node:crypto";
8263
- import { existsSync as existsSync14, mkdirSync as mkdirSync15, readdirSync as readdirSync5, renameSync as renameSync5, rmSync as rmSync11, statSync as statSync7 } from "node:fs";
8264
- 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";
8265
8483
  function bareClonePath(dir, remote) {
8266
8484
  const key = repoKey(remote);
8267
8485
  if (!key) throw new StoreError(`${remote} isn't a repo's URL.`);
8268
- return join20(dir, `${key}.git`);
8486
+ return join21(dir, `${key}.git`);
8269
8487
  }
8270
8488
  async function ensureBareClone(store, remote, env) {
8271
- if (existsSync14(store)) {
8489
+ if (existsSync15(store)) {
8272
8490
  let fetchFailed = null;
8273
8491
  try {
8274
8492
  await git(env, store, "fetch", "--quiet", "--prune", "origin");
@@ -8309,7 +8527,7 @@ function bareClones(dir) {
8309
8527
  return;
8310
8528
  }
8311
8529
  for (const name of entries.sort()) {
8312
- const path = join20(at, name);
8530
+ const path = join21(at, name);
8313
8531
  if (name.endsWith(".git")) {
8314
8532
  try {
8315
8533
  const remote = gitSync(path, "config", "--get", "remote.origin.url");
@@ -8490,7 +8708,7 @@ init_define_HERD_PACKAGE();
8490
8708
  import { execFile as execFile2, execFileSync as execFileSync5, spawn as spawn9 } from "node:child_process";
8491
8709
  import { createHash as createHash3, randomUUID as randomUUID4 } from "node:crypto";
8492
8710
  import {
8493
- existsSync as existsSync15,
8711
+ existsSync as existsSync16,
8494
8712
  mkdirSync as mkdirSync17,
8495
8713
  mkdtempSync,
8496
8714
  readdirSync as readdirSync7,
@@ -8499,16 +8717,16 @@ import {
8499
8717
  rmSync as rmSync13,
8500
8718
  writeFileSync as writeFileSync13
8501
8719
  } from "node:fs";
8502
- import { homedir as homedir6, tmpdir } from "node:os";
8503
- import { join as join22 } from "node:path";
8720
+ import { homedir as homedir7, tmpdir } from "node:os";
8721
+ import { join as join23 } from "node:path";
8504
8722
  import { promisify as promisify2 } from "node:util";
8505
8723
 
8506
8724
  // src/check-registry.ts
8507
8725
  init_define_HERD_PACKAGE();
8508
8726
  import { mkdirSync as mkdirSync16, readdirSync as readdirSync6, readFileSync as readFileSync15, rmSync as rmSync12, writeFileSync as writeFileSync12 } from "node:fs";
8509
- import { homedir as homedir5 } from "node:os";
8510
- import { join as join21 } from "node:path";
8511
- 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");
8512
8730
  var RESERVED_MB = 6 * 1024;
8513
8731
  var MIN_MEMORY_MB = 1024;
8514
8732
  var alive3 = (pid) => {
@@ -8521,7 +8739,7 @@ var alive3 = (pid) => {
8521
8739
  };
8522
8740
  function registerCheck(dir, pid, startedAt = Date.now()) {
8523
8741
  mkdirSync16(dir, { recursive: true });
8524
- const file = join21(dir, `${pid}.json`);
8742
+ const file = join22(dir, `${pid}.json`);
8525
8743
  writeFileSync12(file, JSON.stringify({ pid, startedAt }));
8526
8744
  return () => rmSync12(file, { force: true });
8527
8745
  }
@@ -8537,12 +8755,12 @@ function runningChecks(dir, isAlive = alive3) {
8537
8755
  const pid = Number(name.slice(0, -".json".length));
8538
8756
  let entry = null;
8539
8757
  try {
8540
- entry = JSON.parse(readFileSync15(join21(dir, name), "utf8"));
8758
+ entry = JSON.parse(readFileSync15(join22(dir, name), "utf8"));
8541
8759
  } catch {
8542
8760
  continue;
8543
8761
  }
8544
8762
  if (entry?.pid === pid && isAlive(pid)) count += 1;
8545
- else rmSync12(join21(dir, name), { force: true });
8763
+ else rmSync12(join22(dir, name), { force: true });
8546
8764
  }
8547
8765
  return count;
8548
8766
  }
@@ -8550,9 +8768,9 @@ var memoryShareOf = (totalMb, running) => Math.max(MIN_MEMORY_MB, Math.floor((to
8550
8768
 
8551
8769
  // src/builds.ts
8552
8770
  init_self();
8553
- var sandboxStatusPath = () => join22(herdHome(), "builds", "sandbox.json");
8771
+ var sandboxStatusPath = () => join23(herdHome(), "builds", "sandbox.json");
8554
8772
  function writeSandboxStatus(status2, now = Date.now()) {
8555
- mkdirSync17(join22(herdHome(), "builds"), { recursive: true });
8773
+ mkdirSync17(join23(herdHome(), "builds"), { recursive: true });
8556
8774
  writeFileSync13(sandboxStatusPath(), JSON.stringify({ ...status2, at: now }));
8557
8775
  }
8558
8776
  function readSandboxStatus() {
@@ -8584,7 +8802,7 @@ var real3 = (path) => {
8584
8802
  }
8585
8803
  };
8586
8804
  function worktreeOf(dir, store) {
8587
- if (!existsSync15(join22(dir, ".git"))) return false;
8805
+ if (!existsSync16(join23(dir, ".git"))) return false;
8588
8806
  try {
8589
8807
  return real3(git2(dir, "rev-parse", "--path-format=absolute", "--git-common-dir")) === real3(store);
8590
8808
  } catch {
@@ -8596,36 +8814,54 @@ function cloneFolder(from, to) {
8596
8814
  execFileSync5("cp", [...args, from, to]);
8597
8815
  }
8598
8816
  function moduleFolders(dir) {
8599
- const found = existsSync15(join22(dir, "node_modules")) ? ["node_modules"] : [];
8817
+ const found = existsSync16(join23(dir, "node_modules")) ? ["node_modules"] : [];
8600
8818
  for (const entry of readdirSync7(dir, { withFileTypes: true })) {
8601
8819
  if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
8602
- if (existsSync15(join22(dir, entry.name, "node_modules")))
8603
- found.push(join22(entry.name, "node_modules"));
8820
+ if (existsSync16(join23(dir, entry.name, "node_modules")))
8821
+ found.push(join23(entry.name, "node_modules"));
8604
8822
  }
8605
8823
  }
8606
8824
  return found;
8607
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
+ }
8608
8840
  function lockHash(dir) {
8609
- const lock = readFileSync16(join22(dir, "package-lock.json"));
8841
+ const lock = readFileSync16(join23(dir, checkoutManager(dir).lockfile));
8610
8842
  const major = process.versions.node.split(".")[0];
8611
8843
  return createHash3("sha256").update(lock).update(`${process.platform}-${process.arch}-${major}`).digest("hex").slice(0, 32);
8612
8844
  }
8613
- var npmCi = (dir) => new Promise((resolve7, reject) => {
8614
- const network = insideBuildSandbox() ? ["--offline"] : ["--prefer-offline"];
8615
- const child = spawn9(
8616
- "npm",
8617
- ["ci", ...network, "--no-audit", "--no-fund", "--no-update-notifier"],
8618
- {
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, {
8619
8854
  cwd: dir,
8855
+ env: { ...env, COREPACK_ENABLE_DOWNLOAD_PROMPT: "0" },
8620
8856
  stdio: "ignore"
8621
- }
8622
- );
8623
- child.on("error", reject);
8624
- child.on(
8625
- "close",
8626
- (code) => code === 0 ? resolve7() : reject(new Error(`npm ci exited with ${code}`))
8627
- );
8628
- });
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
+ };
8629
8865
  var BuildRunner = class _BuildRunner {
8630
8866
  constructor(options) {
8631
8867
  this.options = options;
@@ -8633,11 +8869,11 @@ var BuildRunner = class _BuildRunner {
8633
8869
  options;
8634
8870
  running = /* @__PURE__ */ new Map();
8635
8871
  home() {
8636
- return this.options.home ?? join22(herdHome(), "builds");
8872
+ return this.options.home ?? join23(herdHome(), "builds");
8637
8873
  }
8638
8874
  /** Where builds' own bare clones are, apart from the root's checkouts. */
8639
8875
  clonesDir() {
8640
- return join22(this.home(), "clones");
8876
+ return join23(this.home(), "clones");
8641
8877
  }
8642
8878
  /** Herd's bare clone of a build's repo, for builds, whether or not it's there yet. */
8643
8879
  storeFor(build) {
@@ -8658,8 +8894,8 @@ var BuildRunner = class _BuildRunner {
8658
8894
  const store = bareClonePath(this.clonesDir(), remote);
8659
8895
  const env = this.options.gitEnv?.();
8660
8896
  const name = (repoKey(build.repo) ?? build.repo).replace(/[^A-Za-z0-9._-]+/g, "_");
8661
- const dir = join22(this.home(), name, String(slot));
8662
- mkdirSync17(join22(this.home(), name), { recursive: true });
8897
+ const dir = join23(this.home(), name, String(slot));
8898
+ mkdirSync17(join23(this.home(), name), { recursive: true });
8663
8899
  await withStoreLock(
8664
8900
  store,
8665
8901
  async () => {
@@ -8698,30 +8934,30 @@ var BuildRunner = class _BuildRunner {
8698
8934
  /** Makes `node_modules` match the checkout's lockfile: kept, copied from the cache, or installed. */
8699
8935
  async ensureModules(dir) {
8700
8936
  const wanted = lockHash(dir);
8701
- const marker = join22(dir, "node_modules", ".herd-lock");
8702
- if (existsSync15(marker) && readFileSync16(marker, "utf8") === wanted) return;
8703
- 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);
8704
8940
  for (const folder of moduleFolders(dir))
8705
- rmSync13(join22(dir, folder), { recursive: true, force: true });
8706
- if (existsSync15(join22(cache, ".complete"))) {
8707
- for (const folder of readFileSync16(join22(cache, ".complete"), "utf8").split("\n").filter(Boolean)) {
8708
- 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));
8709
8945
  }
8710
8946
  } else {
8711
8947
  this.options.log?.(`Installing node_modules for lockfile ${wanted.slice(0, 8)}.`);
8712
- await (this.options.install ?? npmCi)(dir);
8713
- 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-"));
8714
8950
  const folders = moduleFolders(dir);
8715
8951
  for (const folder of folders) {
8716
- mkdirSync17(join22(staging, folder, ".."), { recursive: true });
8717
- cloneFolder(join22(dir, folder), join22(staging, folder));
8952
+ mkdirSync17(join23(staging, folder, ".."), { recursive: true });
8953
+ cloneFolder(join23(dir, folder), join23(staging, folder));
8718
8954
  }
8719
- writeFileSync13(join22(staging, ".complete"), folders.join("\n"));
8720
- 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 });
8721
8957
  rmSync13(cache, { recursive: true, force: true });
8722
8958
  execFileSync5("mv", [staging, cache]);
8723
8959
  }
8724
- mkdirSync17(join22(dir, "node_modules"), { recursive: true });
8960
+ mkdirSync17(join23(dir, "node_modules"), { recursive: true });
8725
8961
  writeFileSync13(marker, wanted);
8726
8962
  }
8727
8963
  /**
@@ -8732,7 +8968,7 @@ var BuildRunner = class _BuildRunner {
8732
8968
  const env = {
8733
8969
  PATH: process.env.PATH ?? "/usr/bin:/bin",
8734
8970
  HOME: home,
8735
- TMPDIR: join22(home, "tmp"),
8971
+ TMPDIR: join23(home, "tmp"),
8736
8972
  LANG: process.env.LANG ?? "en_US.UTF-8",
8737
8973
  HERD_BUILD_ID: build.id,
8738
8974
  HERD_BUILD_SECRET: build.secret,
@@ -8744,7 +8980,7 @@ var BuildRunner = class _BuildRunner {
8744
8980
  // The sandbox's proxy is the only way out, and Node's own requests use it only when asked.
8745
8981
  NODE_USE_ENV_PROXY: "1",
8746
8982
  // Where `srt` points TMPDIR inside the sandbox: the build's own temporary folder.
8747
- CLAUDE_CODE_TMPDIR: join22(home, "tmp")
8983
+ CLAUDE_CODE_TMPDIR: join23(home, "tmp")
8748
8984
  };
8749
8985
  return env;
8750
8986
  }
@@ -8755,8 +8991,8 @@ var BuildRunner = class _BuildRunner {
8755
8991
  /** Runs `scripts/build.mjs` from the build's own commit, in a process group of its own. */
8756
8992
  async run(build, slot) {
8757
8993
  const dir = await this.prepare(build, slot);
8758
- const home = mkdtempSync(join22(this.options.scratch ?? scratchRoot(), "hb-"));
8759
- mkdirSync17(join22(home, "tmp"), { recursive: true });
8994
+ const home = mkdtempSync(join23(this.options.scratch ?? scratchRoot(), "hb-"));
8995
+ mkdirSync17(join23(home, "tmp"), { recursive: true });
8760
8996
  const run4 = randomUUID4();
8761
8997
  const env = {
8762
8998
  ..._BuildRunner.environment(build, home),
@@ -8766,11 +9002,11 @@ var BuildRunner = class _BuildRunner {
8766
9002
  env.HERD_CHECK_CPUS = String(Math.max(1, Math.floor(this.options.cpus())));
8767
9003
  if (this.options.memoryMb)
8768
9004
  env.HERD_CHECK_MEMORY_MB = String(Math.max(1, Math.floor(this.options.memoryMb())));
8769
- let command = [process.execPath, join22(dir, "scripts", "build.mjs")];
9005
+ let command = [process.execPath, join23(dir, "scripts", "build.mjs")];
8770
9006
  let policyPath = null;
8771
9007
  if (this.sandboxed()) {
8772
9008
  const policy = buildPolicy({
8773
- ownerHome: this.options.ownerHome ?? homedir6(),
9009
+ ownerHome: this.options.ownerHome ?? homedir7(),
8774
9010
  root: this.options.root(),
8775
9011
  convos: convosHome(this.options.root()),
8776
9012
  worktree: dir,
@@ -8781,8 +9017,8 @@ var BuildRunner = class _BuildRunner {
8781
9017
  main: build.main,
8782
9018
  buildsUrl: build.url
8783
9019
  });
8784
- mkdirSync17(join22(this.home(), "policies"), { recursive: true });
8785
- 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`);
8786
9022
  writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
8787
9023
  env.HERD_BUILD_SANDBOX = describePolicy(policy);
8788
9024
  command = [process.execPath, HERD_BIN, "build-sandbox", policyPath, "--", ...command];
@@ -8831,7 +9067,7 @@ var BuildRunner = class _BuildRunner {
8831
9067
  * It goes only by where they run from, never by a build ID, since the daemon a build's own tests
8832
9068
  * start must never stop the build it runs in.
8833
9069
  */
8834
- static async sweep(home = join22(herdHome(), "builds"), list = listProcesses) {
9070
+ static async sweep(home = join23(herdHome(), "builds"), list = listProcesses) {
8835
9071
  const left = buildProcesses(list(), null, home);
8836
9072
  await stopPids(left);
8837
9073
  return left.length;
@@ -8843,37 +9079,37 @@ var BuildRunner = class _BuildRunner {
8843
9079
  */
8844
9080
  static async probe(options = { buildsUrl: "" }) {
8845
9081
  if (insideBuildSandbox()) return { ok: true };
8846
- const herd = options.home ?? join22(herdHome(), "builds");
8847
- const dir = mkdtempSync(join22(scratchRoot(), "hp-"));
8848
- 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");
8849
9085
  try {
8850
9086
  mkdirSync17(herd, { recursive: true });
8851
- 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));
8852
9088
  writeFileSync13(secret, "probe");
8853
9089
  const policy = buildPolicy({
8854
- ownerHome: join22(dir, "owner"),
8855
- root: join22(dir, "owner"),
8856
- convos: join22(dir, "owner"),
8857
- worktree: join22(dir, "work"),
8858
- store: join22(dir, "git"),
8859
- worktreeGitDir: join22(dir, "git"),
8860
- 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"),
8861
9097
  nodePrefix: nodePrefix(),
8862
9098
  main: false,
8863
9099
  buildsUrl: options.buildsUrl || "https://builds.herdingchats.com"
8864
9100
  });
8865
- const policyPath = join22(herd, "probe-policy.json");
9101
+ const policyPath = join23(herd, "probe-policy.json");
8866
9102
  writeFileSync13(policyPath, JSON.stringify(policy), { mode: 384 });
8867
9103
  const script = [
8868
9104
  'const fs = require("node:fs");',
8869
9105
  `try { fs.readFileSync(${JSON.stringify(secret)}); process.exit(10); } catch {}`,
8870
- `fs.writeFileSync(${JSON.stringify(join22(dir, "home", "ok"))}, "ok");`
9106
+ `fs.writeFileSync(${JSON.stringify(join23(dir, "home", "ok"))}, "ok");`
8871
9107
  ].join(" ");
8872
9108
  const child = spawn9(
8873
9109
  process.execPath,
8874
9110
  [HERD_BIN, "build-sandbox", policyPath, "--", process.execPath, "-e", script],
8875
9111
  {
8876
- 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") },
8877
9113
  stdio: ["ignore", "ignore", "pipe"]
8878
9114
  }
8879
9115
  );
@@ -8887,7 +9123,7 @@ var BuildRunner = class _BuildRunner {
8887
9123
  clearTimeout(timer);
8888
9124
  if (status2 === 10)
8889
9125
  return { ok: false, reason: "the sandbox let a build read the owner's files" };
8890
- if (status2 !== 0 || !existsSync15(join22(dir, "home", "ok"))) {
9126
+ if (status2 !== 0 || !existsSync16(join23(dir, "home", "ok"))) {
8891
9127
  const why = stderr.trim().split("\n").at(-1) || `the probe exited with ${status2}`;
8892
9128
  return { ok: false, reason: why.slice(0, 300) };
8893
9129
  }
@@ -9242,7 +9478,7 @@ var CodexDriver = class {
9242
9478
 
9243
9479
  // src/daemon.ts
9244
9480
  init_define_HERD_PACKAGE();
9245
- import { join as join23 } from "node:path";
9481
+ import { join as join24 } from "node:path";
9246
9482
 
9247
9483
  // src/dispatch-connection.ts
9248
9484
  init_define_HERD_PACKAGE();
@@ -9346,7 +9582,7 @@ var DispatchConnection = class {
9346
9582
 
9347
9583
  // src/daemon.ts
9348
9584
  var CONFIG_POLL_MS = 2e3;
9349
- var daemonSocketPath = () => join23(herdHome(), "daemon.sock");
9585
+ var daemonSocketPath = () => join24(herdHome(), "daemon.sock");
9350
9586
  var Daemon = class {
9351
9587
  constructor(options) {
9352
9588
  this.options = options;
@@ -9558,7 +9794,7 @@ var Daemon = class {
9558
9794
 
9559
9795
  // src/folders.ts
9560
9796
  init_define_HERD_PACKAGE();
9561
- 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";
9562
9798
  import { dirname as dirname13, resolve as resolve4 } from "node:path";
9563
9799
  var FOLDER_WAIT_MS = 2 * 60 * 1e3;
9564
9800
  var FOLDER_CHECK_MS = 30 * 1e3;
@@ -9570,7 +9806,7 @@ function linkOf(path) {
9570
9806
  return null;
9571
9807
  }
9572
9808
  }
9573
- function missingFolder(path, required, { exists = existsSync16, linkTarget = linkOf } = {}) {
9809
+ function missingFolder(path, required, { exists = existsSync17, linkTarget = linkOf } = {}) {
9574
9810
  if (exists(path)) return null;
9575
9811
  const target = linkTarget(path);
9576
9812
  if (target) return { path, target };
@@ -9773,23 +10009,23 @@ init_define_HERD_PACKAGE();
9773
10009
  import { execFileSync as execFileSync6 } from "node:child_process";
9774
10010
  import {
9775
10011
  appendFileSync as appendFileSync2,
9776
- existsSync as existsSync17,
10012
+ existsSync as existsSync18,
9777
10013
  mkdirSync as mkdirSync18,
9778
10014
  readFileSync as readFileSync17,
9779
10015
  renameSync as renameSync6,
9780
10016
  writeFileSync as writeFileSync15
9781
10017
  } from "node:fs";
9782
- import { isAbsolute as isAbsolute3, join as join24 } from "node:path";
9783
- 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");
9784
10020
  var git3 = (cwd, ...args) => execFileSync6("git", ["-C", cwd, ...args], {
9785
10021
  encoding: "utf8",
9786
10022
  stdio: ["ignore", "pipe", "pipe"]
9787
10023
  }).trim();
9788
10024
  var shellQuote2 = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
9789
10025
  function writeWorktreeSettings(worktree, settings, ownCommand) {
9790
- const path = join24(worktree, LOCAL_SETTINGS);
10026
+ const path = join25(worktree, LOCAL_SETTINGS);
9791
10027
  let current = {};
9792
- if (existsSync17(path)) {
10028
+ if (existsSync18(path)) {
9793
10029
  try {
9794
10030
  const parsed = JSON.parse(readFileSync17(path, "utf8"));
9795
10031
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
@@ -9811,7 +10047,7 @@ function writeWorktreeSettings(worktree, settings, ownCommand) {
9811
10047
  }
9812
10048
  next.hooks = hooks;
9813
10049
  }
9814
- mkdirSync18(join24(worktree, ".claude"), { recursive: true });
10050
+ mkdirSync18(join25(worktree, ".claude"), { recursive: true });
9815
10051
  const temporary = `${path}.herd-${process.pid}`;
9816
10052
  writeFileSync15(temporary, `${JSON.stringify(next, null, 2)}
9817
10053
  `);
@@ -9831,10 +10067,10 @@ function ignoreLocalSettings(worktree) {
9831
10067
  } catch {
9832
10068
  return;
9833
10069
  }
9834
- const info = join24(isAbsolute3(common) ? common : join24(worktree, common), "info");
9835
- const exclude = join24(info, "exclude");
10070
+ const info = join25(isAbsolute4(common) ? common : join25(worktree, common), "info");
10071
+ const exclude = join25(info, "exclude");
9836
10072
  const line2 = "/.claude/settings.local.json";
9837
- const existing = existsSync17(exclude) ? readFileSync17(exclude, "utf8") : "";
10073
+ const existing = existsSync18(exclude) ? readFileSync17(exclude, "utf8") : "";
9838
10074
  if (existing.split("\n").includes(line2)) return;
9839
10075
  mkdirSync18(info, { recursive: true });
9840
10076
  appendFileSync2(exclude, `${existing && !existing.endsWith("\n") ? "\n" : ""}${line2}
@@ -9879,7 +10115,7 @@ function loginShellEnv() {
9879
10115
 
9880
10116
  // src/machine-checks.ts
9881
10117
  init_define_HERD_PACKAGE();
9882
- import { existsSync as existsSync18, rmSync as rmSync15 } from "node:fs";
10118
+ import { existsSync as existsSync19, rmSync as rmSync15 } from "node:fs";
9883
10119
  var REQUEST_POLL_MS = 2e3;
9884
10120
  var MachineChecks = class {
9885
10121
  constructor(options) {
@@ -9925,7 +10161,7 @@ var MachineChecks = class {
9925
10161
  /** `friday start` asked for every agent to be activated again. */
9926
10162
  takeRequest() {
9927
10163
  const path = activationRequestPath(this.options.herdHome);
9928
- if (!existsSync18(path)) return;
10164
+ if (!existsSync19(path)) return;
9929
10165
  rmSync15(path, { force: true });
9930
10166
  this.activateChosen(true);
9931
10167
  }
@@ -9977,15 +10213,15 @@ init_define_HERD_PACKAGE();
9977
10213
  import { spawn as spawn11 } from "node:child_process";
9978
10214
  import { mkdirSync as mkdirSync19, readdirSync as readdirSync8, rmSync as rmSync16 } from "node:fs";
9979
10215
  import { connect as connect2 } from "node:net";
9980
- import { join as join25 } from "node:path";
10216
+ import { join as join26 } from "node:path";
9981
10217
  init_self();
9982
10218
  import {
9983
10219
  HOST_PROTOCOL,
9984
10220
  hostVersion
9985
10221
  } from "./terminal-host.mjs";
9986
- var hostsDir = () => join25(herdHome(), "terminals");
9987
- var legacyHostSocketPath = () => join25(herdHome(), "terminals.sock");
9988
- 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`);
9989
10225
  var speaksProtocol = (protocol) => protocol === HOST_PROTOCOL || protocol === HOST_PROTOCOL - 1;
9990
10226
  var sleep6 = (ms2) => new Promise((resolve7) => setTimeout(resolve7, ms2));
9991
10227
  function askHost(path, request) {
@@ -10036,7 +10272,7 @@ var HostTerminals = class {
10036
10272
  names = readdirSync8(dir).filter((name) => name.endsWith(".sock"));
10037
10273
  } catch {
10038
10274
  }
10039
- const paths = [legacyHostSocketPath(), ...names.map((name) => join25(dir, name))];
10275
+ const paths = [legacyHostSocketPath(), ...names.map((name) => join26(dir, name))];
10040
10276
  const found = [];
10041
10277
  for (const path of paths) {
10042
10278
  try {
@@ -10275,7 +10511,7 @@ var UsageReporter = class {
10275
10511
  init_define_HERD_PACKAGE();
10276
10512
  init_src();
10277
10513
  import { mkdirSync as mkdirSync23, readFileSync as readFileSync18, writeFileSync as writeFileSync18 } from "node:fs";
10278
- import { join as join32 } from "node:path";
10514
+ import { join as join33 } from "node:path";
10279
10515
 
10280
10516
  // src/workspaces/codex.ts
10281
10517
  init_define_HERD_PACKAGE();
@@ -10287,7 +10523,7 @@ init_define_HERD_PACKAGE();
10287
10523
  init_define_HERD_PACKAGE();
10288
10524
  init_src();
10289
10525
  init_self();
10290
- import { join as join26 } from "node:path";
10526
+ import { join as join27 } from "node:path";
10291
10527
  import { z as z26 } from "zod";
10292
10528
  function hasThread(events) {
10293
10529
  return events.some(
@@ -10322,7 +10558,7 @@ function filesByMessage(events) {
10322
10558
  }
10323
10559
  return files;
10324
10560
  }
10325
- var FILES_FOLDER = join26(".herd", "files");
10561
+ var FILES_FOLDER = join27(".herd", "files");
10326
10562
  function safeFileName(name) {
10327
10563
  const chars = Array.from(name.replace(/[/\\\p{Cc}]/gu, "_").trim());
10328
10564
  while (Buffer.byteLength(chars.join("")) > 200) chars.shift();
@@ -10555,11 +10791,11 @@ async function claimChange(ws, workspace, change) {
10555
10791
  // src/workspaces/delivery.ts
10556
10792
  init_define_HERD_PACKAGE();
10557
10793
  import { randomBytes as randomBytes6, randomUUID as randomUUID6 } from "node:crypto";
10558
- import { join as join30 } from "node:path";
10794
+ import { join as join31 } from "node:path";
10559
10795
 
10560
10796
  // src/workspaces/repos.ts
10561
10797
  init_define_HERD_PACKAGE();
10562
- import { join as join27 } from "node:path";
10798
+ import { join as join28 } from "node:path";
10563
10799
  var rootOf = (ws) => ws.options.root?.() ?? ws.options.config().root ?? defaultRoot();
10564
10800
  var gitEnvOf = (ws) => ws.options.gitEnv?.() ?? loginShellEnv();
10565
10801
  function knownRemotes(ws) {
@@ -10671,7 +10907,7 @@ async function addHints(ws, workspace, hints, handedOff) {
10671
10907
  const added = await addWorktree({
10672
10908
  root,
10673
10909
  remote: cloneSource(hint.remote, knownRemotes(ws)),
10674
- path: join27(workspace.folder, place.name),
10910
+ path: join28(workspace.folder, place.name),
10675
10911
  branch,
10676
10912
  ...handedOff ? { continueFrom: hint.branch ?? branch } : {},
10677
10913
  env: gitEnvOf(ws),
@@ -10723,14 +10959,14 @@ function handoffRepos(workspace) {
10723
10959
  return workspace.worktrees.map((repo) => ({
10724
10960
  folder: repo.folder,
10725
10961
  branch: repo.branch,
10726
- unpushed: hasUnpushed(join27(workspace.folder, repo.folder))
10962
+ unpushed: hasUnpushed(join28(workspace.folder, repo.folder))
10727
10963
  }));
10728
10964
  }
10729
10965
 
10730
10966
  // src/workspaces/setup.ts
10731
10967
  init_define_HERD_PACKAGE();
10732
10968
  import { chmodSync as chmodSync4, mkdirSync as mkdirSync20, writeFileSync as writeFileSync16 } from "node:fs";
10733
- import { join as join28 } from "node:path";
10969
+ import { join as join29 } from "node:path";
10734
10970
 
10735
10971
  // src/hooks.ts
10736
10972
  init_define_HERD_PACKAGE();
@@ -10763,7 +10999,7 @@ function configureClaude(ws, workspace) {
10763
10999
  configureClaudeIn(workspace.folder, (line2) => ws.log(`${workspace.conversationId}: ${line2}`));
10764
11000
  }
10765
11001
  function configureClaudeIn(folder, log) {
10766
- const herd = shellQuote2(join28(herdPathDir(), "herd"));
11002
+ const herd = shellQuote2(join29(herdPathDir(), "herd"));
10767
11003
  try {
10768
11004
  const written = writeWorktreeSettings(
10769
11005
  folder,
@@ -10788,9 +11024,9 @@ function configureClaudeIn(folder, log) {
10788
11024
  }
10789
11025
  }
10790
11026
  function herdPathDir() {
10791
- const dir = join28(herdHome(), "bin");
11027
+ const dir = join29(herdHome(), "bin");
10792
11028
  mkdirSync20(dir, { recursive: true, mode: 448 });
10793
- const shim = join28(dir, "herd");
11029
+ const shim = join29(dir, "herd");
10794
11030
  writeFileSync16(shim, `#!/bin/sh
10795
11031
  exec "${process.execPath}" "${herdBin}" "$@"
10796
11032
  `);
@@ -10976,7 +11212,7 @@ async function followCheckIn(ws, workspace, reading) {
10976
11212
  init_define_HERD_PACKAGE();
10977
11213
  import { randomUUID as randomUUID5 } from "node:crypto";
10978
11214
  import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync21 } from "node:fs";
10979
- import { join as join29 } from "node:path";
11215
+ import { join as join30 } from "node:path";
10980
11216
  function needsInvestigator(ws, workspace, reading) {
10981
11217
  const allowed = ws.options.canInvestigate ? ws.options.canInvestigate() : workspace.agent === "claude-code";
10982
11218
  if (!allowed || !reading || workspace.investigation) return false;
@@ -11141,9 +11377,9 @@ async function followInvestigation(ws, workspace) {
11141
11377
  }
11142
11378
  function saveScreen(ws, conversationId, screen, key) {
11143
11379
  try {
11144
- const dir = join29(herdHome(), "dialogs");
11380
+ const dir = join30(herdHome(), "dialogs");
11145
11381
  mkdirSync21(dir, { recursive: true });
11146
- const file = join29(dir, `${conversationId}-${Date.now()}.txt`);
11382
+ const file = join30(dir, `${conversationId}-${Date.now()}.txt`);
11147
11383
  appendFileSync3(file, `${screen.join("\n")}
11148
11384
 
11149
11385
  Way out: ${key}
@@ -11348,7 +11584,7 @@ async function deliver(ws, conversationId, delivery) {
11348
11584
  await ensureLeadTerminal(ws, workspace);
11349
11585
  const codex = workspace.agent === "codex";
11350
11586
  if (workspace.agent === "agy") {
11351
- writeAgyFiles(workspace.folder, shellQuote2(join30(herdPathDir(), "herd")));
11587
+ writeAgyFiles(workspace.folder, shellQuote2(join31(herdPathDir(), "herd")));
11352
11588
  } else if (!codex) configureClaude(ws, workspace);
11353
11589
  const ownId = codex || workspace.agent === "agy";
11354
11590
  if (!workspace.started) {
@@ -11496,7 +11732,7 @@ import {
11496
11732
  statSync as statSync9,
11497
11733
  writeFileSync as writeFileSync17
11498
11734
  } from "node:fs";
11499
- 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";
11500
11736
  import { Readable } from "node:stream";
11501
11737
  import { pipeline } from "node:stream/promises";
11502
11738
 
@@ -11510,7 +11746,7 @@ import { readFile } from "node:fs/promises";
11510
11746
  init_define_HERD_PACKAGE();
11511
11747
  init_src();
11512
11748
  import { realpathSync as realpathSync9, statSync as statSync8 } from "node:fs";
11513
- 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";
11514
11750
  var TYPES = {
11515
11751
  ".png": "image/png",
11516
11752
  ".jpg": "image/jpeg",
@@ -11535,7 +11771,7 @@ var TYPES = {
11535
11771
  var mediaTypeFor = (path) => TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
11536
11772
  function isInside(root, path) {
11537
11773
  const between = relative2(root, path);
11538
- return between === "" || !between.startsWith("..") && !isAbsolute4(between);
11774
+ return between === "" || !between.startsWith("..") && !isAbsolute5(between);
11539
11775
  }
11540
11776
  function resolveAttachments(worktree, files) {
11541
11777
  let root;
@@ -11565,7 +11801,7 @@ function resolveAttachments(worktree, files) {
11565
11801
  }
11566
11802
  resolved.push({
11567
11803
  path: real4,
11568
- name: basename3(file.path),
11804
+ name: basename4(file.path),
11569
11805
  type: mediaTypeFor(real4),
11570
11806
  size: stats.size,
11571
11807
  ...file.title ? { title: file.title } : {},
@@ -12498,10 +12734,10 @@ async function bringIn(ws, workspace, files) {
12498
12734
  return listed;
12499
12735
  }
12500
12736
  async function download(ws, workspace, file) {
12501
- const path = join31(FILES_FOLDER, safeFileName(file.id), safeFileName(file.name));
12502
- 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);
12503
12739
  if (statSync9(target, { throwIfNoEntry: false })?.size === file.size) return path;
12504
- const herd = join31(workspace.folder, ".herd");
12740
+ const herd = join32(workspace.folder, ".herd");
12505
12741
  if (lstatSync4(herd, { throwIfNoEntry: false })?.isSymbolicLink()) {
12506
12742
  throw new Error("its folder leads outside the worktree");
12507
12743
  }
@@ -12509,7 +12745,7 @@ async function download(ws, workspace, file) {
12509
12745
  if (!realpathSync10(dirname14(target)).startsWith(realpathSync10(workspace.folder) + sep2)) {
12510
12746
  throw new Error("its folder leads outside the worktree");
12511
12747
  }
12512
- writeFileSync17(join31(herd, ".gitignore"), "*\n");
12748
+ writeFileSync17(join32(herd, ".gitignore"), "*\n");
12513
12749
  const response = await ws.api(
12514
12750
  `${ws.conversationPath(workspace.conversationId)}/files/${encodeURIComponent(file.id)}`
12515
12751
  );
@@ -12799,7 +13035,7 @@ var Workspaces = class {
12799
13035
  return this.driver;
12800
13036
  }
12801
13037
  // ---- State, kept on disk so a restarted daemon picks up where it was.
12802
- statePath = () => join32(herdHome(), "state.json");
13038
+ statePath = () => join33(herdHome(), "state.json");
12803
13039
  load() {
12804
13040
  try {
12805
13041
  return stateSchema.parse(JSON.parse(readFileSync18(this.statePath(), "utf8"))).workspaces;
@@ -13028,7 +13264,7 @@ async function runDaemon(args) {
13028
13264
  const folders = new FolderWatch({
13029
13265
  check: () => machineFolderProblem(machineRoot(), convosPath(machineRoot())),
13030
13266
  initial: missing,
13031
- statusPath: join33(herdHome(), "folder-unavailable.json"),
13267
+ statusPath: join34(herdHome(), "folder-unavailable.json"),
13032
13268
  ...process.env.HERD_FOLDER_CHECK_MS ? { everyMs: Number(process.env.HERD_FOLDER_CHECK_MS) } : {},
13033
13269
  onChange: () => daemon?.foldersChanged()
13034
13270
  });
@@ -13128,7 +13364,7 @@ async function runDaemon(args) {
13128
13364
  env: (secret) => envFor(workspaces, secret),
13129
13365
  // As for a conversation: Claude Code's hooks and status line, or agy's. Codex has none.
13130
13366
  configure: (agent, folder) => {
13131
- if (agent === "agy") writeAgyFiles(folder, shellQuote2(join33(herdPathDir(), "herd")));
13367
+ if (agent === "agy") writeAgyFiles(folder, shellQuote2(join34(herdPathDir(), "herd")));
13132
13368
  else if (agent === "claude-code") configureClaudeIn(folder, log);
13133
13369
  },
13134
13370
  ...shellCommand ? { shellCommand } : {},
@@ -13216,7 +13452,7 @@ async function runDaemon(args) {
13216
13452
  capacity: () => sandbox.ok && !folders.problem ? runnerCapacity(daemon?.configuration().config?.builds) : 0,
13217
13453
  // The repos builds have cloned here, as many as the service's hello takes, to put this runner
13218
13454
  // first for their builds.
13219
- 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),
13220
13456
  runner: new BuildRunner({
13221
13457
  root: () => machineRoot(),
13222
13458
  gitEnv,
@@ -13274,10 +13510,10 @@ init_define_HERD_PACKAGE();
13274
13510
  // src/agy-hooks.ts
13275
13511
  init_define_HERD_PACKAGE();
13276
13512
  import { readFileSync as readFileSync20 } from "node:fs";
13277
- import { basename as basename4 } from "node:path";
13513
+ import { basename as basename5 } from "node:path";
13278
13514
  var text3 = (value) => typeof value === "string" ? value : "";
13279
13515
  function lastResponse(transcriptPath) {
13280
- if (!/^transcript.*\.jsonl$/.test(basename4(transcriptPath))) return "";
13516
+ if (!/^transcript.*\.jsonl$/.test(basename5(transcriptPath))) return "";
13281
13517
  let lines;
13282
13518
  try {
13283
13519
  lines = readFileSync20(transcriptPath, "utf8").split("\n");
@@ -13403,8 +13639,8 @@ init_define_HERD_PACKAGE();
13403
13639
  import { spawn as spawn12 } from "node:child_process";
13404
13640
  import { readFileSync as readFileSync21 } from "node:fs";
13405
13641
  import { connect as connect3 } from "node:net";
13406
- import { homedir as homedir7 } from "node:os";
13407
- import { join as join34 } from "node:path";
13642
+ import { homedir as homedir8 } from "node:os";
13643
+ import { join as join35 } from "node:path";
13408
13644
  var MAX_INPUT = 64 * 1024;
13409
13645
  var REPORT_TIMEOUT_MS = 300;
13410
13646
  var OWN_COMMAND_TIMEOUT_MS = 5e3;
@@ -13438,10 +13674,10 @@ function agyStateFrom(input) {
13438
13674
  const state = input?.agent_state;
13439
13675
  return state === "working" || state === "idle" ? state : null;
13440
13676
  }
13441
- function ownAgyStatusLine(home = homedir7()) {
13677
+ function ownAgyStatusLine(home = homedir8()) {
13442
13678
  try {
13443
13679
  const settings = JSON.parse(
13444
- readFileSync21(join34(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
13680
+ readFileSync21(join35(home, ".gemini", "antigravity-cli", "settings.json"), "utf8")
13445
13681
  );
13446
13682
  const command = settings.statusLine?.command;
13447
13683
  if (typeof command !== "string" || !command.trim()) return null;
@@ -13460,8 +13696,8 @@ function commandIn(path) {
13460
13696
  return null;
13461
13697
  }
13462
13698
  }
13463
- function ownStatusLine(projectDir, home = homedir7()) {
13464
- 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"));
13465
13701
  }
13466
13702
  function runOwnCommand(command, input, cwd, timeoutMs = OWN_COMMAND_TIMEOUT_MS) {
13467
13703
  return new Promise((resolve7) => {
@@ -13883,7 +14119,7 @@ async function runTerminalHost(rest) {
13883
14119
 
13884
14120
  // src/commands/repo.ts
13885
14121
  init_define_HERD_PACKAGE();
13886
- import { join as join35 } from "node:path";
14122
+ import { join as join36 } from "node:path";
13887
14123
  import { z as z33 } from "zod";
13888
14124
  var REPO_HELP = `Usage: herd repo <url or owner/name> [folder]
13889
14125
 
@@ -13971,7 +14207,7 @@ async function runRepo(rest) {
13971
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>.`
13972
14208
  );
13973
14209
  }
13974
- const path = join35(here.folder, place.name);
14210
+ const path = join36(here.folder, place.name);
13975
14211
  const recorded = here.recorded.some((repo) => repo.folder === place.name);
13976
14212
  if (place.existing && recorded) {
13977
14213
  const branch = here.recorded.find((repo) => repo.folder === place.name)?.branch ?? here.branch;