create-egregore 0.3.11 → 0.3.12
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/bin/cli.js +21 -0
- package/lib/api.js +2 -2
- package/lib/auth.js +1 -1
- package/lib/setup.js +87 -15
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -81,6 +81,18 @@ async function tokenFlow(api, token) {
|
|
|
81
81
|
process.exit(1);
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// Joiner tokens don't include a github_token — run device flow to get one
|
|
85
|
+
if (!data.github_token || data.needs_cli_auth) {
|
|
86
|
+
ui.info("\nSign in with GitHub to complete setup.\n");
|
|
87
|
+
try {
|
|
88
|
+
data.github_token = await deviceFlow(ui);
|
|
89
|
+
ui.success("Authenticated with GitHub");
|
|
90
|
+
} catch (err) {
|
|
91
|
+
ui.error(`GitHub auth failed: ${err.message}`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
84
96
|
try {
|
|
85
97
|
await install(data, ui);
|
|
86
98
|
} catch (err) {
|
|
@@ -217,6 +229,14 @@ async function setupFlow(api, githubToken, choice) {
|
|
|
217
229
|
// Non-fatal — continue without repos
|
|
218
230
|
}
|
|
219
231
|
|
|
232
|
+
// Transcript sharing consent
|
|
233
|
+
console.log("");
|
|
234
|
+
console.log(" Egregore can collect session transcripts to build");
|
|
235
|
+
console.log(" organizational memory (decisions, patterns, handoffs).");
|
|
236
|
+
console.log(" Transcripts are private to your org. Members can opt out individually.");
|
|
237
|
+
const consentAnswer = await ui.prompt("Enable transcript collection? [Y/n]:");
|
|
238
|
+
const transcriptSharing = !consentAnswer || consentAnswer.toLowerCase() !== "n";
|
|
239
|
+
|
|
220
240
|
const s = ui.spinner(`Setting up Egregore for ${ui.bold(name)}...`);
|
|
221
241
|
try {
|
|
222
242
|
const result = await api.setupOrg(githubToken, {
|
|
@@ -225,6 +245,7 @@ async function setupFlow(api, githubToken, choice) {
|
|
|
225
245
|
is_personal: choice.is_personal || false,
|
|
226
246
|
repos: selectedRepos,
|
|
227
247
|
instance_name: instanceName,
|
|
248
|
+
transcript_sharing: transcriptSharing,
|
|
228
249
|
});
|
|
229
250
|
s.stop("Setup complete on GitHub");
|
|
230
251
|
|
package/lib/api.js
CHANGED
|
@@ -79,8 +79,8 @@ class EgregoreAPI {
|
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
async setupOrg(githubToken, { github_org, org_name, is_personal = false, repos = [], instance_name }) {
|
|
83
|
-
const body = { github_org, org_name, is_personal, repos };
|
|
82
|
+
async setupOrg(githubToken, { github_org, org_name, is_personal = false, repos = [], instance_name, transcript_sharing = false }) {
|
|
83
|
+
const body = { github_org, org_name, is_personal, repos, transcript_sharing };
|
|
84
84
|
if (instance_name) body.instance_name = instance_name;
|
|
85
85
|
return request("POST", `${this.base}/api/org/setup`, {
|
|
86
86
|
headers: { Authorization: `Bearer ${githubToken}` },
|
package/lib/auth.js
CHANGED
package/lib/setup.js
CHANGED
|
@@ -8,10 +8,65 @@ const fs = require("node:fs");
|
|
|
8
8
|
const path = require("node:path");
|
|
9
9
|
const os = require("node:os");
|
|
10
10
|
|
|
11
|
+
const https = require("node:https");
|
|
12
|
+
|
|
11
13
|
function run(cmd, opts = {}) {
|
|
12
14
|
return execSync(cmd, { stdio: "pipe", encoding: "utf-8", timeout: 60000, ...opts }).trim();
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
function ghApi(method, path, token) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const req = https.request(
|
|
20
|
+
{
|
|
21
|
+
hostname: "api.github.com",
|
|
22
|
+
path,
|
|
23
|
+
method,
|
|
24
|
+
headers: {
|
|
25
|
+
Authorization: `Bearer ${token}`,
|
|
26
|
+
Accept: "application/vnd.github+json",
|
|
27
|
+
"User-Agent": "create-egregore",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
(res) => {
|
|
31
|
+
let buf = "";
|
|
32
|
+
res.on("data", (c) => (buf += c));
|
|
33
|
+
res.on("end", () => {
|
|
34
|
+
try {
|
|
35
|
+
resolve({ status: res.statusCode, data: JSON.parse(buf) });
|
|
36
|
+
} catch {
|
|
37
|
+
resolve({ status: res.statusCode, data: buf });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
);
|
|
42
|
+
req.on("error", reject);
|
|
43
|
+
req.end();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Accept pending GitHub repo collaboration invitations for the given org.
|
|
49
|
+
*/
|
|
50
|
+
async function acceptPendingInvitations(githubToken, githubOrg, ui) {
|
|
51
|
+
try {
|
|
52
|
+
const { status, data } = await ghApi("GET", "/user/repository_invitations", githubToken);
|
|
53
|
+
if (status !== 200 || !Array.isArray(data)) return;
|
|
54
|
+
|
|
55
|
+
const matching = data.filter(
|
|
56
|
+
(inv) => inv.repository?.owner?.login?.toLowerCase() === githubOrg.toLowerCase()
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
for (const inv of matching) {
|
|
60
|
+
const patchResult = await ghApi("PATCH", `/user/repository_invitations/${inv.id}`, githubToken);
|
|
61
|
+
if (patchResult.status < 300) {
|
|
62
|
+
ui.success(`Accepted invite to ${inv.repository.full_name}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Non-fatal — user may need to accept manually
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
15
70
|
/**
|
|
16
71
|
* Full local setup from claimed token data.
|
|
17
72
|
*
|
|
@@ -20,7 +75,7 @@ function run(cmd, opts = {}) {
|
|
|
20
75
|
* @param {string} targetDir - where to install (default: cwd)
|
|
21
76
|
*/
|
|
22
77
|
async function install(data, ui, targetDir) {
|
|
23
|
-
const { fork_url, memory_url, github_token, org_name, github_org, slug, api_key, repos = [], telegram_group_link, github_username, github_name } = data;
|
|
78
|
+
const { fork_url, memory_url, github_token, org_name, github_org, slug, api_key, repos = [], telegram_group_link, github_username, github_name, transcript_sharing } = data;
|
|
24
79
|
const base = targetDir || process.cwd();
|
|
25
80
|
|
|
26
81
|
// Directory name from fork URL (what git clone would use), e.g. "egregore-core"
|
|
@@ -37,6 +92,9 @@ async function install(data, ui, targetDir) {
|
|
|
37
92
|
// Configure git credential helper for HTTPS cloning
|
|
38
93
|
configureGitCredentials(github_token);
|
|
39
94
|
|
|
95
|
+
// Accept pending repo collaboration invitations (joiners get invited as collaborators)
|
|
96
|
+
await acceptPendingInvitations(github_token, github_org, ui);
|
|
97
|
+
|
|
40
98
|
// Embed token in URLs as fallback for private repos (credential helper may not work)
|
|
41
99
|
const authedForkUrl = embedToken(fork_url, github_token);
|
|
42
100
|
const authedMemoryUrl = embedToken(memory_url, github_token);
|
|
@@ -108,6 +166,9 @@ async function install(data, ui, targetDir) {
|
|
|
108
166
|
state.github_name = github_name || github_username;
|
|
109
167
|
}
|
|
110
168
|
state.onboarding_complete = true;
|
|
169
|
+
if (transcript_sharing !== undefined) {
|
|
170
|
+
state.transcript_sharing = transcript_sharing;
|
|
171
|
+
}
|
|
111
172
|
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
|
|
112
173
|
|
|
113
174
|
// 5. Register instance + shell alias
|
|
@@ -117,20 +178,26 @@ async function install(data, ui, targetDir) {
|
|
|
117
178
|
|
|
118
179
|
// 6+. Clone managed repos (if any)
|
|
119
180
|
const clonedRepos = [];
|
|
181
|
+
const failedRepos = [];
|
|
120
182
|
for (let i = 0; i < repos.length; i++) {
|
|
121
183
|
const repoName = repos[i];
|
|
122
184
|
ui.step(6 + i, totalSteps, `Cloning ${repoName}...`);
|
|
123
185
|
const repoDir = path.join(base, repoName);
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
186
|
+
try {
|
|
187
|
+
if (fs.existsSync(repoDir)) {
|
|
188
|
+
ui.warn(`${repoName}/ already exists — pulling latest`);
|
|
189
|
+
run("git pull", { cwd: repoDir });
|
|
190
|
+
} else {
|
|
191
|
+
const repoUrl = `https://github.com/${github_org}/${repoName}.git`;
|
|
192
|
+
execFileSync("git", ["clone", embedToken(repoUrl, github_token), repoDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
|
|
193
|
+
try { run(`git remote set-url origin ${repoUrl}`, { cwd: repoDir }); } catch {}
|
|
194
|
+
}
|
|
195
|
+
clonedRepos.push(repoName);
|
|
196
|
+
ui.success(`Cloned ${repoName}`);
|
|
197
|
+
} catch {
|
|
198
|
+
failedRepos.push(repoName);
|
|
199
|
+
ui.warn(`Could not clone ${repoName} — you may not have access yet. Ask the org admin to grant access.`);
|
|
131
200
|
}
|
|
132
|
-
clonedRepos.push(repoName);
|
|
133
|
-
ui.success(`Cloned ${repoName}`);
|
|
134
201
|
}
|
|
135
202
|
|
|
136
203
|
// Done
|
|
@@ -250,11 +317,16 @@ async function installShellAlias(egregoreDir, ui) {
|
|
|
250
317
|
defaultName = slug ? `egregore-${slug}` : "egregore-2";
|
|
251
318
|
}
|
|
252
319
|
|
|
253
|
-
// Ask user
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
320
|
+
// Ask user (skip prompt in non-interactive mode)
|
|
321
|
+
let aliasName;
|
|
322
|
+
if (process.stdin.isTTY) {
|
|
323
|
+
console.log("");
|
|
324
|
+
ui.info(`This instance will be launched with a shell command.`);
|
|
325
|
+
const answer = await ui.prompt(`Command name (Enter for ${ui.bold(defaultName)}):`);
|
|
326
|
+
aliasName = answer || defaultName;
|
|
327
|
+
} else {
|
|
328
|
+
aliasName = defaultName;
|
|
329
|
+
}
|
|
258
330
|
|
|
259
331
|
// Remove old alias for this directory
|
|
260
332
|
let lines = profileContent.split("\n");
|