create-egregore 0.18.7 → 0.18.9
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.
|
|
2
|
+
// egregore-launcher-version: 0.5.24
|
|
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.
|
|
33
|
+
const VERSION = "0.5.24";
|
|
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,6 +99,7 @@ const ANSI = {
|
|
|
99
99
|
clear: "\x1b[2J",
|
|
100
100
|
home: "\x1b[H",
|
|
101
101
|
clearLine: "\x1b[K",
|
|
102
|
+
clearDown: "\x1b[J",
|
|
102
103
|
// Disable every terminal mouse-reporting mode. Some terminals (tmux,
|
|
103
104
|
// iTerm2) or a TUI run earlier in the session leave mouse reporting ON,
|
|
104
105
|
// which turns the scroll wheel into escape sequences that corrupt our
|
|
@@ -126,6 +127,23 @@ function makeMouseFilter() {
|
|
|
126
127
|
let x11 = 0; // raw bytes left to swallow after ESC [ M
|
|
127
128
|
return function isMouseEvent(chunk, key) {
|
|
128
129
|
const seq = (key && key.sequence) || (typeof chunk === "string" ? chunk : "") || "";
|
|
130
|
+
// A real, unshifted navigation/action key is never part of a mouse report
|
|
131
|
+
// — those decompose into "[<"/"[M" plus digit keypresses, never named
|
|
132
|
+
// arrows/enter/etc. If a quirky terminal (e.g. Warp) emitted a malformed or
|
|
133
|
+
// partial mouse sequence, the stuck sgr/x11 state below would otherwise
|
|
134
|
+
// swallow these keys and freeze navigation ("arrows don't work"). Reset and
|
|
135
|
+
// let them through. Digits are deliberately NOT included — they stay
|
|
136
|
+
// filtered mid-sequence so scroll-wheel protection still holds.
|
|
137
|
+
if (key && !key.shift && !key.ctrl && !key.meta) {
|
|
138
|
+
const nm = key.name;
|
|
139
|
+
if (nm === "up" || nm === "down" || nm === "left" || nm === "right" ||
|
|
140
|
+
nm === "return" || nm === "escape" || nm === "home" || nm === "end" ||
|
|
141
|
+
nm === "pageup" || nm === "pagedown") {
|
|
142
|
+
sgr = false;
|
|
143
|
+
x11 = 0;
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
129
147
|
if (sgr) {
|
|
130
148
|
if (/[Mm]/.test(seq)) sgr = false; // SGR terminates on M (press) / m (release)
|
|
131
149
|
return true;
|
|
@@ -1411,7 +1429,9 @@ function renderRuntimePanel({ inst, cursor, width, height }) {
|
|
|
1411
1429
|
const runtimes = runtimeChoicesFor(inst);
|
|
1412
1430
|
const stored = storedRuntimeFor(inst);
|
|
1413
1431
|
const title = inst.slug || inst.name || path.basename(inst.path);
|
|
1414
|
-
|
|
1432
|
+
// Same cap/margin as renderHeroPanel so the centered box keeps its position
|
|
1433
|
+
// when you move from the instance list to the assistant picker.
|
|
1434
|
+
const outerWidth = Math.min(Math.max(50, width - 2), 126);
|
|
1415
1435
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
1416
1436
|
const margin = " ".repeat(Math.max(0, Math.floor((width - outerWidth) / 2)));
|
|
1417
1437
|
const label = " assistant ";
|
|
@@ -1503,11 +1523,25 @@ async function browser(args = {}) {
|
|
|
1503
1523
|
|
|
1504
1524
|
// ── render ────────────────────────────
|
|
1505
1525
|
const render = () => {
|
|
1526
|
+
if (done) return; // the parent stays alive under the spawned assistant — never redraw after teardown
|
|
1506
1527
|
const w = process.stdout.columns || 80;
|
|
1507
1528
|
const h = process.stdout.rows || 24;
|
|
1508
1529
|
|
|
1530
|
+
// One centered block: the hero/runtime box, the list, dividers, preview and
|
|
1531
|
+
// footer all share this gutter and width so the whole UI centers together
|
|
1532
|
+
// (matches renderHeroPanel's outerWidth/margin math).
|
|
1533
|
+
const blockWidth = Math.min(Math.max(50, w - 2), 126);
|
|
1534
|
+
const gutter = Math.max(0, Math.floor((w - blockWidth) / 2));
|
|
1535
|
+
const M = " ".repeat(gutter);
|
|
1536
|
+
|
|
1509
1537
|
const out = [];
|
|
1510
|
-
|
|
1538
|
+
// Home AND clear-to-end-of-screen each frame. The per-line ANSI.clearLine
|
|
1539
|
+
// calls below are belt-and-suspenders; some terminals (notably Warp) don't
|
|
1540
|
+
// apply incremental \x1b[K reliably across an alt-screen redraw, leaving
|
|
1541
|
+
// stale lines — most visibly a duplicated footer. A single clear-down after
|
|
1542
|
+
// home wipes the slate before redraw. It's part of the same atomic write,
|
|
1543
|
+
// so there's no extra flicker.
|
|
1544
|
+
out.push(ANSI.home + ANSI.clearDown);
|
|
1511
1545
|
|
|
1512
1546
|
if (state.runtimeInst) {
|
|
1513
1547
|
const runtimeLines = renderRuntimePanel({
|
|
@@ -1551,9 +1585,9 @@ async function browser(args = {}) {
|
|
|
1551
1585
|
const listRows = Math.max(4, Math.floor(availableRows * 0.48));
|
|
1552
1586
|
const previewRows = Math.max(3, availableRows - listRows - 2); // -2 for dividers
|
|
1553
1587
|
|
|
1554
|
-
// List
|
|
1588
|
+
// List — kept within the centered block so long paths don't spill past it.
|
|
1555
1589
|
const listPad = 4;
|
|
1556
|
-
const listAvail =
|
|
1590
|
+
const listAvail = blockWidth - 4;
|
|
1557
1591
|
const slugW = Math.min(22, Math.max(6, ...registry.map((r) => (r.slug || "").length)));
|
|
1558
1592
|
const nameW = Math.min(24, Math.max(6, ...registry.map((r) => (r.name || "").length)));
|
|
1559
1593
|
|
|
@@ -1570,7 +1604,7 @@ async function browser(args = {}) {
|
|
|
1570
1604
|
if (idx >= v.length) {
|
|
1571
1605
|
if (v.length === 0 && i === Math.floor(listRows / 2)) {
|
|
1572
1606
|
const msg = state.filter ? "(no matches)" : "(empty)";
|
|
1573
|
-
out.push(" ".repeat(listPad) + DIM + msg + RESET + ANSI.clearLine + "\n");
|
|
1607
|
+
out.push(M + " ".repeat(listPad) + DIM + msg + RESET + ANSI.clearLine + "\n");
|
|
1574
1608
|
} else {
|
|
1575
1609
|
out.push(ANSI.clearLine + "\n");
|
|
1576
1610
|
}
|
|
@@ -1584,7 +1618,7 @@ async function browser(args = {}) {
|
|
|
1584
1618
|
const label = isCursor ? `${BOLD}${item.label}${RESET}` : item.label;
|
|
1585
1619
|
const desc = `${DIM}${item.description}${RESET}`;
|
|
1586
1620
|
const line = ` ${cursorMark} ${pad(plus, slugW)} ${pad(label, nameW)} ${truncate(desc, Math.max(0, listAvail - slugW - nameW - 10))}`;
|
|
1587
|
-
out.push(" ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1621
|
+
out.push(M + " ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1588
1622
|
continue;
|
|
1589
1623
|
}
|
|
1590
1624
|
const { e, origIdx } = item;
|
|
@@ -1599,11 +1633,11 @@ async function browser(args = {}) {
|
|
|
1599
1633
|
const name = pad(e.name || "", nameW);
|
|
1600
1634
|
const pathDim = `${DIM}${truncate(e.path, Math.max(0, listAvail - slugW - nameW - 10))}${RESET}`;
|
|
1601
1635
|
const line = ` ${cursorMark} ${defMark} ${slug} ${name} ${pathDim}`;
|
|
1602
|
-
out.push(" ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1636
|
+
out.push(M + " ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1603
1637
|
}
|
|
1604
1638
|
|
|
1605
1639
|
// Divider
|
|
1606
|
-
out.push(DIM +
|
|
1640
|
+
out.push(DIM + M + "─".repeat(blockWidth) + RESET + ANSI.clearLine + "\n");
|
|
1607
1641
|
|
|
1608
1642
|
// Preview
|
|
1609
1643
|
const previewLines = [];
|
|
@@ -1617,15 +1651,20 @@ async function browser(args = {}) {
|
|
|
1617
1651
|
previewLines.push(`${DIM} (no selection)${RESET}`);
|
|
1618
1652
|
} else {
|
|
1619
1653
|
const label = (k) => `${DIM}${pad(k, 10)}${RESET}`;
|
|
1620
|
-
|
|
1621
|
-
|
|
1654
|
+
// A preview line is " " + label(10) + " " + value = 13 + value; bound the
|
|
1655
|
+
// value to blockWidth-15 so every line's right edge lands two cols inside
|
|
1656
|
+
// the box border, level with the box inner content (gutter+blockWidth-2).
|
|
1657
|
+
const valW = Math.max(16, blockWidth - 15);
|
|
1658
|
+
previewLines.push(` ${label("path")} ${truncate(selected.path, valW)}`);
|
|
1659
|
+
if (pv.org) previewLines.push(` ${label("org")} ${truncate(`${pv.org} ${DIM}(${pv.githubOrg || "?"})${RESET}`, valW)}`);
|
|
1622
1660
|
if (pv.branch) previewLines.push(` ${label("branch")} ${BOLD}${pv.branch}${RESET}`);
|
|
1623
|
-
if (pv.lastCommit) previewLines.push(` ${label("last")} ${DIM}${truncate(pv.lastCommit,
|
|
1661
|
+
if (pv.lastCommit) previewLines.push(` ${label("last")} ${DIM}${truncate(pv.lastCommit, valW)}${RESET}`);
|
|
1624
1662
|
if (pv.repos && pv.repos.length) {
|
|
1625
|
-
|
|
1663
|
+
const reposText = `${pv.repos.slice(0, 4).join(", ")}${pv.repos.length > 4 ? ` (+${pv.repos.length - 4} more)` : ""}`;
|
|
1664
|
+
previewLines.push(` ${label("repos")} ${truncate(reposText, valW)}`);
|
|
1626
1665
|
}
|
|
1627
1666
|
if (pv.skills) {
|
|
1628
|
-
const skillLine = selectedWorkflowLine(pv, Math.max(16,
|
|
1667
|
+
const skillLine = selectedWorkflowLine(pv, Math.max(16, blockWidth - 20));
|
|
1629
1668
|
previewLines.push(` ${label("invoke")} ${skillLine}`);
|
|
1630
1669
|
previewLines.push(` ${label("skills")} Codex ${BOLD}${pv.skills.native.length}${RESET} native, ${BOLD}${pv.skills.adapterCount}${RESET} adapters; Claude ${BOLD}${pv.skills.claudeCount || 0}${RESET}`);
|
|
1631
1670
|
}
|
|
@@ -1639,11 +1678,11 @@ async function browser(args = {}) {
|
|
|
1639
1678
|
}
|
|
1640
1679
|
for (let i = 0; i < previewRows; i++) {
|
|
1641
1680
|
const line = previewLines[i] || "";
|
|
1642
|
-
out.push(line + ANSI.clearLine + "\n");
|
|
1681
|
+
out.push((line ? M + line : "") + ANSI.clearLine + "\n");
|
|
1643
1682
|
}
|
|
1644
1683
|
|
|
1645
1684
|
// Divider
|
|
1646
|
-
out.push(DIM +
|
|
1685
|
+
out.push(DIM + M + "─".repeat(blockWidth) + RESET + ANSI.clearLine + "\n");
|
|
1647
1686
|
|
|
1648
1687
|
// Footer
|
|
1649
1688
|
let footer;
|
|
@@ -1664,13 +1703,13 @@ async function browser(args = {}) {
|
|
|
1664
1703
|
const kindColor = state.messageKind === "success" ? GREEN : state.messageKind === "warn" ? YELLOW : CYAN;
|
|
1665
1704
|
footer = ` ${kindColor}●${RESET} ${state.message}${ANSI.clearLine}`;
|
|
1666
1705
|
} else {
|
|
1667
|
-
footer =
|
|
1706
|
+
footer = blockWidth < 96
|
|
1668
1707
|
? ` ${DIM}↑↓${RESET} nav ${DIM}enter${RESET} select ${DIM}n/+${RESET} new ${DIM}i${RESET} invite ${DIM}?${RESET} help ${DIM}q${RESET} quit${ANSI.clearLine}`
|
|
1669
1708
|
: ` ${DIM}↑↓${RESET} nav ${DIM}enter${RESET} select ${DIM}n/+${RESET} new ${DIM}/${RESET} filter ${DIM}i${RESET} invite ${DIM}u${RESET} default ${DIM}r${RESET} rm ${DIM}p${RESET} prune ${DIM}?${RESET} help ${DIM}q${RESET} quit${ANSI.clearLine}`;
|
|
1670
1709
|
}
|
|
1671
|
-
out.push(footer + "\n");
|
|
1710
|
+
out.push(M + footer + "\n");
|
|
1672
1711
|
if (state.showHelp) {
|
|
1673
|
-
out.push(
|
|
1712
|
+
out.push(`${M} ${DIM}● default · ❯ cursor · n/+ starts guided setup · i opens /invite${RESET}${ANSI.clearLine}`);
|
|
1674
1713
|
} else {
|
|
1675
1714
|
out.push(ANSI.clearLine);
|
|
1676
1715
|
}
|
|
@@ -1682,18 +1721,44 @@ async function browser(args = {}) {
|
|
|
1682
1721
|
process.stdout.write(ANSI.altScreenEnter + ANSI.mouseOff + ANSI.cursorHide + ANSI.clear + ANSI.home);
|
|
1683
1722
|
const isMouseEvent = makeMouseFilter();
|
|
1684
1723
|
let done = false;
|
|
1724
|
+
// Named so cleanup() can detach them. The launcher is NOT a true exec — it
|
|
1725
|
+
// stays alive as the parent of the spawned assistant (claude/codex) for the
|
|
1726
|
+
// whole session. The load-bearing detach is the RESIZE listener: Ghostty (and
|
|
1727
|
+
// other terminals) re-send SIGWINCH when a tab regains focus, and a stale
|
|
1728
|
+
// "resize"→render() repaints the launcher frame on top of the running
|
|
1729
|
+
// assistant — exactly the "I switched tabs and the launcher screen came back"
|
|
1730
|
+
// bug. The SIGINT/SIGTERM/exit handlers are dropped too because they're now
|
|
1731
|
+
// pointless: during the live session the child holds the tty in raw mode, so
|
|
1732
|
+
// Ctrl-C reaches it as a 0x03 byte (the parent never sees a SIGINT); and an
|
|
1733
|
+
// out-of-band kill -INT/-TERM exits the parent 130/143 by Node's default
|
|
1734
|
+
// disposition anyway — identical to the explicit handlers we remove.
|
|
1735
|
+
const onResize = () => render(); // render() itself no-ops once done is set
|
|
1736
|
+
let onSigint;
|
|
1737
|
+
let onSigterm;
|
|
1685
1738
|
const cleanup = () => {
|
|
1686
1739
|
if (done) return;
|
|
1687
1740
|
done = true;
|
|
1688
1741
|
process.stdin.setRawMode(false);
|
|
1689
1742
|
process.stdin.pause();
|
|
1690
1743
|
process.stdin.removeAllListeners("keypress");
|
|
1691
|
-
process.stdout.
|
|
1744
|
+
process.stdout.removeListener("resize", onResize);
|
|
1745
|
+
if (onSigint) process.removeListener("SIGINT", onSigint);
|
|
1746
|
+
if (onSigterm) process.removeListener("SIGTERM", onSigterm);
|
|
1747
|
+
process.removeListener("exit", cleanup);
|
|
1748
|
+
// Clear the alt buffer BEFORE leaving it. A well-behaved terminal discards
|
|
1749
|
+
// the alt buffer and restores the primary screen on \x1b[?1049l, so this is
|
|
1750
|
+
// a harmless no-op there. Warp, however, dumps the alt buffer's contents
|
|
1751
|
+
// into the primary scrollback on exit — clearing first means it dumps a
|
|
1752
|
+
// blank screen instead of the whole launcher frame, so the next flow (the
|
|
1753
|
+
// guided "+ new" setup) starts on a clean screen.
|
|
1754
|
+
process.stdout.write(ANSI.mouseOff + ANSI.cursorShow + ANSI.clear + ANSI.home + ANSI.altScreenLeave);
|
|
1692
1755
|
};
|
|
1756
|
+
onSigint = () => { cleanup(); process.exit(130); };
|
|
1757
|
+
onSigterm = () => { cleanup(); process.exit(143); };
|
|
1693
1758
|
process.on("exit", cleanup);
|
|
1694
|
-
process.on("SIGINT",
|
|
1695
|
-
process.on("SIGTERM",
|
|
1696
|
-
process.stdout.on("resize",
|
|
1759
|
+
process.on("SIGINT", onSigint);
|
|
1760
|
+
process.on("SIGTERM", onSigterm);
|
|
1761
|
+
process.stdout.on("resize", onResize);
|
|
1697
1762
|
|
|
1698
1763
|
readline.emitKeypressEvents(process.stdin);
|
|
1699
1764
|
process.stdin.setRawMode(true);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.9",
|
|
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 test/scroll-repro.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 test/launcher-refocus-redraw.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
|
},
|
|
@@ -36,13 +36,16 @@ EMPTY='{"fields":[],"values":[]}'
|
|
|
36
36
|
if [ -z "$API_URL" ] || [ -z "$API_KEY" ]; then
|
|
37
37
|
GRAPH_REASON="missing_config"
|
|
38
38
|
else
|
|
39
|
-
# Store HTTP status code alongside response body
|
|
39
|
+
# Store HTTP status code alongside response body. curl already emits 000
|
|
40
|
+
# on transport failure; keep the exit status separate so it cannot become
|
|
41
|
+
# 000000 and fall through as an invalid JSON response.
|
|
42
|
+
CURL_STATUS=0
|
|
40
43
|
HTTP_CODE=$(curl -s -o "$TMPDIR/activity_raw.json" -w "%{http_code}" \
|
|
41
44
|
"${API_URL}/api/activity/dashboard?github_username=$(printf '%s' "$GH_USER" | jq -sRr @uri)" \
|
|
42
45
|
-H "Authorization: Bearer $API_KEY" \
|
|
43
|
-
--connect-timeout 5 --max-time 15 2>/dev/null ||
|
|
46
|
+
--connect-timeout 5 --max-time 15 2>/dev/null) || CURL_STATUS=$?
|
|
44
47
|
|
|
45
|
-
if [ "$HTTP_CODE" = "000" ]; then
|
|
48
|
+
if [ "$CURL_STATUS" -ne 0 ] || [ "$HTTP_CODE" = "000" ] || ! [[ "$HTTP_CODE" =~ ^[0-9][0-9][0-9]$ ]]; then
|
|
46
49
|
GRAPH_REASON="unreachable"
|
|
47
50
|
elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
|
|
48
51
|
GRAPH_REASON="auth_error"
|
|
@@ -44,12 +44,16 @@ SESSION_ID=$(cat "$HOME/.egregore/session-${PROJ_HASH}.id" 2>/dev/null || echo "
|
|
|
44
44
|
if [ -z "$API_URL" ] || [ -z "$API_KEY" ]; then
|
|
45
45
|
GRAPH_REASON="missing_config"
|
|
46
46
|
else
|
|
47
|
+
# Keep curl's transport status separate from %{http_code}; otherwise a
|
|
48
|
+
# failed request can concatenate 000 + fallback text and look like an
|
|
49
|
+
# unexpected API response.
|
|
50
|
+
CURL_STATUS=0
|
|
47
51
|
HTTP_CODE=$(curl -s -o "$TMPDIR/dashboard_raw.json" -w "%{http_code}" \
|
|
48
52
|
"${API_URL}/api/personal/dashboard?github_username=$(printf '%s' "$GH_USER" | jq -sRr @uri)&time_range=$(printf '%s' "$TIME_RANGE" | jq -sRr @uri)&session_id=$(printf '%s' "$SESSION_ID" | jq -sRr @uri)" \
|
|
49
53
|
-H "Authorization: Bearer $API_KEY" \
|
|
50
|
-
--connect-timeout 5 --max-time 15 2>/dev/null ||
|
|
54
|
+
--connect-timeout 5 --max-time 15 2>/dev/null) || CURL_STATUS=$?
|
|
51
55
|
|
|
52
|
-
if [ "$HTTP_CODE" = "000" ]; then
|
|
56
|
+
if [ "$CURL_STATUS" -ne 0 ] || [ "$HTTP_CODE" = "000" ] || ! [[ "$HTTP_CODE" =~ ^[0-9][0-9][0-9]$ ]]; then
|
|
53
57
|
GRAPH_REASON="unreachable"
|
|
54
58
|
elif [ "$HTTP_CODE" = "401" ] || [ "$HTTP_CODE" = "403" ]; then
|
|
55
59
|
GRAPH_REASON="auth_error"
|