create-egregore 0.17.3 → 0.18.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 +1965 -0
- package/assets/egregore-launcher.sh +385 -0
- package/bin/cli.js +472 -81
- package/lib/api.js +3 -3
- package/lib/auth.js +255 -20
- package/lib/codex-runtime.js +268 -0
- package/lib/commands.js +31 -0
- package/lib/context.js +1 -1
- package/lib/local.js +618 -193
- package/lib/setup.js +837 -85
- package/lib/ui.js +780 -84
- package/package.json +17 -4
- package/runtime/codex/.codex/config.toml +15 -0
- package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
- package/runtime/codex/.codex/hooks.json +17 -0
- package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
- package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
- package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
- package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/comment/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
- package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
- package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/edit/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/edit-process/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
- package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
- package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
- package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
- package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
- package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
- package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
- package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
- package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
- package/runtime/codex/.codex/spec-manifest.json +108 -0
- package/runtime/codex/AGENTS.md +328 -0
- package/runtime/codex/bin/activity-data.sh +180 -0
- package/runtime/codex/bin/agent.sh +669 -0
- package/runtime/codex/bin/codex-install-skills.sh +78 -0
- package/runtime/codex/bin/codex-render-spec.mjs +308 -0
- package/runtime/codex/bin/codex-session-start.sh +238 -0
- package/runtime/codex/bin/codex-skill-render.mjs +315 -0
- package/runtime/codex/bin/codex-sync-skills.sh +223 -0
- package/runtime/codex/bin/dashboard-data.sh +178 -0
- package/runtime/codex/bin/graph-batch.sh +60 -0
- package/runtime/codex/bin/graph-op.sh +397 -0
- package/runtime/codex/bin/graph-wal.sh +228 -0
- package/runtime/codex/bin/graph.sh +141 -0
- package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
- package/runtime/codex/bin/handoff-run.sh +569 -0
- package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
- package/runtime/codex/bin/index-handoff.sh +306 -0
- package/runtime/codex/bin/lib/artifact-id.sh +46 -0
- package/runtime/codex/bin/lib/config.sh +71 -0
- package/runtime/codex/bin/lib/context.sh +511 -0
- package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
- package/runtime/codex/bin/lib/edit-loopback.mjs +652 -0
- package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
- package/runtime/codex/bin/lib/git-sync.sh +262 -0
- package/runtime/codex/bin/lib/greeting.sh +432 -0
- package/runtime/codex/bin/lib/hash.sh +13 -0
- package/runtime/codex/bin/lib/identity.sh +164 -0
- package/runtime/codex/bin/lib/permissions.sh +106 -0
- package/runtime/codex/bin/lib/time.sh +56 -0
- package/runtime/codex/bin/lib/worktree-links.sh +54 -0
- package/runtime/codex/bin/notify.sh +207 -0
- package/runtime/codex/bin/publish-artifact.sh +187 -0
- package/runtime/codex/bin/publish-references.sh +144 -0
- package/runtime/codex/bin/repo-state.sh +99 -0
- package/runtime/codex/bin/telemetry.sh +350 -0
- package/runtime/codex/bin/worktree-create.sh +115 -0
- package/runtime/codex/bin/worktree-remove.sh +50 -0
- package/runtime/codex/bin/worktree.sh +199 -0
- package/runtime/codex/manifest.json +162 -0
package/bin/cli.js
CHANGED
|
@@ -1,24 +1,184 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* create-egregore —
|
|
4
|
+
* create-egregore — set up Egregore for your team in one command.
|
|
5
|
+
*
|
|
6
|
+
* - Installs an `egregore` PATH binary instead of a shell alias
|
|
7
|
+
* - Auto-execs into the selected assistant (Claude Code or Codex) at the end
|
|
5
8
|
*
|
|
6
9
|
* Four modes:
|
|
7
|
-
* npx create-egregore --token st_xxxx
|
|
8
|
-
* npx create-egregore
|
|
9
|
-
* npx create-egregore
|
|
10
|
-
* npx create-egregore join <org> (local mode — join existing)
|
|
10
|
+
* npx -y create-egregore@latest --token st_xxxx (from website)
|
|
11
|
+
* npx -y create-egregore@latest (guided setup / launcher)
|
|
12
|
+
* npx -y create-egregore@latest join <org> (join existing)
|
|
11
13
|
*/
|
|
12
14
|
|
|
13
15
|
const fs = require("node:fs");
|
|
16
|
+
const os = require("node:os");
|
|
14
17
|
const path = require("node:path");
|
|
18
|
+
const { spawnSync } = require("node:child_process");
|
|
15
19
|
const ui = require("../lib/ui");
|
|
16
20
|
const { EgregoreAPI } = require("../lib/api");
|
|
17
|
-
const {
|
|
18
|
-
|
|
21
|
+
const {
|
|
22
|
+
canUseInteractiveBrowserFlow,
|
|
23
|
+
deviceFlow,
|
|
24
|
+
explainNoGhHeadless,
|
|
25
|
+
ghCliExists,
|
|
26
|
+
tryGhAuthOrLogin,
|
|
27
|
+
} = require("../lib/auth");
|
|
28
|
+
const {
|
|
29
|
+
hasCodexRuntimeSupport,
|
|
30
|
+
installCodexRuntime,
|
|
31
|
+
reportCodexRuntimeSummary,
|
|
32
|
+
} = require("../lib/codex-runtime");
|
|
33
|
+
const {
|
|
34
|
+
install,
|
|
35
|
+
ghApi,
|
|
36
|
+
registerInstance,
|
|
37
|
+
installLauncherBinary,
|
|
38
|
+
normalizeRuntime,
|
|
39
|
+
} = require("../lib/setup");
|
|
19
40
|
const { detectContext, isWritable, isSystemDir } = require("../lib/context");
|
|
20
41
|
|
|
21
42
|
const API_URL = process.env.EGREGORE_API_URL || "https://egregore-production-55f2.up.railway.app";
|
|
43
|
+
const INSTANCES_FILE = path.join(os.homedir(), ".egregore", "instances.json");
|
|
44
|
+
|
|
45
|
+
function readLocalInstances() {
|
|
46
|
+
if (!fs.existsSync(INSTANCES_FILE)) return [];
|
|
47
|
+
try {
|
|
48
|
+
let data = JSON.parse(fs.readFileSync(INSTANCES_FILE, "utf-8"));
|
|
49
|
+
// Accept legacy/alt wrapped format: { instances: [...] }
|
|
50
|
+
if (data && !Array.isArray(data) && Array.isArray(data.instances)) {
|
|
51
|
+
data = data.instances;
|
|
52
|
+
}
|
|
53
|
+
if (!Array.isArray(data)) return [];
|
|
54
|
+
// Filter out invalid + dedup by resolved path. Some registries accumulated
|
|
55
|
+
// duplicate entries from old installers that didn't dedup on write.
|
|
56
|
+
const seen = new Set();
|
|
57
|
+
const result = [];
|
|
58
|
+
for (const entry of data) {
|
|
59
|
+
if (!entry || !entry.path || !fs.existsSync(entry.path)) continue;
|
|
60
|
+
const key = path.resolve(entry.path);
|
|
61
|
+
if (seen.has(key)) continue;
|
|
62
|
+
seen.add(key);
|
|
63
|
+
result.push(entry);
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Detect whether `cwd` is itself an egregore install — even if cloned into a
|
|
72
|
+
// dir with a non-canonical name (e.g. `curve-labs-core` instead of `egregore`).
|
|
73
|
+
// Identified by structural artifacts (egregore.json + memory symlink), not by
|
|
74
|
+
// directory name. Returns null when cwd is not an egregore.
|
|
75
|
+
function detectCwdEgregore(cwd) {
|
|
76
|
+
const configPath = path.join(cwd, "egregore.json");
|
|
77
|
+
const memoryPath = path.join(cwd, "memory");
|
|
78
|
+
if (!fs.existsSync(configPath)) return null;
|
|
79
|
+
if (!fs.existsSync(memoryPath)) return null;
|
|
80
|
+
try {
|
|
81
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
82
|
+
return {
|
|
83
|
+
path: cwd,
|
|
84
|
+
slug: config.slug || path.basename(cwd),
|
|
85
|
+
name: config.org_name || path.basename(cwd),
|
|
86
|
+
adopt: true,
|
|
87
|
+
};
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function binaryOnPath(bin) {
|
|
94
|
+
return spawnSync("which", [bin], { stdio: "pipe" }).status === 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function assistantLabel(runtime) {
|
|
98
|
+
return normalizeRuntime(runtime) === "codex" ? "Codex" : "Claude Code";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function upgradeCodexRuntimeForExisting(instances) {
|
|
102
|
+
for (const inst of instances) {
|
|
103
|
+
if (!inst || !inst.path || !fs.existsSync(inst.path)) continue;
|
|
104
|
+
try {
|
|
105
|
+
const summary = installCodexRuntime(inst.path);
|
|
106
|
+
const ready = hasCodexRuntimeSupport(inst.path);
|
|
107
|
+
reportCodexRuntimeSummary(summary, ui, {
|
|
108
|
+
label: `Codex runtime for ${inst.slug || inst.name || path.basename(inst.path)}`,
|
|
109
|
+
includeConflicts: !ready,
|
|
110
|
+
});
|
|
111
|
+
} catch (err) {
|
|
112
|
+
ui.warn(`Could not install Codex runtime for ${inst.path}: ${err.message}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function chooseRuntime(args) {
|
|
118
|
+
if (args.runtime) {
|
|
119
|
+
try {
|
|
120
|
+
return normalizeRuntime(args.runtime);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
ui.error(err.message);
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!process.stdin.isTTY) return "claude-code";
|
|
128
|
+
|
|
129
|
+
const hasClaude = binaryOnPath("claude");
|
|
130
|
+
const hasCodex = binaryOnPath("codex");
|
|
131
|
+
const recommended = !hasClaude && hasCodex ? "codex" : "claude-code";
|
|
132
|
+
const choices = ["claude-code", "codex"]
|
|
133
|
+
.map((runtime) => {
|
|
134
|
+
const installed = runtime === "codex" ? hasCodex : hasClaude;
|
|
135
|
+
const label = assistantLabel(runtime);
|
|
136
|
+
const missingCopy = runtime === "codex"
|
|
137
|
+
? "Install Codex later, then run egregore and choose Codex"
|
|
138
|
+
: "Install Claude Code later, then run egregore and choose Claude Code";
|
|
139
|
+
return {
|
|
140
|
+
label: `${label}${installed ? "" : " (not installed)"}${recommended === runtime ? " (recommended)" : ""}`,
|
|
141
|
+
description: installed
|
|
142
|
+
? (runtime === "codex" ? "Installed · portable agent protocol" : "Installed · Claude Code integration")
|
|
143
|
+
: missingCopy,
|
|
144
|
+
runtime,
|
|
145
|
+
installed,
|
|
146
|
+
};
|
|
147
|
+
})
|
|
148
|
+
.sort((a, b) => Number(b.installed) - Number(a.installed));
|
|
149
|
+
const choice = await ui.choose("Which assistant do you want to use?", choices);
|
|
150
|
+
return choice.runtime;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function upgradeLauncherFromExisting(instances) {
|
|
154
|
+
upgradeCodexRuntimeForExisting(instances);
|
|
155
|
+
const inst = instances.find((entry) => entry && entry.path && fs.existsSync(entry.path));
|
|
156
|
+
if (!inst) return null;
|
|
157
|
+
try {
|
|
158
|
+
return await installLauncherBinary(inst.path, ui);
|
|
159
|
+
} catch {
|
|
160
|
+
// Non-fatal: opening the launcher can still try PATH, and setup retries later.
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function openLauncherBrowser(alias, runtimeOverride) {
|
|
166
|
+
const binary = (alias && alias.binaryPath) || "egregore";
|
|
167
|
+
const launcherArgs = [];
|
|
168
|
+
if (runtimeOverride) launcherArgs.push("--runtime", normalizeRuntime(runtimeOverride));
|
|
169
|
+
launcherArgs.push("browse");
|
|
170
|
+
|
|
171
|
+
const result = spawnSync(binary, launcherArgs, {
|
|
172
|
+
stdio: "inherit",
|
|
173
|
+
cwd: process.cwd(),
|
|
174
|
+
env: { ...process.env },
|
|
175
|
+
});
|
|
176
|
+
if (result.error) {
|
|
177
|
+
ui.warn(`Could not open the egregore launcher: ${result.error.message}`);
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
process.exit(result.signal ? 1 : (result.status ?? 0));
|
|
181
|
+
}
|
|
22
182
|
|
|
23
183
|
function parseArgs(argv) {
|
|
24
184
|
const args = {};
|
|
@@ -32,8 +192,22 @@ function parseArgs(argv) {
|
|
|
32
192
|
args.api = argv[++i];
|
|
33
193
|
} else if (argv[i] === "--open") {
|
|
34
194
|
args.open = true;
|
|
195
|
+
} else if (argv[i] === "--connected" || argv[i] === "--hosted") {
|
|
196
|
+
args.connected = true;
|
|
35
197
|
} else if (argv[i] === "--force") {
|
|
36
198
|
args.force = true;
|
|
199
|
+
} else if (argv[i] === "--no-autolaunch") {
|
|
200
|
+
args.noAutolaunch = true;
|
|
201
|
+
} else if (argv[i] === "--no-screens") {
|
|
202
|
+
args.noScreens = true;
|
|
203
|
+
} else if (argv[i] === "--codex") {
|
|
204
|
+
args.runtime = "codex";
|
|
205
|
+
} else if (argv[i] === "--claude" || argv[i] === "--claude-code") {
|
|
206
|
+
args.runtime = "claude-code";
|
|
207
|
+
} else if (argv[i] === "--runtime" && argv[i + 1]) {
|
|
208
|
+
args.runtime = argv[++i];
|
|
209
|
+
} else if (argv[i].startsWith("--runtime=")) {
|
|
210
|
+
args.runtime = argv[i].split("=")[1];
|
|
37
211
|
} else if (argv[i] === "--help" || argv[i] === "-h") {
|
|
38
212
|
args.help = true;
|
|
39
213
|
} else if (!argv[i].startsWith("-")) {
|
|
@@ -61,24 +235,40 @@ async function maybeRedirect(force) {
|
|
|
61
235
|
if (!ctx) return { targetDir: process.cwd(), prefilledRepo: null };
|
|
62
236
|
|
|
63
237
|
const { repoName, parentDir, originOwner, originName } = ctx;
|
|
238
|
+
const cwdEgregore = detectCwdEgregore(ctx.toplevel);
|
|
239
|
+
const isInsideEgregore = !!cwdEgregore;
|
|
64
240
|
|
|
65
241
|
// Non-TTY guard: we can't prompt. Fail loudly so CI/Docker/piped scripts
|
|
66
242
|
// don't hang forever on readline.
|
|
67
243
|
if (!process.stdin.isTTY) {
|
|
68
244
|
console.log("");
|
|
69
|
-
|
|
70
|
-
|
|
245
|
+
if (isInsideEgregore) {
|
|
246
|
+
ui.error(`Detected an Egregore checkout at ./${repoName} but stdin is not a TTY — cannot prompt.`);
|
|
247
|
+
ui.info(`Re-run from ${parentDir}, or pass --force to install here anyway.`);
|
|
248
|
+
} else {
|
|
249
|
+
ui.error(`Detected a git repo at ./${repoName} but stdin is not a TTY — cannot prompt.`);
|
|
250
|
+
ui.info("Re-run from outside the repo, or pass --force to install here anyway.");
|
|
251
|
+
}
|
|
71
252
|
process.exit(1);
|
|
72
253
|
}
|
|
73
254
|
|
|
74
255
|
const parentWritable = isWritable(parentDir);
|
|
75
256
|
const parentIsSystem = isSystemDir(parentDir);
|
|
257
|
+
const fallbackDir = path.join(os.homedir(), "egregores");
|
|
258
|
+
const recommendedDir = parentWritable && !parentIsSystem ? parentDir : fallbackDir;
|
|
76
259
|
|
|
77
260
|
console.log("");
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
261
|
+
if (isInsideEgregore) {
|
|
262
|
+
ui.warn(`You're inside an Egregore checkout: ${ui.bold(`./${repoName}`)}`);
|
|
263
|
+
console.log("");
|
|
264
|
+
ui.info("To set up another Egregore, put it next to this checkout.");
|
|
265
|
+
ui.info(`Recommended local path: ${ui.cyan(recommendedDir)}`);
|
|
266
|
+
} else {
|
|
267
|
+
ui.warn(`You're inside a git repo: ${ui.bold(`./${repoName}`)}`);
|
|
268
|
+
console.log("");
|
|
269
|
+
ui.info("Egregore lives as a sibling shell that manages your repos,");
|
|
270
|
+
ui.info("not inside them. Your repo stays where it is.");
|
|
271
|
+
}
|
|
82
272
|
|
|
83
273
|
if (parentIsSystem) {
|
|
84
274
|
console.log("");
|
|
@@ -86,8 +276,13 @@ async function maybeRedirect(force) {
|
|
|
86
276
|
}
|
|
87
277
|
|
|
88
278
|
const choices = [];
|
|
89
|
-
|
|
90
|
-
|
|
279
|
+
if (isInsideEgregore) {
|
|
280
|
+
choices.push({
|
|
281
|
+
label: "Use recommended local path",
|
|
282
|
+
description: recommendedDir,
|
|
283
|
+
action: "recommended",
|
|
284
|
+
});
|
|
285
|
+
} else if (parentWritable && !parentIsSystem) {
|
|
91
286
|
choices.push({
|
|
92
287
|
label: `Install in ${parentDir}`,
|
|
93
288
|
description: `Set up alongside ${repoName} and add it to managed repos (Recommended)`,
|
|
@@ -95,8 +290,8 @@ async function maybeRedirect(force) {
|
|
|
95
290
|
});
|
|
96
291
|
}
|
|
97
292
|
choices.push({
|
|
98
|
-
label: "
|
|
99
|
-
description: "
|
|
293
|
+
label: "Choose another folder",
|
|
294
|
+
description: "Paste or type a local folder path",
|
|
100
295
|
action: "pick",
|
|
101
296
|
});
|
|
102
297
|
choices.push({
|
|
@@ -113,7 +308,7 @@ async function maybeRedirect(force) {
|
|
|
113
308
|
process.exit(0);
|
|
114
309
|
}
|
|
115
310
|
|
|
116
|
-
let targetDir = parentDir;
|
|
311
|
+
let targetDir = choice.action === "recommended" ? recommendedDir : parentDir;
|
|
117
312
|
if (choice.action === "pick") {
|
|
118
313
|
const answer = await ui.prompt("Install directory (absolute path):");
|
|
119
314
|
if (!answer) {
|
|
@@ -133,10 +328,19 @@ async function maybeRedirect(force) {
|
|
|
133
328
|
process.exit(1);
|
|
134
329
|
}
|
|
135
330
|
}
|
|
331
|
+
if (choice.action === "recommended") {
|
|
332
|
+
try {
|
|
333
|
+
if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
|
|
334
|
+
fs.accessSync(targetDir, fs.constants.W_OK);
|
|
335
|
+
} catch (err) {
|
|
336
|
+
ui.error(`Cannot write to ${targetDir}: ${err.message}`);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
136
340
|
|
|
137
341
|
return {
|
|
138
342
|
targetDir,
|
|
139
|
-
prefilledRepo: {
|
|
343
|
+
prefilledRepo: isInsideEgregore ? null : {
|
|
140
344
|
name: originName || repoName,
|
|
141
345
|
owner: originOwner,
|
|
142
346
|
localName: repoName,
|
|
@@ -147,21 +351,56 @@ async function maybeRedirect(force) {
|
|
|
147
351
|
|
|
148
352
|
function showHelp() {
|
|
149
353
|
ui.banner();
|
|
150
|
-
ui.info("
|
|
151
|
-
ui.info(" npx create-egregore Interactive setup (API)");
|
|
152
|
-
ui.info(" npx create-egregore --open New project (local mode)");
|
|
153
|
-
ui.info(" npx create-egregore join <org>[/<repo>] Join existing project");
|
|
154
|
-
ui.info(" npx create-egregore --token <setup-token> Install from website");
|
|
354
|
+
ui.info("create-egregore — set up Egregore for your team in one command.");
|
|
155
355
|
ui.info("");
|
|
156
|
-
ui.info("
|
|
157
|
-
ui.info("
|
|
356
|
+
ui.info("Common:");
|
|
357
|
+
ui.info(" npx -y create-egregore@latest Create your Egregore (or open the launcher)");
|
|
358
|
+
ui.info(" npx -y create-egregore@latest join <org>[/<repo>] Join existing project");
|
|
359
|
+
ui.info(" npx -y create-egregore@latest --token <setup-token> Install from website");
|
|
360
|
+
ui.info("");
|
|
361
|
+
ui.info("Advanced options:");
|
|
362
|
+
ui.info(" --open Local founder mode (now the default; flag kept for back-compat)");
|
|
363
|
+
ui.info(" --connected Use the hosted egregore.xyz setup flow instead of local");
|
|
158
364
|
ui.info(" --token <token> Setup token from egregore.xyz");
|
|
159
|
-
ui.info(" --api <url> API URL
|
|
365
|
+
ui.info(" --api <url> Use connected API setup with this URL");
|
|
366
|
+
ui.info(" --codex Prefer Codex for this local install");
|
|
367
|
+
ui.info(" --claude Prefer Claude Code for this local install");
|
|
368
|
+
ui.info(" --runtime <name> Assistant override: claude-code or codex");
|
|
160
369
|
ui.info(" --force Skip the in-repo detection check");
|
|
370
|
+
ui.info(" --no-autolaunch Don't launch the assistant at the end (for CI)");
|
|
371
|
+
ui.info(" --no-screens Skip the full-screen welcome/ready bookends");
|
|
161
372
|
ui.info(" -h, --help Show this help");
|
|
162
373
|
ui.info("");
|
|
163
374
|
}
|
|
164
375
|
|
|
376
|
+
async function preflight() {
|
|
377
|
+
// Verify prerequisites before starting. Fast fail with specific fix hints
|
|
378
|
+
// beats discovering missing tools mid-install. Claude Code is checked
|
|
379
|
+
// separately at auto-exec time since users can install it after.
|
|
380
|
+
const { spawnSync } = require("node:child_process");
|
|
381
|
+
const checks = [
|
|
382
|
+
{ bin: "git", hint: "install Xcode command-line tools or https://git-scm.com" },
|
|
383
|
+
{ bin: "jq", hint: "brew install jq (or https://stedolan.github.io/jq/)" },
|
|
384
|
+
];
|
|
385
|
+
const nodeMajor = parseInt(process.versions.node.split(".")[0], 10);
|
|
386
|
+
const missing = [];
|
|
387
|
+
if (nodeMajor < 18) {
|
|
388
|
+
missing.push({ bin: "node >=18", hint: `you're on Node ${process.versions.node}; upgrade at https://nodejs.org` });
|
|
389
|
+
}
|
|
390
|
+
for (const c of checks) {
|
|
391
|
+
const r = spawnSync("which", [c.bin], { stdio: "pipe" });
|
|
392
|
+
if (r.status !== 0) missing.push(c);
|
|
393
|
+
}
|
|
394
|
+
if (missing.length === 0) return;
|
|
395
|
+
|
|
396
|
+
ui.warn("missing prerequisites:");
|
|
397
|
+
for (const m of missing) {
|
|
398
|
+
ui.info(` ${ui.red("✗")} ${ui.bold(m.bin)} ${ui.dim("— " + m.hint)}`);
|
|
399
|
+
}
|
|
400
|
+
ui.info("");
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
|
|
165
404
|
async function main() {
|
|
166
405
|
const args = parseArgs(process.argv);
|
|
167
406
|
|
|
@@ -169,10 +408,69 @@ async function main() {
|
|
|
169
408
|
showHelp();
|
|
170
409
|
process.exit(0);
|
|
171
410
|
}
|
|
411
|
+
if (args.runtime) {
|
|
412
|
+
try {
|
|
413
|
+
args.runtime = normalizeRuntime(args.runtime);
|
|
414
|
+
} catch (err) {
|
|
415
|
+
ui.error(err.message);
|
|
416
|
+
process.exit(1);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
172
419
|
|
|
173
|
-
|
|
420
|
+
// Local-first product surface: if the user already has egregores installed
|
|
421
|
+
// and didn't pass an explicit mode flag, refresh the launcher and open the
|
|
422
|
+
// same TUI as bare `egregore`. With no installed instances, make bare
|
|
423
|
+
// `npx -y create-egregore@latest` enter the guided local founder setup (`--open`).
|
|
424
|
+
//
|
|
425
|
+
// We also surface the cwd as a launchable when it looks like an egregore
|
|
426
|
+
// install (egregore.json + memory symlink present) but isn't in the registry
|
|
427
|
+
// — common when the egregore was cloned into a non-canonical directory name
|
|
428
|
+
// and so its path was never recorded.
|
|
429
|
+
const noInstallFlags = !args.token && !args.open && !args.connected && !args.subcommand && !args.force && !args.api;
|
|
430
|
+
if (noInstallFlags && process.stdin.isTTY) {
|
|
431
|
+
const registered = readLocalInstances();
|
|
432
|
+
const cwdInstall = detectCwdEgregore(process.cwd());
|
|
433
|
+
const alreadyRegistered =
|
|
434
|
+
cwdInstall && registered.some((i) => path.resolve(i.path) === path.resolve(cwdInstall.path));
|
|
435
|
+
const instances =
|
|
436
|
+
cwdInstall && !alreadyRegistered ? [cwdInstall, ...registered] : registered;
|
|
437
|
+
if (instances.length > 0) {
|
|
438
|
+
if (cwdInstall && !alreadyRegistered) {
|
|
439
|
+
try {
|
|
440
|
+
registerInstance(cwdInstall.slug, cwdInstall.name, cwdInstall.path, args.runtime || "claude-code");
|
|
441
|
+
} catch {}
|
|
442
|
+
}
|
|
443
|
+
const alias = await upgradeLauncherFromExisting(instances);
|
|
444
|
+
if (openLauncherBrowser(alias, args.runtime)) return;
|
|
445
|
+
ui.warn("Falling back to guided local setup.");
|
|
446
|
+
args.open = true;
|
|
447
|
+
}
|
|
448
|
+
if (instances.length === 0) {
|
|
449
|
+
args.open = true;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Bookend: full-screen welcome (alt-screen) or inline banner. Only one
|
|
454
|
+
// or the other — don't double up. `--no-screens` and non-TTY fall back
|
|
455
|
+
// to the inline banner.
|
|
456
|
+
const useScreens = !args.noScreens && ui.canShowScreens();
|
|
457
|
+
if (useScreens) {
|
|
458
|
+
const mode = args.subcommand === "join" ? "join"
|
|
459
|
+
: args.token ? "token"
|
|
460
|
+
: args.open ? "founder"
|
|
461
|
+
: "founder";
|
|
462
|
+
await ui.welcomeScreen({ mode });
|
|
463
|
+
} else {
|
|
464
|
+
ui.banner();
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
await preflight();
|
|
174
468
|
|
|
175
469
|
const ctx = await maybeRedirect(args.force);
|
|
470
|
+
// Thread install options through ctx so downstream flows can use them.
|
|
471
|
+
ctx.noAutolaunch = !!args.noAutolaunch;
|
|
472
|
+
ctx.noScreens = !useScreens;
|
|
473
|
+
ctx.runtime = await chooseRuntime(args);
|
|
176
474
|
|
|
177
475
|
if (args.token) {
|
|
178
476
|
// ===== Connected mode: token provided =====
|
|
@@ -181,19 +479,20 @@ async function main() {
|
|
|
181
479
|
} else if (args.subcommand === "join") {
|
|
182
480
|
// ===== Local mode: join existing =====
|
|
183
481
|
if (!args.joinOrg) {
|
|
184
|
-
ui.error("Usage: npx create-egregore join <github-org>");
|
|
482
|
+
ui.error("Usage: npx -y create-egregore@latest join <github-org>");
|
|
185
483
|
process.exit(1);
|
|
186
484
|
}
|
|
187
485
|
const { localJoinFlow } = require("../lib/local");
|
|
188
486
|
await localJoinFlow(args.joinOrg, ui, ctx);
|
|
189
|
-
} else if (args.
|
|
190
|
-
// =====
|
|
191
|
-
const { localFounderFlow } = require("../lib/local");
|
|
192
|
-
await localFounderFlow(ui, ctx);
|
|
193
|
-
} else {
|
|
194
|
-
// ===== Interactive API flow (default — existing behavior) =====
|
|
487
|
+
} else if (args.connected) {
|
|
488
|
+
// ===== Connected/hosted interactive flow (explicit opt-in) =====
|
|
195
489
|
const api = new EgregoreAPI(args.api || API_URL);
|
|
196
490
|
await interactiveFlow(api, ctx);
|
|
491
|
+
} else {
|
|
492
|
+
// ===== Local founder is the DEFAULT — bare `npx create-egregore` and
|
|
493
|
+
// --open both land here. Hosted is opt-in via --connected/--token.
|
|
494
|
+
const { localFounderFlow } = require("../lib/local");
|
|
495
|
+
await localFounderFlow(ui, ctx);
|
|
197
496
|
}
|
|
198
497
|
}
|
|
199
498
|
|
|
@@ -209,19 +508,34 @@ async function tokenFlow(api, token, ctx) {
|
|
|
209
508
|
ui.info("");
|
|
210
509
|
ui.info("The token may have expired or already been used.");
|
|
211
510
|
ui.info("Visit egregore.xyz to get a new one, or run without --token:");
|
|
212
|
-
ui.info(" npx create-egregore");
|
|
511
|
+
ui.info(" npx -y create-egregore@latest");
|
|
213
512
|
process.exit(1);
|
|
214
513
|
}
|
|
215
514
|
|
|
216
|
-
// Joiner tokens don't include a github_token —
|
|
515
|
+
// Joiner tokens don't include a github_token — try gh CLI first. If gh is
|
|
516
|
+
// installed but logged out, run gh auth login instead of device flow.
|
|
217
517
|
if (!data.github_token || data.needs_cli_auth) {
|
|
218
|
-
ui.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
ui.error(
|
|
518
|
+
const ghAuth = tryGhAuthOrLogin(ui, data.needs_cli_auth ? { mode: "join" } : {});
|
|
519
|
+
if (ghAuth) {
|
|
520
|
+
ui.success(`Using your ${ui.bold("gh")} CLI auth${ghAuth.user ? ` (${ghAuth.user})` : ""}`);
|
|
521
|
+
data.github_token = ghAuth.token;
|
|
522
|
+
} else if (ghCliExists()) {
|
|
523
|
+
ui.error("GitHub CLI auth did not complete.");
|
|
524
|
+
ui.info(`Run ${ui.bold("gh auth login")} and then re-run this command.`);
|
|
224
525
|
process.exit(1);
|
|
526
|
+
} else {
|
|
527
|
+
if (!canUseInteractiveBrowserFlow()) {
|
|
528
|
+
explainNoGhHeadless(ui);
|
|
529
|
+
process.exit(1);
|
|
530
|
+
}
|
|
531
|
+
ui.info("\nSign in to your GitHub to complete setup.\n");
|
|
532
|
+
try {
|
|
533
|
+
data.github_token = await deviceFlow(ui, data.needs_cli_auth ? { mode: "join" } : {});
|
|
534
|
+
ui.success("Authenticated with GitHub");
|
|
535
|
+
} catch (err) {
|
|
536
|
+
ui.error(`GitHub auth failed: ${err.message}`);
|
|
537
|
+
process.exit(1);
|
|
538
|
+
}
|
|
225
539
|
}
|
|
226
540
|
}
|
|
227
541
|
|
|
@@ -236,7 +550,13 @@ async function tokenFlow(api, token, ctx) {
|
|
|
236
550
|
}
|
|
237
551
|
}
|
|
238
552
|
try {
|
|
239
|
-
await install(data, ui, ctx.targetDir, {
|
|
553
|
+
await install(data, ui, ctx.targetDir, {
|
|
554
|
+
skipRepos,
|
|
555
|
+
noAutolaunch: ctx.noAutolaunch,
|
|
556
|
+
noScreens: ctx.noScreens,
|
|
557
|
+
flowVariant: ctx.flowVariant,
|
|
558
|
+
runtime: ctx.runtime,
|
|
559
|
+
});
|
|
240
560
|
if (ctx.prefilledRepo && skipRepos.size === 0) {
|
|
241
561
|
console.log("");
|
|
242
562
|
ui.info(`Tip: add your current repo to managed_repos with:`);
|
|
@@ -249,15 +569,30 @@ async function tokenFlow(api, token, ctx) {
|
|
|
249
569
|
}
|
|
250
570
|
|
|
251
571
|
async function interactiveFlow(api, ctx) {
|
|
252
|
-
// 1. GitHub auth
|
|
253
|
-
ui.info("Let's set up Egregore. First, sign in with GitHub.\n");
|
|
572
|
+
// 1. GitHub auth — gh CLI if available, else device flow.
|
|
254
573
|
let githubToken;
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
ui.success("
|
|
258
|
-
|
|
259
|
-
|
|
574
|
+
const ghAuth = tryGhAuthOrLogin(ui);
|
|
575
|
+
if (ghAuth) {
|
|
576
|
+
ui.success(`Using your ${ui.bold("gh")} CLI auth${ghAuth.user ? ` (${ghAuth.user})` : ""}`);
|
|
577
|
+
githubToken = ghAuth.token;
|
|
578
|
+
} else if (ghCliExists()) {
|
|
579
|
+
ui.error("GitHub CLI auth did not complete.");
|
|
580
|
+
ui.info(`Run ${ui.bold("gh auth login")} and then re-run this command.`);
|
|
260
581
|
process.exit(1);
|
|
582
|
+
} else {
|
|
583
|
+
if (!canUseInteractiveBrowserFlow()) {
|
|
584
|
+
explainNoGhHeadless(ui);
|
|
585
|
+
process.exit(1);
|
|
586
|
+
}
|
|
587
|
+
ui.info("Let's set up Egregore in your own account. First, sign in to your GitHub.\n");
|
|
588
|
+
try {
|
|
589
|
+
// Connected mode enumerates orgs server-side, so it needs read:org here.
|
|
590
|
+
githubToken = await deviceFlow(ui, { scope: "repo read:org" });
|
|
591
|
+
ui.success("Authenticated with GitHub");
|
|
592
|
+
} catch (err) {
|
|
593
|
+
ui.error(`GitHub auth failed: ${err.message}`);
|
|
594
|
+
process.exit(1);
|
|
595
|
+
}
|
|
261
596
|
}
|
|
262
597
|
|
|
263
598
|
// 2. Detect orgs
|
|
@@ -280,9 +615,13 @@ async function interactiveFlow(api, ctx) {
|
|
|
280
615
|
for (const org of orgsData.orgs) {
|
|
281
616
|
if (org.has_egregore) {
|
|
282
617
|
for (const inst of org.instances || []) {
|
|
618
|
+
const desc =
|
|
619
|
+
inst.org_name && inst.org_name !== inst.repo_name
|
|
620
|
+
? `Join existing · ${inst.org_name}`
|
|
621
|
+
: "Join existing";
|
|
283
622
|
choices.push({
|
|
284
|
-
label: `${org.name || org.login} — ${inst.
|
|
285
|
-
description:
|
|
623
|
+
label: `${org.name || org.login} — ${inst.repo_name}`,
|
|
624
|
+
description: desc,
|
|
286
625
|
action: "join",
|
|
287
626
|
login: org.login,
|
|
288
627
|
repo_name: inst.repo_name,
|
|
@@ -312,9 +651,13 @@ async function interactiveFlow(api, ctx) {
|
|
|
312
651
|
// Personal account — show join for each existing instance + setup for new
|
|
313
652
|
if (orgsData.personal.has_egregore) {
|
|
314
653
|
for (const inst of orgsData.personal.instances || []) {
|
|
654
|
+
const desc =
|
|
655
|
+
inst.org_name && inst.org_name !== inst.repo_name
|
|
656
|
+
? `Join existing · ${inst.org_name}`
|
|
657
|
+
: "Join existing";
|
|
315
658
|
choices.push({
|
|
316
|
-
label: `${orgsData.user.login} (personal) — ${inst.
|
|
317
|
-
description:
|
|
659
|
+
label: `${orgsData.user.login} (personal) — ${inst.repo_name}`,
|
|
660
|
+
description: desc,
|
|
318
661
|
action: "join",
|
|
319
662
|
login: orgsData.user.login,
|
|
320
663
|
repo_name: inst.repo_name,
|
|
@@ -394,32 +737,68 @@ async function setupFlow(api, githubToken, choice, ctx) {
|
|
|
394
737
|
selectedRepos.unshift(autoAdopt);
|
|
395
738
|
}
|
|
396
739
|
|
|
397
|
-
// Offer to create a new project repo
|
|
740
|
+
// Offer to create a new project repo. Pre-validate against the predicted
|
|
741
|
+
// egregore + memory repo names so the user can't accidentally create
|
|
742
|
+
// (or worse, "add an existing" via 422-fallback) a repo that collides
|
|
743
|
+
// with their Egregore instance.
|
|
744
|
+
const slugifyRepo = (s) => s.trim().toLowerCase()
|
|
745
|
+
.replace(/[^a-z0-9-]/g, "-")
|
|
746
|
+
.replace(/-+/g, "-")
|
|
747
|
+
.replace(/^-|-$/g, "");
|
|
748
|
+
const predictedEgregoreRepo = instanceName
|
|
749
|
+
? `egregore-${slugifyRepo(instanceName)}`
|
|
750
|
+
: "egregore";
|
|
751
|
+
const predictedMemoryRepo = instanceName
|
|
752
|
+
? `${slugifyRepo(instanceName)}-memory`
|
|
753
|
+
: `${slugifyRepo(choice.login)}-memory`;
|
|
754
|
+
|
|
398
755
|
console.log("");
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const
|
|
402
|
-
if (
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
756
|
+
let projectRepoSlug = null;
|
|
757
|
+
while (true) {
|
|
758
|
+
const newRepoName = await ui.prompt("Create a new project repo? Enter name (or press Enter to skip):");
|
|
759
|
+
if (!newRepoName) break;
|
|
760
|
+
const repoSlug = slugifyRepo(newRepoName);
|
|
761
|
+
if (!repoSlug) {
|
|
762
|
+
ui.warn("That doesn't produce a valid repo name. Try letters, numbers, and dashes.");
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
if (repoSlug === predictedEgregoreRepo) {
|
|
766
|
+
ui.warn(`"${repoSlug}" collides with your Egregore repo. Pick a different name.`);
|
|
767
|
+
ui.info(` ${ui.dim("suggestion:")} ${ui.cyan(`${repoSlug}-app`)}`);
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
if (repoSlug === predictedMemoryRepo) {
|
|
771
|
+
ui.warn(`"${repoSlug}" collides with your memory repo. Pick a different name.`);
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
projectRepoSlug = repoSlug;
|
|
775
|
+
break;
|
|
776
|
+
}
|
|
777
|
+
if (projectRepoSlug) {
|
|
778
|
+
const s2 = ui.spinner(`Creating ${projectRepoSlug}...`);
|
|
779
|
+
try {
|
|
780
|
+
const apiPath = choice.is_personal ? "/user/repos" : `/orgs/${choice.login}/repos`;
|
|
781
|
+
const { status } = await ghApi("POST", apiPath, githubToken, {
|
|
782
|
+
name: projectRepoSlug,
|
|
783
|
+
private: true,
|
|
784
|
+
auto_init: true,
|
|
785
|
+
});
|
|
786
|
+
if (status === 201 || status === 200) {
|
|
787
|
+
selectedRepos.push(projectRepoSlug);
|
|
788
|
+
s2.stop(`Created ${projectRepoSlug}`);
|
|
789
|
+
} else if (status === 422) {
|
|
790
|
+
// Repo already exists. We pre-validated against egregore + memory
|
|
791
|
+
// names, so this is a NEW conflict (some other repo of the user's
|
|
792
|
+
// already has this name). Surface and add as managed — same UX as
|
|
793
|
+
// before but with the accidental-collision-on-egregore-name path
|
|
794
|
+
// already filtered out.
|
|
795
|
+
selectedRepos.push(projectRepoSlug);
|
|
796
|
+
s2.stop(`${projectRepoSlug} already exists — added to managed repos`);
|
|
797
|
+
} else {
|
|
798
|
+
s2.fail(`Could not create ${projectRepoSlug}`);
|
|
422
799
|
}
|
|
800
|
+
} catch (err) {
|
|
801
|
+
s2.fail(`Could not create ${projectRepoSlug}: ${err.message}`);
|
|
423
802
|
}
|
|
424
803
|
}
|
|
425
804
|
|
|
@@ -446,7 +825,13 @@ async function setupFlow(api, githubToken, choice, ctx) {
|
|
|
446
825
|
// Claim the token immediately
|
|
447
826
|
const data = await api.claimToken(result.setup_token);
|
|
448
827
|
const skipRepos = autoAdopt ? new Set([autoAdopt]) : new Set();
|
|
449
|
-
await install(data, ui, ctx.targetDir, {
|
|
828
|
+
await install(data, ui, ctx.targetDir, {
|
|
829
|
+
skipRepos,
|
|
830
|
+
noAutolaunch: ctx.noAutolaunch,
|
|
831
|
+
noScreens: ctx.noScreens,
|
|
832
|
+
flowVariant: ctx.flowVariant,
|
|
833
|
+
runtime: ctx.runtime,
|
|
834
|
+
});
|
|
450
835
|
|
|
451
836
|
if (result.telegram_invite_link) {
|
|
452
837
|
console.log("");
|
|
@@ -477,7 +862,13 @@ async function joinFlow(api, githubToken, choice, ctx) {
|
|
|
477
862
|
if (n && n === ctx.prefilledRepo.name) skipRepos.add(n);
|
|
478
863
|
}
|
|
479
864
|
}
|
|
480
|
-
await install(data, ui, ctx.targetDir, {
|
|
865
|
+
await install(data, ui, ctx.targetDir, {
|
|
866
|
+
skipRepos,
|
|
867
|
+
noAutolaunch: ctx.noAutolaunch,
|
|
868
|
+
noScreens: ctx.noScreens,
|
|
869
|
+
flowVariant: ctx.flowVariant,
|
|
870
|
+
runtime: ctx.runtime,
|
|
871
|
+
});
|
|
481
872
|
} catch (err) {
|
|
482
873
|
s.fail("Join failed");
|
|
483
874
|
ui.error(err.message);
|