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.
Files changed (110) hide show
  1. package/assets/egregore-launcher.js +1965 -0
  2. package/assets/egregore-launcher.sh +385 -0
  3. package/bin/cli.js +472 -81
  4. package/lib/api.js +3 -3
  5. package/lib/auth.js +255 -20
  6. package/lib/codex-runtime.js +268 -0
  7. package/lib/commands.js +31 -0
  8. package/lib/context.js +1 -1
  9. package/lib/local.js +618 -193
  10. package/lib/setup.js +837 -85
  11. package/lib/ui.js +780 -84
  12. package/package.json +17 -4
  13. package/runtime/codex/.codex/config.toml +15 -0
  14. package/runtime/codex/.codex/hooks/branch-guard.js +234 -0
  15. package/runtime/codex/.codex/hooks.json +17 -0
  16. package/runtime/codex/.codex/skills/activity/SKILL.md +70 -0
  17. package/runtime/codex/.codex/skills/add/SKILL.md +25 -0
  18. package/runtime/codex/.codex/skills/announce/SKILL.md +54 -0
  19. package/runtime/codex/.codex/skills/answer/SKILL.md +25 -0
  20. package/runtime/codex/.codex/skills/archive/SKILL.md +41 -0
  21. package/runtime/codex/.codex/skills/ask/SKILL.md +59 -0
  22. package/runtime/codex/.codex/skills/branch/SKILL.md +25 -0
  23. package/runtime/codex/.codex/skills/checkup/SKILL.md +41 -0
  24. package/runtime/codex/.codex/skills/comment/SKILL.md +25 -0
  25. package/runtime/codex/.codex/skills/commit/SKILL.md +25 -0
  26. package/runtime/codex/.codex/skills/contribute/SKILL.md +25 -0
  27. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +25 -0
  28. package/runtime/codex/.codex/skills/dashboard/SKILL.md +59 -0
  29. package/runtime/codex/.codex/skills/deep-reflect/SKILL.md +70 -0
  30. package/runtime/codex/.codex/skills/delete-user/SKILL.md +25 -0
  31. package/runtime/codex/.codex/skills/edit/SKILL.md +25 -0
  32. package/runtime/codex/.codex/skills/edit-process/SKILL.md +25 -0
  33. package/runtime/codex/.codex/skills/egregore-voice/SKILL.md +25 -0
  34. package/runtime/codex/.codex/skills/emissary/SKILL.md +41 -0
  35. package/runtime/codex/.codex/skills/env/SKILL.md +25 -0
  36. package/runtime/codex/.codex/skills/handoff/SKILL.md +67 -0
  37. package/runtime/codex/.codex/skills/harvest/SKILL.md +107 -0
  38. package/runtime/codex/.codex/skills/infra/SKILL.md +41 -0
  39. package/runtime/codex/.codex/skills/ingest/SKILL.md +25 -0
  40. package/runtime/codex/.codex/skills/ingest-google/SKILL.md +25 -0
  41. package/runtime/codex/.codex/skills/ingest-user-interview/SKILL.md +41 -0
  42. package/runtime/codex/.codex/skills/invite/SKILL.md +40 -0
  43. package/runtime/codex/.codex/skills/issue/SKILL.md +41 -0
  44. package/runtime/codex/.codex/skills/me/SKILL.md +25 -0
  45. package/runtime/codex/.codex/skills/meeting/SKILL.md +41 -0
  46. package/runtime/codex/.codex/skills/note/SKILL.md +25 -0
  47. package/runtime/codex/.codex/skills/onboarding/SKILL.md +41 -0
  48. package/runtime/codex/.codex/skills/pr/SKILL.md +25 -0
  49. package/runtime/codex/.codex/skills/project/SKILL.md +41 -0
  50. package/runtime/codex/.codex/skills/pull/SKILL.md +25 -0
  51. package/runtime/codex/.codex/skills/push/SKILL.md +25 -0
  52. package/runtime/codex/.codex/skills/quest/SKILL.md +77 -0
  53. package/runtime/codex/.codex/skills/quest-suggest/SKILL.md +25 -0
  54. package/runtime/codex/.codex/skills/reflect/SKILL.md +41 -0
  55. package/runtime/codex/.codex/skills/review-pr/SKILL.md +41 -0
  56. package/runtime/codex/.codex/skills/save/SKILL.md +62 -0
  57. package/runtime/codex/.codex/skills/setup/SKILL.md +25 -0
  58. package/runtime/codex/.codex/skills/summon/SKILL.md +41 -0
  59. package/runtime/codex/.codex/skills/sync-repos/SKILL.md +25 -0
  60. package/runtime/codex/.codex/skills/telegram-connect/SKILL.md +25 -0
  61. package/runtime/codex/.codex/skills/telemetry/SKILL.md +25 -0
  62. package/runtime/codex/.codex/skills/test/SKILL.md +41 -0
  63. package/runtime/codex/.codex/skills/the-spiral/SKILL.md +84 -0
  64. package/runtime/codex/.codex/skills/todo/SKILL.md +41 -0
  65. package/runtime/codex/.codex/skills/tui-design/SKILL.md +41 -0
  66. package/runtime/codex/.codex/skills/tutorial/SKILL.md +41 -0
  67. package/runtime/codex/.codex/skills/update/SKILL.md +25 -0
  68. package/runtime/codex/.codex/skills/view/SKILL.md +41 -0
  69. package/runtime/codex/.codex/skills/visual-explain/SKILL.md +41 -0
  70. package/runtime/codex/.codex/skills/wrap/SKILL.md +55 -0
  71. package/runtime/codex/.codex/spec-manifest.json +108 -0
  72. package/runtime/codex/AGENTS.md +328 -0
  73. package/runtime/codex/bin/activity-data.sh +180 -0
  74. package/runtime/codex/bin/agent.sh +669 -0
  75. package/runtime/codex/bin/codex-install-skills.sh +78 -0
  76. package/runtime/codex/bin/codex-render-spec.mjs +308 -0
  77. package/runtime/codex/bin/codex-session-start.sh +238 -0
  78. package/runtime/codex/bin/codex-skill-render.mjs +315 -0
  79. package/runtime/codex/bin/codex-sync-skills.sh +223 -0
  80. package/runtime/codex/bin/dashboard-data.sh +178 -0
  81. package/runtime/codex/bin/graph-batch.sh +60 -0
  82. package/runtime/codex/bin/graph-op.sh +397 -0
  83. package/runtime/codex/bin/graph-wal.sh +228 -0
  84. package/runtime/codex/bin/graph.sh +141 -0
  85. package/runtime/codex/bin/handoff-pr-backfill.sh +86 -0
  86. package/runtime/codex/bin/handoff-run.sh +569 -0
  87. package/runtime/codex/bin/handoff-save-egregore.sh +131 -0
  88. package/runtime/codex/bin/index-handoff.sh +306 -0
  89. package/runtime/codex/bin/lib/artifact-id.sh +46 -0
  90. package/runtime/codex/bin/lib/config.sh +71 -0
  91. package/runtime/codex/bin/lib/context.sh +511 -0
  92. package/runtime/codex/bin/lib/dashboard-artifact.sh +142 -0
  93. package/runtime/codex/bin/lib/edit-loopback.mjs +652 -0
  94. package/runtime/codex/bin/lib/ensure-develop.sh +183 -0
  95. package/runtime/codex/bin/lib/git-sync.sh +262 -0
  96. package/runtime/codex/bin/lib/greeting.sh +432 -0
  97. package/runtime/codex/bin/lib/hash.sh +13 -0
  98. package/runtime/codex/bin/lib/identity.sh +164 -0
  99. package/runtime/codex/bin/lib/permissions.sh +106 -0
  100. package/runtime/codex/bin/lib/time.sh +56 -0
  101. package/runtime/codex/bin/lib/worktree-links.sh +54 -0
  102. package/runtime/codex/bin/notify.sh +207 -0
  103. package/runtime/codex/bin/publish-artifact.sh +187 -0
  104. package/runtime/codex/bin/publish-references.sh +144 -0
  105. package/runtime/codex/bin/repo-state.sh +99 -0
  106. package/runtime/codex/bin/telemetry.sh +350 -0
  107. package/runtime/codex/bin/worktree-create.sh +115 -0
  108. package/runtime/codex/bin/worktree-remove.sh +50 -0
  109. package/runtime/codex/bin/worktree.sh +199 -0
  110. package/runtime/codex/manifest.json +162 -0
