create-egregore 0.19.20 → 0.19.22
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 +124 -12
- package/bin/cli.js +3 -4
- package/lib/auth.js +3 -4
- package/lib/path-lookup.js +59 -0
- package/lib/setup.js +52 -6
- package/package.json +2 -2
- package/runtime/codex/.claude/context/notification-consent.md +66 -0
- package/runtime/codex/.codex/hooks/branch-guard.js +14 -3
- package/runtime/codex/.codex/skills/add/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/announce/SKILL.md +21 -8
- package/runtime/codex/.codex/skills/answer/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/archive/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ask/SKILL.md +14 -6
- package/runtime/codex/.codex/skills/audit/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/autosave/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/branch/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/checkup/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/commit/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/contribute/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/delete-user/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/demo-mode/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/deploy-bridge/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/design/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/emissary/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/env/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/handoff/SKILL.md +15 -6
- package/runtime/codex/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/codex/.codex/skills/harvest/SKILL.md +30 -6
- package/runtime/codex/.codex/skills/ingest/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ingest-google/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/invite/SKILL.md +6 -2
- package/runtime/codex/.codex/skills/issue/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/me/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/meeting/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/minion/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/mock/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/note/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/pr/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/product-voice/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/project/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/pull/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/push/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/quest/SKILL.md +6 -3
- package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/reflect/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/save/SKILL.md +6 -4
- package/runtime/codex/.codex/skills/scroll/SKILL.md +92 -23
- package/runtime/codex/.codex/skills/scroll/assets/shell.html +381 -52
- package/runtime/codex/.codex/skills/session-view/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/setup/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/summon/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/sync-repos/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/task/SKILL.md +9 -5
- package/runtime/codex/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/test/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/thread/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/todo/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/update/SKILL.md +8 -4
- package/runtime/codex/.codex/skills/visual-explain/SKILL.md +8 -4
- package/runtime/codex/.codex/spec-manifest.json +4 -4
- package/runtime/codex/AGENTS.md +36 -14
- package/runtime/codex/bin/activity-data.sh +1 -1
- package/runtime/codex/bin/agent.sh +38 -19
- package/runtime/codex/bin/attendant.sh +8 -2
- package/runtime/codex/bin/autosave.sh +1 -1
- package/runtime/codex/bin/capture-reconcile.sh +8 -6
- package/runtime/codex/bin/capture-run.sh +1 -1
- package/runtime/codex/bin/codex-install-skills.sh +1 -1
- package/runtime/codex/bin/codex-render-spec.mjs +12 -5
- package/runtime/codex/bin/codex-session-start.sh +1 -1
- package/runtime/codex/bin/codex-sync-skills.sh +9 -5
- package/runtime/codex/bin/dashboard-data.sh +1 -1
- package/runtime/codex/bin/graph-batch.sh +1 -1
- package/runtime/codex/bin/graph-op.sh +1 -1
- package/runtime/codex/bin/graph-wal.sh +1 -1
- package/runtime/codex/bin/graph.sh +1 -1
- package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/codex/bin/handoff-run.sh +53 -39
- package/runtime/codex/bin/handoff-save-egregore.sh +62 -32
- package/runtime/codex/bin/index-handoff.sh +1 -1
- package/runtime/codex/bin/ingest-graph.sh +1 -1
- package/runtime/codex/bin/ingest.sh +1 -1
- package/runtime/codex/bin/lib/artifact-id.sh +1 -1
- package/runtime/codex/bin/lib/config.sh +56 -20
- package/runtime/codex/bin/lib/context.sh +7 -1
- package/runtime/codex/bin/lib/ensure-develop.sh +1 -1
- package/runtime/codex/bin/lib/git-sync.sh +145 -59
- package/runtime/codex/bin/lib/greeting.sh +14 -0
- package/runtime/codex/bin/lib/hash.sh +1 -1
- package/runtime/codex/bin/lib/noncode.sh +1 -1
- package/runtime/codex/bin/lib/time.sh +1 -1
- package/runtime/codex/bin/notify.sh +492 -153
- package/runtime/codex/bin/person.sh +1 -1
- package/runtime/codex/bin/publish-artifact.sh +39 -4
- package/runtime/codex/bin/publish-references.sh +1 -1
- package/runtime/codex/bin/repo-state.sh +7 -10
- package/runtime/codex/bin/search.sh +1 -1
- package/runtime/codex/bin/session-autosave.sh +1 -1
- package/runtime/codex/bin/session-log.sh +1 -1
- package/runtime/codex/bin/settings.sh +32 -2
- package/runtime/codex/bin/telemetry.sh +1 -1
- package/runtime/codex/bin/worktree-create.sh +14 -4
- package/runtime/codex/bin/worktree-remove.sh +1 -1
- package/runtime/codex/bin/worktree.sh +1 -1
- package/runtime/codex/manifest.json +18 -1
- package/runtime/pi/.claude/context/notification-consent.md +66 -0
- package/runtime/pi/.codex/hooks/branch-guard.js +14 -3
- package/runtime/pi/.codex/skills/add/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/announce/SKILL.md +21 -8
- package/runtime/pi/.codex/skills/answer/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/archive/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ask/SKILL.md +14 -6
- package/runtime/pi/.codex/skills/audit/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/autosave/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/branch/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/checkup/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/commit/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/contribute/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/delete-user/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/demo-mode/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/deploy-bridge/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/design/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/egregore-voice/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/emissary/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/env/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/handoff/SKILL.md +15 -6
- package/runtime/pi/.codex/skills/harvest/QUESTION_PALETTE.md +76 -0
- package/runtime/pi/.codex/skills/harvest/SKILL.md +30 -6
- package/runtime/pi/.codex/skills/ingest/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ingest-google/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/ingest-user-interview/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/invite/SKILL.md +6 -2
- package/runtime/pi/.codex/skills/issue/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/me/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/meeting/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/minion/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/mock/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/note/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/onboarding/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/pr/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/product-voice/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/project/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/pull/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/push/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/quest/SKILL.md +6 -3
- package/runtime/pi/.codex/skills/quest-suggest/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/reflect/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/review-pr/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/save/SKILL.md +6 -4
- package/runtime/pi/.codex/skills/scroll/SKILL.md +92 -23
- package/runtime/pi/.codex/skills/scroll/assets/shell.html +381 -52
- package/runtime/pi/.codex/skills/session-view/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/setup/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/summon/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/sync-repos/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/task/SKILL.md +9 -5
- package/runtime/pi/.codex/skills/teams-connect/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/telegram-connect/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/telemetry/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/test/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/thread/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/todo/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/tui-design/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/tutorial/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/update/SKILL.md +8 -4
- package/runtime/pi/.codex/skills/visual-explain/SKILL.md +8 -4
- package/runtime/pi/.codex/spec-manifest.json +4 -4
- package/runtime/pi/.pi/APPEND_SYSTEM.md +34 -12
- package/runtime/pi/.pi/extensions/egregore.ts +8 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +36 -14
- package/runtime/pi/bin/activity-data.sh +1 -1
- package/runtime/pi/bin/agent.sh +38 -19
- package/runtime/pi/bin/attendant.sh +8 -2
- package/runtime/pi/bin/autosave.sh +1 -1
- package/runtime/pi/bin/capture-reconcile.sh +8 -6
- package/runtime/pi/bin/capture-run.sh +1 -1
- package/runtime/pi/bin/codex-install-skills.sh +1 -1
- package/runtime/pi/bin/codex-render-spec.mjs +12 -5
- package/runtime/pi/bin/codex-session-start.sh +1 -1
- package/runtime/pi/bin/codex-sync-skills.sh +9 -5
- package/runtime/pi/bin/dashboard-data.sh +1 -1
- package/runtime/pi/bin/graph-batch.sh +1 -1
- package/runtime/pi/bin/graph-op.sh +1 -1
- package/runtime/pi/bin/graph-wal.sh +1 -1
- package/runtime/pi/bin/graph.sh +1 -1
- package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/pi/bin/handoff-run.sh +53 -39
- package/runtime/pi/bin/handoff-save-egregore.sh +62 -32
- package/runtime/pi/bin/index-handoff.sh +1 -1
- package/runtime/pi/bin/ingest-graph.sh +1 -1
- package/runtime/pi/bin/ingest.sh +1 -1
- package/runtime/pi/bin/lib/artifact-id.sh +1 -1
- package/runtime/pi/bin/lib/config.sh +56 -20
- package/runtime/pi/bin/lib/context.sh +7 -1
- package/runtime/pi/bin/lib/ensure-develop.sh +1 -1
- package/runtime/pi/bin/lib/git-sync.sh +145 -59
- package/runtime/pi/bin/lib/greeting.sh +14 -0
- package/runtime/pi/bin/lib/hash.sh +1 -1
- package/runtime/pi/bin/lib/noncode.sh +1 -1
- package/runtime/pi/bin/lib/time.sh +1 -1
- package/runtime/pi/bin/notify.sh +492 -153
- package/runtime/pi/bin/person.sh +1 -1
- package/runtime/pi/bin/pi-session-start.sh +1 -1
- package/runtime/pi/bin/publish-artifact.sh +39 -4
- package/runtime/pi/bin/publish-references.sh +1 -1
- package/runtime/pi/bin/repo-state.sh +7 -10
- package/runtime/pi/bin/search.sh +1 -1
- package/runtime/pi/bin/session-autosave.sh +1 -1
- package/runtime/pi/bin/session-log.sh +1 -1
- package/runtime/pi/bin/settings.sh +32 -2
- package/runtime/pi/bin/telemetry.sh +1 -1
- package/runtime/pi/bin/worktree-create.sh +14 -4
- package/runtime/pi/bin/worktree-remove.sh +1 -1
- package/runtime/pi/bin/worktree.sh +1 -1
- package/runtime/pi/manifest.json +18 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// egregore-launcher-version: 0.19.
|
|
2
|
+
// egregore-launcher-version: 0.19.22
|
|
3
3
|
//
|
|
4
4
|
// egregore — launch and browse Egregore instances from your terminal.
|
|
5
5
|
// Installed by create-egregore. Reads ~/.egregore/instances.json.
|
|
@@ -33,7 +33,7 @@ const crypto = require("node:crypto");
|
|
|
33
33
|
const { spawnSync, spawn } = require("node:child_process");
|
|
34
34
|
const readline = require("node:readline");
|
|
35
35
|
|
|
36
|
-
const VERSION = "0.19.
|
|
36
|
+
const VERSION = "0.19.22";
|
|
37
37
|
const REGISTRY = path.join(os.homedir(), ".egregore", "instances.json");
|
|
38
38
|
const DEFAULT_FILE = path.join(os.homedir(), ".egregore", "default");
|
|
39
39
|
const UPDATE_CACHE_FILE = path.join(os.homedir(), ".egregore", "update-check.json");
|
|
@@ -435,7 +435,33 @@ function piTrustArgs() {
|
|
|
435
435
|
}
|
|
436
436
|
|
|
437
437
|
function commandExists(bin) {
|
|
438
|
-
|
|
438
|
+
if (typeof bin !== "string" || bin.length === 0) return false;
|
|
439
|
+
const pathValue = process.env.PATH || process.env.Path || process.env.path || "";
|
|
440
|
+
const separator = process.platform === "win32" ? ";" : ":";
|
|
441
|
+
const names =
|
|
442
|
+
process.platform === "win32" && !path.extname(bin)
|
|
443
|
+
? (process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
|
|
444
|
+
.split(";")
|
|
445
|
+
.filter(Boolean)
|
|
446
|
+
.map((extension) => `${bin}${extension.toLowerCase()}`)
|
|
447
|
+
: [bin];
|
|
448
|
+
|
|
449
|
+
return pathValue.split(separator).some((entry) => {
|
|
450
|
+
const directory = entry ? entry.replace(/^"(.*)"$/, "$1") : process.cwd();
|
|
451
|
+
return names.some((name) => {
|
|
452
|
+
const candidate = path.join(directory, name);
|
|
453
|
+
try {
|
|
454
|
+
if (!fs.statSync(candidate).isFile()) return false;
|
|
455
|
+
fs.accessSync(
|
|
456
|
+
candidate,
|
|
457
|
+
process.platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK,
|
|
458
|
+
);
|
|
459
|
+
return true;
|
|
460
|
+
} catch {
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
});
|
|
439
465
|
}
|
|
440
466
|
|
|
441
467
|
function runtimeChoiceDescription(runtime, inst, stored) {
|
|
@@ -629,7 +655,14 @@ async function renderCodexStartupCard(egregoreDir) {
|
|
|
629
655
|
const result = await runCapturedWithSpinner("bash", [script], {
|
|
630
656
|
cwd: egregoreDir,
|
|
631
657
|
}, "Preparing Codex startup");
|
|
632
|
-
|
|
658
|
+
let output = result.stdout || "";
|
|
659
|
+
if (isConnectedPath(egregoreDir) && !output.includes("◆ CONNECTED")) {
|
|
660
|
+
const connectedLine = " ◆ CONNECTED · hosted graph active\n";
|
|
661
|
+
output = output.includes("\nWhat are you working on?")
|
|
662
|
+
? output.replace("\nWhat are you working on?", `\n${connectedLine}\nWhat are you working on?`)
|
|
663
|
+
: `${output}${output.endsWith("\n") || !output ? "" : "\n"}${connectedLine}`;
|
|
664
|
+
}
|
|
665
|
+
if (output) process.stdout.write(output);
|
|
633
666
|
if (result.status !== 0 && result.stderr) process.stderr.write(result.stderr);
|
|
634
667
|
if (result.status === 0) return true;
|
|
635
668
|
} catch {}
|
|
@@ -751,6 +784,7 @@ async function launch(inst, runtimeOverride) {
|
|
|
751
784
|
if (!runtime) return;
|
|
752
785
|
if (runtime === "claude-code") {
|
|
753
786
|
normalizeClaudeDenyRules(inst.path);
|
|
787
|
+
ensureClaudeModeStatusLine(inst.path);
|
|
754
788
|
}
|
|
755
789
|
const binary = runtimeBinary(runtime);
|
|
756
790
|
if (!commandExists(binary)) {
|
|
@@ -816,13 +850,17 @@ function cmdLs() {
|
|
|
816
850
|
const slugW = Math.min(24, Math.max(...registry.map((r) => (r.slug || "").length)));
|
|
817
851
|
const nameW = Math.min(24, Math.max(...registry.map((r) => (r.name || "").length)));
|
|
818
852
|
const runtimeW = Math.max(9, ...registry.map((r) => runtimeShort(runtimeFor(r)).length));
|
|
853
|
+
const modeW = "◆ CONNECTED".length;
|
|
819
854
|
console.log(`\n ${BOLD}${registry.length}${RESET} egregore${registry.length === 1 ? "" : "s"} registered\n`);
|
|
820
|
-
console.log(` ${DIM}${pad("SLUG", slugW)} ${pad("NAME", nameW)} ${pad("ASSISTANT", runtimeW)} PATH${RESET}`);
|
|
855
|
+
console.log(` ${DIM}${pad("SLUG", slugW)} ${pad("NAME", nameW)} ${pad("MODE", modeW)} ${pad("ASSISTANT", runtimeW)} PATH${RESET}`);
|
|
821
856
|
for (const inst of registry) {
|
|
822
857
|
const marker = inst.slug === def ? `${GREEN}●${RESET}` : " ";
|
|
823
858
|
const slug = inst.slug === def ? `${BOLD}${pad(inst.slug, slugW)}${RESET}` : pad(inst.slug, slugW);
|
|
824
859
|
const runtime = pad(runtimeShort(runtimeFor(inst)), runtimeW);
|
|
825
|
-
|
|
860
|
+
const mode = isConnectedInstance(inst)
|
|
861
|
+
? `${CYAN}${pad("◆ CONNECTED", modeW)}${RESET}`
|
|
862
|
+
: `${DIM}${pad("◇ LOCAL", modeW)}${RESET}`;
|
|
863
|
+
console.log(` ${marker} ${slug} ${pad(inst.name || "", nameW)} ${mode} ${runtime} ${DIM}${inst.path}${RESET}`);
|
|
826
864
|
}
|
|
827
865
|
if (def) console.log(`\n ${GREEN}●${RESET} = default marker (used by non-interactive ${BOLD}egregore${RESET})\n`);
|
|
828
866
|
else console.log("");
|
|
@@ -936,6 +974,22 @@ function readJsonFile(file) {
|
|
|
936
974
|
}
|
|
937
975
|
}
|
|
938
976
|
|
|
977
|
+
function isConnectedConfig(config) {
|
|
978
|
+
return (
|
|
979
|
+
config?.mode === "connected" &&
|
|
980
|
+
typeof config.api_url === "string" &&
|
|
981
|
+
config.api_url.trim().length > 0
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
function isConnectedPath(instancePath) {
|
|
986
|
+
return isConnectedConfig(readJsonFile(path.join(instancePath, "egregore.json")));
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
function isConnectedInstance(inst) {
|
|
990
|
+
return !!inst?.path && isConnectedPath(inst.path);
|
|
991
|
+
}
|
|
992
|
+
|
|
939
993
|
function readEnvValues(file) {
|
|
940
994
|
const values = {};
|
|
941
995
|
if (!fs.existsSync(file)) return values;
|
|
@@ -1233,6 +1287,47 @@ function normalizeClaudeDenyRules(instancePath) {
|
|
|
1233
1287
|
}
|
|
1234
1288
|
}
|
|
1235
1289
|
|
|
1290
|
+
const CLAUDE_MODE_STATUS_MARKER = "# egregore-connected-status-v1";
|
|
1291
|
+
const CLAUDE_DEFAULT_STATUS_COMMAND = "\"${CLAUDE_PROJECT_DIR}/bin/statusline.sh\"";
|
|
1292
|
+
|
|
1293
|
+
function claudeModeStatusCommand(baseCommand) {
|
|
1294
|
+
return [
|
|
1295
|
+
CLAUDE_MODE_STATUS_MARKER,
|
|
1296
|
+
"MODE=$(jq -r 'if (.mode == \"connected\" and ((.api_url // \"\") | length > 0)) then \"connected\" else \"local\" end' \"${CLAUDE_PROJECT_DIR}/egregore.json\" 2>/dev/null)",
|
|
1297
|
+
`BASE=$( ( ${baseCommand} ) 2>/dev/null || true)`,
|
|
1298
|
+
"if [ \"$MODE\" = \"connected\" ]; then",
|
|
1299
|
+
" case \"$BASE\" in",
|
|
1300
|
+
" *\"◆ CONNECTED\"*) printf '%s\\n' \"$BASE\" ;;",
|
|
1301
|
+
" \"\") printf '◆ CONNECTED\\n' ;;",
|
|
1302
|
+
" *) printf '◆ CONNECTED · %s\\n' \"$BASE\" ;;",
|
|
1303
|
+
" esac",
|
|
1304
|
+
"else",
|
|
1305
|
+
" printf '%s\\n' \"$BASE\"",
|
|
1306
|
+
"fi",
|
|
1307
|
+
].join("\n");
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
function ensureClaudeModeStatusLine(instancePath) {
|
|
1311
|
+
if (!isConnectedPath(instancePath)) return false;
|
|
1312
|
+
const file = path.join(instancePath, ".claude", "settings.local.json");
|
|
1313
|
+
const settings = readJsonFile(file);
|
|
1314
|
+
const current = settings?.statusLine;
|
|
1315
|
+
if (current?.type === "command" && current?.command?.includes(CLAUDE_MODE_STATUS_MARKER)) {
|
|
1316
|
+
return false;
|
|
1317
|
+
}
|
|
1318
|
+
const baseCommand = current?.type === "command" && current?.command?.trim()
|
|
1319
|
+
? current.command
|
|
1320
|
+
: CLAUDE_DEFAULT_STATUS_COMMAND;
|
|
1321
|
+
settings.statusLine = {
|
|
1322
|
+
...(current || {}),
|
|
1323
|
+
type: "command",
|
|
1324
|
+
command: claudeModeStatusCommand(baseCommand),
|
|
1325
|
+
};
|
|
1326
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
1327
|
+
writeJsonFileAtomic(file, settings);
|
|
1328
|
+
return true;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1236
1331
|
function writeEnvValueAtomic(file, key, value) {
|
|
1237
1332
|
const lines = fs.existsSync(file)
|
|
1238
1333
|
? fs.readFileSync(file, "utf-8").split(/\r?\n/)
|
|
@@ -1323,6 +1418,8 @@ async function cmdConnect(args) {
|
|
|
1323
1418
|
}
|
|
1324
1419
|
const activation = state.connected_activation || {};
|
|
1325
1420
|
if (cfg.mode === "connected" && activation.status === "connected") {
|
|
1421
|
+
normalizeClaudeDenyRules(inst.path);
|
|
1422
|
+
ensureClaudeModeStatusLine(inst.path);
|
|
1326
1423
|
console.log(`\n ${GREEN}✓${RESET} ${BOLD}${inst.slug}${RESET} is already connected.\n`);
|
|
1327
1424
|
return;
|
|
1328
1425
|
}
|
|
@@ -1462,6 +1559,7 @@ async function cmdConnect(args) {
|
|
|
1462
1559
|
});
|
|
1463
1560
|
writeEnvValueAtomic(envFile, "EGREGORE_API_KEY", claim.data.api_key);
|
|
1464
1561
|
normalizeClaudeDenyRules(inst.path);
|
|
1562
|
+
ensureClaudeModeStatusLine(inst.path);
|
|
1465
1563
|
persistConnectState(inst, {
|
|
1466
1564
|
status: "backfilling",
|
|
1467
1565
|
activation_protocol: claim.data.activation_protocol,
|
|
@@ -1938,7 +2036,8 @@ function loadPreview(inst) {
|
|
|
1938
2036
|
data.telegram = !!(cfg.telegram_chat_id || cfg.telegram_group_link);
|
|
1939
2037
|
data.org = cfg.org_name;
|
|
1940
2038
|
data.githubOrg = cfg.github_org;
|
|
1941
|
-
data.
|
|
2039
|
+
data.connected = isConnectedConfig(cfg);
|
|
2040
|
+
data.mode = data.connected ? "connected" : "local";
|
|
1942
2041
|
data.flowVariant = cfg.flow_variant;
|
|
1943
2042
|
}
|
|
1944
2043
|
} catch {}
|
|
@@ -2011,7 +2110,8 @@ function renderHeroPanel({ selected, pv, width, height, countSuffix, filter, fil
|
|
|
2011
2110
|
}
|
|
2012
2111
|
lines.push(emptyBoxLine(innerWidth, margin));
|
|
2013
2112
|
lines.push(updateBand || mid);
|
|
2014
|
-
|
|
2113
|
+
const connectedHero = pv?.connected ? ` · ${CYAN}◆ CONNECTED${RESET}` : "";
|
|
2114
|
+
lines.push(boxLine(`${BOLD}${title}${RESET} ${DIM}· ${org} · ${runtime}${RESET}${connectedHero}${DIM} · ${countSuffix} · ${filterText}${RESET}`, innerWidth, margin));
|
|
2015
2115
|
lines.push(boxLine(`${DIM}skills${RESET} ${skillStatus}`, innerWidth, margin));
|
|
2016
2116
|
lines.push(emptyBoxLine(innerWidth, margin));
|
|
2017
2117
|
|
|
@@ -2065,6 +2165,13 @@ function renderRuntimePanel({ inst, cursor, width, height }) {
|
|
|
2065
2165
|
emptyBoxLine(innerWidth, margin),
|
|
2066
2166
|
boxLine(`${BOLD}${CYAN}Launch ${title}${RESET}`, innerWidth, margin),
|
|
2067
2167
|
boxLine(`${DIM}${inst.path}${RESET}`, innerWidth, margin),
|
|
2168
|
+
boxLine(
|
|
2169
|
+
isConnectedInstance(inst)
|
|
2170
|
+
? `${CYAN}◆ CONNECTED${RESET} ${DIM}· hosted graph active${RESET}`
|
|
2171
|
+
: `${DIM}◇ LOCAL · Markdown + Git${RESET}`,
|
|
2172
|
+
innerWidth,
|
|
2173
|
+
margin,
|
|
2174
|
+
),
|
|
2068
2175
|
emptyBoxLine(innerWidth, margin),
|
|
2069
2176
|
];
|
|
2070
2177
|
|
|
@@ -2381,10 +2488,13 @@ async function browser(args = {}) {
|
|
|
2381
2488
|
? `${DIM}${pad(e.name || "", nameW)}${RESET}`
|
|
2382
2489
|
: pad(e.name || "", nameW);
|
|
2383
2490
|
const pathAvail = Math.max(0, listAvail - slugW - nameW - 10);
|
|
2491
|
+
const connected = isConnectedInstance(e);
|
|
2492
|
+
const modeBadge = connected ? `${CYAN}◆ CONNECTED${RESET}` : "";
|
|
2493
|
+
const adjustedPathAvail = Math.max(0, pathAvail - (connected ? "◆ CONNECTED ".length : 0));
|
|
2384
2494
|
const pathDim = isMissing
|
|
2385
|
-
? `${RED}✗${RESET} ${DIM}${truncate(e.path, Math.max(8,
|
|
2386
|
-
: `${DIM}${truncate(e.path,
|
|
2387
|
-
const line = ` ${cursorMark} ${defMark} ${slug} ${name} ${pathDim}`;
|
|
2495
|
+
? `${RED}✗${RESET} ${DIM}${truncate(e.path, Math.max(8, adjustedPathAvail - 12))}${RESET} ${RED}(missing)${RESET}`
|
|
2496
|
+
: `${DIM}${truncate(e.path, adjustedPathAvail)}${RESET}`;
|
|
2497
|
+
const line = ` ${cursorMark} ${defMark} ${slug} ${name} ${modeBadge}${modeBadge ? " " : ""}${pathDim}`;
|
|
2388
2498
|
out.push(M + " ".repeat(listPad - 3) + line + ANSI.clearLine + "\n");
|
|
2389
2499
|
}
|
|
2390
2500
|
|
|
@@ -2411,6 +2521,9 @@ async function browser(args = {}) {
|
|
|
2411
2521
|
? ` ${label("path")} ${RED}✗${RESET} ${truncate(selected.path, Math.max(8, valW - 12))} ${RED}(missing)${RESET}`
|
|
2412
2522
|
: ` ${label("path")} ${truncate(selected.path, valW)}`);
|
|
2413
2523
|
if (pv.org) previewLines.push(` ${label("org")} ${truncate(`${pv.org} ${DIM}(${pv.githubOrg || "?"})${RESET}`, valW)}`);
|
|
2524
|
+
previewLines.push(pv.connected
|
|
2525
|
+
? ` ${label("mode")} ${CYAN}◆ CONNECTED${RESET} ${DIM}· hosted graph active${RESET}`
|
|
2526
|
+
: ` ${label("mode")} ${DIM}◇ LOCAL · Markdown + Git${RESET}`);
|
|
2414
2527
|
if (pv.branch) previewLines.push(` ${label("branch")} ${BOLD}${pv.branch}${RESET}`);
|
|
2415
2528
|
if (pv.lastCommit) previewLines.push(` ${label("last")} ${DIM}${truncate(pv.lastCommit, valW)}${RESET}`);
|
|
2416
2529
|
if (pv.repos && pv.repos.length) {
|
|
@@ -2424,7 +2537,6 @@ async function browser(args = {}) {
|
|
|
2424
2537
|
}
|
|
2425
2538
|
const modeBits = [];
|
|
2426
2539
|
modeBits.push(runtimeShort(runtimeFor(selected, args.runtimeOverride)));
|
|
2427
|
-
if (pv.mode) modeBits.push(pv.mode);
|
|
2428
2540
|
if (pv.flowVariant && pv.flowVariant !== "legacy") modeBits.push(pv.flowVariant);
|
|
2429
2541
|
if (pv.telegram) modeBits.push("telegram");
|
|
2430
2542
|
if (selected.slug === def) modeBits.push("default");
|
package/bin/cli.js
CHANGED
|
@@ -44,6 +44,7 @@ const {
|
|
|
44
44
|
redactToken,
|
|
45
45
|
} = require("../lib/setup");
|
|
46
46
|
const { detectContext, isWritable, isSystemDir } = require("../lib/context");
|
|
47
|
+
const { commandExists } = require("../lib/path-lookup");
|
|
47
48
|
|
|
48
49
|
const API_URL = process.env.EGREGORE_API_URL || "https://egregore-production-55f2.up.railway.app";
|
|
49
50
|
const INSTANCES_FILE = path.join(os.homedir(), ".egregore", "instances.json");
|
|
@@ -97,7 +98,7 @@ function detectCwdEgregore(cwd) {
|
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
function binaryOnPath(bin) {
|
|
100
|
-
return
|
|
101
|
+
return commandExists(bin);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
function assistantLabel(runtime) {
|
|
@@ -406,7 +407,6 @@ async function preflight() {
|
|
|
406
407
|
// Verify prerequisites before starting. Fast fail with specific fix hints
|
|
407
408
|
// beats discovering missing tools mid-install. Claude Code is checked
|
|
408
409
|
// separately at auto-exec time since users can install it after.
|
|
409
|
-
const { spawnSync } = require("node:child_process");
|
|
410
410
|
const checks = [
|
|
411
411
|
{ bin: "git", hint: "install Xcode command-line tools or https://git-scm.com" },
|
|
412
412
|
{ bin: "jq", hint: "brew install jq (or https://stedolan.github.io/jq/)" },
|
|
@@ -417,8 +417,7 @@ async function preflight() {
|
|
|
417
417
|
missing.push({ bin: "node >=18", hint: `you're on Node ${process.versions.node}; upgrade at https://nodejs.org` });
|
|
418
418
|
}
|
|
419
419
|
for (const c of checks) {
|
|
420
|
-
|
|
421
|
-
if (r.status !== 0) missing.push(c);
|
|
420
|
+
if (!commandExists(c.bin)) missing.push(c);
|
|
422
421
|
}
|
|
423
422
|
if (missing.length === 0) return;
|
|
424
423
|
|
package/lib/auth.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const https = require("node:https");
|
|
7
7
|
const { spawnSync } = require("node:child_process");
|
|
8
|
+
const { commandExists } = require("./path-lookup");
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Try to get a token from the user's `gh` CLI, if installed and authenticated.
|
|
@@ -31,8 +32,7 @@ function envGhAuth() {
|
|
|
31
32
|
|
|
32
33
|
function ghCliExists() {
|
|
33
34
|
if (process.env.EGREGORE_SKIP_GH === "1") return false;
|
|
34
|
-
|
|
35
|
-
return which.status === 0;
|
|
35
|
+
return commandExists("gh");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
function canUseInteractiveBrowserFlow() {
|
|
@@ -46,8 +46,7 @@ const GH_INSTALL_URL = "https://cli.github.com";
|
|
|
46
46
|
// and repos vary too much for a single command, so we point at the canonical
|
|
47
47
|
// page there (and always print GH_INSTALL_URL alongside on every platform).
|
|
48
48
|
function _has(b) {
|
|
49
|
-
|
|
50
|
-
catch { return false; }
|
|
49
|
+
return commandExists(b);
|
|
51
50
|
}
|
|
52
51
|
|
|
53
52
|
// True on a machine with no way to open a browser (server / SSH). macOS and
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("node:fs");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
function executableFile(candidate, platform) {
|
|
7
|
+
try {
|
|
8
|
+
if (!fs.statSync(candidate).isFile()) return false;
|
|
9
|
+
fs.accessSync(
|
|
10
|
+
candidate,
|
|
11
|
+
platform === "win32" ? fs.constants.F_OK : fs.constants.X_OK,
|
|
12
|
+
);
|
|
13
|
+
return true;
|
|
14
|
+
} catch {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function executableNames(command, env, platform) {
|
|
20
|
+
if (platform !== "win32" || path.extname(command)) return [command];
|
|
21
|
+
const pathExt = env.PATHEXT || ".COM;.EXE;.BAT;.CMD";
|
|
22
|
+
return pathExt
|
|
23
|
+
.split(";")
|
|
24
|
+
.filter(Boolean)
|
|
25
|
+
.map((extension) => `${command}${extension.toLowerCase()}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a command from PATH without relying on the external `which` utility.
|
|
30
|
+
*
|
|
31
|
+
* NixOS provides declared executables through PATH but does not guarantee
|
|
32
|
+
* `which` is installed. Keeping this lookup in Node also avoids spawning a
|
|
33
|
+
* subprocess for every prerequisite and harness check.
|
|
34
|
+
*/
|
|
35
|
+
function commandExists(command, options = {}) {
|
|
36
|
+
if (typeof command !== "string" || command.length === 0) return false;
|
|
37
|
+
|
|
38
|
+
const env = options.env || process.env;
|
|
39
|
+
const platform = options.platform || process.platform;
|
|
40
|
+
const cwd = options.cwd || process.cwd();
|
|
41
|
+
const separator = platform === "win32" ? ";" : ":";
|
|
42
|
+
const pathValue = env.PATH || env.Path || env.path || "";
|
|
43
|
+
const names = executableNames(command, env, platform);
|
|
44
|
+
const hasPathSeparator =
|
|
45
|
+
path.isAbsolute(command) || command.includes("/") || command.includes("\\");
|
|
46
|
+
|
|
47
|
+
if (hasPathSeparator) {
|
|
48
|
+
return names.some((name) =>
|
|
49
|
+
executableFile(path.isAbsolute(name) ? name : path.resolve(cwd, name), platform),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return pathValue.split(separator).some((entry) => {
|
|
54
|
+
const directory = entry ? entry.replace(/^"(.*)"$/, "$1") : cwd;
|
|
55
|
+
return names.some((name) => executableFile(path.join(directory, name), platform));
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = { commandExists };
|
package/lib/setup.js
CHANGED
|
@@ -17,6 +17,7 @@ const {
|
|
|
17
17
|
installPiRuntime,
|
|
18
18
|
reportPiRuntimeSummary,
|
|
19
19
|
} = require("./pi-runtime");
|
|
20
|
+
const { commandExists } = require("./path-lookup");
|
|
20
21
|
|
|
21
22
|
const https = require("node:https");
|
|
22
23
|
|
|
@@ -154,6 +155,45 @@ function slugifyName(name) {
|
|
|
154
155
|
.replace(/^-|-$/g, "");
|
|
155
156
|
}
|
|
156
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Choose an install directory without ever adopting an unrelated checkout.
|
|
160
|
+
*
|
|
161
|
+
* Most Egregore forks are named "egregore", so deriving the target only from
|
|
162
|
+
* the fork basename makes different organizations collide in the same parent
|
|
163
|
+
* directory. If that default path is already a Git checkout with another
|
|
164
|
+
* origin, leave it untouched and use egregore-{org-slug}. The fallback must
|
|
165
|
+
* also be absent or point at the requested fork; otherwise stop rather than
|
|
166
|
+
* guessing at another path.
|
|
167
|
+
*/
|
|
168
|
+
function resolveEgregoreInstallDir(base, forkUrl, slug) {
|
|
169
|
+
const forkDirName = forkUrl.split("/").pop().replace(/\.git$/, "");
|
|
170
|
+
const requestedDir = path.join(base, forkDirName);
|
|
171
|
+
const requestedGitPath = path.join(requestedDir, ".git");
|
|
172
|
+
|
|
173
|
+
if (!fs.existsSync(requestedGitPath) || gitRemoteMatches(requestedDir, forkUrl)) {
|
|
174
|
+
return { egregoreDir: requestedDir, redirectedFrom: null };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const safeSlug = slugifyName(slug) || "instance";
|
|
178
|
+
const fallbackDir = path.join(base, `egregore-${safeSlug}`);
|
|
179
|
+
if (path.resolve(fallbackDir) === path.resolve(requestedDir)) {
|
|
180
|
+
throw new Error(
|
|
181
|
+
`Refusing to install into ${requestedDir}: its Git origin does not match ${forkUrl}`
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (fs.existsSync(fallbackDir)) {
|
|
186
|
+
const fallbackGitPath = path.join(fallbackDir, ".git");
|
|
187
|
+
if (!fs.existsSync(fallbackGitPath) || !gitRemoteMatches(fallbackDir, forkUrl)) {
|
|
188
|
+
throw new Error(
|
|
189
|
+
`Refusing to install into ${fallbackDir}: the fallback path already belongs to another checkout or directory`
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return { egregoreDir: fallbackDir, redirectedFrom: requestedDir };
|
|
195
|
+
}
|
|
196
|
+
|
|
157
197
|
function normalizeUsageType(data = {}) {
|
|
158
198
|
if (data.usage_type === "founder_group" || data.usage_type === "joiner_group") {
|
|
159
199
|
return data.usage_type;
|
|
@@ -341,8 +381,17 @@ async function install(data, ui, targetDir, opts = {}) {
|
|
|
341
381
|
const preferredRuntime = normalizeRuntime(opts.runtime);
|
|
342
382
|
|
|
343
383
|
// Directory name from fork URL (what git clone would use), e.g. "egregore"
|
|
344
|
-
|
|
345
|
-
const
|
|
384
|
+
let forkDirName = fork_url.split("/").pop().replace(/\.git$/, "");
|
|
385
|
+
const {
|
|
386
|
+
egregoreDir,
|
|
387
|
+
redirectedFrom,
|
|
388
|
+
} = resolveEgregoreInstallDir(base, fork_url, slug || github_org || org_name);
|
|
389
|
+
if (redirectedFrom) {
|
|
390
|
+
forkDirName = path.basename(egregoreDir);
|
|
391
|
+
ui.warn(
|
|
392
|
+
`${redirectedFrom} belongs to another Git origin — leaving it untouched and installing at ${egregoreDir}`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
346
395
|
const memoryDirName = memory_url
|
|
347
396
|
.split("/")
|
|
348
397
|
.pop()
|
|
@@ -953,10 +1002,6 @@ function installCodexSkills(egregoreDir) {
|
|
|
953
1002
|
return false;
|
|
954
1003
|
}
|
|
955
1004
|
|
|
956
|
-
function commandExists(bin) {
|
|
957
|
-
return spawnSync("which", [bin], { stdio: "pipe" }).status === 0;
|
|
958
|
-
}
|
|
959
|
-
|
|
960
1005
|
function registerInstance(slug, name, egregoreDir, runtime) {
|
|
961
1006
|
const registryDir = path.join(os.homedir(), ".egregore");
|
|
962
1007
|
const registryFile = path.join(registryDir, "instances.json");
|
|
@@ -1433,5 +1478,6 @@ module.exports = {
|
|
|
1433
1478
|
initMemoryDirs,
|
|
1434
1479
|
ensureDevelop,
|
|
1435
1480
|
ensureConnectedConfig,
|
|
1481
|
+
resolveEgregoreInstallDir,
|
|
1436
1482
|
run,
|
|
1437
1483
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-egregore",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.22",
|
|
4
4
|
"description": "Set up Egregore for your team in one command — installs the egregore launcher and your choice of Claude Code, Codex, or Pi.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build:runtime": "node scripts/build-runtime-bundle.mjs && node scripts/build-pi-runtime-bundle.mjs",
|
|
14
14
|
"build:pi-runtime": "node scripts/build-pi-runtime-bundle.mjs",
|
|
15
|
-
"test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-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/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.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/launcher-permissions.test.js test/launcher-update.test.js test/connected-config.test.js",
|
|
15
|
+
"test": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test 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/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/pi-runtime.test.js test/pi-product-workflows.test.js test/installer-runtime-source.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/launcher-connected-status.test.js test/launcher-permissions.test.js test/launcher-update.test.js test/connected-config.test.js",
|
|
16
16
|
"test:e2e": "node --test test/e2e-local.test.js",
|
|
17
17
|
"test:all": "node scripts/build-runtime-bundle.mjs --check && node scripts/build-pi-runtime-bundle.mjs --check && node --test test/"
|
|
18
18
|
},
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# External notification consent
|
|
2
|
+
|
|
3
|
+
This protocol applies to every Telegram, Teams, or other external notification
|
|
4
|
+
in every harness. It applies even when the user asked to announce, notify,
|
|
5
|
+
handoff, invite, or message someone, and even when the harness is running with
|
|
6
|
+
broad tool permissions.
|
|
7
|
+
|
|
8
|
+
## Invariant
|
|
9
|
+
|
|
10
|
+
Never dispatch an external notification until the human has approved that one
|
|
11
|
+
exact delivery in a dedicated checkpoint. The checkpoint must show:
|
|
12
|
+
|
|
13
|
+
- the organization;
|
|
14
|
+
- the final recipient or group;
|
|
15
|
+
- every channel that will receive it;
|
|
16
|
+
- the exact final message, including links.
|
|
17
|
+
|
|
18
|
+
A workflow request, plan approval, batch approval, prior approval, broad
|
|
19
|
+
permission mode, or approval of a related action is not notification consent.
|
|
20
|
+
There is no standing "always allow" choice. One approval authorizes one attempt
|
|
21
|
+
to send one immutable message.
|
|
22
|
+
|
|
23
|
+
## Required flow
|
|
24
|
+
|
|
25
|
+
1. Finish the message first. Publish any artifact and insert its real URL
|
|
26
|
+
before planning; never change the message after preview.
|
|
27
|
+
2. Resolve the exact delivery without sending:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
PLAN_JSON=$(bash bin/notify.sh plan send "$RECIPIENT" "$MESSAGE")
|
|
31
|
+
# or
|
|
32
|
+
PLAN_JSON=$(bash bin/notify.sh plan group "$MESSAGE")
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
3. Read `plan_id` and `digest` from `PLAN_JSON`. Show the organization,
|
|
36
|
+
recipient, all `channels`/`deliveries`, and exact `message` in a dedicated
|
|
37
|
+
Send / Edit / Cancel question. Use the harness's structured question UI
|
|
38
|
+
when available. Otherwise show the same fields in plain text and stop for
|
|
39
|
+
the user's answer.
|
|
40
|
+
4. Only if the user selects Send for that exact preview:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
APPROVAL_JSON=$(bash bin/notify.sh approve \
|
|
44
|
+
"$PLAN_ID" "$DIGEST" APPROVE_EXACT_NOTIFICATION)
|
|
45
|
+
APPROVAL_TOKEN=$(printf '%s' "$APPROVAL_JSON" | jq -r '.approval_token')
|
|
46
|
+
bash bin/notify.sh dispatch "$PLAN_ID" "$APPROVAL_TOKEN"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
5. If the user chooses Edit, cancel the old plan, edit, create a new plan, and
|
|
50
|
+
preview again. If they choose Cancel, cancel it and do not send. If the plan
|
|
51
|
+
expires or dispatch fails, create a new plan and obtain new consent; never
|
|
52
|
+
retry from the old approval.
|
|
53
|
+
|
|
54
|
+
## Hard rules
|
|
55
|
+
|
|
56
|
+
- Never call notification API endpoints directly. Use `bin/notify.sh`.
|
|
57
|
+
- Never use legacy `notify.sh send` or `notify.sh group`; they only create a
|
|
58
|
+
proposal and intentionally cannot dispatch.
|
|
59
|
+
- Never substitute a group when a direct message cannot be delivered.
|
|
60
|
+
- Never add a channel after preview.
|
|
61
|
+
- Never approve or dispatch from a detached/background process, hook, cron
|
|
62
|
+
job, batch execution, or unattended agent. Those paths may create a plan and
|
|
63
|
+
report that human approval is pending.
|
|
64
|
+
- Multiple direct recipients require a separate preview and approval for each
|
|
65
|
+
exact message. A group fanout may use one approval only when every receiving
|
|
66
|
+
channel is listed in the preview.
|
|
@@ -145,8 +145,19 @@ function currentAuthor(projectDir) {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
function
|
|
149
|
-
|
|
148
|
+
function configuredBaseBranch(projectDir) {
|
|
149
|
+
try {
|
|
150
|
+
const raw = fs.readFileSync(path.join(projectDir, "egregore.json"), "utf-8");
|
|
151
|
+
const value = JSON.parse(raw).base_branch;
|
|
152
|
+
return typeof value === "string" && value.trim() ? value.trim() : "develop";
|
|
153
|
+
} catch {
|
|
154
|
+
return "develop";
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isProtectedBranch(branch, projectDir) {
|
|
159
|
+
return branch === configuredBaseBranch(projectDir) ||
|
|
160
|
+
branch === "develop" || branch === "main" || branch === "master";
|
|
150
161
|
}
|
|
151
162
|
|
|
152
163
|
function isExemptPath(rawPath, projectDir, memoryDir) {
|
|
@@ -403,7 +414,7 @@ function main() {
|
|
|
403
414
|
if (context.external) return;
|
|
404
415
|
const projectDir = context.projectDir;
|
|
405
416
|
const branch = currentBranch(projectDir);
|
|
406
|
-
if (!isProtectedBranch(branch)) return;
|
|
417
|
+
if (!isProtectedBranch(branch, projectDir)) return;
|
|
407
418
|
|
|
408
419
|
// Consent bypass: the user explicitly chose to work on this protected branch.
|
|
409
420
|
// Written only after they pick "Proceed on <branch>"; cleared on session start,
|
|
@@ -17,9 +17,13 @@ when it is available; otherwise render compact numbered choices with an
|
|
|
17
17
|
|
|
18
18
|
1. Read `.claude/skills/add/SKILL.md` for the workflow details.
|
|
19
19
|
2. Run the referenced `bin/` scripts directly from Codex.
|
|
20
|
-
3. Treat graph
|
|
21
|
-
|
|
22
|
-
4.
|
|
20
|
+
3. Treat graph and publish steps as best-effort unless that workflow explicitly
|
|
21
|
+
says they are required.
|
|
22
|
+
4. For every external notification, follow
|
|
23
|
+
`.claude/context/notification-consent.md`: plan without sending, then show
|
|
24
|
+
a separate exact Send / Edit / Cancel checkpoint. Never infer notification
|
|
25
|
+
consent from the workflow request or a batch approval.
|
|
26
|
+
5. Keep local-mode behavior filesystem-first and avoid graph or notification
|
|
23
27
|
calls when `egregore.json` declares `"mode": "local"`.
|
|
24
|
-
|
|
28
|
+
6. Never call the deprecated `egregore-handoff` CLI for Egregore project
|
|
25
29
|
handoffs.
|
|
@@ -22,33 +22,46 @@ bash bin/publish-artifact.sh document "$FILE_PATH" --title "$TITLE" --author "$A
|
|
|
22
22
|
Use `handoff`, `quest`, or `document` as the artifact type when appropriate.
|
|
23
23
|
Never fabricate a URL.
|
|
24
24
|
|
|
25
|
-
3.
|
|
26
|
-
|
|
25
|
+
3. Follow `.claude/context/notification-consent.md`. Create a plan without
|
|
26
|
+
sending:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
PLAN_JSON=$(bash bin/notify.sh plan group "$MESSAGE")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
4. Preview the plan's exact organization, all deliveries/channels, and exact
|
|
33
|
+
final message. Confirm in a separate checkpoint with structured Codex
|
|
34
|
+
question tooling when available; otherwise render those fields followed by:
|
|
27
35
|
|
|
28
36
|
```text
|
|
29
37
|
Send this announcement?
|
|
30
38
|
1. Send
|
|
31
39
|
2. Edit
|
|
32
|
-
|
|
40
|
+
3. Cancel
|
|
33
41
|
```
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
5. If the user edits, cancel the old plan, redraft, plan, and preview again.
|
|
44
|
+
If they cancel, cancel the plan and stop. Only after they select Send for
|
|
45
|
+
that exact preview, run:
|
|
36
46
|
|
|
37
47
|
```bash
|
|
38
|
-
bash bin/notify.sh
|
|
48
|
+
APPROVAL_JSON=$(bash bin/notify.sh approve "$PLAN_ID" "$DIGEST" APPROVE_EXACT_NOTIFICATION)
|
|
49
|
+
APPROVAL_TOKEN=$(printf '%s' "$APPROVAL_JSON" | jq -r '.approval_token')
|
|
50
|
+
bash bin/notify.sh dispatch "$PLAN_ID" "$APPROVAL_TOKEN"
|
|
39
51
|
```
|
|
40
52
|
|
|
41
|
-
|
|
53
|
+
6. Emit telemetry in the background:
|
|
42
54
|
|
|
43
55
|
```bash
|
|
44
56
|
bash bin/telemetry.sh emit "command" '{"command":"announce"}' >/dev/null 2>&1 &
|
|
45
57
|
```
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
7. Confirm whether the exact approved group send succeeded.
|
|
48
60
|
|
|
49
61
|
## Rules
|
|
50
62
|
|
|
51
|
-
- Always
|
|
63
|
+
- The request to announce is not dispatch consent. Always use a dedicated
|
|
64
|
+
exact-delivery checkpoint.
|
|
52
65
|
- Do not expose notification credentials.
|
|
53
66
|
- Use `bin/notify.sh`; do not call Telegram directly.
|
|
54
67
|
- Do not use Claude Code commands.
|