create-egregore 0.3.11 → 0.3.13

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);
@@ -107,7 +165,14 @@ async function install(data, ui, targetDir) {
107
165
  state.github_username = github_username;
108
166
  state.github_name = github_name || github_username;
109
167
  }
110
- state.onboarding_complete = true;
168
+ state.onboarding_complete = false;
169
+ state.usage_type = "joiner_group";
170
+ state.org_setup = true;
171
+ state.github_configured = true;
172
+ state.workspace_ready = true;
173
+ if (transcript_sharing !== undefined) {
174
+ state.transcript_sharing = transcript_sharing;
175
+ }
111
176
  fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + "\n");
112
177
 
113
178
  // 5. Register instance + shell alias
@@ -117,20 +182,26 @@ async function install(data, ui, targetDir) {
117
182
 
118
183
  // 6+. Clone managed repos (if any)
119
184
  const clonedRepos = [];
185
+ const failedRepos = [];
120
186
  for (let i = 0; i < repos.length; i++) {
121
187
  const repoName = repos[i];
122
188
  ui.step(6 + i, totalSteps, `Cloning ${repoName}...`);
123
189
  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 {}
190
+ try {
191
+ if (fs.existsSync(repoDir)) {
192
+ ui.warn(`${repoName}/ already exists pulling latest`);
193
+ run("git pull", { cwd: repoDir });
194
+ } else {
195
+ const repoUrl = `https://github.com/${github_org}/${repoName}.git`;
196
+ execFileSync("git", ["clone", embedToken(repoUrl, github_token), repoDir], { stdio: "pipe", encoding: "utf-8", timeout: 60000 });
197
+ try { run(`git remote set-url origin ${repoUrl}`, { cwd: repoDir }); } catch {}
198
+ }
199
+ clonedRepos.push(repoName);
200
+ ui.success(`Cloned ${repoName}`);
201
+ } catch {
202
+ failedRepos.push(repoName);
203
+ ui.warn(`Could not clone ${repoName} — you may not have access yet. Ask the org admin to grant access.`);
131
204
  }
132
- clonedRepos.push(repoName);
133
- ui.success(`Cloned ${repoName}`);
134
205
  }
135
206
 
136
207
  // Done
@@ -250,11 +321,16 @@ async function installShellAlias(egregoreDir, ui) {
250
321
  defaultName = slug ? `egregore-${slug}` : "egregore-2";
251
322
  }
252
323
 
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;
324
+ // Ask user (skip prompt in non-interactive mode)
325
+ let aliasName;
326
+ if (process.stdin.isTTY) {
327
+ console.log("");
328
+ ui.info(`This instance will be launched with a shell command.`);
329
+ const answer = await ui.prompt(`Command name (Enter for ${ui.bold(defaultName)}):`);
330
+ aliasName = answer || defaultName;
331
+ } else {
332
+ aliasName = defaultName;
333
+ }
258
334
 
259
335
  // Remove old alias for this directory
260
336
  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.13",
4
4
  "description": "Set up Egregore for your team in one command",
5
5
  "license": "MIT",
6
6
  "bin": {