package/lib/ui.js CHANGED
@@ -1,158 +1,854 @@
1
1
  /**
2
2
  * Terminal UI — colors, spinner, prompts, banner.
3
3
  * Zero dependencies. Node 18+ built-ins only.
4
+ *
5
+ * Respects NO_COLOR (https://no-color.org) and non-TTY output.
6
+ * All public surfaces preserved so existing callers in cli.js /
7
+ * local.js / setup.js continue working.
4
8
  */
5
9
 
6
10
  const readline = require("node:readline");
7
11
 
8
- const RESET = "\x1b[0m";
9
- const BOLD = "\x1b[1m";
10
- const DIM = "\x1b[2m";
11
- const GREEN = "\x1b[32m";
12
- const CYAN = "\x1b[36m";
13
- const YELLOW = "\x1b[33m";
14
- const RED = "\x1b[31m";
15
- const WHITE = "\x1b[37m";
12
+ // ── colors ──────────────────────────────────────────────────────────
13
+ //
14
+ // Detect once on module load; `process.stdout.isTTY` check keeps pipes
15
+ // and CI clean. Every escape sequence is an empty string when disabled.
16
16
 
17
- const BANNER = `
18
- ${BOLD}${CYAN} ███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗
19
- ██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██╔══██╗██╔════╝
20
- █████╗ ██║ ███╗██████╔╝█████╗ ██║ ███╗██║ ██║██████╔╝█████╗
21
- ██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██╗██╔══╝
22
- ███████╗╚██████╔╝██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║███████╗
23
- ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝${RESET}
24
- `;
17
+ const COLOR = process.stdout.isTTY && !process.env.NO_COLOR;
18
+ const esc = (code) => (COLOR ? `\x1b[${code}m` : "");
25
19
 
