create-egregore 0.18.4 → 0.18.6

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // egregore-launcher-version: 0.5.18
2
+ // egregore-launcher-version: 0.5.20
3
3
  //
4
4
  // egregore — launch and browse Egregore instances from your terminal.
5
5
  // Installed by create-egregore. Reads ~/.egregore/instances.json.
@@ -30,7 +30,7 @@ const https = require("node:https");
30
30
  const { spawnSync, spawn } = require("node:child_process");
31
31
  const readline = require("node:readline");
32
32
 
33
- const VERSION = "0.5.18";
33
+ const VERSION = "0.5.20";
34
34
  const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
35
35
  const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
36
36
  const PUBLIC_NPX_PACKAGE = "create-egregore";
@@ -99,9 +99,57 @@ const ANSI = {
99
99
  clear: "\x1b[2J",
100
100
  home: "\x1b[H",
101
101
  clearLine: "\x1b[K",
102
+ // Disable every terminal mouse-reporting mode. Some terminals (tmux,
103
+ // iTerm2) or a TUI run earlier in the session leave mouse reporting ON,
104
+ // which turns the scroll wheel into escape sequences that corrupt our
105
+ // pickers. Emitting this on picker entry makes the wheel do native
106
+ // scrollback instead. Modes: 1000 (click), 1002 (drag), 1003 (any-motion),
107
+ // 1006 (SGR encoding), 1015 (urxvt encoding).
108
+ mouseOff: "\x1b[?1000l\x1b[?1002l\x1b[?1003l\x1b[?1006l\x1b[?1015l",
102
109
  moveTo: (row, col) => `\x1b[${row};${col}H`,
103
110
  };
104
111
 
112
+ // Scroll-wheel / mouse-event filter (defense-in-depth for ANSI.mouseOff).
113
+ // With mouse reporting on, the scroll wheel and shift+arrow arrive as escape
114
+ // sequences. Node's readline decomposes them into stray keypresses:
115
+ // • SGR mouse "\x1b[<b;x;yM" → an event with key.code "[<", then each
116
+ // coordinate DIGIT as its own keypress (a "1"/"2" hits the picker's
117
+ // number quick-select and LAUNCHES a runtime), then a final "M".
118
+ // • X11 mouse "\x1b[M" + 3 raw bytes → key.code "[M" then 3 stray chars.
119
+ // • shift+arrow "\x1b[1;2A" / "\x1b[1;2B" → key.name "up"/"down" with
120
+ // key.shift set, which would advance the selection.
121
+ // This factory returns a stateful predicate: pass it every (chunk, key) at the
122
+ // top of a keypress handler and early-return when it reports true, so no
123
+ // mouse-derived byte is ever treated as navigation.
124
+ function makeMouseFilter() {
125
+ let sgr = false; // inside an SGR mouse report (ESC [ < ... M|m)
126
+ let x11 = 0; // raw bytes left to swallow after ESC [ M
127
+ return function isMouseEvent(chunk, key) {
128
+ const seq = (key && key.sequence) || (typeof chunk === "string" ? chunk : "") || "";
129
+ if (sgr) {
130
+ if (/[Mm]/.test(seq)) sgr = false; // SGR terminates on M (press) / m (release)
131
+ return true;
132
+ }
133
+ if (x11 > 0) {
134
+ x11 -= seq.length || 1;
135
+ return true;
136
+ }
137
+ if ((key && key.code === "[<") || seq.startsWith("\x1b[<")) {
138
+ sgr = !/[Mm]$/.test(seq); // single-chunk variant already includes terminator
139
+ return true;
140
+ }
141
+ if ((key && key.code === "[M") || seq.startsWith("\x1b[M")) {
142
+ const extra = Math.max(0, seq.length - 3); // bytes past "ESC [ M" in this chunk
143
+ x11 = Math.max(0, 3 - extra);
144
+ return true;
145
+ }
146
+ if (key && key.shift && (key.name === "up" || key.name === "down")) {
147
+ return true; // shift+arrow scroll fallback
148
+ }
149
+ return false;
150
+ };
151
+ }
152
+
105
153
  const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
106
154
  const visLen = (s) => stripAnsi(s).length;
107
155
  const pad = (s, n) => s + " ".repeat(Math.max(0, n - visLen(s)));
@@ -152,6 +200,28 @@ function die(msg) {
152
200
  process.exit(1);
153
201
  }
154
202
 
