create-egregore 0.14.1 → 0.15.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/lib/auth.js +12 -5
- package/lib/local.js +58 -91
- package/package.json +1 -1
package/lib/auth.js
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
const https = require("node:https");
|
|
7
7
|
|
|
8
|
-
const
|
|
8
|
+
const GITHUB_APP_CLIENT_ID = "Iv23li2obNsAjakoK2RE"; // GitHub App — founder flow (clean install screen)
|
|
9
|
+
const OAUTH_APP_CLIENT_ID = "Ov23liYZjJbbbNgnJ9vH"; // OAuth App — join flow (repo:invite only)
|
|
9
10
|
|
|
10
11
|
function post(url, body) {
|
|
11
12
|
return new Promise((resolve, reject) => {
|
|
@@ -72,12 +73,18 @@ function copyToClipboard(text) {
|
|
|
72
73
|
/**
|
|
73
74
|
* Run GitHub Device Flow.
|
|
74
75
|
* Returns the access token string, or throws on failure.
|
|
76
|
+
* @param {object} [opts] - Options
|
|
77
|
+
* @param {string} [opts.mode] - "join" uses OAuth App with repo:invite scope
|
|
75
78
|
*/
|
|
76
|
-
async function deviceFlow(ui) {
|
|
79
|
+
async function deviceFlow(ui, opts = {}) {
|
|
80
|
+
const isJoin = opts.mode === "join";
|
|
81
|
+
const clientId = isJoin ? OAUTH_APP_CLIENT_ID : GITHUB_APP_CLIENT_ID;
|
|
82
|
+
const scope = isJoin ? "repo:invite" : "";
|
|
83
|
+
|
|
77
84
|
// 1. Request device code
|
|
78
85
|
const codeResp = await post("https://github.com/login/device/code", {
|
|
79
|
-
client_id:
|
|
80
|
-
scope
|
|
86
|
+
client_id: clientId,
|
|
87
|
+
scope,
|
|
81
88
|
});
|
|
82
89
|
|
|
83
90
|
if (!codeResp.device_code) {
|
|
@@ -111,7 +118,7 @@ async function deviceFlow(ui) {
|
|
|
111
118
|
await sleep(interval);
|
|
112
119
|
|
|
113
120
|
const tokenResp = await post("https://github.com/login/oauth/access_token", {
|
|
114
|
-
client_id:
|
|
121
|
+
client_id: clientId,
|
|
115
122
|
device_code,
|
|
116
123
|
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
117
124
|
});
|
package/lib/local.js
CHANGED
|
@@ -568,18 +568,11 @@ async function localJoinFlow(orgArg, ui) {
|
|
|
568
568
|
explicitRepo = null;
|
|
569
569
|
}
|
|
570
570
|
|
|
571
|
-
// 1.
|
|
571
|
+
// 1. GitHub auth — OAuth with repo:invite scope (narrow: just invitation acceptance)
|
|
572
572
|
ui.info(`Joining ${ui.bold(orgLogin)}. First, sign in with GitHub.\n`);
|
|
573
|
-
ui.info("Install the Egregore app on your GitHub account.");
|
|
574
|
-
ui.info("This lets Egregore access the repos you've been invited to.\n");
|
|
575
|
-
openBrowser(APP_INSTALL_URL);
|
|
576
|
-
await ui.prompt("Press Enter when done...");
|
|
577
|
-
|
|
578
|
-
// 2. GitHub auth (auto-approved after App install)
|
|
579
|
-
ui.info("");
|
|
580
573
|
let githubToken;
|
|
581
574
|
try {
|
|
582
|
-
githubToken = await deviceFlow(ui);
|
|
575
|
+
githubToken = await deviceFlow(ui, { mode: "join" });
|
|
583
576
|
ui.success("Authenticated with GitHub");
|
|
584
577
|
} catch (err) {
|
|
585
578
|
ui.error(`GitHub auth failed: ${err.message}`);
|
|
@@ -589,54 +582,51 @@ async function localJoinFlow(orgArg, ui) {
|
|
|
589
582
|
// 2. Get user info
|
|
590
583
|
const user = await getUser(githubToken);
|
|
591
584
|
|
|
592
|
-
// 3. Accept pending invitations
|
|
585
|
+
// 3. Accept pending invitations
|
|
593
586
|
console.log("");
|
|
594
|
-
const s1 = ui.spinner("
|
|
587
|
+
const s1 = ui.spinner("Accepting invitations...");
|
|
595
588
|
await acceptPendingInvitations(githubToken, orgLogin, ui);
|
|
596
|
-
s1.stop("
|
|
589
|
+
s1.stop("Invitations accepted");
|
|
597
590
|
|
|
598
|
-
// 4.
|
|
599
|
-
|
|
591
|
+
// 4. Determine repo name
|
|
592
|
+
const repoName = explicitRepo || "egregore";
|
|
593
|
+
if (!repoName) {
|
|
594
|
+
ui.error("Usage: npx create-egregore join <org>/<repo>");
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
600
597
|
|
|
601
|
-
//
|
|
602
|
-
//
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
s2.stop("Access confirmed");
|
|
617
|
-
} else if (!repoName) {
|
|
618
|
-
ui.error(`No egregore repo found in ${orgLogin}`);
|
|
619
|
-
ui.error("Make sure the org admin has set up Egregore and invited you.");
|
|
620
|
-
ui.info("If the repo has a custom name, use: npx create-egregore join <org>/<repo>");
|
|
598
|
+
// 5. Clone repos using git credentials (SSH keys / gh CLI / credential helper)
|
|
599
|
+
// The OAuth token only has repo:invite scope — cloning uses the joiner's own git setup.
|
|
600
|
+
const base = process.cwd();
|
|
601
|
+
const egregoreDir = path.join(base, repoName);
|
|
602
|
+
|
|
603
|
+
console.log("");
|
|
604
|
+
const s2 = ui.spinner(`Cloning ${orgLogin}/${repoName}...`);
|
|
605
|
+
try {
|
|
606
|
+
execFileSync("git", ["clone", `https://github.com/${orgLogin}/${repoName}.git`, egregoreDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
|
|
607
|
+
s2.stop(`Cloned ${repoName}`);
|
|
608
|
+
} catch (err) {
|
|
609
|
+
s2.fail(`Could not clone ${orgLogin}/${repoName}`);
|
|
610
|
+
ui.error("Make sure you have git credentials set up (SSH keys or gh auth login).");
|
|
611
|
+
ui.error("Also verify the org admin has set up Egregore and invited you.");
|
|
621
612
|
process.exit(1);
|
|
622
613
|
}
|
|
614
|
+
setGitIdentity(egregoreDir, user);
|
|
623
615
|
|
|
624
|
-
//
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
s3.fail("Could not read egregore.json");
|
|
629
|
-
ui.error("The egregore repo exists but has no egregore.json. Ask the admin to set up first.");
|
|
616
|
+
// 6. Read egregore.json from local clone
|
|
617
|
+
const configPath = path.join(egregoreDir, "egregore.json");
|
|
618
|
+
if (!fs.existsSync(configPath)) {
|
|
619
|
+
ui.error("The repo has no egregore.json. Ask the admin to set up first.");
|
|
630
620
|
process.exit(1);
|
|
631
621
|
}
|
|
632
622
|
let config;
|
|
633
623
|
try {
|
|
634
|
-
config = JSON.parse(
|
|
624
|
+
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
635
625
|
} catch {
|
|
636
|
-
|
|
626
|
+
ui.error("Invalid egregore.json");
|
|
637
627
|
process.exit(1);
|
|
638
628
|
}
|
|
639
|
-
|
|
629
|
+
ui.success(`Found ${ui.bold(config.org_name || orgLogin)}`);
|
|
640
630
|
|
|
641
631
|
const orgName = config.org_name || orgLogin;
|
|
642
632
|
const memoryRepoName = config.memory_repo
|
|
@@ -644,49 +634,34 @@ async function localJoinFlow(orgArg, ui) {
|
|
|
644
634
|
: `${orgLogin}-memory`;
|
|
645
635
|
const managedRepos = config.repos || [];
|
|
646
636
|
|
|
647
|
-
//
|
|
648
|
-
if (!(await repoExists(githubToken, orgLogin, memoryRepoName))) {
|
|
649
|
-
console.log("");
|
|
650
|
-
ui.info("Accept the memory repo invitation in your browser.");
|
|
651
|
-
openBrowser(`https://github.com/${orgLogin}/${memoryRepoName}`);
|
|
652
|
-
await ui.prompt("Press Enter after accepting...");
|
|
653
|
-
|
|
654
|
-
if (!(await repoExists(githubToken, orgLogin, memoryRepoName))) {
|
|
655
|
-
ui.error(`Still can't access ${orgLogin}/${memoryRepoName}`);
|
|
656
|
-
ui.error("Make sure the org admin has invited you to the memory repo.");
|
|
657
|
-
process.exit(1);
|
|
658
|
-
}
|
|
659
|
-
ui.success("Access confirmed");
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
// 6. Clone locally
|
|
663
|
-
const base = process.cwd();
|
|
664
|
-
const forkUrl = `https://github.com/${orgLogin}/${repoName}.git`;
|
|
637
|
+
// 7. Clone memory + managed repos
|
|
665
638
|
const memoryUrl = config.memory_repo || `https://github.com/${orgLogin}/${memoryRepoName}.git`;
|
|
666
|
-
const egregoreDir = path.join(base, repoName);
|
|
667
639
|
const memoryDir = path.join(base, memoryRepoName);
|
|
668
640
|
|
|
669
|
-
const totalSteps =
|
|
670
|
-
|
|
641
|
+
const totalSteps = 4 + managedRepos.length;
|
|
642
|
+
// Store the invite token for later use (invitation acceptance, not cloning)
|
|
643
|
+
const inviteToken = githubToken;
|
|
671
644
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
645
|
+
// Clone memory using git credentials (same as main repo — no token in URL)
|
|
646
|
+
ui.step(1, totalSteps, "Cloning shared memory...");
|
|
647
|
+
try {
|
|
648
|
+
execFileSync("git", ["clone", memoryUrl, memoryDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
|
|
649
|
+
ui.success("Cloned memory");
|
|
650
|
+
} catch {
|
|
651
|
+
ui.warn("Could not clone memory repo — you may not have access yet.");
|
|
652
|
+
}
|
|
678
653
|
setGitIdentity(memoryDir, user);
|
|
679
654
|
|
|
680
|
-
//
|
|
681
|
-
ui.step(
|
|
655
|
+
// Symlink
|
|
656
|
+
ui.step(2, totalSteps, "Linking memory...");
|
|
682
657
|
createSymlink(egregoreDir, memoryDir, ui);
|
|
683
658
|
|
|
684
|
-
//
|
|
685
|
-
ui.step(
|
|
686
|
-
fs.writeFileSync(path.join(egregoreDir, ".env"), `GITHUB_TOKEN=${
|
|
659
|
+
// Write .env — store invite token for session-start invitation checks
|
|
660
|
+
ui.step(3, totalSteps, "Writing credentials...");
|
|
661
|
+
fs.writeFileSync(path.join(egregoreDir, ".env"), `GITHUB_TOKEN=${inviteToken}\n`, { mode: 0o600 });
|
|
687
662
|
ui.success("Credentials saved");
|
|
688
663
|
|
|
689
|
-
//
|
|
664
|
+
// "What should we call you?" + write state
|
|
690
665
|
console.log("");
|
|
691
666
|
const displayName = await ui.prompt(`What should we call you? [${user.login}]:`);
|
|
692
667
|
|
|
@@ -703,30 +678,22 @@ async function localJoinFlow(orgArg, ui) {
|
|
|
703
678
|
};
|
|
704
679
|
fs.writeFileSync(path.join(egregoreDir, ".egregore-state.json"), JSON.stringify(state, null, 2) + "\n");
|
|
705
680
|
|
|
706
|
-
//
|
|
707
|
-
ui.step(
|
|
681
|
+
// Register + alias
|
|
682
|
+
ui.step(4, totalSteps, "Registering instance...");
|
|
708
683
|
registerInstance(repoName, orgName, egregoreDir);
|
|
709
684
|
const alias = await installShellAlias(egregoreDir, ui);
|
|
710
685
|
|
|
711
|
-
//
|
|
686
|
+
// Clone managed repos using git credentials
|
|
712
687
|
for (let i = 0; i < managedRepos.length; i++) {
|
|
713
688
|
const mr = managedRepos[i];
|
|
714
|
-
ui.step(
|
|
689
|
+
ui.step(5 + i, totalSteps, `Cloning ${mr}...`);
|
|
715
690
|
const repoDir = path.join(base, mr);
|
|
716
691
|
try {
|
|
717
|
-
|
|
718
|
-
cloneRepo(repoUrl, repoDir, githubToken, ui, mr);
|
|
692
|
+
execFileSync("git", ["clone", `https://github.com/${orgLogin}/${mr}.git`, repoDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
|
|
719
693
|
setGitIdentity(repoDir, user);
|
|
694
|
+
ui.success(`Cloned ${mr}`);
|
|
720
695
|
} catch {
|
|
721
|
-
|
|
722
|
-
await new Promise((r) => setTimeout(r, 3000));
|
|
723
|
-
try {
|
|
724
|
-
const repoUrl = `https://github.com/${orgLogin}/${mr}.git`;
|
|
725
|
-
cloneRepo(repoUrl, repoDir, githubToken, ui, mr);
|
|
726
|
-
setGitIdentity(repoDir, user);
|
|
727
|
-
} catch {
|
|
728
|
-
ui.warn(`Could not clone ${mr} — you may not have access yet.`);
|
|
729
|
-
}
|
|
696
|
+
ui.warn(`Could not clone ${mr} — you may not have access yet.`);
|
|
730
697
|
}
|
|
731
698
|
}
|
|
732
699
|
|