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 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
@@ -6,7 +6,7 @@
6
6
  const https = require("node:https");
7
7
 
8
8
  const CLIENT_ID = "Ov23lizB4nYEeIRsHTdb";
9
- const SCOPE = "repo,admin:org";
9
+ const SCOPE = "repo,read:org";
10
10
 
11
11
  function post(url, body) {
12
12
  return new Promise((resolve, reject) => {
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
- if (fs.existsSync(repoDir)) {
125
- ui.warn(`${repoName}/ already exists — pulling latest`);
126
- run("git pull", { cwd: repoDir });
127
- } else {
128
- const repoUrl = `https://github.com/${github_org}/${repoName}.git`;
129
- execFileSync("git", ["clone", embedToken(repoUrl, github_token), repoDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
130
- try { run(`git remote set-url origin ${repoUrl}`, { cwd: repoDir }); } catch {}
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
- console.log("");
255
- ui.info(`This instance will be launched with a shell command.`);
256
- const answer = await ui.prompt(`Command name (Enter for ${ui.bold(defaultName)}):`);
257
- const aliasName = answer || defaultName;
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");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-egregore",
3
- "version": "0.3.11",
3
+ "version": "0.3.12",
4
4
  "description": "Set up Egregore for your team in one command",
5
5
  "license": "MIT",
6
6
  "bin": {