claudish 13.0.0 → 13.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,104 @@
1
+ // Which environment names Bun's env-file autoload sets in a directory — asked of Bun.
2
+ //
3
+ // Bun loads `.env`, `.env.local`, `.env.<NODE_ENV>` and `.env.<NODE_ENV>.local` (plus
4
+ // any `--env-file`) into `process.env` before a script runs, and marks nothing as
5
+ // coming from a file. Every claudish launch path turns that off with `--no-env-file`.
6
+ // When a process was started without it anyway (`bun <path>/claudish.cjs`,
7
+ // `bun <path>/index.ts`), claudish cannot recover the real environment, but it can
8
+ // learn which NAMES the files set, and drop those. Bun is asked rather than imitated:
9
+ // its file list and its parser differ from dotenv's and change between releases, and a
10
+ // re-implementation of either is how an earlier check was bypassed.
11
+ //
12
+ // The probe runs Bun in the same directory with an empty environment and prints the
13
+ // names it ends up with. It runs once with NODE_ENV unset and once with the current
14
+ // value, because NODE_ENV picks the file list and a `.env` may itself have set it.
15
+ // `--config=<null device>` keeps the directory's `bunfig.toml` out, so the probe runs
16
+ // no `preload` from the checkout; a bunfig can only turn the autoload off, which makes
17
+ // this answer a superset of what was loaded, the safe direction.
18
+ //
19
+ // Shared on purpose: the npm launcher requires it, and so does `src/launch-env.ts`
20
+ // (lazily, from the package's `bin/`), so there is one implementation.
21
+
22
+ const { spawnSync } = require("node:child_process");
23
+ const { devNull } = require("node:os");
24
+
25
+ const PROBE_TIMEOUT_MS = 15_000;
26
+ const PRINT_NAMES = "JSON.stringify(Object.keys(process.env))";
27
+
28
+ /** Bun's flag that turns its env-file autoload off. */
29
+ const NO_ENV_FILE_FLAG = "--no-env-file";
30
+
31
+ /** The `--env-file` arguments in Bun's own argv, so the probe loads the same files. */
32
+ function envFileArgs(execArgv) {
33
+ const out = [];
34
+ for (let i = 0; i < execArgv.length; i++) {
35
+ const arg = execArgv[i];
36
+ if (arg.startsWith("--env-file=")) out.push(arg);
37
+ else if (arg === "--env-file" && i + 1 < execArgv.length) out.push(arg, execArgv[++i]);
38
+ }
39
+ return out;
40
+ }
41
+
42
+ /** The minimal environment a probe needs to start on this platform. */
43
+ function probeBaseEnv(platform, env) {
44
+ if (platform !== "win32") return {};
45
+ const base = {};
46
+ for (const key of ["SystemRoot", "SYSTEMROOT"]) if (env[key]) base[key] = env[key];
47
+ return base;
48
+ }
49
+
50
+ /** One probe run: the names Bun's process has under `runEnv`, or null on any failure. */
51
+ function probeNames(bunPath, args, cwd, runEnv) {
52
+ const r = spawnSync(bunPath, args, {
53
+ cwd,
54
+ env: runEnv,
55
+ encoding: "utf-8",
56
+ stdio: ["ignore", "pipe", "ignore"],
57
+ timeout: PROBE_TIMEOUT_MS,
58
+ windowsHide: true,
59
+ });
60
+ if (r.status !== 0 || typeof r.stdout !== "string") return null;
61
+ try {
62
+ const parsed = JSON.parse(r.stdout.trim().split("\n").pop() || "");
63
+ return Array.isArray(parsed) ? parsed.filter((name) => typeof name === "string") : null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ /**
70
+ * The names Bun's env-file loading sets in `cwd` for a Bun started with `execArgv`.
71
+ * Returns null when Bun could not answer: the caller must then fail closed.
72
+ */
73
+ function envFileKeys({ bunPath, execArgv, cwd, env, platform = process.platform }) {
74
+ const base = probeBaseEnv(platform, env);
75
+ const runs = [base];
76
+ if (env.NODE_ENV !== undefined) runs.push({ ...base, NODE_ENV: env.NODE_ENV });
77
+ const args = [...envFileArgs(execArgv), `--config=${devNull}`, "--print", PRINT_NAMES];
78
+ const names = new Set();
79
+ for (const runEnv of runs) {
80
+ const found = probeNames(bunPath, args, cwd, runEnv);
81
+ if (found === null) return null;
82
+ for (const name of found) if (!Object.hasOwn(runEnv, name)) names.add(name);
83
+ }
84
+ return [...names];
85
+ }
86
+
87
+ /** The one stderr line for names dropped because Bun's env-file autoload was on. */
88
+ function droppedByAutoloadNotice(names) {
89
+ const them = names.length === 1 ? "it" : "them";
90
+ return `[claudish] ignoring ${names.join(", ")}: Bun loaded this directory's env files before claudish started, so a repository may have set ${them}. claudish reads provider API keys and model settings from .env itself. Run the installed claudish command, or start Bun with ${NO_ENV_FILE_FLAG}.`;
91
+ }
92
+
93
+ /** The one stderr line when Bun could not say which names its env files set. */
94
+ function probeFailedNotice() {
95
+ return `[claudish] Bun loaded this directory's env files before claudish started, and claudish could not tell which values came from them, so it will not start. Run the installed claudish command, or start Bun with ${NO_ENV_FILE_FLAG}.`;
96
+ }
97
+
98
+ module.exports = {
99
+ NO_ENV_FILE_FLAG,
100
+ envFileArgs,
101
+ envFileKeys,
102
+ droppedByAutoloadNotice,
103
+ probeFailedNotice,
104
+ };
package/dist/index.js CHANGED
@@ -575,7 +575,7 @@ var init_onepassword_config = __esm(() => {
575
575
  });
576
576
 
577
577
  // src/version.ts
578
- var VERSION = "13.0.0";
578
+ var VERSION = "13.0.2";
579
579
 
580
580
  // src/logger.ts
581
581
  import { appendFile, existsSync as existsSync2, mkdirSync, readdirSync, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
@@ -29842,9 +29842,9 @@ async function flushStats() {
29842
29842
  }
29843
29843
  } catch {}
29844
29844
  }
29845
- function showMonthlyBanner() {
29845
+ function showMonthlyBanner(env = process.env) {
29846
29846
  try {
29847
- if (isStatsDisabledByEnv())
29847
+ if (isStatsDisabledByEnv() || isPaneChild(env))
29848
29848
  return;
29849
29849
  const profileConfig = loadConfig();
29850
29850
  const consent = profileConfig.stats;
@@ -29976,6 +29976,7 @@ Usage: claudish stats on|off|status|reset
29976
29976
  }
29977
29977
  var STATS_ENDPOINT = "https://claudish.com/v1/stats", FLUSH_INTERVAL_MS2, MONTHLY_INTERVAL_MS, SEND_TIMEOUT_MS = 5000, statsEnabled = false, initialized2 = false, claudishVersion2 = "", installMethod2 = "unknown", envAttributes;
29978
29978
  var init_stats = __esm(() => {
29979
+ init_child_env();
29979
29980
  init_profile_config();
29980
29981
  init_model_parser();
29981
29982
  init_stats_buffer();
@@ -68612,44 +68613,76 @@ import { basename as basename2, dirname as dirname14, join as join41 } from "pat
68612
68613
  function collapse(s) {
68613
68614
  return s.trim().replace(/\s+/g, " ");
68614
68615
  }
68615
- function parsePsTable(text) {
68616
+ function etimeSeconds(etime) {
68617
+ const [days, clock] = etime.includes("-") ? etime.split("-") : ["0", etime];
68618
+ const parts = (clock ?? "").split(":").map(Number);
68619
+ while (parts.length < 3)
68620
+ parts.unshift(0);
68621
+ const [h = 0, m = 0, s = 0] = parts;
68622
+ return Number(days) * 86400 + h * 3600 + m * 60 + s;
68623
+ }
68624
+ function parsePsTable(text, spawnedAtMs) {
68616
68625
  const rows = [];
68617
68626
  for (const line of text.split(`
68618
68627
  `)) {
68619
68628
  const m = line.match(PS_ROW);
68620
68629
  if (!m)
68621
68630
  continue;
68622
- rows.push({
68631
+ const row = {
68623
68632
  pid: Number(m[1]),
68624
68633
  ppid: Number(m[2]),
68625
68634
  pgid: Number(m[3]),
68626
- lstart: collapse(m[4] ?? ""),
68627
- command: (m[5] ?? "").trim()
68628
- });
68635
+ lstart: collapse(m[5] ?? ""),
68636
+ command: (m[6] ?? "").trim()
68637
+ };
68638
+ if (m[4] && spawnedAtMs !== undefined)
68639
+ row.startMs = spawnedAtMs - etimeSeconds(m[4]) * 1000;
68640
+ rows.push(row);
68629
68641
  }
68630
68642
  return rows;
68631
68643
  }
68644
+ function startedAt(row, startMs) {
68645
+ return row.startMs === undefined || Math.abs(row.startMs - startMs) < START_TOLERANCE_MS;
68646
+ }
68647
+ function pidMayBeAlive(pid) {
68648
+ if (!Number.isInteger(pid) || pid <= 0)
68649
+ return true;
68650
+ try {
68651
+ process.kill(pid, 0);
68652
+ return true;
68653
+ } catch (e) {
68654
+ return e.code !== "ESRCH";
68655
+ }
68656
+ }
68657
+ function processTableTrusted(rows) {
68658
+ return rows.some((r) => r.pid === process.pid);
68659
+ }
68660
+ function tableOf(stdout, spawnedAtMs) {
68661
+ if (stdout === null)
68662
+ return null;
68663
+ const rows = parsePsTable(stdout, spawnedAtMs);
68664
+ return processTableTrusted(rows) ? rows : null;
68665
+ }
68632
68666
  function readProcessTable() {
68667
+ const spawnedAt = Date.now();
68668
+ let stdout;
68633
68669
  try {
68634
- return parsePsTable(execFileSync2("ps", PS_ARGS, {
68635
- env: PS_ENV,
68636
- encoding: "utf8",
68637
- maxBuffer: 64 * 1024 * 1024,
68670
+ stdout = psSpawn.execFileSync("ps", PS_ARGS, {
68671
+ ...PS_OPTIONS,
68638
68672
  stdio: ["ignore", "pipe", "ignore"]
68639
- }));
68673
+ });
68640
68674
  } catch {
68641
- return [];
68675
+ stdout = null;
68642
68676
  }
68643
- }
68644
- function processTableTrusted(table) {
68645
- return table.some((r) => r.pid === process.pid);
68677
+ return tableOf(stdout, spawnedAt);
68646
68678
  }
68647
68679
  function readProcessTableAsync() {
68648
68680
  return new Promise((resolve) => {
68681
+ const spawnedAt = Date.now();
68649
68682
  try {
68650
- execFile2("ps", PS_ARGS, { env: PS_ENV, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 }, (err, stdout) => resolve(err ? [] : parsePsTable(String(stdout))));
68683
+ psSpawn.execFile("ps", PS_ARGS, PS_OPTIONS, (err, stdout) => resolve(tableOf(err ? null : String(stdout), spawnedAt)));
68651
68684
  } catch {
68652
- resolve([]);
68685
+ resolve(tableOf(null, spawnedAt));
68653
68686
  }
68654
68687
  });
68655
68688
  }
@@ -68666,12 +68699,12 @@ function isPaneMagmuxCommand(command, paneId) {
68666
68699
  return command.includes(magmuxNeedle(paneId));
68667
68700
  }
68668
68701
  function isPaneMagmux(pid, paneId, table) {
68669
- const rows = table ?? readProcessTable();
68702
+ const rows = table ?? readProcessTable() ?? [];
68670
68703
  const row = rows.find((r) => r.pid === pid);
68671
68704
  return !!row && isPaneMagmuxCommand(row.command, paneId);
68672
68705
  }
68673
68706
  function findPaneMagmux(paneId, table) {
68674
- return (table ?? readProcessTable()).filter((r) => isPaneMagmuxCommand(r.command, paneId));
68707
+ return (table ?? readProcessTable() ?? []).filter((r) => isPaneMagmuxCommand(r.command, paneId));
68675
68708
  }
68676
68709
  function paneIdentityMatches(command, pane) {
68677
68710
  return command.includes(sessionNeedle(pane.sessionUuid)) || command.includes(launcherNeedle(pane.ctlDir));
@@ -68824,11 +68857,12 @@ function watcherArgv(a) {
68824
68857
  function isPaneWatcherCommand(command, paneId) {
68825
68858
  return command.includes(` ${WATCHER_ARGV0} ${paneId} `);
68826
68859
  }
68827
- var PS_ARGS, PS_ROW, PS_ENV, LAUNCH_DIR_RE, PANE_ID_RE, WATCHER_FUNCTIONS = `
68860
+ var PS_ARGS, PS_ROW, START_TOLERANCE_MS = 2000, PS_ENV, PS_OPTIONS, psSpawn, LAUNCH_DIR_RE, PANE_ID_RE, WATCHER_FUNCTIONS = `
68828
68861
  set -f
68829
68862
  export LC_ALL=C
68830
68863
  PANE=$1 UUID=$2 CTL=$3 SOCKP=$4 REC=$5 TURN=$6 ROOT=$7
68831
68864
  pstable() { ps -ax -ww -o pid=,pgid=,lstart=,command=; }
68865
+ ps_ok() { ps -o pid= -p $$ >/dev/null 2>&1; }
68832
68866
  mag=""
68833
68867
  mag_ok() {
68834
68868
  case "$mag" in (''|*[!0-9]*) return 1;; esac
@@ -68877,6 +68911,9 @@ launch_dir_ok() {
68877
68911
  `, WATCHER_MAIN = `
68878
68912
  while IFS= read -r l; do [ "$l" = done ] && exit 0; done
68879
68913
  # EOF without "done": the owner is gone (SIGKILL included: the kernel closed its end).
68914
+ # No readable table (a sandbox refuses the setuid ps): nothing can be verified, so nothing
68915
+ # is signalled or removed; the files stay for a sweep that can read the table.
68916
+ ps_ok || exit 1
68880
68917
  find_mag
68881
68918
  find_pg
68882
68919
  for s in TERM KILL; do
@@ -68894,9 +68931,11 @@ launch_dir_ok "$TURN" && rm -rf -- "$TURN"
68894
68931
  exit 0
68895
68932
  `, WATCHER_SCRIPT, WATCHER_ARGV0 = "claudish-pane-watcher";
68896
68933
  var init_process_identity = __esm(() => {
68897
- PS_ARGS = ["-ax", "-ww", "-o", "pid=,ppid=,pgid=,lstart=,command="];
68898
- PS_ROW = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+\s+\S+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s?(.*)$/;
68934
+ PS_ARGS = ["-ax", "-ww", "-o", "pid=,ppid=,pgid=,etime=,lstart=,command="];
68935
+ PS_ROW = /^\s*(\d+)\s+(\d+)\s+(\d+)\s+(?:((?:\d+-)?(?:\d+:)?\d+:\d+)\s+)?(\S+\s+\S+\s+\d+\s+\d\d:\d\d:\d\d\s+\d{4})\s?(.*)$/;
68899
68936
  PS_ENV = { PATH: "/bin:/usr/bin:/usr/sbin:/sbin", LC_ALL: "C" };
68937
+ PS_OPTIONS = { env: PS_ENV, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 };
68938
+ psSpawn = { execFile: execFile2, execFileSync: execFileSync2 };
68900
68939
  LAUNCH_DIR_RE = /^launch-[A-Za-z0-9]{6}$/;
68901
68940
  PANE_ID_RE = /^[A-Za-z0-9_-]{1,64}$/;
68902
68941
  WATCHER_SCRIPT = `${WATCHER_FUNCTIONS}${WATCHER_MAIN}`;
@@ -69570,7 +69609,10 @@ var init_magmux_client = __esm(() => {
69570
69609
 
69571
69610
  // src/pane/pane-registry.ts
69572
69611
  import {
69612
+ constants,
69613
+ accessSync,
69573
69614
  existsSync as existsSync31,
69615
+ lstatSync as lstatSync3,
69574
69616
  readFileSync as readFileSync29,
69575
69617
  readdirSync as readdirSync4,
69576
69618
  renameSync as renameSync4,
@@ -69582,14 +69624,21 @@ import { join as join43 } from "path";
69582
69624
  function ownerStartOfSelf() {
69583
69625
  if (ownStart)
69584
69626
  return ownStart;
69585
- const row = readProcessTable().find((r) => r.pid === process.pid);
69627
+ const row = readProcessTable()?.find((r) => r.pid === process.pid);
69586
69628
  if (row)
69587
69629
  ownStart = row.lstart;
69588
69630
  return row?.lstart ?? "";
69589
69631
  }
69590
- function ownerAlive(rec, table) {
69591
- const row = table.find((r) => r.pid === rec.ownerPid);
69592
- return !!row && row.lstart === rec.ownerStart;
69632
+ function ownerAlive(owner, table) {
69633
+ if (!table)
69634
+ return pidMayBeAlive(owner.ownerPid);
69635
+ const row = table.find((r) => r.pid === owner.ownerPid);
69636
+ if (!row)
69637
+ return false;
69638
+ if (owner.ownerStart)
69639
+ return row.lstart === owner.ownerStart;
69640
+ const minted = ownerOfPaneId(owner.paneId);
69641
+ return !minted || startedAt(row, minted.startMs);
69593
69642
  }
69594
69643
  function recordsDir(root) {
69595
69644
  return join43(root, "panes");
@@ -69722,7 +69771,9 @@ async function tick() {
69722
69771
  return;
69723
69772
  ticking = true;
69724
69773
  try {
69725
- refreshGroups(await readProcessTableAsync());
69774
+ const table = await readProcessTableAsync();
69775
+ if (table)
69776
+ refreshGroups(table);
69726
69777
  for (const p of live.values())
69727
69778
  if (p.reapFailed)
69728
69779
  await p.retryReap();
@@ -69824,9 +69875,9 @@ function sweepTargets(rec, root, table) {
69824
69875
  return { mag: m.mag, group, unverified: m.unverified || groupImpostor };
69825
69876
  }
69826
69877
  async function killVerified(rec, root) {
69827
- const t = sweepTargets(rec, root, readProcessTable());
69878
+ const t = sweepTargets(rec, root, readProcessTable() ?? []);
69828
69879
  for (const sig of ["SIGTERM", "SIGKILL"]) {
69829
- const table = readProcessTable();
69880
+ const table = readProcessTable() ?? [];
69830
69881
  if (t.group && groupCheck(table, t.group))
69831
69882
  signal(-t.group.pgid, sig);
69832
69883
  for (const pid of liveEscaped(table, t.group))
@@ -69839,12 +69890,15 @@ async function killVerified(rec, root) {
69839
69890
  }
69840
69891
  async function sweepRecord(rec, root, out) {
69841
69892
  const table = readProcessTable();
69842
- if (!processTableTrusted(table)) {
69843
- out.kept.push(rec.paneId);
69893
+ if (ownerAlive(rec, table)) {
69894
+ if (!table)
69895
+ out.kept.push(rec.paneId);
69844
69896
  return;
69845
69897
  }
69846
- if (ownerAlive(rec, table))
69898
+ if (!table) {
69899
+ await sweepUnidentifiable(rec, root, out);
69847
69900
  return;
69901
+ }
69848
69902
  const watcher = rec.watcherPid ? table.find((r) => r.pid === rec.watcherPid) : undefined;
69849
69903
  if (watcher && isPaneWatcherCommand(watcher.command, rec.paneId)) {
69850
69904
  out.kept.push(rec.paneId);
@@ -69855,11 +69909,7 @@ async function sweepRecord(rec, root, out) {
69855
69909
  if (anyVerified)
69856
69910
  await killVerified(rec, root);
69857
69911
  const after = readProcessTable();
69858
- if (!processTableTrusted(after)) {
69859
- out.kept.push(rec.paneId);
69860
- return;
69861
- }
69862
- const stillAlive = t0.group && groupCheck(after, t0.group) || liveEscaped(after, t0.group).length > 0 || t0.mag !== null && isPaneMagmux(t0.mag, rec.paneId, after);
69912
+ const stillAlive = !after || t0.group && groupCheck(after, t0.group) || liveEscaped(after, t0.group).length > 0 || t0.mag !== null && isPaneMagmux(t0.mag, rec.paneId, after);
69863
69913
  if (stillAlive) {
69864
69914
  out.kept.push(rec.paneId);
69865
69915
  return;
@@ -69872,40 +69922,96 @@ async function sweepRecord(rec, root, out) {
69872
69922
  else
69873
69923
  out.cleaned.push(rec.paneId);
69874
69924
  }
69925
+ async function probeSocket(sockPath, magmuxMayLive) {
69926
+ const dial = dialSocket(sockPath);
69927
+ let timer;
69928
+ const timeout = new Promise((resolve) => {
69929
+ timer = setTimeout(() => resolve("timeout"), SOCKET_PROBE_TIMEOUT_MS);
69930
+ });
69931
+ try {
69932
+ const won = await Promise.race([dial, timeout]);
69933
+ if (won !== "timeout")
69934
+ return { state: "answers", client: won };
69935
+ dial.then((c) => c.close(), () => {
69936
+ return;
69937
+ });
69938
+ return { state: "inconclusive", code: "ETIMEDOUT" };
69939
+ } catch (e) {
69940
+ const code = dialErrno(e.errno ?? "unknown", sockPath);
69941
+ const stale = code === "ENOENT" || code === "ECONNREFUSED" && !magmuxMayLive;
69942
+ return stale ? { state: "stale", code } : { state: "inconclusive", code };
69943
+ } finally {
69944
+ clearTimeout(timer);
69945
+ }
69946
+ }
69947
+ function dialErrno(reported, sockPath) {
69948
+ if (reported !== "ENOENT")
69949
+ return reported;
69950
+ let isSocket;
69951
+ try {
69952
+ isSocket = lstatSync3(sockPath).isSocket();
69953
+ } catch (e) {
69954
+ return e.code ?? "unknown";
69955
+ }
69956
+ if (!isSocket)
69957
+ return "ENOTSOCK";
69958
+ try {
69959
+ accessSync(sockPath, constants.W_OK);
69960
+ } catch {
69961
+ return "EACCES";
69962
+ }
69963
+ return "ECONNREFUSED";
69964
+ }
69965
+ async function sweepUnidentifiable(rec, root, out) {
69966
+ if (rec.watcherPid && pidMayBeAlive(rec.watcherPid)) {
69967
+ out.kept.push(rec.paneId);
69968
+ return;
69969
+ }
69970
+ const magmuxMayLive = rec.magmuxPid === null || pidMayBeAlive(rec.magmuxPid);
69971
+ const probe = await probeSocket(sockPathOf(root, rec.paneId), magmuxMayLive);
69972
+ if (probe.state !== "stale") {
69973
+ if (probe.state === "answers")
69974
+ probe.client.close();
69975
+ out.kept.push(rec.paneId);
69976
+ return;
69977
+ }
69978
+ removePaneFiles(root, rec);
69979
+ out.cleaned.push(rec.paneId);
69980
+ }
69875
69981
  function ownerOfPaneId(id) {
69876
69982
  const m = id.match(/^c(\d+)-([0-9a-z]+)-/);
69877
69983
  if (!m)
69878
69984
  return null;
69879
69985
  return { pid: Number(m[1]), startMs: Number.parseInt(m[2] ?? "", 36) };
69880
69986
  }
69881
- function ownerOfIdAlive(id, table) {
69987
+ function ownerOfSocketId(id) {
69882
69988
  const o = ownerOfPaneId(id);
69883
- if (!o)
69884
- return true;
69885
- const row = table.find((r) => r.pid === o.pid);
69886
- if (!row)
69887
- return false;
69888
- const started = Date.parse(row.lstart);
69889
- return !Number.isFinite(started) || Math.abs(started - o.startMs) < 2000;
69989
+ return o ? { paneId: id, ownerPid: o.pid, ownerStart: "" } : null;
69890
69990
  }
69891
69991
  async function sweepRecordlessSocket(root, name, out) {
69892
69992
  const id = name.slice("magmux-".length, -".sock".length);
69893
69993
  const table = readProcessTable();
69894
- if (!processTableTrusted(table)) {
69895
- out.kept.push(id);
69896
- return;
69897
- }
69898
- if (ownerOfIdAlive(id, table))
69994
+ const owner = ownerOfSocketId(id);
69995
+ if (!owner || ownerAlive(owner, table))
69899
69996
  return;
69900
69997
  const sockPath = join43(root, name);
69901
- let client = null;
69902
- try {
69903
- client = await MagmuxClient.dial(sockPath);
69904
- } catch {
69998
+ const magmuxMayLive = !table || table.some((r) => isPaneMagmuxCommand(r.command, id));
69999
+ const probe = await probeSocket(sockPath, magmuxMayLive);
70000
+ if (probe.state === "stale") {
69905
70001
  rmSync6(sockPath, { force: true });
69906
70002
  out.cleaned.push(id);
69907
70003
  return;
69908
70004
  }
70005
+ if (probe.state === "inconclusive") {
70006
+ out.kept.push(id);
70007
+ return;
70008
+ }
70009
+ const client = probe.client;
70010
+ if (!table) {
70011
+ client.close();
70012
+ out.kept.push(id);
70013
+ return;
70014
+ }
69909
70015
  const list = await client.request({
69910
70016
  type: "list"
69911
70017
  });
@@ -69948,7 +70054,7 @@ async function sweepOrphanPanes(root = sockRootFor()) {
69948
70054
  }
69949
70055
  return out;
69950
70056
  }
69951
- var MAX_LIVE_PANES = 48, PaneLimitError, ownStart = null, live, reserved = 0, ticker = null, ticking = false, hookOpts, hooksInstalled = false, stdinHooked = false, shuttingDown = false, SIGNALS, sweptRoots;
70057
+ var MAX_LIVE_PANES = 48, PaneLimitError, ownStart = null, live, reserved = 0, ticker = null, ticking = false, hookOpts, hooksInstalled = false, stdinHooked = false, shuttingDown = false, SIGNALS, SOCKET_PROBE_TIMEOUT_MS = 2000, dialSocket = (p) => MagmuxClient.dial(p), sweptRoots;
69952
70058
  var init_pane_registry = __esm(() => {
69953
70059
  init_magmux_client();
69954
70060
  init_pane_launch();
@@ -72225,7 +72331,10 @@ class PaneSessionImpl {
72225
72331
  setPanePid(pid) {
72226
72332
  this.panePidValue = pid;
72227
72333
  updateRecord(this.root, this.paneId, { panePid: pid });
72228
- readProcessTableAsync().then((t) => refreshGroupOf(this, t));
72334
+ readProcessTableAsync().then((t) => {
72335
+ if (t)
72336
+ refreshGroupOf(this, t);
72337
+ });
72229
72338
  }
72230
72339
  onFrame(f) {
72231
72340
  if (f.pane !== undefined && f.pane !== 0)
@@ -74161,6 +74270,8 @@ ${screenTailOf(this.screen)}`
74161
74270
  }
74162
74271
  async captureGroupBeforeReap() {
74163
74272
  const table = await readProcessTableAsync();
74273
+ if (!table)
74274
+ return;
74164
74275
  if (!this.panePidValue && this.magmux?.pid) {
74165
74276
  const lead = table.find((r) => r.ppid === this.magmux?.pid && r.pgid === r.pid);
74166
74277
  if (lead) {
@@ -74175,12 +74286,16 @@ ${screenTailOf(this.screen)}`
74175
74286
  if (!this.group)
74176
74287
  return false;
74177
74288
  const table = await readProcessTableAsync();
74289
+ if (!table)
74290
+ return true;
74178
74291
  return groupCheck(table, this.group) || liveEscaped(table, this.group).length > 0;
74179
74292
  }
74180
74293
  async backstopTargets() {
74181
74294
  if (!this.group)
74182
74295
  return [];
74183
74296
  const table = await readProcessTableAsync();
74297
+ if (!table)
74298
+ return [];
74184
74299
  return [
74185
74300
  ...groupCheck(table, this.group) ? [-this.group.pgid] : [],
74186
74301
  ...liveEscaped(table, this.group)
@@ -74980,7 +75095,7 @@ var init_session_discovery = __esm(() => {
74980
75095
  import { randomBytes as randomBytes8, randomUUID as randomUUID6 } from "crypto";
74981
75096
  import {
74982
75097
  existsSync as existsSync33,
74983
- lstatSync as lstatSync3,
75098
+ lstatSync as lstatSync4,
74984
75099
  mkdirSync as mkdirSync21,
74985
75100
  readFileSync as readFileSync32,
74986
75101
  readdirSync as readdirSync8,
@@ -75383,7 +75498,7 @@ function validateSessionPath(sessionPath) {
75383
75498
  const tail = [];
75384
75499
  for (;; ) {
75385
75500
  try {
75386
- lstatSync3(existing);
75501
+ lstatSync4(existing);
75387
75502
  break;
75388
75503
  } catch {
75389
75504
  const parent = dirname16(existing);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claudish",
3
- "version": "13.0.0",
3
+ "version": "13.0.2",
4
4
  "description": "Run Claude Code with any model - OpenRouter, Ollama, LM Studio & local models",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -49,6 +49,7 @@
49
49
  },
50
50
  "files": [
51
51
  "dist/",
52
+ "bin/",
52
53
  "AI_AGENT_GUIDE.md",
53
54
  "skills/"
54
55
  ],
@@ -68,10 +69,10 @@
68
69
  "ai"
69
70
  ],
70
71
  "optionalDependencies": {
71
- "@claudish/magmux-darwin-arm64": "13.0.0",
72
- "@claudish/magmux-darwin-x64": "13.0.0",
73
- "@claudish/magmux-linux-arm64": "13.0.0",
74
- "@claudish/magmux-linux-x64": "13.0.0"
72
+ "@claudish/magmux-darwin-arm64": "13.0.2",
73
+ "@claudish/magmux-darwin-x64": "13.0.2",
74
+ "@claudish/magmux-linux-arm64": "13.0.2",
75
+ "@claudish/magmux-linux-x64": "13.0.2"
75
76
  },
76
77
  "author": "Jack Rudenko <i@madappgang.com>",
77
78
  "license": "MIT",