203
+ // process.cwd() throws (EPERM/ENOENT, "uv_cwd") when the shell's working
204
+ // directory was moved, deleted, or recreated out from under it. Fall back to
205
+ // home so spawned children never inherit a dead cwd.
206
+ function safeCwd() {
207
+ try {
208
+ return process.cwd();
209
+ } catch {
210
+ return os.homedir();
211
+ }
212
+ }
213
+
214
+ // Turn a raw startup error into actionable guidance when it's a stale-cwd one.
215
+ function friendlyError(err) {
216
+ const msg = (err && (err.message || String(err))) || "unknown error";
217
+ if (/uv_cwd|getcwd/i.test(msg)) {
218
+ return "your terminal's working directory is stale or inaccessible.\n" +
219
+ " It was likely moved, deleted, or recreated out from under the shell.\n" +
220
+ " Open a new terminal (or run: cd ~), then run: egregore";
221
+ }
222
+ return msg;
223
+ }
224
+
155
225
  function readRegistry() {
156
226
  if (!fs.existsSync(REGISTRY)) return [];
157
227
  try {
@@ -226,7 +296,7 @@ function pathContains(parent, child) {
226
296
  return rel === "" || (!!rel && !rel.startsWith("..") && !path.isAbsolute(rel));
227
297
  }
228
298
 
229
- function initialCursorForCwd(registry, cwd = process.cwd()) {
299
+ function initialCursorForCwd(registry, cwd = safeCwd()) {
230
300
  let best = -1;
231
301
  let bestLen = -1;
232
302
  for (let i = 0; i < registry.length; i++) {
@@ -363,6 +433,7 @@ function pickRuntimeInteractive(inst, runtimes, stored) {
363
433
  let done = false;
364
434
  const title = inst.slug || inst.name || path.basename(inst.path);
365
435
  const previousRaw = !!process.stdin.isRaw;
436
+ const isMouseEvent = makeMouseFilter();
366
437
 
367
438
  const erase = () => {
368
439
  if (!rendered) return;
@@ -378,6 +449,7 @@ function pickRuntimeInteractive(inst, runtimes, stored) {
378
449
  if (done) return;
379
450
  done = true;
380
451
  process.stdin.removeListener("keypress", onKey);
452
+ process.stdout.write(ANSI.mouseOff);
381
453
  if (process.stdin.setRawMode) process.stdin.setRawMode(previousRaw);
382
454
  if (!previousRaw) process.stdin.pause();
383
455
  erase();
@@ -399,6 +471,7 @@ function pickRuntimeInteractive(inst, runtimes, stored) {
399
471
  };
400
472
 
401
473
  function onKey(chunk, key = {}) {
474
+ if (isMouseEvent(chunk, key)) return; // drop scroll-wheel / mouse escape sequences
402
475
  if (key.ctrl && key.name === "c") {
403
476
  cleanup();
404
477
  process.exit(130);
@@ -424,6 +497,7 @@ function pickRuntimeInteractive(inst, runtimes, stored) {
424
497
  readline.emitKeypressEvents(process.stdin);
425
498
  if (process.stdin.setRawMode) process.stdin.setRawMode(true);
426
499
  process.stdin.resume();
500
+ process.stdout.write(ANSI.mouseOff);
427
501
  process.stdin.on("keypress", onKey);
428
502
  render();
429
503
  });
@@ -572,6 +646,18 @@ function runtimeLaunchArgs(runtime, egregoreDir) {
572
646
  async function launch(inst, runtimeOverride) {
573
647
  if (!inst) die("no instance to launch");
574
648
  if (!fs.existsSync(inst.path)) die(`instance path does not exist: ${inst.path}`);
649
+ // existsSync can pass while the path is still untraversable (a parent lost
650
+ // execute permission, or the dir was recreated under the shell). realpathSync
651
+ // walks the full path and surfaces that before we spawn the harness into a
652
+ // dead directory and produce a cryptic getcwd/EPERM cascade.
653
+ try {
654
+ fs.realpathSync(inst.path);
655
+ fs.accessSync(inst.path, fs.constants.X_OK);
656
+ } catch {
657
+ die(`instance directory isn't accessible: ${inst.path}\n` +
658
+ " Your terminal's working directory may be stale (the folder was moved or recreated).\n" +
659
+ " Open a new terminal (or run: cd ~), then run: egregore");
660
+ }
575
661
  const runtime = await chooseRuntimeForLaunch(inst, runtimeOverride);
576
662
  if (!runtime) return;
577
663
  const binary = runtimeBinary(runtime);
@@ -609,7 +695,7 @@ function setupNewEgregore(runtimeOverride) {
609
695
  if (runtimeOverride) args.push("--runtime", runtimeOverride);
610
696
  const child = spawn("npx", args, {
611
697
  stdio: "inherit",
612
- cwd: process.cwd(),
698
+ cwd: safeCwd(),
613
699
  env: { ...process.env },
614
700
  });
615
701
  child.on("exit", (code, signal) => {
@@ -1586,7 +1672,8 @@ async function browser(args = {}) {
1586
1672
  };
1587
1673
 
1588
1674
  // ── setup / teardown ───────────────────
1589
- process.stdout.write(ANSI.altScreenEnter + ANSI.cursorHide + ANSI.clear + ANSI.home);
1675
+ process.stdout.write(ANSI.altScreenEnter + ANSI.mouseOff + ANSI.cursorHide + ANSI.clear + ANSI.home);
1676
+ const isMouseEvent = makeMouseFilter();
1590
1677
  let done = false;
1591
1678
  const cleanup = () => {
1592
1679
  if (done) return;
@@ -1594,7 +1681,7 @@ async function browser(args = {}) {
1594
1681
  process.stdin.setRawMode(false);
1595
1682
  process.stdin.pause();
1596
1683
  process.stdin.removeAllListeners("keypress");
1597
- process.stdout.write(ANSI.cursorShow + ANSI.altScreenLeave);
1684
+ process.stdout.write(ANSI.mouseOff + ANSI.cursorShow + ANSI.altScreenLeave);
1598
1685
  };
1599
1686
  process.on("exit", cleanup);
1600
1687
  process.on("SIGINT", () => { cleanup(); process.exit(130); });
@@ -1618,6 +1705,7 @@ async function browser(args = {}) {
1618
1705
  return new Promise((resolve) => {
1619
1706
  process.stdin.on("keypress", (chunk, key) => {
1620
1707
  if (!key) return;
1708
+ if (isMouseEvent(chunk, key)) return; // drop scroll-wheel / mouse escape sequences
1621
1709
 
1622
1710
  if (state.runtimeInst) {
1623
1711
  const runtimes = runtimeChoicesFor(state.runtimeInst, args.runtimeOverride);
@@ -1910,6 +1998,15 @@ async function numberedPicker(registry, runtimeOverride) {
1910
1998
  // ── main ────────────────────────────────────────────────────────────
1911
1999
 
1912
2000
  function main() {
2001
+ // Self-heal a stale/inaccessible working directory before anything spawns —
2002
+ // otherwise every child (harness, npx) inherits the dead cwd and fails with
2003
+ // getcwd/EPERM. Home is always a safe place to stand.
2004
+ try {
2005
+ process.cwd();
2006
+ } catch {
2007
+ try { process.chdir(os.homedir()); } catch { /* nothing more we can do */ }
2008
+ }
2009
+
1913
2010
  const parsed = parseRuntimeFlags(process.argv.slice(2));
1914
2011
  const args = parsed.args;
1915
2012
  const runtimeOverride = parsed.runtimeOverride;
@@ -1968,8 +2065,8 @@ function main() {
1968
2065
  try {
1969
2066
  const r = main();
1970
2067
  if (r && typeof r.then === "function") {
1971
- r.catch((err) => die(err.message || String(err)));
2068
+ r.catch((err) => die(friendlyError(err)));
1972
2069
  }
1973
2070
  } catch (err) {
1974
- die(err.message || String(err));
2071
+ die(friendlyError(err));
1975
2072
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.18.4",
3
+ "version": "0.18.6",
4
4
  "description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code or Codex.",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -11,7 +11,7 @@
11
11
  },
12
12
  "scripts": {
13
13
  "build:runtime": "node scripts/build-runtime-bundle.mjs",
14
- "test": "node scripts/build-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/installer-runtime-source.test.js",
14
+ "test": "node scripts/build-runtime-bundle.mjs --check && node --test test/context.test.js test/parse-join-target.test.js test/skip-repos.test.js test/onboarding-shortcircuit.test.js test/welcome-screen.test.js test/runtime-selection.test.js test/command-generation.test.js test/auth-gh.test.js test/gauntlet-fixes.test.js test/codex-runtime.test.js test/installer-runtime-source.test.js test/scroll-repro.test.js",
15
15
  "test:e2e": "node --test test/e2e-local.test.js",
16
16
  "test:all": "node scripts/build-runtime-bundle.mjs --check && node --test test/"
17
17
  },
@@ -134,7 +134,7 @@ DASHBOARD_URL=$(echo "$_DB_PUBLISH_OUT" | grep -o 'https://[^ ]*' | tail -1)
134
134
  if [ -z "$DASHBOARD_URL" ]; then
135
135
  _DB_HTML="/tmp/egregore-artifacts/dashboard-${EGREGORE_SESSION_ID}.html"
136
136
  mkdir -p /tmp/egregore-artifacts
137
- if npx egregore-artifacts document "$_DB_MD" --output "$_DB_HTML" --no-open >/dev/null 2>&1 && [ -f "$_DB_HTML" ]; then
137
+ if npx egregore-artifacts@latest document "$_DB_MD" --output "$_DB_HTML" --no-open >/dev/null 2>&1 && [ -f "$_DB_HTML" ]; then
138
138
  DASHBOARD_URL="file://${_DB_HTML}"
139
139
  fi
140
140
  fi
@@ -109,7 +109,7 @@ else
109
109
  exit 0
110
110
  fi
111
111
  else
112
- if ! npx egregore-artifacts "${RENDER_ARGS[@]}" >/dev/null 2>&1; then
112
+ if ! npx egregore-artifacts@latest "${RENDER_ARGS[@]}" >/dev/null 2>&1; then
113
113
  exit 0
114
114
  fi
115
115
  fi