create-egregore 0.21.4 → 0.21.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.
- package/assets/egregore-launcher.js +88 -14
- package/bin/cli.js +6 -3
- package/lib/api.js +26 -22
- package/lib/connect-runtime.js +13 -8
- package/lib/hosted-api.js +70 -0
- package/lib/local.js +3 -17
- package/lib/setup.js +6 -1
- package/package.json +2 -2
- package/runtime/claude/.claude/context/telemetry.md +8 -18
- package/runtime/claude/.claude/skills/emissary/SKILL.md +1 -1
- package/runtime/claude/.claude/skills/telemetry/SKILL.md +3 -3
- package/runtime/claude/.claude/skills/update/SKILL.md +46 -32
- package/runtime/claude/bin/handoff-run.sh +1 -1
- package/runtime/claude/bin/hosting-ops.sh +5 -243
- package/runtime/claude/bin/lib/framework-update.sh +3 -1
- package/runtime/claude/bin/lib/git-sync.sh +2 -2
- package/runtime/claude/bin/notify.sh +1 -1
- package/runtime/claude/bin/post-update.sh +13 -0
- package/runtime/claude/bin/publish-artifact.sh +1 -1
- package/runtime/claude/bin/telemetry.sh +7 -10
- package/runtime/claude/egregore_runtime/contracts.py +38 -0
- package/runtime/claude/egregore_runtime/member_authority.py +405 -0
- package/runtime/claude/egregore_runtime/upgrade.py +41 -2
- package/runtime/claude/manifest.json +1 -1
- package/runtime/codex/bin/handoff-run.sh +1 -1
- package/runtime/codex/bin/hosting-ops.sh +5 -243
- package/runtime/codex/bin/lib/framework-update.sh +3 -1
- package/runtime/codex/bin/lib/git-sync.sh +2 -2
- package/runtime/codex/bin/notify.sh +1 -1
- package/runtime/codex/bin/post-update.sh +13 -0
- package/runtime/codex/bin/publish-artifact.sh +1 -1
- package/runtime/codex/bin/telemetry.sh +7 -10
- package/runtime/codex/egregore_runtime/contracts.py +38 -0
- package/runtime/codex/egregore_runtime/member_authority.py +405 -0
- package/runtime/codex/egregore_runtime/upgrade.py +41 -2
- package/runtime/codex/manifest.json +1 -1
- package/runtime/pi/bin/handoff-run.sh +1 -1
- package/runtime/pi/bin/hosting-ops.sh +5 -243
- package/runtime/pi/bin/lib/framework-update.sh +3 -1
- package/runtime/pi/bin/lib/git-sync.sh +2 -2
- package/runtime/pi/bin/notify.sh +1 -1
- package/runtime/pi/bin/post-update.sh +13 -0
- package/runtime/pi/bin/publish-artifact.sh +1 -1
- package/runtime/pi/bin/telemetry.sh +7 -10
- package/runtime/pi/egregore_runtime/contracts.py +38 -0
- package/runtime/pi/egregore_runtime/member_authority.py +405 -0
- package/runtime/pi/egregore_runtime/upgrade.py +41 -2
- package/runtime/pi/manifest.json +1 -1
- package/runtime/prime/bin/handoff-run.sh +1 -1
- package/runtime/prime/bin/hosting-ops.sh +5 -243
- package/runtime/prime/bin/lib/framework-update.sh +3 -1
- package/runtime/prime/bin/lib/git-sync.sh +2 -2
- package/runtime/prime/bin/notify.sh +1 -1
- package/runtime/prime/bin/post-update.sh +13 -0
- package/runtime/prime/bin/publish-artifact.sh +1 -1
- package/runtime/prime/bin/telemetry.sh +7 -10
- package/runtime/prime/egregore_runtime/contracts.py +38 -0
- package/runtime/prime/egregore_runtime/member_authority.py +405 -0
- package/runtime/prime/egregore_runtime/upgrade.py +41 -2
- package/runtime/prime/manifest.json +1 -1
- package/runtime/claude/bin/build-sealed-dataroom.mjs +0 -722
- package/runtime/codex/bin/build-sealed-dataroom.mjs +0 -722
- package/runtime/pi/bin/build-sealed-dataroom.mjs +0 -722
- package/runtime/prime/bin/build-sealed-dataroom.mjs +0 -722
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.21.
|
|
2
|
+
// egregore-launcher-version: 0.21.6
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -35,7 +35,7 @@ const crypto = require("node:crypto");
|
|
|
35
35
|
const { spawnSync, spawn } = require("node:child_process");
|
|
36
36
|
const readline = require("node:readline");
|
|
37
37
|
|
|
38
|
-
const VERSION = "0.21.
|
|
38
|
+
const VERSION = "0.21.6";
|
|
39
39
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
40
40
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
41
41
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
|
@@ -46,10 +46,72 @@ const UPDATE_REGISTRY_URL =
|
|
|
46
46
|
const UPDATE_AVAILABLE_CACHE_TTL_MS = 6 * 60 * 60 * 1000;
|
|
47
47
|
const UPDATE_FAILURE_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
48
48
|
const UPDATE_CHECK_TIMEOUT_MS = 1500;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
// Hosted API address. The API moved to api.egregore.xyz after the October 2026
|
|
50
|
+
// security reset; its old origin is retired and must never receive a token or
|
|
51
|
+
// key, even from an egregore.json or environment saved before the move. The
|
|
52
|
+
// launcher is installed standalone, so this is an inline copy of
|
|
53
|
+
// lib/hosted-api.js; test/hosted-api.test.js holds the two in step and both
|
|
54
|
+
// to the rule in bin/tests/fixtures/hosted-api-url-vectors.json.
|
|
55
|
+
const HOSTED_API_ORIGIN = "https://api.egregore.xyz";
|
|
56
|
+
const RETIRED_API_ORIGIN = "https://egregore-production-55f2.up.railway.app";
|
|
57
|
+
const RETIRED_API_HOST = RETIRED_API_ORIGIN.slice("https://".length);
|
|
58
|
+
const REFUSED_API_ADDRESS_NOTICE = "The hosted services address isn't a plain web address, so nothing was sent. "
|
|
59
|
+
+ `Correct api_url in egregore.json; the hosted address is ${HOSTED_API_ORIGIN}.`;
|
|
60
|
+
// A plain web address: http or https, an ASCII host of letters, digits, dots,
|
|
61
|
+
// hyphens and underscores, an optional port and a plain path. Every transport
|
|
62
|
+
// reads it the same way. Anything else is refused: curl, urllib and WHATWG
|
|
63
|
+
// parsers disagree about where userinfo, backslashes, percent-encoding, glob
|
|
64
|
+
// characters, queries, whitespace or non-ASCII send a request.
|
|
65
|
+
const PLAIN_API_URL = /^https?:\/\/([A-Za-z0-9._-]+)(:[0-9]{1,5})?(\/[A-Za-z0-9._~/-]*)?$/;
|
|
66
|
+
|
|
67
|
+
// Thrown for a refused address; callers send nothing.
|
|
68
|
+
class RefusedApiAddressError extends Error {
|
|
69
|
+
constructor() {
|
|
70
|
+
super(REFUSED_API_ADDRESS_NOTICE);
|
|
71
|
+
this.name = "RefusedApiAddressError";
|
|
72
|
+
this.code = "EGREGORE_REFUSED_API_ADDRESS";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// The saved text without surrounding ASCII whitespace.
|
|
77
|
+
function apiUrlText(value) {
|
|
78
|
+
const text = value === null || value === undefined ? "" : String(value);
|
|
79
|
+
return text.replace(/^[ \t\n\r\f\v]+|[ \t\n\r\f\v]+$/g, "");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// The plain address's host and path, the host flagged when it is the retired
|
|
83
|
+
// one (case and trailing dots ignored), or null when the text is not plain.
|
|
84
|
+
function plainApiUrl(text) {
|
|
85
|
+
const plain = PLAIN_API_URL.exec(text);
|
|
86
|
+
if (!plain) return null;
|
|
87
|
+
return { retired: plain[1].toLowerCase().replace(/\.+$/, "") === RETIRED_API_HOST, path: plain[3] || "" };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// True when the address is a plain address on the retired origin.
|
|
91
|
+
function retiredApiUrl(value) {
|
|
92
|
+
const plain = plainApiUrl(apiUrlText(value));
|
|
93
|
+
return plain !== null && plain.retired;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The address to send requests to, trailing slashes trimmed: a plain retired
|
|
97
|
+
// address becomes the hosted API with its path kept; any other plain address
|
|
98
|
+
// keeps its text; empty stays empty. Anything else throws
|
|
99
|
+
// RefusedApiAddressError, whose message says why, and nothing is sent.
|
|
100
|
+
function hostedApiUrl(value) {
|
|
101
|
+
const text = apiUrlText(value);
|
|
102
|
+
if (!text) return "";
|
|
103
|
+
const plain = plainApiUrl(text);
|
|
104
|
+
if (plain === null) throw new RefusedApiAddressError();
|
|
105
|
+
return (plain.retired ? HOSTED_API_ORIGIN + plain.path : text).replace(/\/+$/, "");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const CONNECT_API_URL = (() => {
|
|
109
|
+
// A refused override leaves no address, so connectedApiRequest sends nothing.
|
|
110
|
+
try { return hostedApiUrl(process.env.EGREGORE_CONNECT_API_URL || HOSTED_API_ORIGIN); } catch (error) {
|
|
111
|
+
if (error instanceof RefusedApiAddressError) return "";
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
})();
|
|
53
115
|
|
|
54
116
|
const NATIVE_CODEX_SKILLS = [
|
|
55
117
|
"activity",
|
|
@@ -1482,7 +1544,7 @@ function githubRequest(token, method, apiPath, body) {
|
|
|
1482
1544
|
}
|
|
1483
1545
|
|
|
1484
1546
|
function apiRequest(apiUrl, apiKey, apiPath, body) {
|
|
1485
|
-
const url = new URL(apiPath, apiUrl
|
|
1547
|
+
const url = new URL(apiPath, hostedApiUrl(apiUrl) + "/");
|
|
1486
1548
|
return requestJson({
|
|
1487
1549
|
hostname: url.hostname,
|
|
1488
1550
|
port: url.port || undefined,
|
|
@@ -1494,6 +1556,7 @@ function apiRequest(apiUrl, apiKey, apiPath, body) {
|
|
|
1494
1556
|
}
|
|
1495
1557
|
|
|
1496
1558
|
function connectedApiRequest(githubToken, method, apiPath, body, headers = {}) {
|
|
1559
|
+
if (!CONNECT_API_URL) return Promise.reject(new RefusedApiAddressError());
|
|
1497
1560
|
const url = new URL(apiPath, CONNECT_API_URL + "/");
|
|
1498
1561
|
return requestJson({
|
|
1499
1562
|
hostname: url.hostname,
|
|
@@ -1652,8 +1715,14 @@ function adapterErrorText(detail) {
|
|
|
1652
1715
|
|
|
1653
1716
|
function connectError(result, fallback) {
|
|
1654
1717
|
if (!result) return fallback;
|
|
1718
|
+
// Unavailability says nothing about the person's credentials, and an edge
|
|
1719
|
+
// or proxy page (HTML or plain text) is never printed raw.
|
|
1720
|
+
if ([502, 503, 504].includes(result.status)) {
|
|
1721
|
+
return "Egregore's hosted services are temporarily unavailable. Local memory and search still work.";
|
|
1722
|
+
}
|
|
1655
1723
|
const data = result.data || {};
|
|
1656
|
-
|
|
1724
|
+
if (typeof data === "string") return fallback;
|
|
1725
|
+
return adapterErrorText(data.detail || data.message || fallback);
|
|
1657
1726
|
}
|
|
1658
1727
|
|
|
1659
1728
|
function sleep(ms) {
|
|
@@ -1716,7 +1785,12 @@ function validatedConnectedClaim(config, response, expectedOrgIds = []) {
|
|
|
1716
1785
|
typeof response?.api_key !== "string" || !response.api_key.trim()) {
|
|
1717
1786
|
die("Connected did not return usable access settings. Local configuration is unchanged; run the same command to retry.");
|
|
1718
1787
|
}
|
|
1719
|
-
|
|
1788
|
+
let apiUrl = "";
|
|
1789
|
+
try { apiUrl = hostedApiUrl(response.api_url); } catch (error) {
|
|
1790
|
+
if (!(error instanceof RefusedApiAddressError)) throw error;
|
|
1791
|
+
die("Connected returned an address that isn't a plain web address. Local configuration is unchanged, and nothing was sent with its key.");
|
|
1792
|
+
}
|
|
1793
|
+
return { orgId, apiUrl, apiKey: response.api_key };
|
|
1720
1794
|
}
|
|
1721
1795
|
|
|
1722
1796
|
function hostedMemorySyncEnabled(config) {
|
|
@@ -1893,10 +1967,10 @@ async function cmdConnect(args) {
|
|
|
1893
1967
|
!intentId ||
|
|
1894
1968
|
!resumable
|
|
1895
1969
|
) {
|
|
1896
|
-
// Local installs
|
|
1897
|
-
//
|
|
1898
|
-
//
|
|
1899
|
-
//
|
|
1970
|
+
// Local installs never register with the platform. Connect requires it
|
|
1971
|
+
// functionally, and choosing Connect is the consent — register on demand.
|
|
1972
|
+
// Idempotent for members of already-registered orgs. Identity conflicts
|
|
1973
|
+
// must stop before checkout.
|
|
1900
1974
|
const registration = await connectedApiRequest(githubToken, "POST", "/api/org/register", {
|
|
1901
1975
|
org_name: cfg.org_name || inst.slug,
|
|
1902
1976
|
github_org: cfg.github_org || "",
|
|
@@ -2239,7 +2313,7 @@ function tryNotifyInvite(inst, username, message, cfg, env) {
|
|
|
2239
2313
|
}
|
|
2240
2314
|
|
|
2241
2315
|
async function connectedInvite(inst, username, cfg, env) {
|
|
2242
|
-
const apiUrl = cfg.api_url
|
|
2316
|
+
const apiUrl = hostedApiUrl(cfg.api_url);
|
|
2243
2317
|
const apiKey = env.EGREGORE_API_KEY || "";
|
|
2244
2318
|
const ghToken = env.GITHUB_TOKEN || "";
|
|
2245
2319
|
if (!apiUrl || !apiKey || !ghToken) return null;
|
package/bin/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ const path = require("node:path");
|
|
|
18
18
|
const { spawnSync } = require("node:child_process");
|
|
19
19
|
const ui = require("../lib/ui");
|
|
20
20
|
const { EgregoreAPI } = require("../lib/api");
|
|
21
|
+
const { HOSTED_API_ORIGIN, hostedApiUrl } = require("../lib/hosted-api");
|
|
21
22
|
const {
|
|
22
23
|
canUseInteractiveBrowserFlow,
|
|
23
24
|
deviceFlow,
|
|
@@ -40,7 +41,9 @@ const {
|
|
|
40
41
|
const { detectContext, isWritable, isSystemDir } = require("../lib/context");
|
|
41
42
|
const { commandExists } = require("../lib/path-lookup");
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
// Resolved when a hosted flow starts, so a refused EGREGORE_API_URL stops
|
|
45
|
+
// that flow with the reason and never falls back to another address.
|
|
46
|
+
function hostedApiSetting() { return hostedApiUrl(process.env.EGREGORE_API_URL || HOSTED_API_ORIGIN); }
|
|
44
47
|
const INSTANCES_FILE = path.join(os.homedir(), ".egregore", "instances.json");
|
|
45
48
|
|
|
46
49
|
function readLocalInstances() {
|
|
@@ -605,7 +608,7 @@ async function main() {
|
|
|
605
608
|
|
|
606
609
|
if (args.token) {
|
|
607
610
|
// ===== Connected mode: token provided =====
|
|
608
|
-
const api = new EgregoreAPI(args.api ||
|
|
611
|
+
const api = new EgregoreAPI(args.api || hostedApiSetting());
|
|
609
612
|
await tokenFlow(api, args.token, ctx);
|
|
610
613
|
} else if (args.subcommand === "join") {
|
|
611
614
|
// ===== Local mode: join existing =====
|
|
@@ -617,7 +620,7 @@ async function main() {
|
|
|
617
620
|
await localJoinFlow(args.joinOrg, ui, ctx);
|
|
618
621
|
} else if (args.connected) {
|
|
619
622
|
// ===== Connected/hosted interactive flow (explicit opt-in) =====
|
|
620
|
-
const api = new EgregoreAPI(args.api ||
|
|
623
|
+
const api = new EgregoreAPI(args.api || hostedApiSetting());
|
|
621
624
|
await interactiveFlow(api, ctx);
|
|
622
625
|
} else {
|
|
623
626
|
// ===== Local founder is the DEFAULT — bare `npx create-egregore` and
|
package/lib/api.js
CHANGED
|
@@ -5,8 +5,22 @@
|
|
|
5
5
|
|
|
6
6
|
const https = require("node:https");
|
|
7
7
|
const { URL } = require("node:url");
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
const { HOSTED_API_ORIGIN, hostedApiUrl } = require("./hosted-api");
|
|
9
|
+
|
|
10
|
+
const UNAVAILABLE =
|
|
11
|
+
"Egregore's hosted services are temporarily unavailable. Local memory and search still work.";
|
|
12
|
+
|
|
13
|
+
// A failed response becomes one short line. Proxy and edge pages are HTML or
|
|
14
|
+
// plain text, so a body that is not JSON is never shown to the person.
|
|
15
|
+
function responseError(statusCode, data) {
|
|
16
|
+
if ([502, 503, 504].includes(statusCode)) return new Error(UNAVAILABLE);
|
|
17
|
+
let detail = "";
|
|
18
|
+
try {
|
|
19
|
+
const json = JSON.parse(data);
|
|
20
|
+
if (json && typeof json.detail === "string") detail = json.detail;
|
|
21
|
+
} catch {}
|
|
22
|
+
return new Error(detail || `HTTP ${statusCode}`);
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
function request(method, url, { body, headers = {}, timeout = 30000 } = {}) {
|
|
12
26
|
return new Promise((resolve, reject) => {
|
|
@@ -28,19 +42,14 @@ function request(method, url, { body, headers = {}, timeout = 30000 } = {}) {
|
|
|
28
42
|
let data = "";
|
|
29
43
|
res.on("data", (chunk) => (data += chunk));
|
|
30
44
|
res.on("end", () => {
|
|
45
|
+
if (res.statusCode >= 400) {
|
|
46
|
+
reject(responseError(res.statusCode, data));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
31
49
|
try {
|
|
32
|
-
|
|
33
|
-
if (res.statusCode >= 400) {
|
|
34
|
-
reject(new Error(json.detail || `HTTP ${res.statusCode}`));
|
|
35
|
-
} else {
|
|
36
|
-
resolve(json);
|
|
37
|
-
}
|
|
50
|
+
resolve(JSON.parse(data));
|
|
38
51
|
} catch {
|
|
39
|
-
|
|
40
|
-
reject(new Error(`HTTP ${res.statusCode}: ${data}`));
|
|
41
|
-
} else {
|
|
42
|
-
resolve(data);
|
|
43
|
-
}
|
|
52
|
+
resolve(data);
|
|
44
53
|
}
|
|
45
54
|
});
|
|
46
55
|
});
|
|
@@ -60,7 +69,9 @@ function request(method, url, { body, headers = {}, timeout = 30000 } = {}) {
|
|
|
60
69
|
|
|
61
70
|
class EgregoreAPI {
|
|
62
71
|
constructor(baseUrl) {
|
|
63
|
-
|
|
72
|
+
// A retired address (an old --api or EGREGORE_API_URL) never receives the
|
|
73
|
+
// GitHub token these calls carry.
|
|
74
|
+
this.base = hostedApiUrl(baseUrl || HOSTED_API_ORIGIN);
|
|
64
75
|
}
|
|
65
76
|
|
|
66
77
|
async claimToken(token, githubToken) {
|
|
@@ -99,13 +110,6 @@ class EgregoreAPI {
|
|
|
99
110
|
});
|
|
100
111
|
}
|
|
101
112
|
|
|
102
|
-
async registerOrg(githubToken, { org_name, github_org, slug, flow_variant, org_id, repo_name }) {
|
|
103
|
-
return request("POST", `${this.base}/api/org/register`, {
|
|
104
|
-
headers: { Authorization: `Bearer ${githubToken}` },
|
|
105
|
-
body: { org_name, github_org, slug, ...(org_id ? { org_id } : {}), ...(repo_name ? { repo_name } : {}), ...(flow_variant ? { flow_variant } : {}) },
|
|
106
|
-
});
|
|
107
|
-
}
|
|
108
|
-
|
|
109
113
|
async exchangeCode(code) {
|
|
110
114
|
return request("POST", `${this.base}/api/auth/github/callback`, {
|
|
111
115
|
body: { code },
|
|
@@ -117,4 +121,4 @@ class EgregoreAPI {
|
|
|
117
121
|
}
|
|
118
122
|
}
|
|
119
123
|
|
|
120
|
-
module.exports = { EgregoreAPI, request };
|
|
124
|
+
module.exports = { EgregoreAPI, request, responseError, UNAVAILABLE };
|
package/lib/connect-runtime.js
CHANGED
|
@@ -8,6 +8,7 @@ const path = require("node:path");
|
|
|
8
8
|
const { URL } = require("node:url");
|
|
9
9
|
|
|
10
10
|
const PACKAGE = require("../package.json");
|
|
11
|
+
const { RefusedApiAddressError, hostedApiUrl } = require("./hosted-api");
|
|
11
12
|
const LOCAL_MANIFEST = path.join(".egregore", "connect-runtime-manifest.json");
|
|
12
13
|
const PACK_PATH = "/api/runtime/connect-pack";
|
|
13
14
|
const MAX_PACK_BYTES = 12 * 1024 * 1024;
|
|
@@ -63,12 +64,14 @@ function connectCredentials(egregoreDir) {
|
|
|
63
64
|
}
|
|
64
65
|
const apiKey = readApiKey(egregoreDir);
|
|
65
66
|
if (!apiKey) return { eligible: false, reason: "missing-api-key" };
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
67
|
+
// An address saved before the October 2026 reset never receives the key,
|
|
68
|
+
// and one that is not plain is refused: nothing is sent.
|
|
69
|
+
let apiUrl;
|
|
70
|
+
try { apiUrl = hostedApiUrl(config.api_url); } catch (error) {
|
|
71
|
+
if (error instanceof RefusedApiAddressError) return { eligible: false, reason: "refused-address" };
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
return { eligible: true, reason: "connected", apiUrl, apiKey };
|
|
72
75
|
}
|
|
73
76
|
|
|
74
77
|
function connectEligibility(egregoreDir) {
|
|
@@ -152,8 +155,10 @@ function validateConnectPack(pack) {
|
|
|
152
155
|
function requestConnectPack(apiUrl, apiKey, timeout = 30000) {
|
|
153
156
|
return new Promise((resolve, reject) => {
|
|
154
157
|
let parsed;
|
|
155
|
-
try { parsed = new URL(`${apiUrl}${PACK_PATH}`); }
|
|
156
|
-
|
|
158
|
+
try { parsed = new URL(`${hostedApiUrl(apiUrl)}${PACK_PATH}`); } catch (error) {
|
|
159
|
+
reject(error instanceof RefusedApiAddressError ? error : new Error("Connected instance has an invalid API URL"));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
157
162
|
const transport = parsed.protocol === "http:" ? http : https;
|
|
158
163
|
const request = transport.request(parsed, {
|
|
159
164
|
method: "GET",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// The hosted API moved to api.egregore.xyz after the October 2026 security
|
|
4
|
+
// reset. Its old Railway origin is retired and no longer ours to trust: a
|
|
5
|
+
// token or key sent there could reach whoever holds that address next. Every
|
|
6
|
+
// request that carries a credential resolves its address here, so an
|
|
7
|
+
// egregore.json or environment saved before the move never reaches it.
|
|
8
|
+
//
|
|
9
|
+
// The rule and the table every resolver follows:
|
|
10
|
+
// bin/tests/fixtures/hosted-api-url-vectors.json. The launcher
|
|
11
|
+
// (assets/egregore-launcher.js) is installed standalone and carries an inline
|
|
12
|
+
// copy of the code below, and bin/connector-google/src/hosted-api.ts a typed
|
|
13
|
+
// one; test/hosted-api.test.js holds all three to the table and to each other.
|
|
14
|
+
|
|
15
|
+
const HOSTED_API_ORIGIN = "https://api.egregore.xyz";
|
|
16
|
+
const RETIRED_API_ORIGIN = "https://egregore-production-55f2.up.railway.app";
|
|
17
|
+
const RETIRED_API_HOST = RETIRED_API_ORIGIN.slice("https://".length);
|
|
18
|
+
const REFUSED_API_ADDRESS_NOTICE = "The hosted services address isn't a plain web address, so nothing was sent. "
|
|
19
|
+
+ `Correct api_url in egregore.json; the hosted address is ${HOSTED_API_ORIGIN}.`;
|
|
20
|
+
// A plain web address: http or https, an ASCII host of letters, digits, dots,
|
|
21
|
+
// hyphens and underscores, an optional port and a plain path. Every transport
|
|
22
|
+
// reads it the same way. Anything else is refused: curl, urllib and WHATWG
|
|
23
|
+
// parsers disagree about where userinfo, backslashes, percent-encoding, glob
|
|
24
|
+
// characters, queries, whitespace or non-ASCII send a request.
|
|
25
|
+
const PLAIN_API_URL = /^https?:\/\/([A-Za-z0-9._-]+)(:[0-9]{1,5})?(\/[A-Za-z0-9._~/-]*)?$/;
|
|
26
|
+
|
|
27
|
+
// Thrown for a refused address; callers send nothing.
|
|
28
|
+
class RefusedApiAddressError extends Error {
|
|
29
|
+
constructor() {
|
|
30
|
+
super(REFUSED_API_ADDRESS_NOTICE);
|
|
31
|
+
this.name = "RefusedApiAddressError";
|
|
32
|
+
this.code = "EGREGORE_REFUSED_API_ADDRESS";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// The saved text without surrounding ASCII whitespace.
|
|
37
|
+
function apiUrlText(value) {
|
|
38
|
+
const text = value === null || value === undefined ? "" : String(value);
|
|
39
|
+
return text.replace(/^[ \t\n\r\f\v]+|[ \t\n\r\f\v]+$/g, "");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// The plain address's host and path, the host flagged when it is the retired
|
|
43
|
+
// one (case and trailing dots ignored), or null when the text is not plain.
|
|
44
|
+
function plainApiUrl(text) {
|
|
45
|
+
const plain = PLAIN_API_URL.exec(text);
|
|
46
|
+
if (!plain) return null;
|
|
47
|
+
return { retired: plain[1].toLowerCase().replace(/\.+$/, "") === RETIRED_API_HOST, path: plain[3] || "" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// True when the address is a plain address on the retired origin.
|
|
51
|
+
function retiredApiUrl(value) {
|
|
52
|
+
const plain = plainApiUrl(apiUrlText(value));
|
|
53
|
+
return plain !== null && plain.retired;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// The address to send requests to, trailing slashes trimmed: a plain retired
|
|
57
|
+
// address becomes the hosted API with its path kept; any other plain address
|
|
58
|
+
// keeps its text; empty stays empty. Anything else throws
|
|
59
|
+
// RefusedApiAddressError, whose message says why, and nothing is sent.
|
|
60
|
+
function hostedApiUrl(value) {
|
|
61
|
+
const text = apiUrlText(value);
|
|
62
|
+
if (!text) return "";
|
|
63
|
+
const plain = plainApiUrl(text);
|
|
64
|
+
if (plain === null) throw new RefusedApiAddressError();
|
|
65
|
+
return (plain.retired ? HOSTED_API_ORIGIN + plain.path : text).replace(/\/+$/, "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = {
|
|
69
|
+
HOSTED_API_ORIGIN, RETIRED_API_ORIGIN, REFUSED_API_ADDRESS_NOTICE, RefusedApiAddressError, retiredApiUrl, hostedApiUrl,
|
|
70
|
+
};
|
package/lib/local.js
CHANGED
|
@@ -35,7 +35,6 @@ const {
|
|
|
35
35
|
ensureInstallerMemberRole,
|
|
36
36
|
run,
|
|
37
37
|
} = require("./setup");
|
|
38
|
-
const { EgregoreAPI } = require("./api");
|
|
39
38
|
const { joinCommand } = require("./commands");
|
|
40
39
|
|
|
41
40
|
// Template repo for new egregore instances (OSS)
|
|
@@ -1157,8 +1156,6 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
1157
1156
|
base_branch: "main",
|
|
1158
1157
|
repos: selectedRepos,
|
|
1159
1158
|
upstream_url: "https://github.com/egregore-labs/egregore.git",
|
|
1160
|
-
report_url: "https://xgksfrirtdumacvmfkzj.supabase.co",
|
|
1161
|
-
report_key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inhna3NmcmlydGR1bWFjdm1ma3pqIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzA5OTA1ODksImV4cCI6MjA4NjU2NjU4OX0.1UWNgER6nQazrayly1KDrswClURUkGKxeirZg5L8uk4",
|
|
1162
1159
|
});
|
|
1163
1160
|
// Joiners resolve the org by reading egregore.json from the remote — if
|
|
1164
1161
|
// this push silently fails (observed: transient failure right after
|
|
@@ -1179,20 +1176,9 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
1179
1176
|
ui.info(` Re-run this installer to retry, or push egregore.json from the cloned repo.`);
|
|
1180
1177
|
}
|
|
1181
1178
|
|
|
1182
|
-
//
|
|
1183
|
-
//
|
|
1184
|
-
|
|
1185
|
-
try {
|
|
1186
|
-
const api = new EgregoreAPI();
|
|
1187
|
-
await api.registerOrg(githubToken, {
|
|
1188
|
-
org_name: orgName,
|
|
1189
|
-
github_org: githubOrg,
|
|
1190
|
-
slug,
|
|
1191
|
-
org_id: egreConfig.org_id,
|
|
1192
|
-
repo_name: repoName,
|
|
1193
|
-
});
|
|
1194
|
-
} catch {}
|
|
1195
|
-
}
|
|
1179
|
+
// A Local install registers nothing with a hosted service. The GitHub token
|
|
1180
|
+
// goes to GitHub only; `egregore connect` registers the organization when a
|
|
1181
|
+
// person explicitly chooses Connected.
|
|
1196
1182
|
|
|
1197
1183
|
// 9. Init memory dirs on remote (so joiners get the structure)
|
|
1198
1184
|
const memDirs = ["people", "identity", "invites", "handoffs", "knowledge/questions", "knowledge/decisions", "knowledge/patterns", "knowledge/findings", "quests", "wraps"];
|
package/lib/setup.js
CHANGED
|
@@ -8,6 +8,7 @@ const fs = require("node:fs");
|
|
|
8
8
|
const path = require("node:path");
|
|
9
9
|
const os = require("node:os");
|
|
10
10
|
const { runRendererInstaller } = require("./renderer-install");
|
|
11
|
+
const { RefusedApiAddressError, hostedApiUrl } = require("./hosted-api");
|
|
11
12
|
const {
|
|
12
13
|
hasCodexRuntimeSupport,
|
|
13
14
|
installCodexRuntime,
|
|
@@ -849,7 +850,11 @@ function ensureConnectedConfig(repoDir, data, ui) {
|
|
|
849
850
|
}
|
|
850
851
|
|
|
851
852
|
let changed = false;
|
|
852
|
-
|
|
853
|
+
let tokenApiUrl = "";
|
|
854
|
+
try { tokenApiUrl = data.api_url ? hostedApiUrl(data.api_url) : ""; } catch (error) {
|
|
855
|
+
if (!(error instanceof RefusedApiAddressError)) throw error;
|
|
856
|
+
throw new Error("Connected setup returned an address that isn't a plain web address; egregore.json is unchanged.");
|
|
857
|
+
}
|
|
853
858
|
if (tokenApiUrl && config.api_url !== tokenApiUrl) {
|
|
854
859
|
config.api_url = tokenApiUrl;
|
|
855
860
|
changed = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.6",
|
|
4
4
|
"description": "Set up Egregore for your team in one command \u2014 installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test:e2e": "node --test test/e2e-local.test.js",
|
|
20
20
|
"test:all": "npm run test:runtime && node --test test/",
|
|
21
21
|
"test:runtime": "node ../../bin/capability-distribution.mjs validate --root ../.. && node scripts/build-runtime-bundle.mjs --check && node scripts/build-claude-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node scripts/build-prime-runtime-bundle.mjs --check && node ../../bin/prime-render-spec.mjs --check && node ../../bin/pi-render-spec.mjs --check && node ../../api/scripts/build-connect-runtime-pack.mjs --check && bash ../../bin/tests/test-codex-native-skills.sh && bash ../../bin/tests/test-codex-spec-render.sh && node ../../bin/capability-distribution.mjs skill-contract --root ../.. --strict-contract && bash ../../bin/tests/test-bundle-skill-scripts.sh && node --test test/bundle-skill-scripts.test.js",
|
|
22
|
-
"test:package": "node --test --test-concurrency=1 test/invite-install.test.js test/capability-distribution.test.js test/connect-runtime.test.js test/context.test.js test/path-lookup.test.js test/nixos-portability.test.js test/parse-join-target.test.js test/skip-repos.test.js test/install-origin-guard.test.js test/onboarding-shortcircuit.test.js test/runtime-identity.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/claude-runtime.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/prime-runtime.test.js test/prime-branch-policy.test.js test/prime-bridge-skill.test.js test/pi-observe-context.test.js test/native-command-observe.test.js test/prime-rpc-driver.test.js test/pi-session-smoke.test.js test/prime-session-smoke.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/runtime-ownership.test.js test/installer-finalization.test.js test/renderer-install.test.js test/runtime-readiness.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/installer-connect-choice.test.js test/launcher-connected-status.test.js test/launcher-telemetry.test.js test/launcher-runtime.test.js test/launcher-settings.test.js test/launcher-runtime-candidate.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/harness-add.test.js test/connected-config.test.js test/workflow-settings.test.js"
|
|
22
|
+
"test:package": "node --test --test-concurrency=1 test/invite-install.test.js test/capability-distribution.test.js test/connect-runtime.test.js test/hosted-api.test.js test/context.test.js test/path-lookup.test.js test/nixos-portability.test.js test/parse-join-target.test.js test/skip-repos.test.js test/install-origin-guard.test.js test/onboarding-shortcircuit.test.js test/runtime-identity.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/claude-runtime.test.js test/codex-runtime.test.js test/pi-runtime.test.js test/prime-runtime.test.js test/prime-branch-policy.test.js test/prime-bridge-skill.test.js test/pi-observe-context.test.js test/native-command-observe.test.js test/prime-rpc-driver.test.js test/pi-session-smoke.test.js test/prime-session-smoke.test.js test/pi-product-workflows.test.js test/installer-runtime-source.test.js test/runtime-ownership.test.js test/installer-finalization.test.js test/renderer-install.test.js test/runtime-readiness.test.js test/scroll-repro.test.js test/launcher-refocus-redraw.test.js test/multiselect-remote-search.test.js test/back-navigation.test.js test/manifest-form.test.js test/launcher-delete.test.js test/launcher-connect.test.js test/installer-connect-choice.test.js test/launcher-connected-status.test.js test/launcher-telemetry.test.js test/launcher-runtime.test.js test/launcher-settings.test.js test/launcher-runtime-candidate.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/harness-add.test.js test/connected-config.test.js test/workflow-settings.test.js"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin/",
|
|
@@ -162,21 +162,11 @@ can explicitly choose a public upstream report. An internal issue stays within
|
|
|
162
162
|
its organization's memory or configured tracker; it does not automatically
|
|
163
163
|
reach Egregore support. `/wrap` does not collect or submit session feedback.
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
submission.
|
|
174
|
-
|
|
175
|
-
Never invoke legacy feedback automatically, generate it from a transcript, or
|
|
176
|
-
use it as a fallback when private support fails. Only explicitly selected and
|
|
177
|
-
reviewed content may be shared. The helper adds legacy user and organization
|
|
178
|
-
labels; it does not guarantee anonymization or scrub arbitrary input.
|
|
179
|
-
|
|
180
|
-
Legacy feedback can be disabled with `EGREGORE_NO_REPORTS=1` or
|
|
181
|
-
`DO_NOT_TRACK=1` in the process environment, or `"session_reports": false` in
|
|
182
|
-
`.egregore-state.json`. These switches do not grant consent to submit content.
|
|
165
|
+
Anonymous session feedback is retired. `bin/session-report.sh` remains only as
|
|
166
|
+
a compatibility notice: it never reads the submitted content, sends a request,
|
|
167
|
+
or saves a report. Old `report_url` and `report_key` configuration fields are
|
|
168
|
+
ignored. Existing local feedback files are left untouched.
|
|
169
|
+
|
|
170
|
+
Never invoke legacy feedback automatically or use it as a fallback when private
|
|
171
|
+
support fails. Use the reviewed issue workflow above; private support retains
|
|
172
|
+
its authenticated organization identity and exact preview/confirmation boundary.
|
|
@@ -206,7 +206,7 @@ Below the box, one line:
|
|
|
206
206
|
[open emissary →]({url})
|
|
207
207
|
```
|
|
208
208
|
|
|
209
|
-
Share the bare page link — not a `/raw` URL or a "Run this … packet" prompt. With the emissary skill installed, pasting the link is enough; this de-alarmed handover
|
|
209
|
+
Share the bare page link — not a `/raw` URL or a "Run this … packet" prompt. With the emissary skill installed, pasting the link is enough; this de-alarmed handover avoids tripping a recipient agent's prompt-injection alarms.
|
|
210
210
|
|
|
211
211
|
If natural, add one short sentence of sign-off — what the recipient should do next, or "shared publicly — anyone with the link can run it."
|
|
212
212
|
|
|
@@ -86,9 +86,9 @@ command containing its one-time token. A changed target, changed buffer,
|
|
|
86
86
|
expired token, or bad token requires a new proposal. Local events remain after
|
|
87
87
|
a successful share.
|
|
88
88
|
|
|
89
|
-
Local instances have no default outbound sink. They may export
|
|
90
|
-
explicit endpoint
|
|
91
|
-
|
|
89
|
+
Local instances have no default outbound sink. They may export or name an
|
|
90
|
+
explicit endpoint. The former public relay is retired: `--legacy-public-relay`
|
|
91
|
+
now refuses and sends nothing.
|
|
92
92
|
|
|
93
93
|
### clear
|
|
94
94
|
|
|
@@ -137,32 +137,6 @@ if [ '{in_worktree}' = "true" ]; then
|
|
|
137
137
|
fi
|
|
138
138
|
```
|
|
139
139
|
|
|
140
|
-
If `{in_worktree}` is `true`, review the changes and continue the worktree update:
|
|
141
|
-
|
|
142
|
-
```bash
|
|
143
|
-
if [ '{in_worktree}' = "true" ]; then
|
|
144
|
-
# Show what changed
|
|
145
|
-
git -C '{main_dir}' diff --stat HEAD
|
|
146
|
-
|
|
147
|
-
# Commit on base branch in main repo (.codex/skills/ carries the
|
|
148
|
-
# regenerated adapters)
|
|
149
|
-
git -C '{main_dir}' add -A .claude/ .pi/ .prime/ bin/ loom/ CLAUDE.md skills/ AGENTS.md .codex/spec-manifest.json .codex/skills/ 2>/dev/null
|
|
150
|
-
if ! git -C '{main_dir}' diff --cached --quiet 2>/dev/null; then
|
|
151
|
-
EGREGORE_FRAMEWORK_UPDATE=1 git -C '{main_dir}' commit -m "Update Egregore framework from upstream"
|
|
152
|
-
git -C '{main_dir}' push origin '{base}' --quiet 2>/dev/null || true
|
|
153
|
-
fi
|
|
154
|
-
|
|
155
|
-
# Rebase worktree branch onto updated base
|
|
156
|
-
git stash --quiet 2>/dev/null || true
|
|
157
|
-
git fetch origin '{base}' --quiet
|
|
158
|
-
if ! git rebase 'origin/{base}' --quiet 2>/dev/null; then
|
|
159
|
-
git rebase --abort 2>/dev/null || true
|
|
160
|
-
echo '⚠ Rebase had conflicts — aborted. Run: git rebase origin/{base} and resolve manually.'
|
|
161
|
-
fi
|
|
162
|
-
git stash pop --quiet 2>/dev/null || true
|
|
163
|
-
fi
|
|
164
|
-
```
|
|
165
|
-
|
|
166
140
|
### Non-worktree path
|
|
167
141
|
|
|
168
142
|
If `{in_worktree}` is `false`, use `false` as `{switched}` initially.
|
|
@@ -219,30 +193,70 @@ if [ '{in_worktree}' = "false" ]; then
|
|
|
219
193
|
fi
|
|
220
194
|
```
|
|
221
195
|
|
|
222
|
-
**Framework paths synced:** `bin/`, `.claude/commands/`, `.claude/skills/`, `.claude/hooks/`, `.claude/context/`, `.claude/agents/`, `.pi/`, `.prime/`, `loom/`, `CLAUDE.md`, `skills/`
|
|
196
|
+
**Framework paths synced:** `bin/`, `egregore_runtime/`, `.claude/commands/`, `.claude/skills/`, `.claude/hooks/`, `.claude/context/`, `.claude/agents/`, `.pi/`, `.prime/`, `loom/`, `CLAUDE.md`, `skills/`
|
|
223
197
|
**Regenerated:** `AGENTS.md` + `.codex/spec-manifest.json` via `bin/codex-render-spec.mjs`, `.pi/APPEND_SYSTEM.md` + `.pi/spec-manifest.json` via `bin/pi-render-spec.mjs`, `.prime/agent/APPEND_SYSTEM.md` + `.prime/agent/spec-manifest.json` via `bin/prime-render-spec.mjs`, then generated Codex adapter skills via `bin/codex-sync-skills.sh`, so all derived harnesses stay aligned with `CLAUDE.md` and the canonical skills.
|
|
224
198
|
**Never touched:** `egregore.json`, `.env`, `memory/`, `.egregore-state.json`, `.mcp.json`
|
|
225
199
|
**Org-owned skills:** names in `egregore.json` → `owned_skills[]` are restored from the org's committed state after the overlay (`bin/restore-owned-skills.sh`) — upstream never overwrites them; collisions are reported instead.
|
|
226
200
|
|
|
227
201
|
## Step 3: Post-update migrations
|
|
228
202
|
|
|
229
|
-
After syncing, run `bin/post-update.sh`
|
|
203
|
+
After syncing, run the updated `bin/post-update.sh` before committing or rebasing.
|
|
204
|
+
The first update through an older overlay can add the missing Runtime core here.
|
|
205
|
+
In worktrees, run in `{main_dir}` so migrations affect the main checkout.
|
|
206
|
+
Stop on migration failure; do not commit or rebase a partial update.
|
|
230
207
|
|
|
231
208
|
```bash
|
|
232
209
|
if [ '{in_worktree}' = "true" ]; then
|
|
233
|
-
[ -
|
|
210
|
+
if [ -f '{main_dir}/bin/post-update.sh' ]; then
|
|
211
|
+
(cd '{main_dir}' && bash bin/post-update.sh) || exit 1
|
|
212
|
+
fi
|
|
234
213
|
else
|
|
235
|
-
[ -
|
|
214
|
+
if [ -f bin/post-update.sh ]; then
|
|
215
|
+
bash bin/post-update.sh || exit 1
|
|
216
|
+
fi
|
|
236
217
|
fi
|
|
237
218
|
```
|
|
238
219
|
|
|
239
|
-
## Step 4: Commit and
|
|
220
|
+
## Step 4: Commit and restore the working branch
|
|
221
|
+
|
|
222
|
+
If `{upstream_url}` is `none`, skip this step and continue with Step 5; no
|
|
223
|
+
framework overlay was applied. Otherwise, commit the overlay and migrations
|
|
224
|
+
together before the working branch receives the update.
|
|
225
|
+
|
|
226
|
+
If `{in_worktree}` is `true`, review the changes and continue the worktree update:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
if [ '{in_worktree}' = "true" ]; then
|
|
230
|
+
# Show what changed
|
|
231
|
+
git -C '{main_dir}' diff --stat HEAD
|
|
232
|
+
|
|
233
|
+
# Commit on base branch in main repo (.codex/skills/ carries the
|
|
234
|
+
# regenerated adapters)
|
|
235
|
+
git -C '{main_dir}' add -A .claude/ .pi/ .prime/ bin/ loom/ CLAUDE.md skills/ AGENTS.md .codex/spec-manifest.json .codex/skills/ 2>/dev/null
|
|
236
|
+
# Older upstreams lack the Runtime core; a missing pathspec would abort the add.
|
|
237
|
+
[ ! -e '{main_dir}/egregore_runtime' ] || git -C '{main_dir}' add -A -- egregore_runtime/ 2>/dev/null
|
|
238
|
+
if ! git -C '{main_dir}' diff --cached --quiet 2>/dev/null; then
|
|
239
|
+
EGREGORE_FRAMEWORK_UPDATE=1 git -C '{main_dir}' commit -m "Update Egregore framework from upstream"
|
|
240
|
+
git -C '{main_dir}' push origin '{base}' --quiet 2>/dev/null || true
|
|
241
|
+
fi
|
|
242
|
+
|
|
243
|
+
# Rebase worktree branch onto updated base
|
|
244
|
+
git stash --quiet 2>/dev/null || true
|
|
245
|
+
git fetch origin '{base}' --quiet
|
|
246
|
+
if ! git rebase 'origin/{base}' --quiet 2>/dev/null; then
|
|
247
|
+
git rebase --abort 2>/dev/null || true
|
|
248
|
+
echo '⚠ Rebase had conflicts — aborted. Run: git rebase origin/{base} and resolve manually.'
|
|
249
|
+
fi
|
|
250
|
+
git stash pop --quiet 2>/dev/null || true
|
|
251
|
+
fi
|
|
252
|
+
```
|
|
240
253
|
|
|
241
|
-
|
|
254
|
+
For the non-worktree case:
|
|
242
255
|
|
|
243
256
|
```bash
|
|
244
257
|
if [ '{in_worktree}' = "false" ]; then
|
|
245
258
|
git add -A .claude/ .pi/ .prime/ bin/ loom/ CLAUDE.md skills/ AGENTS.md .codex/spec-manifest.json 2>/dev/null
|
|
259
|
+
[ ! -e egregore_runtime ] || git add -A -- egregore_runtime/ 2>/dev/null
|
|
246
260
|
if ! git diff --cached --quiet 2>/dev/null; then
|
|
247
261
|
EGREGORE_FRAMEWORK_UPDATE=1 git commit -m "Update Egregore framework from upstream"
|
|
248
262
|
git push origin '{base}' --quiet 2>/dev/null || true
|