26
- function banner() {
27
- console.log(BANNER);
20
+ const RESET = esc(0);
21
+ const BOLD = esc(1);
22
+ const DIM = esc(2);
23
+ const ITALIC = esc(3);
24
+ const UNDER = esc(4);
25
+ const INVERT = esc(7);
26
+
27
+ const BLACK = esc(30);
28
+ const RED = esc(31);
29
+ const GREEN = esc(32);
30
+ const YELLOW = esc(33);
31
+ const BLUE = esc(34);
32
+ const MAGENTA = esc(35);
33
+ const CYAN = esc(36);
34
+ const WHITE = esc(37);
35
+ const GRAY = esc(90);
36
+
37
+ const BG_CYAN = esc(46);
38
+
39
+ // ── layout helpers ──────────────────────────────────────────────────
40
+
41
+ const termWidth = () => {
42
+ const w = process.stdout.columns || 80;
43
+ return Math.min(Math.max(w, 40), 100);
44
+ };
45
+
46
+ const pad = (s, width) => {
47
+ // Visible-length aware: strip escape sequences for width calc
48
+ const visible = s.replace(/\x1b\[[0-9;]*m/g, "");
49
+ return s + " ".repeat(Math.max(0, width - visible.length));
50
+ };
51
+
52
+ // ── banners ─────────────────────────────────────────────────────────
53
+
54
+ const BANNER_LINES = [
55
+ " ███████╗ ██████╗ ██████╗ ███████╗ ██████╗ ██████╗ ██████╗ ███████╗",
56
+ " ██╔════╝██╔════╝ ██╔══██╗██╔════╝██╔════╝ ██╔═══██╗██╔══██╗██╔════╝",
57
+ " █████╗ ██║ ███╗██████╔╝█████╗ ██║ ███╗██║ ██║██████╔╝█████╗",
58
+ " ██╔══╝ ██║ ██║██╔══██╗██╔══╝ ██║ ██║██║ ██║██╔══██╗██╔══╝",
59
+ " ███████╗╚██████╔╝██║ ██║███████╗╚██████╔╝╚██████╔╝██║ ██║███████╗",
60
+ " ╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝",
61
+ ];
62
+
63
+ const SIGIL_LINES = [
64
+ " ◆ ",
65
+ " ╱ ╲ ",
66
+ " ◆───◆ ",
67
+ " ╲ ╱ ",
68
+ " ◆ ",
69
+ ];
70
+
71
+ const COMMAND_GROUPS = [
72
+ { label: "core", cmds: "/reflect /handoff /quest /todo" },
73
+ { label: "share", cmds: "/save /push /branch /pr" },
74
+ { label: "knowledge", cmds: "/add /note /meeting /ingest" },
75
+ { label: "identity", cmds: "/me /invite /announce" },
76
+ ];
77
+
78
+ function banner(subtitle) {
79
+ process.stdout.write("\n");
80
+ for (const line of BANNER_LINES) {
81
+ process.stdout.write(`${BOLD}${CYAN}${line}${RESET}\n`);
82
+ }
83
+ if (subtitle) {
84
+ process.stdout.write(`\n ${DIM}${subtitle}${RESET}\n`);
85
+ }
86
+ process.stdout.write("\n");
28
87
  }
29
88
 
89
+ function bannerCompact(label) {
90
+ // One-line brand tag for subsequent screens after the big banner.
91
+ process.stdout.write(`\n ${BOLD}${CYAN}egregore${RESET}${DIM} · ${label || "setup"}${RESET}\n\n`);
92
+ }
93
+
94
+ // ── structural primitives ───────────────────────────────────────────
95
+
96
+ function section(title, subtitle) {
97
+ // A divider line + bold title. Use between major phases of a flow.
98
+ const w = termWidth() - 4;
99
+ process.stdout.write(`\n ${DIM}${"─".repeat(w)}${RESET}\n`);
100
+ process.stdout.write(` ${BOLD}${title}${RESET}`);
101
+ if (subtitle) {
102
+ process.stdout.write(` ${DIM}${subtitle}${RESET}`);
103
+ }
104
+ process.stdout.write("\n\n");
105
+ }
106
+
107
+ function note(lines) {
108
+ // A soft-highlighted multi-line note. Use for framing text, insights,
109
+ // explanations that deserve more weight than a plain info() but don't
110
+ // need a full box.
111
+ const arr = Array.isArray(lines) ? lines : [lines];
112
+ process.stdout.write("\n");
113
+ for (const line of arr) {
114
+ if (line === "") {
115
+ process.stdout.write(` ${CYAN}│${RESET}\n`);
116
+ } else {
117
+ process.stdout.write(` ${CYAN}│${RESET} ${line}\n`);
118
+ }
119
+ }
120
+ process.stdout.write("\n");
121
+ }
122
+
123
+ function card(title, body) {
124
+ // A bordered card for standout content (e.g. an invite command to share).
125
+ const bodyLines = Array.isArray(body) ? body : [body];
126
+ const contentWidth = Math.max(
127
+ title.length,
128
+ ...bodyLines.map((l) => l.replace(/\x1b\[[0-9;]*m/g, "").length),
129
+ );
130
+ const boxW = contentWidth + 4;
131
+
132
+ process.stdout.write("\n");
133
+ process.stdout.write(` ${DIM}┌─ ${title} ${"─".repeat(Math.max(0, boxW - title.length - 4))}┐${RESET}\n`);
134
+ process.stdout.write(` ${DIM}│${" ".repeat(boxW)}│${RESET}\n`);
135
+ for (const line of bodyLines) {
136
+ const visLen = line.replace(/\x1b\[[0-9;]*m/g, "").length;
137
+ const padded = line + " ".repeat(Math.max(0, boxW - visLen - 2));
138
+ process.stdout.write(` ${DIM}│${RESET} ${BOLD}${padded}${RESET}${DIM}│${RESET}\n`);
139
+ }
140
+ process.stdout.write(` ${DIM}│${" ".repeat(boxW)}│${RESET}\n`);
141
+ process.stdout.write(` ${DIM}└${"─".repeat(boxW)}┘${RESET}\n\n`);
142
+ }
143
+
144
+ // ── messages ────────────────────────────────────────────────────────
145
+
30
146
  function info(msg) {
31
- console.log(` ${msg}`);
147
+ process.stdout.write(` ${msg}\n`);
32
148
  }
33
149
 
34
150
  function success(msg) {
35
- console.log(` ${GREEN}✓${RESET} ${msg}`);
151
+ process.stdout.write(` ${GREEN}✓${RESET} ${msg}\n`);
36
152
  }
37
153
 
38
154
  function warn(msg) {
39
- console.log(` ${YELLOW}!${RESET} ${msg}`);
155
+ process.stdout.write(` ${YELLOW}!${RESET} ${msg}\n`);
40
156
  }
41
157
 
42
158
  function error(msg) {
43
- console.error(` ${RED}✗${RESET} ${msg}`);
159
+ process.stderr.write(` ${RED}✗${RESET} ${msg}\n`);
44
160
  }
45
161
 
46
162
  function step(n, total, msg) {
47
- console.log(`\n ${DIM}[${n}/${total}]${RESET} ${msg}`);
163
+ process.stdout.write(`\n ${DIM}[${n}/${total}]${RESET} ${msg}\n`);
48
164
  }
49
165
 
50
- function dim(msg) {
51
- return `${DIM}${msg}${RESET}`;
52
- }
166
+ // ── inline formatters ──────────────────────────────────────────────
53
167
 
54
- function bold(msg) {
55
- return `${BOLD}${msg}${RESET}`;
56
- }
168
+ const dim = (m) => `${DIM}${m}${RESET}`;
169
+ const bold = (m) => `${BOLD}${m}${RESET}`;
170
+ const cyan = (m) => `${CYAN}${m}${RESET}`;
171
+ const green = (m) => `${GREEN}${m}${RESET}`;
172
+ const yellow = (m) => `${YELLOW}${m}${RESET}`;
173
+ const red = (m) => `${RED}${m}${RESET}`;
57
174
 
58
- function cyan(msg) {
59
- return `${CYAN}${msg}${RESET}`;
60
- }
175
+ // ── spinner ─────────────────────────────────────────────────────────
61
176
 
62
- // Simple spinner
63
177
  function spinner(msg) {
64
178
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
65
179
  let i = 0;
66
- const interval = setInterval(() => {
67
- process.stdout.write(`\r ${CYAN}${frames[i++ % frames.length]}${RESET} ${msg}`);
68
- }, 80);
180
+ const tty = process.stdout.isTTY;
181
+ let interval = null;
182
+ if (tty) {
183
+ interval = setInterval(() => {
184
+ process.stdout.write(`\r ${CYAN}${frames[i++ % frames.length]}${RESET} ${msg}`);
185
+ }, 80);
186
+ } else {
187
+ // Non-TTY: just print once, don't animate
188
+ process.stdout.write(` … ${msg}\n`);
189
+ }
69
190
  return {
70
191
  stop(result) {
71
- clearInterval(interval);
72
- process.stdout.write(`\r\x1b[2K ${GREEN}✓${RESET} ${result || msg}\n`);
192
+ if (interval) clearInterval(interval);
193
+ if (tty) process.stdout.write(`\r\x1b[2K`);
194
+ process.stdout.write(` ${GREEN}✓${RESET} ${result || msg}\n`);
73
195
  },
74
196
  fail(result) {
75
- clearInterval(interval);
76
- process.stdout.write(`\r\x1b[2K ${RED}✗${RESET} ${result || msg}\n`);
197
+ if (interval) clearInterval(interval);
198
+ if (tty) process.stdout.write(`\r\x1b[2K`);
199
+ process.stdout.write(` ${RED}✗${RESET} ${result || msg}\n`);
200
+ },
201
+ // Stop animating and erase the line without printing a result symbol —
202
+ // for when the caller prints its own success/failure line next.
203
+ clear() {
204
+ if (interval) clearInterval(interval);
205
+ if (tty) process.stdout.write(`\r\x1b[2K`);
77
206
  },
78
207
  };
79
208
  }
80
209
 
81
- // Prompt for input
82
- function prompt(question) {
210
+ // ── prompts ─────────────────────────────────────────────────────────
211
+
212
+ function prompt(question, { default: defaultValue } = {}) {
83
213
  return new Promise((resolve) => {
84
214
  const rl = readline.createInterface({
85
215
  input: process.stdin,
86
216
  output: process.stdout,
87
217
  });
88
- rl.question(` ${question} `, (answer) => {
218
+ const label = defaultValue
219
+ ? `${CYAN}?${RESET} ${question} ${DIM}[${defaultValue}]${RESET} `
220
+ : `${CYAN}?${RESET} ${question} `;
221
+ rl.question(` ${label}`, (answer) => {
89
222
  rl.close();
90
- resolve(answer.trim());
223
+ const trimmed = answer.trim();
224
+ resolve(trimmed || (defaultValue ?? ""));
91
225
  });
92
226
  });
93
227
  }
94
228
 
95
- // Prompt with numbered choices
229
+ async function confirm(question, defaultYes = false) {
230
+ const hint = defaultYes ? `${DIM}[Y/n]${RESET}` : `${DIM}[y/N]${RESET}`;
231
+ const answer = await prompt(`${question} ${hint}`);
232
+ if (!answer) return defaultYes;
233
+ return /^y(es)?$/i.test(answer);
234
+ }
235
+
236
+ // ── interactive pickers ─────────────────────────────────────────────
237
+ //
238
+ // Arrow-key navigation + type-to-filter + Enter to select + Esc to cancel.
239
+ // Uses Node readline raw mode; no external dependencies.
240
+ //
241
+ // Falls back to numbered input when stdin/stdout aren't TTYs (CI, pipes,
242
+ // non-interactive shells).
243
+
244
+ function canInteract() {
245
+ return process.stdin.isTTY && process.stdout.isTTY && !process.env.EGREGORE_NO_INTERACTIVE;
246
+ }
247
+
248
+ // Visible length (strips ANSI escape sequences) for padding math.
249
+ const visibleLen = (s) => s.replace(/\x1b\[[0-9;]*m/g, "").length;
250
+
251
+ function visualRows(line, width = process.stdout.columns || 80) {
252
+ return Math.max(1, Math.ceil(visibleLen(line) / Math.max(1, width)));
253
+ }
254
+
255
+ // Render a picker frame and return { lines: number } so the next frame can
256
+ // rewind and clear. Shared by choose + multiSelect.
257
+ function renderPicker({ question, filterText, lines, help }) {
258
+ const out = [];
259
+ const filterLabel = filterText
260
+ ? ` ${DIM}filter:${RESET} ${filterText}${CYAN}▌${RESET}`
261
+ : "";
262
+ out.push(` ${CYAN}◆${RESET} ${BOLD}${question}${RESET}${filterLabel ? " " + filterLabel : ""}`);
263
+ out.push("");
264
+ for (const line of lines) out.push(line);
265
+ if (help) {
266
+ out.push("");
267
+ out.push(` ${DIM}${help}${RESET}`);
268
+ }
269
+ const text = out.join("\n") + "\n";
270
+ process.stdout.write(text);
271
+ const width = process.stdout.columns || 80;
272
+ return out.reduce((sum, line) => sum + visualRows(line, width), 0);
273
+ }
274
+
275
+ function clearLines(n) {
276
+ if (n <= 0) return;
277
+ // Move up n lines and clear from cursor to end of screen.
278
+ process.stdout.write(`\x1b[${n}A\x1b[0J`);
279
+ }
280
+
281
+ function filterOptions(options, filterText, getText) {
282
+ if (!filterText) return options.map((o, i) => ({ opt: o, origIdx: i }));
283
+ const q = filterText.toLowerCase();
284
+ const result = [];
285
+ for (let i = 0; i < options.length; i++) {
286
+ const text = getText(options[i]).toLowerCase();
287
+ if (text.includes(q)) result.push({ opt: options[i], origIdx: i });
288
+ }
289
+ return result;
290
+ }
291
+
292
+ // ── choose: interactive single-select picker ────────────────────────
293
+ //
294
+ // options: [{ label, description }]
295
+ // returns: the picked option (same object reference as in options[])
296
+ // keys:
297
+ // ↑ ↓ navigate
298
+ // enter select
299
+ // type filter (case-insensitive substring match on label + desc)
300
+ // backspace pop filter
301
+ // esc cancel (returns the first option, or throws if opts.abortOnEsc)
302
+ // ctrl-c abort process
303
+
96
304
  async function choose(question, options, hint) {
97
- console.log(`\n ${question}\n`);
305
+ if (!canInteract()) return legacyChoose(question, options, hint);
306
+ return interactiveChoose(question, options, hint);
307
+ }
308
+
309
+ async function interactiveChoose(question, options, hint) {
310
+ return new Promise((resolve) => {
311
+ let cursor = 0;
312
+ let filterText = "";
313
+ let visible = filterOptions(options, filterText, (o) => `${o.label} ${o.description || ""}`);
314
+ let lastLineCount = 0;
315
+
316
+ const labelWidth = Math.min(
317
+ 40,
318
+ Math.max(...options.map((o) => o.label.length)),
319
+ );
320
+
321
+ const render = (first = false) => {
322
+ const lines = visible.map((entry, i) => {
323
+ const { opt } = entry;
324
+ const selected = i === cursor;
325
+ const marker = selected ? `${CYAN}❯${RESET}` : " ";
326
+ const labelPart = selected ? `${BOLD}${CYAN}${pad(opt.label, labelWidth)}${RESET}` : pad(opt.label, labelWidth);
327
+ const desc = opt.description ? ` ${DIM}${opt.description}${RESET}` : "";
328
+ return ` ${marker} ${labelPart}${desc}`;
329
+ });
330
+ if (visible.length === 0) {
331
+ lines.push(` ${DIM}(no matches)${RESET}`);
332
+ }
333
+ if (!first) clearLines(lastLineCount);
334
+ lastLineCount = renderPicker({
335
+ question,
336
+ filterText,
337
+ lines,
338
+ help: hint || `${DIM}↑↓${RESET} navigate · ${DIM}enter${RESET} select · type to filter · ${DIM}esc${RESET} cancel`,
339
+ });
340
+ };
341
+
342
+ const cleanup = () => {
343
+ process.stdin.setRawMode(false);
344
+ process.stdin.pause();
345
+ process.stdin.removeListener("keypress", onKey);
346
+ };
347
+
348
+ const finish = (value) => {
349
+ cleanup();
350
+ clearLines(lastLineCount);
351
+ process.stdout.write(` ${CYAN}◆${RESET} ${BOLD}${question}${RESET} ${DIM}→${RESET} ${BOLD}${value.label}${RESET}\n`);
352
+ resolve(value);
353
+ };
354
+
355
+ const onKey = (_str, key) => {
356
+ if (!key) return;
357
+ if ((key.ctrl && key.name === "c") || key.name === "escape") {
358
+ cleanup();
359
+ process.stdout.write(`\n ${DIM}(cancelled)${RESET}\n`);
360
+ process.exit(130);
361
+ }
362
+ if (key.name === "return") {
363
+ if (visible.length > 0) finish(visible[cursor].opt);
364
+ return;
365
+ }
366
+ if (key.name === "up" || (key.ctrl && key.name === "p")) {
367
+ cursor = (cursor - 1 + visible.length) % Math.max(1, visible.length);
368
+ render();
369
+ return;
370
+ }
371
+ if (key.name === "down" || (key.ctrl && key.name === "n")) {
372
+ cursor = (cursor + 1) % Math.max(1, visible.length);
373
+ render();
374
+ return;
375
+ }
376
+ if (key.name === "backspace") {
377
+ filterText = filterText.slice(0, -1);
378
+ visible = filterOptions(options, filterText, (o) => `${o.label} ${o.description || ""}`);
379
+ cursor = 0;
380
+ render();
381
+ return;
382
+ }
383
+ // Printable character → append to filter
384
+ if (_str && _str.length === 1 && !key.ctrl && !key.meta && _str.charCodeAt(0) >= 32) {
385
+ filterText += _str;
386
+ visible = filterOptions(options, filterText, (o) => `${o.label} ${o.description || ""}`);
387
+ cursor = 0;
388
+ render();
389
+ }
390
+ };
391
+
392
+ readline.emitKeypressEvents(process.stdin);
393
+ process.stdin.setRawMode(true);
394
+ process.stdin.resume();
395
+ process.stdin.on("keypress", onKey);
396
+ render(true);
397
+ });
398
+ }
399
+
400
+ async function legacyChoose(question, options, hint) {
401
+ process.stdout.write(`\n ${CYAN}◆${RESET} ${BOLD}${question}${RESET}\n\n`);
402
+ const labelWidth = Math.min(40, Math.max(...options.map((o) => o.label.length)));
98
403
  for (let i = 0; i < options.length; i++) {
99
404
  const { label, description } = options[i];
100
- console.log(` ${BOLD}${i + 1}.${RESET} ${label}${description ? ` ${DIM}— ${description}${RESET}` : ""}`);
405
+ const num = String(i + 1).padStart(2, " ");
406
+ const paddedLabel = pad(label, labelWidth);
407
+ const desc = description ? ` ${DIM}— ${description}${RESET}` : "";
408
+ process.stdout.write(` ${DIM}${num})${RESET} ${BOLD}${paddedLabel}${RESET}${desc}\n`);
101
409
  }
102
- if (hint) console.log(`\n ${DIM}${hint}${RESET}`);
103
- console.log();
104
-
410
+ if (hint) process.stdout.write(`\n ${DIM}${hint}${RESET}\n`);
105
411
  while (true) {
106
- const answer = await prompt(`Pick [1-${options.length}]:`);
412
+ const answer = await prompt(`pick ${DIM}[1-${options.length}]${RESET}:`);
413
+ if (!answer) { warn(`Enter a number between 1 and ${options.length}.`); continue; }
107
414
  const n = parseInt(answer, 10);
108
- if (n >= 1 && n <= options.length) {
109
- return options[n - 1];
110
- }
111
- warn(`Enter a number between 1 and ${options.length}`);
415
+ if (n >= 1 && n <= options.length) return options[n - 1];
416
+ const labelMatch = options.find((o) => o.label.toLowerCase() === answer.toLowerCase());
417
+ if (labelMatch) return labelMatch;
418
+ warn(`Not a valid option. Enter 1-${options.length} or a label.`);
112
419
  }
113
420
  }
114
421
 
115
- // Prompt with multi-select (comma-separated numbers, or Enter to skip)
422
+ // ── multiSelect: interactive multi-select picker ────────────────────
423
+ //
424
+ // options: [{ name, language?, description? }]
425
+ // returns: array of picked names
426
+ // keys:
427
+ // ↑ ↓ navigate
428
+ // space toggle current
429
+ // a toggle all (respecting filter)
430
+ // enter confirm
431
+ // type filter
432
+ // esc confirm with current selection
433
+
116
434
  async function multiSelect(question, options) {
117
435
  if (options.length === 0) return [];
118
- console.log(`\n ${question}\n`);
436
+ if (!canInteract()) return legacyMultiSelect(question, options);
437
+ return interactiveMultiSelect(question, options);
438
+ }
439
+
440
+ async function interactiveMultiSelect(question, options) {
441
+ return new Promise((resolve) => {
442
+ let cursor = 0;
443
+ let filterText = "";
444
+ const picked = new Set();
445
+ let visible = filterOptions(options, filterText, (o) => `${o.name} ${o.description || ""} ${o.language || ""}`);
446
+ let lastLineCount = 0;
447
+
448
+ const nameWidth = Math.min(36, Math.max(...options.map((o) => (o.name || "").length)));
449
+
450
+ const render = (first = false) => {
451
+ const lines = visible.map((entry, i) => {
452
+ const { opt } = entry;
453
+ const isCursor = i === cursor;
454
+ const isPicked = picked.has(opt.name);
455
+ const cursorMark = isCursor ? `${CYAN}❯${RESET}` : " ";
456
+ const checkbox = isPicked ? `${GREEN}[✓]${RESET}` : `${DIM}[ ]${RESET}`;
457
+ const name = isCursor ? `${BOLD}${CYAN}${pad(opt.name, nameWidth)}${RESET}` : pad(opt.name, nameWidth);
458
+ const lang = opt.language ? ` ${DIM}(${opt.language})${RESET}` : "";
459
+ const desc = opt.description ? ` ${DIM}${opt.description}${RESET}` : "";
460
+ return ` ${cursorMark} ${checkbox} ${name}${lang}${desc}`;
461
+ });
462
+ if (visible.length === 0) {
463
+ lines.push(` ${DIM}(no matches)${RESET}`);
464
+ }
465
+ lines.push("");
466
+ lines.push(` ${DIM}${picked.size} selected${RESET}`);
467
+ if (!first) clearLines(lastLineCount);
468
+ lastLineCount = renderPicker({
469
+ question,
470
+ filterText,
471
+ lines,
472
+ help: `${DIM}↑↓${RESET} nav · ${DIM}space${RESET} toggle · ${DIM}a${RESET} all · ${DIM}enter${RESET} confirm · type to filter`,
473
+ });
474
+ };
475
+
476
+ const cleanup = () => {
477
+ process.stdin.setRawMode(false);
478
+ process.stdin.pause();
479
+ process.stdin.removeListener("keypress", onKey);
480
+ };
481
+
482
+ const finish = () => {
483
+ cleanup();
484
+ clearLines(lastLineCount);
485
+ const summary = picked.size === 0 ? `${DIM}none${RESET}` : `${BOLD}${picked.size}${RESET} ${DIM}selected${RESET}`;
486
+ process.stdout.write(` ${CYAN}◆${RESET} ${BOLD}${question}${RESET} ${DIM}→${RESET} ${summary}\n`);
487
+ resolve([...picked]);
488
+ };
489
+
490
+ const toggleCurrent = () => {
491
+ if (visible.length === 0) return;
492
+ const name = visible[cursor].opt.name;
493
+ if (picked.has(name)) picked.delete(name);
494
+ else picked.add(name);
495
+ };
496
+
497
+ const toggleAll = () => {
498
+ // If every visible option is already picked, unselect all visible; else pick all visible.
499
+ const allPicked = visible.every((v) => picked.has(v.opt.name));
500
+ for (const v of visible) {
501
+ if (allPicked) picked.delete(v.opt.name);
502
+ else picked.add(v.opt.name);
503
+ }
504
+ };
505
+
506
+ const onKey = (_str, key) => {
507
+ if (!key) return;
508
+ if ((key.ctrl && key.name === "c") || key.name === "escape") {
509
+ cleanup();
510
+ process.stdout.write(`\n ${DIM}(cancelled)${RESET}\n`);
511
+ process.exit(130);
512
+ }
513
+ if (key.name === "return") return finish();
514
+ if (key.name === "up" || (key.ctrl && key.name === "p")) {
515
+ cursor = (cursor - 1 + visible.length) % Math.max(1, visible.length);
516
+ render();
517
+ return;
518
+ }
519
+ if (key.name === "down" || (key.ctrl && key.name === "n")) {
520
+ cursor = (cursor + 1) % Math.max(1, visible.length);
521
+ render();
522
+ return;
523
+ }
524
+ if (key.name === "space") {
525
+ toggleCurrent();
526
+ render();
527
+ return;
528
+ }
529
+ if (_str === "a" && !filterText) {
530
+ toggleAll();
531
+ render();
532
+ return;
533
+ }
534
+ if (key.name === "backspace") {
535
+ filterText = filterText.slice(0, -1);
536
+ visible = filterOptions(options, filterText, (o) => `${o.name} ${o.description || ""} ${o.language || ""}`);
537
+ cursor = 0;
538
+ render();
539
+ return;
540
+ }
541
+ if (_str && _str.length === 1 && !key.ctrl && !key.meta && _str.charCodeAt(0) >= 32) {
542
+ filterText += _str;
543
+ visible = filterOptions(options, filterText, (o) => `${o.name} ${o.description || ""} ${o.language || ""}`);
544
+ cursor = 0;
545
+ render();
546
+ }
547
+ };
548
+
549
+ readline.emitKeypressEvents(process.stdin);
550
+ process.stdin.setRawMode(true);
551
+ process.stdin.resume();
552
+ process.stdin.on("keypress", onKey);
553
+ render(true);
554
+ });
555
+ }
556
+
557
+ async function legacyMultiSelect(question, options) {
558
+ process.stdout.write(`\n ${CYAN}◆${RESET} ${BOLD}${question}${RESET}\n\n`);
559
+ const nameWidth = Math.min(36, Math.max(...options.map((o) => (o.name || "").length)));
119
560
  for (let i = 0; i < options.length; i++) {
120
561
  const { name, language, description } = options[i];
121
- const lang = language ? `${DIM}(${language})${RESET} ` : "";
122
- const desc = description ? `${DIM}— ${description}${RESET}` : "";
123
- console.log(` ${BOLD}${i + 1}.${RESET} ${name} ${lang}${desc}`);
562
+ const num = String(i + 1).padStart(2, " ");
563
+ const paddedName = pad(name || "", nameWidth);
564
+ const lang = language ? ` ${DIM}(${language})${RESET}` : "";
565
+ const desc = description ? ` ${DIM}— ${description}${RESET}` : "";
566
+ process.stdout.write(` ${DIM}${num})${RESET} ${paddedName}${lang}${desc}\n`);
567
+ }
568
+ process.stdout.write(`\n ${DIM}numbers (e.g. 1,3-5), 'all', or Enter to skip${RESET}\n`);
569
+ const answer = await prompt("select:");
570
+ if (!answer || /^none$/i.test(answer)) return [];
571
+ if (/^all$/i.test(answer)) return options.map((o) => o.name);
572
+ const picked = new Set();
573
+ for (const part of answer.split(",").map((s) => s.trim()).filter(Boolean)) {
574
+ const range = part.match(/^(\d+)\s*-\s*(\d+)$/);
575
+ if (range) {
576
+ const [, a, b] = range;
577
+ const lo = Math.min(+a, +b);
578
+ const hi = Math.max(+a, +b);
579
+ for (let n = lo; n <= hi; n++) {
580
+ if (n >= 1 && n <= options.length) picked.add(options[n - 1].name);
581
+ }
582
+ } else {
583
+ const n = parseInt(part, 10);
584
+ if (n >= 1 && n <= options.length) picked.add(options[n - 1].name);
585
+ }
124
586
  }
125
- console.log(`\n ${DIM}Enter numbers (e.g. 1,3,5), 'all', or press Enter to skip${RESET}`);
587
+ return [...picked];
588
+ }
589
+
590
+ // ── commandBox ──────────────────────────────────────────────────────
591
+ //
592
+ // Preserved for back-compat; card() is the newer more-flexible version.
126
593
 
127
- const answer = await prompt("Repos:");
128
- if (!answer) return [];
129
- if (answer.toLowerCase() === "all") return options.map((o) => o.name);
594
+ function commandBox(label, cmd) {
595
+ card(label, cmd);
596
+ }
130
597
 
131
- const selected = [];
132
- for (const part of answer.split(",")) {
133
- const n = parseInt(part.trim(), 10);
134
- if (n >= 1 && n <= options.length) {
135
- selected.push(options[n - 1].name);
598
+ // ── bookend screens (alt-screen) ───────────────────────────────────
599
+ //
600
+ // Full-screen welcome + summary moments that bracket the linear install
601
+ // flow. Enter alt-screen, render, wait for ENTER, exit alt-screen. This
602
+ // preserves both the user's prior shell scrollback AND the install
603
+ // transcript — the full-screen moment is the only thing that disappears.
604
+ //
605
+ // Fallback: callers should check canShowScreens() and fall back to inline
606
+ // renderers for non-TTY / CI / --no-screens.
607
+
608
+ const ALT_SCREEN_IN = "\x1b[?1049h";
609
+ const ALT_SCREEN_OUT = "\x1b[?1049l";
610
+ const CURSOR_HIDE = "\x1b[?25l";
611
+ const CURSOR_SHOW = "\x1b[?25h";
612
+ const CLEAR_HOME = "\x1b[2J\x1b[H";
613
+
614
+ function canShowScreens() {
615
+ return !!(process.stdin.isTTY && process.stdout.isTTY && !process.env.EGREGORE_NO_SCREENS);
616
+ }
617
+
618
+ function termSize() {
619
+ return {
620
+ w: process.stdout.columns || 80,
621
+ h: process.stdout.rows || 24,
622
+ };
623
+ }
624
+
625
+ // Visible length (strips ANSI) for centering math.
626
+ function _visLen(s) {
627
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
628
+ }
629
+
630
+ // Center a string horizontally by prepending spaces.
631
+ function _center(line, w) {
632
+ const pad = Math.max(0, Math.floor((w - _visLen(line)) / 2));
633
+ return " ".repeat(pad) + line;
634
+ }
635
+
636
+ // Pad vertical space to push content toward center-ish.
637
+ function _vpad(n) {
638
+ return Array.from({ length: Math.max(0, n) }, () => "").join("\n");
639
+ }
640
+
641
+ async function _waitForEnter() {
642
+ return new Promise((resolve) => {
643
+ let done = false;
644
+ const onKey = (chunk, key) => {
645
+ if (done) return;
646
+ if (key && key.ctrl && key.name === "c") {
647
+ // Clean exit on ctrl-c — teardown alt-screen first
648
+ process.stdout.write(CURSOR_SHOW + ALT_SCREEN_OUT);
649
+ process.stdin.setRawMode(false);
650
+ process.stdin.pause();
651
+ process.stdin.removeListener("keypress", onKey);
652
+ process.exit(130);
653
+ }
654
+ if (key && (key.name === "return" || key.name === "escape" || key.name === "space")) {
655
+ done = true;
656
+ process.stdin.setRawMode(false);
657
+ process.stdin.pause();
658
+ process.stdin.removeListener("keypress", onKey);
659
+ resolve(key.name);
660
+ }
661
+ };
662
+ readline.emitKeypressEvents(process.stdin);
663
+ process.stdin.setRawMode(true);
664
+ process.stdin.resume();
665
+ process.stdin.on("keypress", onKey);
666
+ });
667
+ }
668
+
669
+ function _readPkgVersion() {
670
+ try {
671
+ const fs = require("node:fs");
672
+ const path = require("node:path");
673
+ const raw = fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf-8");
674
+ return JSON.parse(raw).version;
675
+ } catch {
676
+ return "?";
677
+ }
678
+ }
679
+
680
+ function _renderWelcomeBody({ w, h, version }) {
681
+ const panelInner = Math.min(76, Math.max(62, w - 6));
682
+ const panelOuter = panelInner + 2;
683
+ const showBanner = h >= 28 && w >= 74;
684
+
685
+ const lines = [];
686
+
687
+ if (showBanner) {
688
+ for (const l of BANNER_LINES) {
689
+ lines.push(`${BOLD}${CYAN}${l}${RESET}`);
136
690
  }
691
+ } else {
692
+ lines.push(`${BOLD}${CYAN}EGREGORE${RESET}`);
137
693
  }
138
- return selected;
694
+ lines.push("");
695
+
696
+ const leftWidth = 14;
697
+ const gutter = 2;
698
+ const rightWidth = panelInner - leftWidth - gutter - 2;
699
+
700
+ const leftLines = [
701
+ "",
702
+ ...SIGIL_LINES.map((l) => `${CYAN}${l}${RESET}`),
703
+ "",
704
+ "",
705
+ `${BOLD}create-egregore${RESET}`,
706
+ `${DIM}v${version}${RESET}`,
707
+ ];
708
+
709
+ const rightLines = [];
710
+ rightLines.push(`${BOLD}What you're installing${RESET}`);
711
+ rightLines.push("");
712
+ for (const g of COMMAND_GROUPS) {
713
+ rightLines.push(`${DIM}${pad(g.label, 10)}${RESET} ${g.cmds}`);
714
+ }
715
+ rightLines.push("");
716
+ rightLines.push(`${BOLD}Mode${RESET}`);
717
+ rightLines.push(`${DIM}local-first · memory lives in ./memory/${RESET}`);
718
+ rightLines.push(`${DIM}files are yours · git is the source of truth${RESET}`);
719
+ rightLines.push("");
720
+ rightLines.push(`${BOLD}Next${RESET}`);
721
+ rightLines.push(`${DIM}guided setup → choose an assistant${RESET}`);
722
+
723
+ const panelHeight = Math.max(leftLines.length, rightLines.length);
724
+ while (leftLines.length < panelHeight) leftLines.push("");
725
+ while (rightLines.length < panelHeight) rightLines.push("");
726
+
727
+ const label = " create-egregore ";
728
+ const dashesLeft = 2;
729
+ const dashesRight = Math.max(0, panelInner - label.length - dashesLeft);
730
+ lines.push(`${DIM}┌${"─".repeat(dashesLeft)}${RESET}${DIM}${label}${RESET}${DIM}${"─".repeat(dashesRight)}┐${RESET}`);
731
+ lines.push(`${DIM}│${RESET}${" ".repeat(panelInner)}${DIM}│${RESET}`);
732
+
733
+ for (let i = 0; i < panelHeight; i++) {
734
+ const left = leftLines[i];
735
+ const leftPad = " ".repeat(Math.max(0, leftWidth - visibleLen(left)));
736
+ const right = rightLines[i];
737
+ const rightPad = " ".repeat(Math.max(0, rightWidth - visibleLen(right)));
738
+ const inner = ` ${left}${leftPad}${" ".repeat(gutter)}${right}${rightPad} `;
739
+ const innerPad = " ".repeat(Math.max(0, panelInner - visibleLen(inner)));
740
+ lines.push(`${DIM}│${RESET}${inner}${innerPad}${DIM}│${RESET}`);
741
+ }
742
+
743
+ lines.push(`${DIM}│${RESET}${" ".repeat(panelInner)}${DIM}│${RESET}`);
744
+ lines.push(`${DIM}└${"─".repeat(panelInner)}┘${RESET}`);
745
+
746
+ lines.push("");
747
+ lines.push(`${DIM}a shared memory layer for teams using Claude Code or Codex${RESET}`);
748
+ lines.push("");
749
+ lines.push(`${DIM}press ${RESET}${BOLD}↵${RESET}${DIM} to begin · ctrl-c to exit${RESET}`);
750
+
751
+ const margin = Math.max(0, Math.floor((w - panelOuter) / 2));
752
+ const rendered = lines.map((l) => " ".repeat(margin) + l);
753
+ const topPad = Math.max(0, Math.floor((h - lines.length - 1) / 4));
754
+ const bottomPad = Math.max(0, h - lines.length - topPad - 1);
755
+
756
+ return { body: rendered.join("\n"), topPad, bottomPad };
139
757
  }
140
758
 
141
- function commandBox(label, cmd) {
142
- const inner = ` ${cmd} `;
143
- const width = Math.max(inner.length, label.length + 5);
144
- const pad = (s) => s + " ".repeat(Math.max(0, width - s.length));
145
- const top = `┌─ ${label} ${"─".repeat(Math.max(0, width - label.length - 4))}┐`;
146
- const blank = `│${" ".repeat(width)}│`;
147
- const bot = `└${"─".repeat(width)}┘`;
148
- console.log(`\n ${DIM}${top}${RESET}`);
149
- console.log(` ${DIM}${blank}${RESET}`);
150
- console.log(` ${DIM}│${RESET}${BOLD}${pad(inner)}${RESET}${DIM}│${RESET}`);
151
- console.log(` ${DIM}${blank}${RESET}`);
152
- console.log(` ${DIM}${bot}${RESET}\n`);
759
+ async function welcomeScreen(_opts = {}) {
760
+ const { w, h } = termSize();
761
+ const version = _readPkgVersion();
762
+ const { body, topPad, bottomPad } = _renderWelcomeBody({ w, h, version });
763
+
764
+ process.stdout.write(ALT_SCREEN_IN + CURSOR_HIDE + CLEAR_HOME);
765
+ process.stdout.write(_vpad(topPad));
766
+ process.stdout.write("\n");
767
+ process.stdout.write(body);
768
+ process.stdout.write(_vpad(bottomPad));
769
+
770
+ const result = await _waitForEnter();
771
+ process.stdout.write(CURSOR_SHOW + ALT_SCREEN_OUT);
772
+ if (result === "escape") process.exit(0);
773
+ }
774
+
775
+ // Summary / ready screen — bookend at the end of the install. Shows the
776
+ // workspace that was created, the framing Insight (so the first Claude
777
+ // session can skip re-rendering it), and a "press enter to start your
778
+ // first session" prompt. After enter, alt-screen leaves and the caller
779
+ // execs into `claude start`.
780
+ async function summaryScreen({ orgName, workspaces = [], next = "start your first session", setup = null } = {}) {
781
+ const { w, h } = termSize();
782
+ const lines = [];
783
+
784
+ lines.push(`${BOLD}${GREEN}✓ You're done${RESET}`);
785
+ lines.push("");
786
+ lines.push(`${DIM}Egregore is ready for${RESET} ${BOLD}${orgName}${RESET}`);
787
+ lines.push("");
788
+
789
+ if (setup) {
790
+ const rows = [
791
+ ["workspace", setup.workspace || "created"],
792
+ ["invite", setup.invite || "skipped"],
793
+ ["Telegram", setup.telegram || "skipped"],
794
+ ["assistant", setup.assistant || ""],
795
+ ["next", setup.command || "egregore"],
796
+ ].filter(([, value]) => value);
797
+ lines.push(`${DIM}setup${RESET}`);
798
+ for (const [label, value] of rows) {
799
+ lines.push(` ${GREEN}●${RESET} ${DIM}${pad(label, 10)}${RESET} ${value}`);
800
+ }
801
+ lines.push("");
802
+ }
803
+
804
+ if (workspaces.length) {
805
+ lines.push(`${DIM}workspace${RESET}`);
806
+ for (const ws of workspaces) {
807
+ const label = ws.label || "";
808
+ const desc = ws.description || "";
809
+ lines.push(` ${GREEN}●${RESET} ${CYAN}${label}${RESET} ${DIM}${desc}${RESET}`);
810
+ }
811
+ lines.push("");
812
+ }
813
+
814
+ // Framing Insight — this is the copy that /onboarding used to render.
815
+ // Showing it here means the first Claude session can skip it entirely.
816
+ lines.push("");
817
+ lines.push(`${DIM}${"─".repeat(Math.min(48, w - 20))}${RESET}`);
818
+ lines.push("");
819
+ lines.push("Every session leaves traces — decisions, patterns, context.");
820
+ lines.push(`${DIM}Egregore makes those traces persistent and shared.${RESET}`);
821
+ lines.push("");
822
+ lines.push("You do your work. When you're done, you hand off what you");
823
+ lines.push(`learned. The next session — yours or someone else's —`);
824
+ lines.push(`${DIM}starts smarter.${RESET}`);
825
+ lines.push("");
826
+ lines.push(`${DIM}${"─".repeat(Math.min(48, w - 20))}${RESET}`);
827
+ lines.push("");
828
+ lines.push("");
829
+ lines.push(`${DIM}press ${RESET}${BOLD}↵${RESET}${DIM} to ${next} · esc to skip launch${RESET}`);
830
+
831
+ const rendered = lines.map((l) => _center(l, w));
832
+ const topPad = Math.max(1, Math.floor((h - lines.length - 2) / 4));
833
+ const bottomPad = Math.max(0, h - lines.length - topPad - 1);
834
+
835
+ process.stdout.write(ALT_SCREEN_IN + CURSOR_HIDE + CLEAR_HOME);
836
+ process.stdout.write(_vpad(topPad));
837
+ process.stdout.write("\n");
838
+ process.stdout.write(rendered.join("\n"));
839
+ process.stdout.write(_vpad(bottomPad));
840
+
841
+ const result = await _waitForEnter();
842
+ process.stdout.write(CURSOR_SHOW + ALT_SCREEN_OUT);
843
+ return result; // "return" (launch) | "escape" (skip launch) | "space"
153
844
  }
154
845
 
155
846
  module.exports = {
156
- banner, info, success, warn, error, step, dim, bold, cyan,
157
- spinner, prompt, choose, multiSelect, commandBox,
847
+ banner, bannerCompact, section, note, card,
848
+ info, success, warn, error, step,
849
+ dim, bold, cyan, green, yellow, red,
850
+ spinner, prompt, choose, multiSelect, confirm,
851
+ commandBox,
852
+ canShowScreens, welcomeScreen, summaryScreen,
853
+ _renderWelcomeBody, _readPkgVersion,
158
854
  };