create-egregore 0.18.17 → 0.19.0
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 +121 -51
- package/lib/local.js +237 -144
- package/lib/ui.js +465 -18
- package/package.json +2 -2
- package/runtime/codex/.codex/spec-manifest.json +7 -1
- package/runtime/codex/AGENTS.md +7 -1
- package/runtime/codex/bin/codex-render-spec.mjs +7 -0
- package/runtime/codex/bin/lib/git-sync.sh +3 -3
- package/runtime/codex/bin/lib/identity.sh +10 -0
- package/runtime/codex/bin/telemetry.sh +54 -13
package/lib/local.js
CHANGED
|
@@ -539,14 +539,14 @@ async function maybeSetupTelegramGroup(ui, {
|
|
|
539
539
|
ui.info(`Bot add link: ${ui.cyan(botLink)}`);
|
|
540
540
|
openBrowser(botLink);
|
|
541
541
|
|
|
542
|
-
await ui.prompt("Press Enter here after the bot is in the group");
|
|
542
|
+
await ui.prompt("Press Enter here after the bot is in the group", { hint: "enter continue · ctrl-c quit" });
|
|
543
543
|
ui.note([
|
|
544
544
|
"Step 4: In Telegram, copy a group invite link.",
|
|
545
545
|
"Paste it below so teammate invites include the Telegram group too.",
|
|
546
546
|
"If you skip this, Egregore will still work, but invites will not include Telegram.",
|
|
547
547
|
]);
|
|
548
548
|
|
|
549
|
-
const groupLink = await ui.prompt("Telegram group join link (paste here, or Enter to skip)");
|
|
549
|
+
const groupLink = await ui.prompt("Telegram group join link (paste here, or Enter to skip)", { hint: "paste the link · enter skip" });
|
|
550
550
|
const cleaned = groupLink.trim();
|
|
551
551
|
let storedGroupLink = "";
|
|
552
552
|
if (cleaned && !looksLikeUrl(cleaned)) {
|
|
@@ -653,105 +653,178 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
653
653
|
// 2. Get user info
|
|
654
654
|
const user = await getUser(githubToken);
|
|
655
655
|
|
|
656
|
-
// 3.
|
|
657
|
-
//
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
s
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
656
|
+
// 3–6. Home → new/existing → naming → repo picker → repo names, as a
|
|
657
|
+
// step machine so esc walks BACKWARDS through the flow (field request:
|
|
658
|
+
// a wrong org pick shouldn't mean Ctrl-C and start over). Every answer
|
|
659
|
+
// lives in `st`, so a revisited step re-offers the previous choice.
|
|
660
|
+
// Creation (step 7 below) is the point of no return — no back from there.
|
|
661
|
+
const slugifyName = (s) => (s || "")
|
|
662
|
+
.toLowerCase()
|
|
663
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
664
|
+
.replace(/-+/g, "-")
|
|
665
|
+
.replace(/^-|-$/g, "");
|
|
666
|
+
const BACK = ui.BACK;
|
|
667
|
+
const SKIP = Symbol("step-skip"); // deterministic step — passes through in both directions
|
|
668
|
+
|
|
669
|
+
const st = {
|
|
670
|
+
orgs: null, // fetched once
|
|
671
|
+
githubOrg: null, isOrg: false,
|
|
672
|
+
isNewProject: null,
|
|
673
|
+
orgName: null, description: "", newProjectRepo: null,
|
|
674
|
+
selectedRepoNames: null, // raw picker selection (preserved across back)
|
|
675
|
+
selectedRepos: [], // enriched {name, base_branch}
|
|
676
|
+
repoName: null, memoryRepoName: null,
|
|
677
|
+
reposCache: null, // {org, repos, teamRepoCount}
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
const stepHome = async () => {
|
|
681
|
+
if (!st.orgs) {
|
|
682
|
+
ui.section("Pick a home", "where should this Egregore live?");
|
|
683
|
+
const s = ui.spinner("Checking your GitHub accounts...");
|
|
684
|
+
st.orgs = await listOrgs(githubToken);
|
|
685
|
+
s.stop("Accounts loaded");
|
|
686
|
+
}
|
|
687
|
+
if (st.orgs.length === 0) {
|
|
688
|
+
if (!st.githubOrg) {
|
|
689
|
+
st.githubOrg = user.login;
|
|
690
|
+
st.isOrg = false;
|
|
691
|
+
ui.info(`Using your personal account: ${ui.bold(st.githubOrg)}`);
|
|
692
|
+
}
|
|
693
|
+
return SKIP;
|
|
672
694
|
}
|
|
673
695
|
const orgChoice = await ui.choose(
|
|
674
696
|
"Where should Egregore live?",
|
|
675
|
-
|
|
697
|
+
[
|
|
698
|
+
{ label: user.login, description: "Personal account" },
|
|
699
|
+
...st.orgs.map((o) => ({ label: o.login, description: "Organization" })),
|
|
700
|
+
],
|
|
676
701
|
"Don't see your org? Authorize it for your account on GitHub, then re-run.",
|
|
702
|
+
{ escAction: "back" },
|
|
677
703
|
);
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
704
|
+
if (orgChoice === BACK) return BACK; // first step — driver clamps to a re-ask
|
|
705
|
+
st.githubOrg = orgChoice.label;
|
|
706
|
+
st.isOrg = st.githubOrg !== user.login;
|
|
707
|
+
return true;
|
|
708
|
+
};
|
|
681
709
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
710
|
+
const stepWorkType = async () => {
|
|
711
|
+
// In-repo detection already answered this — pass through both ways.
|
|
712
|
+
if (ctx.prefilledRepo) {
|
|
713
|
+
if (st.isNewProject === null) {
|
|
714
|
+
st.isNewProject = false;
|
|
715
|
+
ui.info(`${ui.green("→")} existing project — we'll manage ${ui.cyan(ctx.prefilledRepo.localName)} plus any others you pick.`);
|
|
716
|
+
}
|
|
717
|
+
return SKIP;
|
|
718
|
+
}
|
|
691
719
|
const action = await ui.choose("What are you working on?", [
|
|
692
720
|
{ label: "Starting something new", description: "Create a project from scratch" },
|
|
693
721
|
{ label: "Existing project", description: "Connect repos I already have" },
|
|
694
|
-
]);
|
|
695
|
-
|
|
696
|
-
|
|
722
|
+
], undefined, { escAction: "back" });
|
|
723
|
+
if (action === BACK) return BACK;
|
|
724
|
+
st.isNewProject = action.label.startsWith("Starting");
|
|
725
|
+
return true;
|
|
726
|
+
};
|
|
697
727
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
const
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
728
|
+
const stepNameManifest = async () => {
|
|
729
|
+
// One live manifest instead of scattered naming prompts: name the thing
|
|
730
|
+
// first, watch the repo plan derive from it in place, override any line.
|
|
731
|
+
// Every collision check runs against names VISIBLE on screen — the old
|
|
732
|
+
// flow rejected a code-repo name for colliding with an Egregore repo
|
|
733
|
+
// name the user had never been shown.
|
|
734
|
+
console.log("");
|
|
735
|
+
const isNew = st.isNewProject;
|
|
736
|
+
const validSlug = (v) => (slugifyName(v)
|
|
737
|
+
? null
|
|
738
|
+
: "That doesn't produce a valid repo name. Try letters, numbers, and dashes.");
|
|
739
|
+
const fields = [
|
|
740
|
+
{
|
|
741
|
+
key: "orgName",
|
|
742
|
+
label: "Egregore",
|
|
743
|
+
note: "team or group name",
|
|
744
|
+
hint: "type your preferred Egregore name or hit enter to pick the naming below",
|
|
745
|
+
initial: st.orgName || undefined,
|
|
746
|
+
derive: () => st.githubOrg,
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
key: "repoName",
|
|
750
|
+
label: "Egregore repo",
|
|
751
|
+
note: "config + skills",
|
|
752
|
+
hint: `enter keeps this name · type to change · created in ${st.githubOrg}`,
|
|
753
|
+
tree: true,
|
|
754
|
+
initial: st.repoName || undefined,
|
|
755
|
+
derive: (v) => slugifyName(v.orgName) || "egregore",
|
|
756
|
+
normalize: (raw) => slugifyName(raw),
|
|
757
|
+
validate: (v) => validSlug(v),
|
|
758
|
+
},
|
|
759
|
+
{
|
|
760
|
+
key: "memoryRepoName",
|
|
761
|
+
label: "memory repo",
|
|
762
|
+
note: "shared memory",
|
|
763
|
+
hint: "enter keeps this name · type to change",
|
|
764
|
+
tree: true,
|
|
765
|
+
initial: st.memoryRepoName || undefined,
|
|
766
|
+
derive: (v) => `${v.repoName || "egregore"}-memory`,
|
|
767
|
+
normalize: (raw) => slugifyName(raw),
|
|
768
|
+
validate: (v, others) => validSlug(v)
|
|
769
|
+
|| (v === others.repoName ? "Memory repo can't share the Egregore repo's name." : null),
|
|
770
|
+
},
|
|
771
|
+
...(isNew ? [{
|
|
772
|
+
key: "codeRepo",
|
|
773
|
+
label: "code repo",
|
|
774
|
+
note: "your project · optional",
|
|
775
|
+
hint: "add a repo for your code project — or enter to skip",
|
|
776
|
+
tree: true,
|
|
777
|
+
optional: true,
|
|
778
|
+
initial: st.newProjectRepo || undefined,
|
|
779
|
+
normalize: (raw) => slugifyName(raw),
|
|
780
|
+
validate: (v, others) => validSlug(v)
|
|
781
|
+
|| (v === others.repoName ? `"${v}" is the Egregore repo above. Pick a different name — say, ${v}-app.` : null)
|
|
782
|
+
|| (v === others.memoryRepoName ? `"${v}" is the memory repo above. Pick a different name.` : null),
|
|
783
|
+
}] : []),
|
|
784
|
+
];
|
|
785
|
+
const result = await ui.manifestForm({
|
|
786
|
+
title: "Name it",
|
|
787
|
+
subtitle: `repos land in ${st.githubOrg}`,
|
|
788
|
+
fields,
|
|
789
|
+
});
|
|
790
|
+
if (result === BACK) return BACK;
|
|
791
|
+
const v = result.values;
|
|
792
|
+
st.orgName = v.orgName;
|
|
793
|
+
st.repoName = v.repoName;
|
|
794
|
+
st.memoryRepoName = v.memoryRepoName;
|
|
795
|
+
st.newProjectRepo = isNew ? (v.codeRepo || null) : null;
|
|
796
|
+
return true;
|
|
797
|
+
};
|
|
721
798
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (!slug) {
|
|
730
|
-
ui.warn("That doesn't produce a valid repo name. Try letters, numbers, and dashes.");
|
|
731
|
-
continue;
|
|
732
|
-
}
|
|
733
|
-
if (slug === predictedRepoName) {
|
|
734
|
-
ui.warn(`"${slug}" is already the name of your Egregore repo. Pick a different name.`);
|
|
735
|
-
ui.info(` ${ui.dim("suggestion:")} ${ui.cyan(`${slug}-app`)}`);
|
|
736
|
-
continue;
|
|
737
|
-
}
|
|
738
|
-
if (slug === predictedMemoryRepoName) {
|
|
739
|
-
ui.warn(`"${slug}" collides with your memory repo. Pick a different name.`);
|
|
740
|
-
continue;
|
|
741
|
-
}
|
|
742
|
-
newProjectRepo = slug;
|
|
743
|
-
break;
|
|
799
|
+
// The one-liner is repo metadata, not a naming decision — it lives OUTSIDE
|
|
800
|
+
// the manifest so enter on the name lands directly in the naming tree
|
|
801
|
+
// (the manifest's hint promises exactly that).
|
|
802
|
+
const stepDescription = async () => {
|
|
803
|
+
if (!st.isNewProject) {
|
|
804
|
+
st.description = "";
|
|
805
|
+
return SKIP;
|
|
744
806
|
}
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
807
|
+
console.log("");
|
|
808
|
+
const desc = await ui.promptOrBack("what are you building? (one line)", {
|
|
809
|
+
default: st.description || undefined,
|
|
810
|
+
hint: "one line on what you're making · enter to skip · ↑ back",
|
|
811
|
+
});
|
|
812
|
+
if (desc === BACK) return BACK;
|
|
813
|
+
st.description = desc;
|
|
814
|
+
return true;
|
|
815
|
+
};
|
|
749
816
|
|
|
817
|
+
const stepRepos = async () => {
|
|
818
|
+
if (st.isNewProject) {
|
|
819
|
+
st.selectedRepoNames = null;
|
|
820
|
+
st.selectedRepos = [];
|
|
821
|
+
return SKIP;
|
|
822
|
+
}
|
|
750
823
|
// Auto-adopt the user's current repo if it's under the chosen GitHub owner.
|
|
751
824
|
const autoAdopt =
|
|
752
825
|
ctx.prefilledRepo &&
|
|
753
826
|
ctx.prefilledRepo.owner &&
|
|
754
|
-
ctx.prefilledRepo.owner.toLowerCase() === githubOrg.toLowerCase()
|
|
827
|
+
ctx.prefilledRepo.owner.toLowerCase() === st.githubOrg.toLowerCase()
|
|
755
828
|
? ctx.prefilledRepo.name
|
|
756
829
|
: null;
|
|
757
830
|
|
|
@@ -759,47 +832,58 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
759
832
|
// 500 most recently updated repos, which buries a team's repos in a big
|
|
760
833
|
// org — so the user's team repos are loaded too and boosted to the top,
|
|
761
834
|
// and typing in the picker live-searches the WHOLE org (see
|
|
762
|
-
// searchOrgRepos), so no repo is unreachable.
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
835
|
+
// searchOrgRepos), so no repo is unreachable. The load is cached per
|
|
836
|
+
// owner so back-navigation doesn't refetch.
|
|
837
|
+
if (!st.reposCache || st.reposCache.org !== st.githubOrg) {
|
|
838
|
+
const s0 = ui.spinner("Loading repos...");
|
|
839
|
+
const repos = [];
|
|
840
|
+
let teamRepoCount = 0;
|
|
841
|
+
try {
|
|
842
|
+
const [orgRepos, teams] = await Promise.all([
|
|
843
|
+
listOrgRepos(githubToken, st.githubOrg, st.isOrg),
|
|
844
|
+
st.isOrg ? listUserTeams(githubToken, st.githubOrg) : Promise.resolve([]),
|
|
845
|
+
]);
|
|
846
|
+
const teamRepoLists = await Promise.all(
|
|
847
|
+
teams.map((t) => listTeamRepos(githubToken, st.githubOrg, t.slug).catch(() => [])),
|
|
848
|
+
);
|
|
849
|
+
const seen = new Set();
|
|
850
|
+
for (const list of teamRepoLists) {
|
|
851
|
+
for (const r of list) {
|
|
852
|
+
if (seen.has(r.name)) continue;
|
|
853
|
+
seen.add(r.name);
|
|
854
|
+
repos.push(r);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
teamRepoCount = repos.length;
|
|
858
|
+
for (const r of orgRepos) {
|
|
777
859
|
if (seen.has(r.name)) continue;
|
|
778
860
|
seen.add(r.name);
|
|
779
861
|
repos.push(r);
|
|
780
862
|
}
|
|
863
|
+
s0.stop(teamRepoCount > 0 ? `Loaded repos — your teams' ${teamRepoCount} first` : "Loaded repos");
|
|
864
|
+
} catch {
|
|
865
|
+
s0.stop("Could not load repos");
|
|
781
866
|
}
|
|
782
|
-
|
|
783
|
-
for (const r of orgRepos) {
|
|
784
|
-
if (seen.has(r.name)) continue;
|
|
785
|
-
seen.add(r.name);
|
|
786
|
-
repos.push(r);
|
|
787
|
-
}
|
|
788
|
-
s0.stop(teamRepoCount > 0 ? `Loaded repos — your teams' ${teamRepoCount} first` : "Loaded repos");
|
|
789
|
-
} catch {
|
|
790
|
-
s0.stop("Could not load repos");
|
|
867
|
+
st.reposCache = { org: st.githubOrg, repos, teamRepoCount };
|
|
791
868
|
}
|
|
869
|
+
const repos = st.reposCache.repos;
|
|
792
870
|
|
|
793
871
|
const filtered = autoAdopt ? repos.filter((r) => r.name !== autoAdopt) : repos;
|
|
794
872
|
if (autoAdopt) {
|
|
795
873
|
console.log("");
|
|
796
874
|
ui.info(`${ui.cyan(autoAdopt)} will be managed automatically (your current repo)`);
|
|
797
875
|
}
|
|
798
|
-
|
|
876
|
+
const picked = await ui.multiSelect(
|
|
799
877
|
"Which other repos should Egregore manage?",
|
|
800
878
|
filtered,
|
|
801
|
-
{
|
|
879
|
+
{
|
|
880
|
+
remoteSearch: (term) => searchOrgRepos(githubToken, st.githubOrg, st.isOrg, term),
|
|
881
|
+
preselected: st.selectedRepoNames || [],
|
|
882
|
+
escAction: "back",
|
|
883
|
+
},
|
|
802
884
|
);
|
|
885
|
+
if (picked === BACK) return BACK;
|
|
886
|
+
let selectedRepos = picked;
|
|
803
887
|
|
|
804
888
|
// Non-interactive terminals get no live search — keep a typed-names
|
|
805
889
|
// escape hatch there, validated so a typo doesn't become a ghost repo.
|
|
@@ -808,11 +892,11 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
808
892
|
for (const raw of (extraRaw || "").split(",")) {
|
|
809
893
|
const name = raw.trim();
|
|
810
894
|
if (!name || selectedRepos.includes(name)) continue;
|
|
811
|
-
if (await repoExists(githubToken, githubOrg, name)) {
|
|
895
|
+
if (await repoExists(githubToken, st.githubOrg, name)) {
|
|
812
896
|
selectedRepos.push(name);
|
|
813
897
|
ui.success(`Added ${name}`);
|
|
814
898
|
} else {
|
|
815
|
-
ui.warn(`No repo ${githubOrg}/${name} — skipping`);
|
|
899
|
+
ui.warn(`No repo ${st.githubOrg}/${name} — skipping`);
|
|
816
900
|
}
|
|
817
901
|
}
|
|
818
902
|
}
|
|
@@ -820,6 +904,7 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
820
904
|
selectedRepos.unshift(autoAdopt);
|
|
821
905
|
}
|
|
822
906
|
if (autoAdopt) skipRepos.add(autoAdopt);
|
|
907
|
+
st.selectedRepoNames = [...selectedRepos];
|
|
823
908
|
|
|
824
909
|
// Detect default branch for each selected repo and convert to {name, base_branch} format
|
|
825
910
|
if (selectedRepos.length > 0) {
|
|
@@ -827,14 +912,13 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
827
912
|
const reposWithBranch = [];
|
|
828
913
|
for (const repoName of selectedRepos) {
|
|
829
914
|
try {
|
|
830
|
-
const info = await detectRepoBranches(githubToken, githubOrg, repoName);
|
|
915
|
+
const info = await detectRepoBranches(githubToken, st.githubOrg, repoName);
|
|
831
916
|
reposWithBranch.push({ name: repoName, base_branch: info ? info.defaultBranch : "main" });
|
|
832
917
|
} catch {
|
|
833
918
|
reposWithBranch.push({ name: repoName, base_branch: "main" });
|
|
834
919
|
}
|
|
835
920
|
}
|
|
836
921
|
s0b.stop("Checked branches");
|
|
837
|
-
// Replace flat list with enriched objects
|
|
838
922
|
selectedRepos = reposWithBranch;
|
|
839
923
|
console.log("");
|
|
840
924
|
ui.info("Detected branch setup:");
|
|
@@ -843,42 +927,48 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
843
927
|
}
|
|
844
928
|
console.log("");
|
|
845
929
|
}
|
|
930
|
+
st.selectedRepos = selectedRepos;
|
|
931
|
+
return true;
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// Direction-aware driver: BACK rewinds one step; SKIP (deterministic
|
|
935
|
+
// steps) passes through in whichever direction we're moving, so backing
|
|
936
|
+
// over an auto-answered step doesn't bounce the user forward again.
|
|
937
|
+
// ALL naming (Egregore + repo plan) happens in the manifest BEFORE the
|
|
938
|
+
// existing-project repo picker — name the thing first, then attach repos.
|
|
939
|
+
const steps = [stepHome, stepWorkType, stepNameManifest, stepDescription, stepRepos];
|
|
940
|
+
let stepIdx = 0;
|
|
941
|
+
let stepDir = 1;
|
|
942
|
+
while (stepIdx < steps.length) {
|
|
943
|
+
const result = await steps[stepIdx]();
|
|
944
|
+
if (result === BACK) {
|
|
945
|
+
stepDir = -1;
|
|
946
|
+
stepIdx = Math.max(0, stepIdx - 1);
|
|
947
|
+
continue;
|
|
948
|
+
}
|
|
949
|
+
if (result === SKIP) {
|
|
950
|
+
stepIdx += stepDir;
|
|
951
|
+
if (stepIdx < 0) { stepIdx = 0; stepDir = 1; }
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
stepDir = 1;
|
|
955
|
+
stepIdx++;
|
|
846
956
|
}
|
|
847
957
|
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
const
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
958
|
+
const githubOrg = st.githubOrg;
|
|
959
|
+
const isOrg = st.isOrg;
|
|
960
|
+
const isNewProject = st.isNewProject;
|
|
961
|
+
const orgName = st.orgName;
|
|
962
|
+
const description = st.description;
|
|
963
|
+
let newProjectRepo = st.newProjectRepo;
|
|
964
|
+
let selectedRepos = st.selectedRepos;
|
|
965
|
+
const repoName = st.repoName;
|
|
966
|
+
const memoryRepoName = st.memoryRepoName;
|
|
856
967
|
const nameSlug = slugifyName(orgName) || "egregore";
|
|
857
968
|
// Org slug prefixed with GitHub owner — globally unique across all egregores
|
|
858
969
|
const ghPrefix = slugifyName(githubOrg);
|
|
859
970
|
const slug = ghPrefix === nameSlug ? nameSlug : `${ghPrefix}-${nameSlug}`;
|
|
860
971
|
|
|
861
|
-
console.log("");
|
|
862
|
-
ui.note([
|
|
863
|
-
`Two repos will be created in ${ui.bold(githubOrg)}: the Egregore repo`,
|
|
864
|
-
"(config + skills) and its shared-memory repo. Enter keeps the defaults.",
|
|
865
|
-
]);
|
|
866
|
-
let repoName;
|
|
867
|
-
for (;;) {
|
|
868
|
-
repoName = slugifyName(await ui.prompt("Egregore repo name", { default: nameSlug }));
|
|
869
|
-
if (!repoName) { ui.warn("That doesn't produce a valid repo name. Try letters, numbers, and dashes."); continue; }
|
|
870
|
-
if (newProjectRepo && repoName === newProjectRepo) { ui.warn(`"${repoName}" is already your project repo. Pick a different name.`); continue; }
|
|
871
|
-
break;
|
|
872
|
-
}
|
|
873
|
-
let memoryRepoName;
|
|
874
|
-
for (;;) {
|
|
875
|
-
memoryRepoName = slugifyName(await ui.prompt("memory repo name", { default: `${repoName}-memory` }));
|
|
876
|
-
if (!memoryRepoName) { ui.warn("That doesn't produce a valid repo name. Try letters, numbers, and dashes."); continue; }
|
|
877
|
-
if (memoryRepoName === repoName) { ui.warn("Memory repo can't share the Egregore repo's name."); continue; }
|
|
878
|
-
if (newProjectRepo && memoryRepoName === newProjectRepo) { ui.warn(`"${memoryRepoName}" is already your project repo. Pick a different name.`); continue; }
|
|
879
|
-
break;
|
|
880
|
-
}
|
|
881
|
-
|
|
882
972
|
// Guard: if egregore repo already exists with a config, don't overwrite
|
|
883
973
|
console.log("");
|
|
884
974
|
const s1 = ui.spinner("Checking for existing Egregore...");
|
|
@@ -1067,7 +1157,10 @@ async function localFounderFlow(ui, ctx = {}) {
|
|
|
1067
1157
|
|
|
1068
1158
|
// 14. "What should we call you?" + write state
|
|
1069
1159
|
ui.section("Your name", "this is how teammates will see you in handoffs");
|
|
1070
|
-
const displayName = await ui.prompt("what should we call you?", {
|
|
1160
|
+
const displayName = await ui.prompt("what should we call you?", {
|
|
1161
|
+
default: user.login,
|
|
1162
|
+
hint: `type a display name · enter keeps ${user.login}`,
|
|
1163
|
+
});
|
|
1071
1164
|
const memberRole = await ensureInstallerMemberRole(
|
|
1072
1165
|
{ usage_type: "founder_group", member_role: ctx.memberRole },
|
|
1073
1166
|
{},
|