create-egregore 0.18.8 → 0.18.10
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";
|
|
@@ -1429,7 +1429,9 @@ function renderRuntimePanel({ inst, cursor, width, height }) {
|
|
|
1429
1429
|
const runtimes = runtimeChoicesFor(inst);
|
|
1430
1430
|
const stored = storedRuntimeFor(inst);
|
|
1431
1431
|
const title = inst.slug || inst.name || path.basename(inst.path);
|
|
1432
|
-
|
|
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);
|
|
1433
1435
|
const innerWidth = Math.max(48, outerWidth - 2);
|
|
1434
1436
|
const margin = " ".repeat(Math.max(0, Math.floor((width - outerWidth) / 2)));
|
|
1435
1437
|
const label = " assistant ";
|
|
@@ -1521,9 +1523,17 @@ async function browser(args = {}) {
|
|
|
1521
1523
|
|
|
1522
1524
|
// ── render ────────────────────────────
|
|
1523
1525
|
const render = () => {
|
|
1526
|
+
if (done) return; // the parent stays alive under the spawned assistant — never redraw after teardown
|
|
1524
1527
|
const w = process.stdout.columns || 80;
|
|
1525
1528
|
const h = process.stdout.rows || 24;
|
|
1526
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
|
+
|
|
1527
1537
|
const out = [];
|
|
1528
1538
|
// Home AND clear-to-end-of-screen each frame. The per-line ANSI.clearLine
|
|
1529
1539
|
// calls below are belt-and-suspenders; some terminals (notably Warp) don't
|
|
@@ -1575,9 +1585,9 @@ async function browser(args = {}) {
|
|
|
1575
1585
|
const listRows = Math.max(4, Math.floor(availableRows * 0.48));
|
|
1576
1586
|
const previewRows = Math.max(3, availableRows - listRows - 2); // -2 for dividers
|
|
1577
1587
|
|
|
1578
|
-
// List
|
|
1588
|
+
// List — kept within the centered block so long paths don't spill past it.
|
|
1579
1589
|
const listPad = 4;
|
|
1580
|
-
const listAvail =
|
|
1590
|
+
const listAvail = blockWidth - 4;
|
|
1581
1591
|
const slugW = Math.min(22, Math.max(6, ...registry.map((r) => (r.slug || "").length)));
|
|
1582
1592
|
const nameW = Math.min(24, Math.max(6, ...registry.map((r) => (r.name || "").length)));
|
|
1583
1593
|
|
|
@@ -1594,7 +1604,7 @@ async function browser(args = {}) {
|
|
|
1594
1604
|
if (idx >= v.length) {
|
|
1595
1605
|
if (v.length === 0 && i === Math.floor(listRows / 2)) {
|
|
1596
1606
|
const msg = state.filter ? "(no matches)" : "(empty)";
|
|
1597
|
-
out.push(" ".repeat(listPad) + DIM + msg + RESET + ANSI.clearLine + "\n");
|
|
1607
|
+
out.push(M + " ".repeat(listPad) + DIM + msg + RESET + ANSI.clearLine + "\n");
|
|
1598
1608
|
} else {
|
|
1599
1609
|
out.push(ANSI.clearLine + "\n");
|
|
1600
1610
|
}
|
|
@@ -1608,7 +1618,7 @@ async function browser(args = {}) {
|
|
|
1608
1618
|
const label = isCursor ? `${BOLD}${item.label}${RESET}` : item.label;
|
|
1609
1619
|
const desc = `${DIM}${item.description}${RESET}`;
|
|
1610
1620
|
const line = ` ${cursorMark} ${pad(plus, slugW)} ${pad(label, nameW)} ${truncate(desc, Math.max(0, listAvail - slugW - nameW - 10))}`;
|
|
1611
|
-
out.push(" ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1621
|
+
out.push(M + " ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1612
1622
|
continue;
|
|
1613
1623
|
}
|
|
1614
1624
|
const { e, origIdx } = item;
|
|
@@ -1623,11 +1633,11 @@ async function browser(args = {}) {
|
|
|
1623
1633
|
const name = pad(e.name || "", nameW);
|
|
1624
1634
|
const pathDim = `${DIM}${truncate(e.path, Math.max(0, listAvail - slugW - nameW - 10))}${RESET}`;
|
|
1625
1635
|
const line = ` ${cursorMark} ${defMark} ${slug} ${name} ${pathDim}`;
|
|
1626
|
-
out.push(" ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1636
|
+
out.push(M + " ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
1627
1637
|
}
|
|
1628
1638
|
|
|
1629
1639
|
// Divider
|
|
1630
|
-
out.push(DIM +
|
|
1640
|
+
out.push(DIM + M + "─".repeat(blockWidth) + RESET + ANSI.clearLine + "\n");
|
|
1631
1641
|
|
|
1632
1642
|
// Preview
|
|
1633
1643
|
const previewLines = [];
|
|
@@ -1641,15 +1651,20 @@ async function browser(args = {}) {
|
|
|
1641
1651
|
previewLines.push(`${DIM} (no selection)${RESET}`);
|
|
1642
1652
|
} else {
|
|
1643
1653
|
const label = (k) => `${DIM}${pad(k, 10)}${RESET}`;
|
|
1644
|
-
|
|
1645
|
-
|
|
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)}`);
|
|
1646
1660
|
if (pv.branch) previewLines.push(` ${label("branch")} ${BOLD}${pv.branch}${RESET}`);
|
|
1647
|
-
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}`);
|
|
1648
1662
|
if (pv.repos && pv.repos.length) {
|
|
1649
|
-
|
|
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)}`);
|
|
1650
1665
|
}
|
|
1651
1666
|
if (pv.skills) {
|
|
1652
|
-
const skillLine = selectedWorkflowLine(pv, Math.max(16,
|
|
1667
|
+
const skillLine = selectedWorkflowLine(pv, Math.max(16, blockWidth - 20));
|
|
1653
1668
|
previewLines.push(` ${label("invoke")} ${skillLine}`);
|
|
1654
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}`);
|
|
1655
1670
|
}
|
|
@@ -1663,11 +1678,11 @@ async function browser(args = {}) {
|
|
|
1663
1678
|
}
|
|
1664
1679
|
for (let i = 0; i < previewRows; i++) {
|
|
1665
1680
|
const line = previewLines[i] || "";
|
|
1666
|
-
out.push(line + ANSI.clearLine + "\n");
|
|
1681
|
+
out.push((line ? M + line : "") + ANSI.clearLine + "\n");
|
|
1667
1682
|
}
|
|
1668
1683
|
|
|
1669
1684
|
// Divider
|
|
1670
|
-
out.push(DIM +
|
|
1685
|
+
out.push(DIM + M + "─".repeat(blockWidth) + RESET + ANSI.clearLine + "\n");
|
|
1671
1686
|
|
|
1672
1687
|
// Footer
|
|
1673
1688
|
let footer;
|
|
@@ -1688,13 +1703,13 @@ async function browser(args = {}) {
|
|
|
1688
1703
|
const kindColor = state.messageKind === "success" ? GREEN : state.messageKind === "warn" ? YELLOW : CYAN;
|
|
1689
1704
|
footer = ` ${kindColor}●${RESET} ${state.message}${ANSI.clearLine}`;
|
|
1690
1705
|
} else {
|
|
1691
|
-
footer =
|
|
1706
|
+
footer = blockWidth < 96
|
|
1692
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}`
|
|
1693
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}`;
|
|
1694
1709
|
}
|
|
1695
|
-
out.push(footer + "\n");
|
|
1710
|
+
out.push(M + footer + "\n");
|
|
1696
1711
|
if (state.showHelp) {
|
|
1697
|
-
out.push(
|
|
1712
|
+
out.push(`${M} ${DIM}● default · ❯ cursor · n/+ starts guided setup · i opens /invite${RESET}${ANSI.clearLine}`);
|
|
1698
1713
|
} else {
|
|
1699
1714
|
out.push(ANSI.clearLine);
|
|
1700
1715
|
}
|
|
@@ -1706,12 +1721,30 @@ async function browser(args = {}) {
|
|
|
1706
1721
|
process.stdout.write(ANSI.altScreenEnter + ANSI.mouseOff + ANSI.cursorHide + ANSI.clear + ANSI.home);
|
|
1707
1722
|
const isMouseEvent = makeMouseFilter();
|
|
1708
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;
|
|
1709
1738
|
const cleanup = () => {
|
|
1710
1739
|
if (done) return;
|
|
1711
1740
|
done = true;
|
|
1712
1741
|
process.stdin.setRawMode(false);
|
|
1713
1742
|
process.stdin.pause();
|
|
1714
1743
|
process.stdin.removeAllListeners("keypress");
|
|
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);
|
|
1715
1748
|
// Clear the alt buffer BEFORE leaving it. A well-behaved terminal discards
|
|
1716
1749
|
// the alt buffer and restores the primary screen on \x1b[?1049l, so this is
|
|
1717
1750
|
// a harmless no-op there. Warp, however, dumps the alt buffer's contents
|
|
@@ -1720,10 +1753,12 @@ async function browser(args = {}) {
|
|
|
1720
1753
|
// guided "+ new" setup) starts on a clean screen.
|
|
1721
1754
|
process.stdout.write(ANSI.mouseOff + ANSI.cursorShow + ANSI.clear + ANSI.home + ANSI.altScreenLeave);
|
|
1722
1755
|
};
|
|
1756
|
+
onSigint = () => { cleanup(); process.exit(130); };
|
|
1757
|
+
onSigterm = () => { cleanup(); process.exit(143); };
|
|
1723
1758
|
process.on("exit", cleanup);
|
|
1724
|
-
process.on("SIGINT",
|
|
1725
|
-
process.on("SIGTERM",
|
|
1726
|
-
process.stdout.on("resize",
|
|
1759
|
+
process.on("SIGINT", onSigint);
|
|
1760
|
+
process.on("SIGTERM", onSigterm);
|
|
1761
|
+
process.stdout.on("resize", onResize);
|
|
1727
1762
|
|
|
1728
1763
|
readline.emitKeypressEvents(process.stdin);
|
|
1729
1764
|
process.stdin.setRawMode(true);
|
package/lib/local.js
CHANGED
|
@@ -125,6 +125,62 @@ async function getFileContent(token, owner, repo, filePath) {
|
|
|
125
125
|
return Buffer.from(data.content, "base64").toString("utf-8");
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
// True if `owner` resolves to a real GitHub account (user OR org — both live
|
|
129
|
+
// under /users/{login}). Lets join distinguish "wrong owner" from "owner has
|
|
130
|
+
// no Egregore", which is the difference between a typo and a missing repo.
|
|
131
|
+
async function ownerExists(token, owner) {
|
|
132
|
+
const { status } = await ghApi("GET", `/users/${owner}`, token);
|
|
133
|
+
return status === 200;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Repo names under `owner` that the authenticated user can actually reach —
|
|
137
|
+
// org membership or a (already-accepted) personal-repo collaborator invite.
|
|
138
|
+
// /user/repos is the only endpoint that honors private + collaborator repos
|
|
139
|
+
// across arbitrary owners, so we list it and filter by owner.
|
|
140
|
+
async function listAccessibleRepos(token, owner) {
|
|
141
|
+
const ownerLc = owner.toLowerCase();
|
|
142
|
+
const names = [];
|
|
143
|
+
let page = 1;
|
|
144
|
+
while (page <= 4) { // cap at 400 accessible repos
|
|
145
|
+
const url = `/user/repos?per_page=100&sort=updated&affiliation=owner,collaborator,organization_member&page=${page}`;
|
|
146
|
+
const { status, data } = await ghApi("GET", url, token);
|
|
147
|
+
if (status !== 200 || !Array.isArray(data) || data.length === 0) break;
|
|
148
|
+
for (const r of data) {
|
|
149
|
+
if (r.owner?.login?.toLowerCase() === ownerLc) names.push(r.name);
|
|
150
|
+
}
|
|
151
|
+
if (data.length < 100) break;
|
|
152
|
+
page += 1;
|
|
153
|
+
}
|
|
154
|
+
return names;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Discover every repo under `owner` that carries an egregore.json on its
|
|
158
|
+
// default branch. Probes the conventional name first, then Egregore-looking
|
|
159
|
+
// names, then the rest — capped so a large account doesn't fan out forever.
|
|
160
|
+
// Returns [{ repo, config }] so the caller never re-fetches the config.
|
|
161
|
+
async function discoverEgregoreRepos(token, owner) {
|
|
162
|
+
const seen = new Set();
|
|
163
|
+
const candidates = [];
|
|
164
|
+
const add = (name) => {
|
|
165
|
+
const key = name.toLowerCase();
|
|
166
|
+
if (!seen.has(key)) { seen.add(key); candidates.push(name); }
|
|
167
|
+
};
|
|
168
|
+
add("egregore"); // conventional default — also covers public repos not in the list
|
|
169
|
+
const accessible = await listAccessibleRepos(token, owner);
|
|
170
|
+
for (const n of accessible.filter((n) => /egregore/i.test(n))) add(n);
|
|
171
|
+
for (const n of accessible.filter((n) => !/egregore/i.test(n))) add(n);
|
|
172
|
+
|
|
173
|
+
const MAX_PROBES = 20;
|
|
174
|
+
const found = [];
|
|
175
|
+
for (const repo of candidates.slice(0, MAX_PROBES)) {
|
|
176
|
+
let raw = null;
|
|
177
|
+
try { raw = await getFileContent(token, owner, repo, "egregore.json"); } catch { raw = null; }
|
|
178
|
+
if (!raw) continue;
|
|
179
|
+
try { found.push({ repo, config: JSON.parse(raw) }); } catch { /* malformed config — skip */ }
|
|
180
|
+
}
|
|
181
|
+
return found;
|
|
182
|
+
}
|
|
183
|
+
|
|
128
184
|
async function putFileContent(token, owner, repo, filePath, content, message) {
|
|
129
185
|
// Get SHA if file already exists
|
|
130
186
|
const { status: existStatus, data: existData } = await ghApi(
|
|
@@ -1253,22 +1309,63 @@ async function localJoinFlow(orgArg, ui, ctx = {}) {
|
|
|
1253
1309
|
await acceptPendingInvitations(githubToken, orgLogin, ui);
|
|
1254
1310
|
s1.stop("Invitations accepted");
|
|
1255
1311
|
|
|
1256
|
-
// 4. Resolve
|
|
1257
|
-
//
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1312
|
+
// 4. Resolve which repo under `orgLogin` holds egregore.json. An explicit
|
|
1313
|
+
// repo is read directly; a bare owner is discovered (the founder flow
|
|
1314
|
+
// names the repo after the org slug, NOT "egregore", so we can't assume).
|
|
1315
|
+
// The shared install() helper owns all local setup after this point.
|
|
1316
|
+
const s2 = ui.spinner(
|
|
1317
|
+
explicitRepo
|
|
1318
|
+
? `Reading ${orgLogin}/${explicitRepo}/egregore.json...`
|
|
1319
|
+
: `Finding the Egregore in ${orgLogin}...`,
|
|
1320
|
+
);
|
|
1321
|
+
let candidates = [];
|
|
1261
1322
|
try {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1323
|
+
if (explicitRepo) {
|
|
1324
|
+
const raw = await getFileContent(githubToken, orgLogin, explicitRepo, "egregore.json");
|
|
1325
|
+
if (raw) candidates = [{ repo: explicitRepo, config: JSON.parse(raw) }];
|
|
1326
|
+
} else {
|
|
1327
|
+
candidates = await discoverEgregoreRepos(githubToken, orgLogin);
|
|
1328
|
+
}
|
|
1329
|
+
} catch {
|
|
1330
|
+
candidates = [];
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
if (candidates.length === 0) {
|
|
1334
|
+
s2.fail("Could not find an Egregore to join");
|
|
1335
|
+
const exists = await ownerExists(githubToken, orgLogin);
|
|
1336
|
+
if (!exists) {
|
|
1337
|
+
// The owner itself isn't a GitHub account — almost always a repo name
|
|
1338
|
+
// passed where owner/repo was meant (e.g. `join xenocracy` for
|
|
1339
|
+
// `fcdagdelen/xenocracy`).
|
|
1340
|
+
ui.error(`No GitHub user or organization named ${ui.bold(orgLogin)}.`);
|
|
1341
|
+
ui.info(`Join targets are ${ui.bold("owner/repo")} — e.g. ${ui.bold(joinCommand("acme", "acme"))}.`);
|
|
1342
|
+
} else if (explicitRepo) {
|
|
1343
|
+
ui.error(`${ui.bold(`${orgLogin}/${explicitRepo}`)} has no egregore.json on its default branch.`);
|
|
1344
|
+
ui.info("Confirm the repo name, that egregore.json is committed to the default branch, and that you accepted the GitHub invitation.");
|
|
1345
|
+
} else {
|
|
1346
|
+
ui.error(`No Egregore repo found under ${ui.bold(orgLogin)} that you can access.`);
|
|
1347
|
+
ui.info(`If you know it, pass the repo: ${ui.bold(joinCommand(orgLogin, "<repo>"))}. Otherwise ask the admin for the exact ${ui.bold("owner/repo")}.`);
|
|
1348
|
+
}
|
|
1270
1349
|
process.exit(1);
|
|
1271
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
let repoName;
|
|
1353
|
+
let config;
|
|
1354
|
+
if (candidates.length === 1) {
|
|
1355
|
+
({ repo: repoName, config } = candidates[0]);
|
|
1356
|
+
s2.stop(`Found ${orgLogin}/${repoName}`);
|
|
1357
|
+
} else {
|
|
1358
|
+
s2.stop(`Found ${candidates.length} Egregores in ${orgLogin}`);
|
|
1359
|
+
const picked = await ui.choose(
|
|
1360
|
+
`Which Egregore in ${orgLogin}?`,
|
|
1361
|
+
candidates.map((c) => ({
|
|
1362
|
+
label: `${orgLogin}/${c.repo}`,
|
|
1363
|
+
description: c.config.org_name || "",
|
|
1364
|
+
_candidate: c,
|
|
1365
|
+
})),
|
|
1366
|
+
);
|
|
1367
|
+
({ repo: repoName, config } = picked._candidate);
|
|
1368
|
+
}
|
|
1272
1369
|
ui.success(`Found ${ui.bold(config.org_name || orgLogin)}`);
|
|
1273
1370
|
|
|
1274
1371
|
const orgName = config.org_name || orgLogin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.10",
|
|
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"
|