synabun 2026.3.310 → 2026.3.312

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/setup.js CHANGED
@@ -1,337 +1,315 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * SynaBun — Single-command setup & launch
5
- *
6
- * Usage: node setup.js (or: npm start)
7
- *
8
- * 1. Checks Node.js version (>=22)
9
- * 2. Installs npm deps for neural-interface/ and mcp-server/
10
- * 3. Builds the MCP server TypeScript (if dist/ is missing or stale)
11
- * 4. Starts the Neural Interface Express server
12
- * 5. Auto-opens browser to onboarding wizard (or main page if setup complete)
13
- */
14
-
15
- import { execSync, spawn, exec } from 'node:child_process';
16
- import { existsSync, readFileSync, cpSync } from 'node:fs';
17
- import { resolve, dirname } from 'node:path';
18
- import { fileURLToPath } from 'node:url';
19
- import { platform } from 'node:os';
20
-
21
- const __dirname = dirname(fileURLToPath(import.meta.url));
22
-
23
- // ── ANSI color helpers ──
24
-
25
- const c = {
26
- reset: '\x1b[0m',
27
- bold: '\x1b[1m',
28
- dim: '\x1b[2m',
29
- cyan: '\x1b[36m',
30
- green: '\x1b[32m',
31
- yellow: '\x1b[33m',
32
- red: '\x1b[31m',
33
- };
34
-
35
- function ok(msg) { console.log(` ${c.green}\u2713${c.reset} ${msg}`); }
36
- function warn(msg) { console.log(` ${c.yellow}!${c.reset} ${msg}`); }
37
- function fail(msg) { console.log(` ${c.red}\u2717${c.reset} ${msg}`); }
38
- function info(msg) { console.log(` ${c.cyan}\u2192${c.reset} ${msg}`); }
39
-
40
- // ── Global install detection & CWD scaffold ──
41
-
42
- function isGlobalInstall() {
43
- const normalized = __dirname.replace(/\\/g, '/');
44
- return normalized.includes('/node_modules/synabun');
45
- }
46
-
47
- function isAlreadyScaffolded(dir) {
48
- return existsSync(resolve(dir, 'neural-interface', 'server.js'));
49
- }
50
-
51
- function getLocalVersion(dir) {
52
- try {
53
- return JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf-8')).version || '0.0.0';
54
- } catch { return '0.0.0'; }
55
- }
56
-
57
- function getGlobalVersion() {
58
- return JSON.parse(readFileSync(resolve(__dirname, 'package.json'), 'utf-8')).version;
59
- }
60
-
61
- function scaffoldToCwd(targetDir) {
62
- info('Scaffolding SynaBun into current directory...');
63
-
64
- cpSync(__dirname, targetDir, {
65
- recursive: true,
66
- filter: (src) => {
67
- const normalized = src.replace(/\\/g, '/');
68
- // Skip node_modules (installed fresh by setup)
69
- if (normalized.includes('/node_modules')) return false;
70
- // Skip .env (generated fresh per installation)
71
- if (normalized.endsWith('/.env')) return false;
72
- return true;
73
- },
74
- });
75
-
76
- ok(`Scaffolded to ${targetDir}`);
77
- }
78
-
79
- // ── Phase 1: Prerequisite checks ──
80
-
81
- function checkNodeVersion() {
82
- const [major] = process.versions.node.split('.').map(Number);
83
- if (major < 22) {
84
- fail(`Node.js 22+ required, found v${process.versions.node}`);
85
- process.exit(1);
86
- }
87
- ok(`Node.js v${process.versions.node}`);
88
- }
89
-
90
- // ── Phase 2: Dependency installation ──
91
-
92
- function needsInstall(dir) {
93
- // Check for .package-lock.json which npm creates after a successful install
94
- // A bare node_modules/ directory (e.g. from a failed npx or partial install) is not enough
95
- return !existsSync(resolve(dir, 'node_modules', '.package-lock.json'));
96
- }
97
-
98
- function installDeps(name, dir) {
99
- if (!needsInstall(dir)) {
100
- ok(`${name} dependencies already installed`);
101
- return;
102
- }
103
-
104
- info(`Installing ${name} dependencies...`);
105
- try {
106
- execSync('npm install --omit=dev --ignore-scripts', {
107
- cwd: dir,
108
- stdio: 'inherit',
109
- timeout: 300_000,
110
- });
111
- ok(`${name} dependencies installed`);
112
- } catch (err) {
113
- fail(`Failed to install ${name} dependencies`);
114
- console.error(err.stderr?.toString() || err.message);
115
- process.exit(1);
116
- }
117
- }
118
-
119
- // ── Phase 3: Playwright Chromium (for browser automation) ──
120
-
121
- function installPlaywrightChromium() {
122
- const niDir = resolve(__dirname, 'neural-interface');
123
- // Check if already installed by resolving the executable path
124
- try {
125
- const result = execSync('node -e "const pw=require(\'playwright\');const p=pw.chromium.executablePath();process.stdout.write(p)"', {
126
- cwd: niDir, encoding: 'utf8', timeout: 10_000,
127
- });
128
- if (existsSync(result)) {
129
- ok('Playwright Chromium already installed');
130
- return;
131
- }
132
- } catch { /* not installed */ }
133
-
134
- info('Installing Playwright Chromium (for browser automation)...');
135
- try {
136
- execSync('npx playwright install chromium', {
137
- cwd: niDir,
138
- stdio: 'inherit',
139
- timeout: 120_000,
140
- });
141
- ok('Playwright Chromium installed');
142
- } catch (err) {
143
- // Non-fatal — system Chrome will be used as fallback
144
- console.log(' (optional) Playwright Chromium install failed — system Chrome will be used');
145
- }
146
- }
147
-
148
- // ── Phase 4: MCP server build ──
149
-
150
- function needsBuild() {
151
- const distIndex = resolve(__dirname, 'mcp-server', 'dist', 'index.js');
152
- return !existsSync(distIndex);
153
- }
154
-
155
- function buildMcpServer() {
156
- if (!needsBuild()) {
157
- ok('MCP server already built');
158
- return;
159
- }
160
-
161
- // dist/ ships pre-built in the npm package — build is a fallback only
162
- warn('MCP server dist/ not found — attempting build (requires TypeScript)');
163
- try {
164
- execSync('npx tsc', {
165
- cwd: resolve(__dirname, 'mcp-server'),
166
- stdio: 'pipe',
167
- timeout: 60_000,
168
- });
169
- ok('MCP server built');
170
- } catch (err) {
171
- warn('MCP server build failed (TypeScript not available) — reinstall with: npm install -g synabun@latest');
172
- }
173
- }
174
-
175
- // ── Phase 4: Setup state detection ──
176
-
177
- function isSetupComplete() {
178
- const envPath = resolve(__dirname, '.env');
179
- try {
180
- const content = readFileSync(envPath, 'utf-8');
181
- for (const line of content.split('\n')) {
182
- const trimmed = line.trim();
183
- if (trimmed.startsWith('SETUP_COMPLETE=')) {
184
- return trimmed.split('=')[1] === 'true';
185
- }
186
- }
187
- } catch {}
188
- return false;
189
- }
190
-
191
- // ── Phase 5: Server launch + browser open ──
192
-
193
- function openBrowser(url) {
194
- const plat = platform();
195
- const fallback = () => { warn('Could not open browser automatically'); info(`Open manually: ${url}`); };
196
-
197
- if (plat === 'win32') {
198
- exec(`start "" "${url}"`, (err) => { if (err) fallback(); });
199
- } else if (plat === 'darwin') {
200
- exec(`open "${url}"`, (err) => { if (err) fallback(); });
201
- } else {
202
- exec(`xdg-open "${url}"`, (err) => { if (err) fallback(); });
203
- }
204
- }
205
-
206
- function startServer() {
207
- const serverPath = resolve(__dirname, 'neural-interface', 'server.js');
208
-
209
- info('Starting Neural Interface server...');
210
- console.log('');
211
-
212
- const child = spawn('node', [serverPath], {
213
- cwd: resolve(__dirname, 'neural-interface'),
214
- stdio: ['ignore', 'pipe', 'pipe'],
215
- });
216
-
217
- let opened = false;
218
- const setupComplete = isSetupComplete();
219
-
220
- child.stdout.on('data', (data) => {
221
- process.stdout.write(data.toString());
222
-
223
- if (!opened && data.toString().includes('Server:')) {
224
- opened = true;
225
- const port = data.toString().match(/Server:\s+http:\/\/localhost:(\d+)/)?.[1] || '3344';
226
- const url = `http://localhost:${port}${setupComplete ? '/' : '/onboarding.html'}`;
227
-
228
- if (!setupComplete) setTimeout(() => openBrowser(url), 1500);
229
- }
230
- });
231
-
232
- child.stderr.on('data', (data) => {
233
- process.stderr.write(data.toString());
234
- });
235
-
236
- child.on('exit', (code) => {
237
- if (code !== 0 && code !== null) {
238
- fail(`Server exited with code ${code}`);
239
- process.exit(code);
240
- }
241
- });
242
-
243
- // Clean shutdown on Ctrl+C
244
- process.on('SIGINT', () => { child.kill('SIGINT'); process.exit(0); });
245
- process.on('SIGTERM', () => { child.kill('SIGTERM'); process.exit(0); });
246
- }
247
-
248
- // ── Main ──
249
-
250
- function main() {
251
- // ── Global install: scaffold to CWD + delegate ──
252
- if (isGlobalInstall()) {
253
- const cwd = process.cwd();
254
- const globalVer = getGlobalVersion();
255
-
256
- console.log('');
257
- console.log(` ${c.cyan}╔═══════════════════════════════════╗${c.reset}`);
258
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
259
- console.log(` ${c.cyan}║${c.reset} ${c.bold}${c.cyan}[██] [██]${c.reset} ${c.bold}${c.cyan}SynaBun${c.reset} ${c.cyan}║${c.reset}`);
260
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
261
- console.log(` ${c.cyan}║${c.reset} ${c.dim}Persistent Vector Memory${c.reset} ${c.cyan}║${c.reset}`);
262
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
263
- console.log(` ${c.cyan}╚═══════════════════════════════════╝${c.reset}`);
264
- console.log(` ${c.dim}synabun.ai${c.reset}`);
265
- console.log('');
266
-
267
- if (!isAlreadyScaffolded(cwd)) {
268
- info(`Scaffolding SynaBun v${globalVer} into:`);
269
- console.log(` ${c.cyan}${cwd}${c.reset}`);
270
- console.log('');
271
- scaffoldToCwd(cwd);
272
- } else {
273
- const localVer = getLocalVersion(cwd);
274
- if (localVer !== globalVer) {
275
- info(`Updating SynaBun: ${localVer} → ${globalVer}`);
276
- scaffoldToCwd(cwd);
277
- ok('Updated');
278
- } else {
279
- ok(`SynaBun v${globalVer} up to date`);
280
- }
281
- }
282
-
283
- console.log('');
284
-
285
- // Delegate to the local copy so __dirname resolves to CWD
286
- const child = spawn('node', [resolve(cwd, 'setup.js')], {
287
- cwd,
288
- stdio: 'inherit',
289
- });
290
- child.on('exit', (code) => process.exit(code ?? 0));
291
- process.on('SIGINT', () => { child.kill('SIGINT'); });
292
- process.on('SIGTERM', () => { child.kill('SIGTERM'); });
293
- return;
294
- }
295
-
296
- // ── Local/dev install: normal flow ──
297
- console.log('');
298
- console.log(` ${c.cyan}╔═══════════════════════════════════╗${c.reset}`);
299
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
300
- console.log(` ${c.cyan}║${c.reset} ${c.bold}${c.cyan}[██] [██]${c.reset} ${c.bold}${c.cyan}SynaBun${c.reset} ${c.cyan}║${c.reset}`);
301
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
302
- console.log(` ${c.cyan}║${c.reset} ${c.dim}Persistent Vector Memory${c.reset} ${c.cyan}║${c.reset}`);
303
- console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
304
- console.log(` ${c.cyan}╚═══════════════════════════════════╝${c.reset}`);
305
- console.log(` ${c.dim}synabun.ai${c.reset}`);
306
- console.log('');
307
-
308
- // Prerequisites
309
- checkNodeVersion();
310
- console.log('');
311
-
312
- // Dependencies
313
- installDeps('Neural Interface', resolve(__dirname, 'neural-interface'));
314
- installDeps('MCP Server', resolve(__dirname, 'mcp-server'));
315
- console.log('');
316
-
317
- // Playwright browser
318
- installPlaywrightChromium();
319
- console.log('');
320
-
321
- // Build
322
- buildMcpServer();
323
- console.log('');
324
-
325
- // State
326
- if (isSetupComplete()) {
327
- ok('Setup already complete');
328
- } else {
329
- info('First-time setup \u2014 opening onboarding wizard');
330
- }
331
- console.log('');
332
-
333
- // Launch
334
- startServer();
335
- }
336
-
337
- main();
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SynaBun — Single-command setup & launch
5
+ *
6
+ * Usage: node setup.js (or: npm start)
7
+ *
8
+ * 1. Checks Node.js version (>=22)
9
+ * 2. Ensures data directory exists (~/.synabun or %APPDATA%/synabun)
10
+ * 3. Migrates data from old scaffolded installs if detected
11
+ * 4. Installs npm deps for neural-interface/ and mcp-server/
12
+ * 5. Builds the MCP server TypeScript (if dist/ is missing or stale)
13
+ * 6. Starts the Neural Interface Express server
14
+ * 7. Auto-opens browser to onboarding wizard (or main page if setup complete)
15
+ */
16
+
17
+ import { execSync, spawn, exec } from 'node:child_process';
18
+ import { existsSync, readFileSync, cpSync, readdirSync, mkdirSync } from 'node:fs';
19
+ import { resolve, dirname } from 'node:path';
20
+ import { fileURLToPath } from 'node:url';
21
+ import { platform } from 'node:os';
22
+ import { getDataHome, ensureDataDirs, PACKAGE_ROOT } from './lib/paths.js';
23
+
24
+ const __dirname = dirname(fileURLToPath(import.meta.url));
25
+
26
+ // ── ANSI color helpers ──
27
+
28
+ const c = {
29
+ reset: '\x1b[0m',
30
+ bold: '\x1b[1m',
31
+ dim: '\x1b[2m',
32
+ cyan: '\x1b[36m',
33
+ green: '\x1b[32m',
34
+ yellow: '\x1b[33m',
35
+ red: '\x1b[31m',
36
+ };
37
+
38
+ function ok(msg) { console.log(` ${c.green}\u2713${c.reset} ${msg}`); }
39
+ function warn(msg) { console.log(` ${c.yellow}!${c.reset} ${msg}`); }
40
+ function fail(msg) { console.log(` ${c.red}\u2717${c.reset} ${msg}`); }
41
+ function info(msg) { console.log(` ${c.cyan}\u2192${c.reset} ${msg}`); }
42
+
43
+ // ── Data home resolution ──
44
+
45
+ const DATA_HOME = getDataHome();
46
+
47
+ // ── Migration: detect old scaffolded install in CWD ──
48
+
49
+ function migrateFromScaffold() {
50
+ const cwd = process.cwd();
51
+
52
+ // Don't migrate if we're already in the data home or package root
53
+ if (cwd === DATA_HOME || cwd === PACKAGE_ROOT) return;
54
+
55
+ // Don't migrate if data home already has a .env (already set up)
56
+ if (existsSync(resolve(DATA_HOME, '.env'))) return;
57
+
58
+ // Detect old scaffolded install: has neural-interface/server.js + .env
59
+ const hasScaffold = existsSync(resolve(cwd, 'neural-interface', 'server.js'))
60
+ && existsSync(resolve(cwd, '.env'));
61
+
62
+ if (!hasScaffold) return;
63
+
64
+ info('Detected old scaffolded install — migrating data...');
65
+
66
+ // Migrate .env
67
+ cpSync(resolve(cwd, '.env'), resolve(DATA_HOME, '.env'));
68
+
69
+ // Migrate data/ directory contents
70
+ const dataDir = resolve(cwd, 'data');
71
+ if (existsSync(dataDir)) {
72
+ cpSync(dataDir, resolve(DATA_HOME, 'data'), {
73
+ recursive: true,
74
+ filter: (src) => !src.replace(/\\/g, '/').includes('/node_modules'),
75
+ });
76
+ }
77
+
78
+ // Migrate mcp-server/data/ to mcp-data/
79
+ const mcpDataDir = resolve(cwd, 'mcp-server', 'data');
80
+ if (existsSync(mcpDataDir)) {
81
+ cpSync(mcpDataDir, resolve(DATA_HOME, 'mcp-data'), { recursive: true });
82
+ }
83
+
84
+ ok(`Migrated data to ${DATA_HOME}`);
85
+ info('You can safely delete the old scaffolded files from:');
86
+ console.log(` ${c.dim}${cwd}${c.reset}`);
87
+ console.log('');
88
+ }
89
+
90
+ // ── Phase 1: Prerequisite checks ──
91
+
92
+ function checkNodeVersion() {
93
+ const [major] = process.versions.node.split('.').map(Number);
94
+ if (major < 22) {
95
+ fail(`Node.js 22+ required, found v${process.versions.node}`);
96
+ process.exit(1);
97
+ }
98
+ ok(`Node.js v${process.versions.node}`);
99
+ }
100
+
101
+ // ── Phase 2: Dependency installation ──
102
+
103
+ function needsInstall(dir) {
104
+ return !existsSync(resolve(dir, 'node_modules', '.package-lock.json'));
105
+ }
106
+
107
+ function installDeps(name, dir, { includeDev = false } = {}) {
108
+ if (!needsInstall(dir)) {
109
+ ok(`${name} dependencies already installed`);
110
+ return;
111
+ }
112
+
113
+ info(`Installing ${name} dependencies...`);
114
+ try {
115
+ const omitFlag = includeDev ? '' : ' --omit=dev';
116
+ execSync(`npm install${omitFlag} --ignore-scripts`, {
117
+ cwd: dir,
118
+ stdio: 'inherit',
119
+ timeout: 300_000,
120
+ });
121
+ ok(`${name} dependencies installed`);
122
+ } catch (err) {
123
+ fail(`Failed to install ${name} dependencies`);
124
+ console.error(err.stderr?.toString() || err.message);
125
+ process.exit(1);
126
+ }
127
+ }
128
+
129
+ // ── Phase 3: Playwright Chromium (for browser automation) ──
130
+
131
+ function installPlaywrightChromium() {
132
+ const niDir = resolve(PACKAGE_ROOT, 'neural-interface');
133
+ try {
134
+ const result = execSync('node -e "const pw=require(\'playwright\');const p=pw.chromium.executablePath();process.stdout.write(p)"', {
135
+ cwd: niDir, encoding: 'utf8', timeout: 10_000,
136
+ });
137
+ if (existsSync(result)) {
138
+ ok('Playwright Chromium already installed');
139
+ return;
140
+ }
141
+ } catch { /* not installed */ }
142
+
143
+ info('Installing Playwright Chromium (for browser automation)...');
144
+ try {
145
+ execSync('npx playwright install chromium', {
146
+ cwd: niDir,
147
+ stdio: 'inherit',
148
+ timeout: 120_000,
149
+ });
150
+ ok('Playwright Chromium installed');
151
+ } catch (err) {
152
+ console.log(' (optional) Playwright Chromium install failed — system Chrome will be used');
153
+ }
154
+ }
155
+
156
+ // ── Phase 4: MCP server build ──
157
+
158
+ function needsBuild() {
159
+ const distIndex = resolve(PACKAGE_ROOT, 'mcp-server', 'dist', 'index.js');
160
+ return !existsSync(distIndex);
161
+ }
162
+
163
+ function buildMcpServer() {
164
+ if (!needsBuild()) {
165
+ ok('MCP server already built');
166
+ return;
167
+ }
168
+
169
+ warn('MCP server dist/ not found — attempting build (requires TypeScript)');
170
+ try {
171
+ execSync('npx tsc', {
172
+ cwd: resolve(PACKAGE_ROOT, 'mcp-server'),
173
+ stdio: 'pipe',
174
+ timeout: 60_000,
175
+ });
176
+ ok('MCP server built');
177
+ } catch (err) {
178
+ warn('MCP server build failed (TypeScript not available) — reinstall with: npm install -g synabun@latest');
179
+ }
180
+ }
181
+
182
+ // ── Phase 5: Setup state detection ──
183
+
184
+ function isSetupComplete() {
185
+ const envPath = resolve(DATA_HOME, '.env');
186
+ try {
187
+ const content = readFileSync(envPath, 'utf-8');
188
+ for (const line of content.split('\n')) {
189
+ const trimmed = line.trim();
190
+ if (trimmed.startsWith('SETUP_COMPLETE=')) {
191
+ return trimmed.split('=')[1] === 'true';
192
+ }
193
+ }
194
+ } catch {}
195
+ return false;
196
+ }
197
+
198
+ // ── Phase 6: Server launch + browser open ──
199
+
200
+ function openBrowser(url) {
201
+ const plat = platform();
202
+ const fallback = () => { warn('Could not open browser automatically'); info(`Open manually: ${url}`); };
203
+
204
+ if (plat === 'win32') {
205
+ exec(`start "" "${url}"`, (err) => { if (err) fallback(); });
206
+ } else if (plat === 'darwin') {
207
+ exec(`open "${url}"`, (err) => { if (err) fallback(); });
208
+ } else {
209
+ exec(`xdg-open "${url}"`, (err) => { if (err) fallback(); });
210
+ }
211
+ }
212
+
213
+ function startServer() {
214
+ const serverPath = resolve(PACKAGE_ROOT, 'neural-interface', 'server.js');
215
+
216
+ info('Starting Neural Interface server...');
217
+ console.log('');
218
+
219
+ const child = spawn('node', [serverPath], {
220
+ cwd: resolve(PACKAGE_ROOT, 'neural-interface'),
221
+ stdio: ['ignore', 'pipe', 'pipe'],
222
+ env: {
223
+ ...process.env,
224
+ SYNABUN_DATA_HOME: DATA_HOME,
225
+ MEMORY_DATA_DIR: resolve(DATA_HOME, 'mcp-data'),
226
+ },
227
+ });
228
+
229
+ let opened = false;
230
+ const setupComplete = isSetupComplete();
231
+
232
+ child.stdout.on('data', (data) => {
233
+ process.stdout.write(data.toString());
234
+
235
+ if (!opened && data.toString().includes('Server:')) {
236
+ opened = true;
237
+ const port = data.toString().match(/Server:\s+http:\/\/localhost:(\d+)/)?.[1] || '3344';
238
+ const url = `http://localhost:${port}${setupComplete ? '/' : '/onboarding.html'}`;
239
+
240
+ if (!setupComplete) setTimeout(() => openBrowser(url), 1500);
241
+ }
242
+ });
243
+
244
+ child.stderr.on('data', (data) => {
245
+ process.stderr.write(data.toString());
246
+ });
247
+
248
+ child.on('exit', (code) => {
249
+ if (code !== 0 && code !== null) {
250
+ fail(`Server exited with code ${code}`);
251
+ process.exit(code);
252
+ }
253
+ });
254
+
255
+ // Clean shutdown on Ctrl+C
256
+ process.on('SIGINT', () => { child.kill('SIGINT'); process.exit(0); });
257
+ process.on('SIGTERM', () => { child.kill('SIGTERM'); process.exit(0); });
258
+ }
259
+
260
+ // ── Main ──
261
+
262
+ function main() {
263
+ const version = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf-8')).version;
264
+
265
+ // Banner
266
+ console.log('');
267
+ console.log(` ${c.cyan}╔═══════════════════════════════════╗${c.reset}`);
268
+ console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
269
+ console.log(` ${c.cyan}║${c.reset} ${c.bold}${c.cyan}[██] [██]${c.reset} ${c.bold}${c.cyan}SynaBun${c.reset} ${c.cyan}║${c.reset}`);
270
+ console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
271
+ console.log(` ${c.cyan}║${c.reset} ${c.dim}Persistent Vector Memory${c.reset} ${c.cyan}║${c.reset}`);
272
+ console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);
273
+ console.log(` ${c.cyan}╚═══════════════════════════════════╝${c.reset}`);
274
+ console.log(` ${c.dim}synabun.ai${c.reset}`);
275
+ console.log('');
276
+ info(`v${version}`);
277
+ info(`Data: ${DATA_HOME}`);
278
+ console.log('');
279
+
280
+ // Ensure data directories
281
+ ensureDataDirs(DATA_HOME);
282
+
283
+ // Migrate old scaffolded installs
284
+ migrateFromScaffold();
285
+
286
+ // Prerequisites
287
+ checkNodeVersion();
288
+ console.log('');
289
+
290
+ // Dependencies (installed in global package location)
291
+ installDeps('Neural Interface', resolve(PACKAGE_ROOT, 'neural-interface'));
292
+ installDeps('MCP Server', resolve(PACKAGE_ROOT, 'mcp-server'), { includeDev: needsBuild() });
293
+ console.log('');
294
+
295
+ // Playwright browser
296
+ installPlaywrightChromium();
297
+ console.log('');
298
+
299
+ // Build
300
+ buildMcpServer();
301
+ console.log('');
302
+
303
+ // State
304
+ if (isSetupComplete()) {
305
+ ok('Setup already complete');
306
+ } else {
307
+ info('First-time setup \u2014 opening onboarding wizard');
308
+ }
309
+ console.log('');
310
+
311
+ // Launch
312
+ startServer();
313
+ }
314
+
315
+ main();