synabun 2026.3.308 → 2026.3.310

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/CLAUDE.md ADDED
@@ -0,0 +1,150 @@
1
+ # CLAUDE.md
2
+
3
+ ====================================END SYNBUN
4
+
5
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
6
+
7
+ ## What is SynaBun
8
+
9
+ Persistent vector memory for AI assistants — SQLite + local Transformers.js embeddings (all-MiniLM-L6-v2, 384 dims). No external services, no API keys, no Docker. Installed via `npm install -g synabun` or cloned from GitHub.
10
+
11
+ ## Commands
12
+
13
+ ```bash
14
+ # Start everything (installs deps, builds MCP, launches Neural Interface)
15
+ npm start # runs setup.js
16
+
17
+ # Neural Interface only (Express server on :3344)
18
+ node neural-interface/server.js
19
+
20
+ # Build MCP server (required after any TypeScript change)
21
+ cd mcp-server && npm run build # ALWAYS use npm run build, never npx tsc
22
+
23
+ # Dev mode (auto-rebuild on change)
24
+ cd mcp-server && npm run dev # tsx src/index.ts
25
+
26
+ # Tests
27
+ cd mcp-server && npm test # vitest run
28
+ cd mcp-server && npm run test:watch # vitest watch mode
29
+
30
+ # Register MCP with Claude Code
31
+ claude mcp add SynaBun -s user -- node "/path/to/Synabun/mcp-server/run.mjs"
32
+ ```
33
+
34
+ ## Architecture
35
+
36
+ Two independently running processes that share one SQLite database:
37
+
38
+ ### MCP Server (`mcp-server/`)
39
+ TypeScript, compiles to `dist/`. Entry point: `run.mjs` → `dist/preload.js` → `dist/index.js`. Communicates with AI tools via MCP protocol (stdio or HTTP). 72 tools across 9 groups: Memory (8), Browser (39), Whiteboard (5), Cards (5), Discord (8), Leonardo (4), Git (1), Loop (1), TicTacToe (1).
40
+
41
+ - `src/index.ts` — tool registration, schema refresh, server creation
42
+ - `src/config.ts` — env config, project detection from `claude-code-projects.json`
43
+ - `src/types.ts` — `MemoryPayload` interface
44
+ - `src/http.ts` — HTTP MCP transport (stateless, fresh server per request)
45
+ - `src/services/sqlite.ts` — storage layer, vectors as Float32Array BLOBs, cosine similarity in JS
46
+ - `src/services/local-embeddings.ts` — Transformers.js embedding generation
47
+ - `src/services/categories.ts` — category CRUD, hierarchy, dynamic schema refresh
48
+ - `src/tools/` — one file per tool (or per tool group with a registration function)
49
+
50
+ ### Neural Interface (`neural-interface/`)
51
+ Express server (`server.js`, ~15K lines) serving a vanilla JS + Three.js frontend. 55+ REST API endpoints. Runs on port 3344.
52
+
53
+ - `server.js` — monolithic Express server with all API routes, WebSocket, terminal (node-pty), Playwright browser management, session registry, loop orchestration
54
+ - `public/index.html` — 3D force-directed graph (Three.js)
55
+ - `public/index2d.html` — 2D canvas variant
56
+ - `public/onboarding.html` — first-time setup wizard
57
+ - `public/claude-chat.html` — standalone Claude Code chat page
58
+ - `public/shared/` — modular JS/CSS (~50 files): `ui-*.js` modules, `storage.js` (server-synced state), `styles.css`, `utils.js`
59
+ - `lib/db.js` — shared SQLite access (same DB as MCP server)
60
+ - `lib/session-indexer.js` — conversation session indexing
61
+ - `skins/` — theme system (CSS + JSON per skin)
62
+ - `i18n/` — internationalization (en.json)
63
+ - `templates/CLAUDE-template.md` — source of truth for memory rulesets injected into project CLAUDE.md files
64
+
65
+ ### Hooks (`hooks/claude-code/`)
66
+ 7 ESM (.mjs) lifecycle hooks for Claude Code, registered in `.claude/settings.json`:
67
+
68
+ | Hook | Event | Purpose |
69
+ |------|-------|---------|
70
+ | `session-start.mjs` | SessionStart | Greeting, boot sequence, compaction recovery, loop detection |
71
+ | `prompt-submit.mjs` | UserPromptSubmit | Tiered recall nudges, loop iteration injection |
72
+ | `pre-compact.mjs` | PreCompact | Transcript capture before context compaction |
73
+ | `stop.mjs` | Stop | Combined obligations: compaction, loops, task memory, user learning |
74
+ | `post-remember.mjs` | PostToolUse | Edit tracking, flag clearing, user learning |
75
+ | `pre-websearch.mjs` | PreToolUse | Blocks WebSearch/WebFetch during active browser sessions |
76
+ | `post-plan.mjs` | PostToolUse | Auto-stores plans as memories on ExitPlanMode |
77
+
78
+ `shared.mjs` contains utilities used across hooks.
79
+
80
+ ### Skills (`skills/`)
81
+ Claude Code slash commands with `SKILL.md` entry points and `modules/` subdirectories:
82
+ - `synabun/` — `/synabun` command hub (brainstorm, audit, health, search)
83
+ - `leonardo/` — `/leonardo` master media prompter (video, image, model advisor, prompt library)
84
+
85
+ ### Data Flow
86
+ ```
87
+ AI tool calls remember → MCP server embeds content → SQLite stores vector + payload
88
+ AI tool calls recall → MCP server embeds query → cosine similarity search → re-ranked results
89
+ Neural Interface → reads same SQLite DB → renders 3D graph, REST API, WebSocket
90
+ Hooks → fire on Claude Code events → inject context, enforce memory obligations
91
+ ```
92
+
93
+ ### Shared State
94
+ - `data/memory.db` — single SQLite database (memories, session chunks, trash)
95
+ - `data/ui-state.json` — Neural Interface UI state (synced via `storage.js`)
96
+ - `data/pending-compact/` — per-session compaction flags
97
+ - `data/pending-remember/` — per-session edit tracking flags
98
+ - `mcp-server/data/custom-categories-{connId}.json` — per-connection category definitions
99
+
100
+ ## Key Patterns
101
+
102
+ - **Category descriptions are routing instructions**: Claude reads all category descriptions from the MCP tool schema to decide where to store memories. Descriptions must be prescriptive rules, not vague labels.
103
+ - **Dynamic schema refresh**: Category changes trigger `refreshCategorySchemas()` → `notifications/tools/list_changed` → Claude re-fetches tool list. No restart needed.
104
+ - **Per-tab session isolation**: Project/model/effort state stored per-tab in sidepanel (`ui-claude-panel.js`) and per-tab via `sessionStorage` in standalone (`claude-chat.js`). Global `ui-state.json` keys are "last used" defaults only.
105
+ - **Browser sessions**: Playwright with persistent Chromium profiles. Multi-session isolation via dedicated browser session per automation. Leonardo tools are 100% browser-based (no API key).
106
+ - **Compaction chain**: PreCompact → flag → SessionStart injects → Claude remembers(conversations) → PostToolUse clears → Stop allows.
107
+ - **MCP calls must be sequential**: Parallel MCP tool calls can cascade failures. Always call memory tools one at a time.
108
+
109
+ ## Environment
110
+
111
+ - Node.js 22.5+ required (for built-in `node:sqlite`)
112
+ - `.env` at project root — `SQLITE_DB_PATH`, `NEURAL_PORT` (default 3344), `DISCORD_BOT_TOKEN`, `DISCORD_GUILD_ID`
113
+ - CLAUDE.md is gitignored (user-local)
114
+
115
+ ## Persistent Memory
116
+
117
+ You have persistent vector memory via the `SynaBun` MCP server (72 tools).
118
+ Core memory tools: remember, recall, forget, restore, reflect, memories, sync, category.
119
+
120
+ ### Auto-Recall
121
+ - Session start: recall current project context
122
+ - Topic mentioned: recall what you know
123
+ - Before architecture decisions: recall past decisions
124
+ - Debugging: recall similar bugs
125
+
126
+ ### Auto-Remember (MANDATORY)
127
+ After ANY task (bug fix, feature, refactor, config change, investigation, architecture decision), MUST `remember` BEFORE responding.
128
+
129
+ Steps:
130
+ 1. `remember` — what + why + how, appropriate category, set project, related_files, importance (5=routine, 6-7=significant, 8+=critical), and 3-5 tags
131
+
132
+ `remember` returns the full UUID and accepts all fields (tags, importance) directly. No need to recall+reflect afterward.
133
+
134
+ **NOT triggered by:** Simple Q&A, file reads with no findings, trivial typos.
135
+
136
+ **Importance scale:** 1-2=trivial, 3-4=low, 5=normal, 6-7=significant, 8-9=critical, 10=foundational. User says "remember this" → 8+. Architecture decisions → 8+.
137
+
138
+ ### Response Ordering
139
+ When finishing a task: call `remember` (and any other memory tools) **FIRST**, then write your completion summary **LAST**. Never summary-then-tools — the stop hook will block you.
140
+
141
+ ### Tool Quirks
142
+ - `remember` returns the full UUID and accepts tags + importance directly.
143
+ - `reflect` requires FULL UUID — use the one from `remember`, or `recall` to find existing memories.
144
+ - Sequential MCP calls only — never parallel.
145
+
146
+ ### Plan Mode
147
+ When in plan mode and you need clarification:
148
+ - ALWAYS use `AskUserQuestion` to present options — never plain text questions
149
+ - Use `ExitPlanMode` for final plan approval
150
+ - Use `multiSelect: true` in AskUserQuestion when options aren't mutually exclusive
@@ -309,7 +309,7 @@ async function main() {
309
309
  if (toolName === 'EnterPlanMode') {
310
310
  debugLog('EnterPlanMode fired — injecting AskUserQuestion reminder');
311
311
  process.stdout.write(JSON.stringify({
312
- additionalContext: `SynaBun: You are now in plan mode. When you have questions or need clarification from the user, you MUST use the \`AskUserQuestion\` tool — do NOT write questions as plain text. Load it via \`ToolSearch\` if its schema is not yet available. Use \`ExitPlanMode\` for final plan approval.\n\n**Plan file location**: Write plan files to \`${PLANS_DIR}/\` — NOT \`~/.claude/plans/\`. This avoids sensitive-file permission prompts that stall the session.`,
312
+ additionalContext: `SynaBun: You are now in plan mode.\n\n**CRITICAL CONSTRAINT**: Do NOT make code changes (Edit, Write, NotebookEdit). Plan mode is RESEARCH AND PLANNING ONLY. Investigate, read files, search code — then present your plan. Do NOT implement anything until the user explicitly approves the plan and you exit plan mode.\n\nWhen you have questions or need clarification, you MUST use the \`AskUserQuestion\` tool — do NOT write questions as plain text. Load it via \`ToolSearch\` if its schema is not yet available. Use \`ExitPlanMode\` for final plan approval.\n\n**Plan file location**: Write plan files to \`${PLANS_DIR}/\` — NOT \`~/.claude/plans/\`. This avoids sensitive-file permission prompts.`,
313
313
  }));
314
314
  return;
315
315
  }
@@ -464,6 +464,8 @@ async function main() {
464
464
  ``,
465
465
  `If an autonomous loop was active before compaction, the Stop hook will resume it automatically. Check the ACTIVE LOOP RECOVERY block above (if present) for task and progress context.`,
466
466
  ``,
467
+ `**Plan files**: Write plan files to the project's \`data/plans/\` directory — NOT \`~/.claude/plans/\`. This avoids sensitive-file permission prompts that don't persist across compactions.`,
468
+ ``,
467
469
  `---`,
468
470
  ``,
469
471
  );
@@ -8925,10 +8925,10 @@
8925
8925
  .settings-panel {
8926
8926
  position: fixed;
8927
8927
  z-index: 300000;
8928
- width: 720px;
8928
+ width: 900px;
8929
8929
  max-width: 95vw;
8930
8930
  min-width: 480px;
8931
- height: 500px;
8931
+ height: 650px;
8932
8932
  max-height: 88vh;
8933
8933
  min-height: 320px;
8934
8934
  display: flex;
@@ -2678,8 +2678,8 @@ export async function openSettingsModal() {
2678
2678
  overlay.id = 'settings-panel';
2679
2679
 
2680
2680
  // Always open centered at default size
2681
- overlay.style.left = Math.max(20, (window.innerWidth - 720) / 2) + 'px';
2682
- overlay.style.top = Math.max(48, (window.innerHeight - 500) / 2) + 'px';
2681
+ overlay.style.left = Math.max(20, (window.innerWidth - 900) / 2) + 'px';
2682
+ overlay.style.top = Math.max(48, (window.innerHeight - 650) / 2) + 'px';
2683
2683
 
2684
2684
  // ── Build variant tab bodies ──
2685
2685
  let variantTabBodies = '';
@@ -6161,9 +6161,22 @@ app.get('/api/system/backup', async (req, res) => {
6161
6161
  for (const f of ['ui-state.json', 'greeting-config.json', 'hook-features.json',
6162
6162
  'claude-code-projects.json', 'mcp-api-key.json', 'keybinds.json', 'cli-config.json',
6163
6163
  'invite-key.json', 'invite-proxy.json', 'invite-permissions.json',
6164
- 'loop-templates.json', 'loop-schedules.json', 'auto-backup-config.json']) {
6164
+ 'loop-templates.json', 'loop-schedules.json', 'auto-backup-config.json',
6165
+ 'cost-tracking.json', 'skin-config.json', 'image-favorites.json',
6166
+ 'browser-config.json', 'browser-storage.json', 'last-session.json',
6167
+ 'invite-sessions.json']) {
6165
6168
  addFile(`data/${f}`, resolve(dataDir, f));
6166
6169
  }
6170
+
6171
+ // Session history caches (dynamically named per project cwd)
6172
+ if (existsSync(dataDir)) {
6173
+ for (const f of readdirSync(dataDir)) {
6174
+ if (f.startsWith('sessions-cache-') && f.endsWith('.json')) {
6175
+ addFile(`data/${f}`, resolve(dataDir, f));
6176
+ }
6177
+ }
6178
+ }
6179
+
6167
6180
  addJsonDir('data/pending-remember', resolve(dataDir, 'pending-remember'));
6168
6181
  addJsonDir('data/pending-compact', resolve(dataDir, 'pending-compact'));
6169
6182
  addJsonDir('data/loop', resolve(dataDir, 'loop'));
@@ -6172,6 +6185,9 @@ app.get('/api/system/backup', async (req, res) => {
6172
6185
  addFile('data/custom-icons.json', resolve(dataDir, 'custom-icons.json'));
6173
6186
  addDirRecursive('data/custom-icons', resolve(dataDir, 'custom-icons'));
6174
6187
 
6188
+ // Screenshots, whiteboard captures, staged images
6189
+ addDirRecursive('data/images', resolve(dataDir, 'images'));
6190
+
6175
6191
  // 3. mcp-server/data/
6176
6192
  if (existsSync(CATEGORIES_DATA_DIR)) {
6177
6193
  for (const f of readdirSync(CATEGORIES_DATA_DIR)) {
@@ -6209,7 +6225,19 @@ app.get('/api/system/backup', async (req, res) => {
6209
6225
  }
6210
6226
  }
6211
6227
 
6212
- // 7. SQLite database file
6228
+ // 7. Installed skins (user-customizable themes)
6229
+ const skinsDir = join(__dirname, 'skins');
6230
+ if (existsSync(skinsDir)) {
6231
+ addDirRecursive('skins', skinsDir);
6232
+ }
6233
+
6234
+ // 8. Global Claude Code settings (hooks, permissions)
6235
+ const home = process.env.USERPROFILE || process.env.HOME || '';
6236
+ if (home) {
6237
+ addFile('claude-settings.json', join(home, '.claude', 'settings.json'));
6238
+ }
6239
+
6240
+ // 9. SQLite database file
6213
6241
  const dbPath = getDbPath();
6214
6242
  if (existsSync(dbPath)) {
6215
6243
  try {
@@ -6346,6 +6374,25 @@ app.post('/api/system/restore',
6346
6374
  writeFileSync(target, entry.getData());
6347
6375
  results.files.push(rel);
6348
6376
  }
6377
+
6378
+ // Installed skins → neural-interface/skins/
6379
+ if (rel.startsWith('skins/') && rel !== 'skins/') {
6380
+ const target = join(__dirname, rel);
6381
+ mkdirSync(dirname(target), { recursive: true });
6382
+ writeFileSync(target, entry.getData());
6383
+ results.files.push(rel);
6384
+ }
6385
+
6386
+ // Global Claude Code settings → ~/.claude/settings.json
6387
+ if (rel === 'claude-settings.json') {
6388
+ const home = process.env.USERPROFILE || process.env.HOME || '';
6389
+ if (home) {
6390
+ const target = join(home, '.claude', 'settings.json');
6391
+ mkdirSync(dirname(target), { recursive: true });
6392
+ writeFileSync(target, entry.getData());
6393
+ results.files.push(rel);
6394
+ }
6395
+ }
6349
6396
  }
6350
6397
  }
6351
6398
 
@@ -6480,15 +6527,31 @@ function buildBackupZipToFile(destPath) {
6480
6527
  for (const f of ['ui-state.json', 'greeting-config.json', 'hook-features.json',
6481
6528
  'claude-code-projects.json', 'mcp-api-key.json', 'keybinds.json', 'cli-config.json',
6482
6529
  'invite-key.json', 'invite-proxy.json', 'invite-permissions.json',
6483
- 'loop-templates.json', 'loop-schedules.json']) {
6530
+ 'loop-templates.json', 'loop-schedules.json', 'auto-backup-config.json',
6531
+ 'cost-tracking.json', 'skin-config.json', 'image-favorites.json',
6532
+ 'browser-config.json', 'browser-storage.json', 'last-session.json',
6533
+ 'invite-sessions.json']) {
6484
6534
  addFile(`data/${f}`, resolve(dataDir, f));
6485
6535
  }
6536
+
6537
+ // Session history caches (dynamically named per project cwd)
6538
+ if (existsSync(dataDir)) {
6539
+ for (const f of readdirSync(dataDir)) {
6540
+ if (f.startsWith('sessions-cache-') && f.endsWith('.json')) {
6541
+ addFile(`data/${f}`, resolve(dataDir, f));
6542
+ }
6543
+ }
6544
+ }
6545
+
6486
6546
  addJsonDir('data/pending-remember', resolve(dataDir, 'pending-remember'));
6487
6547
  addJsonDir('data/pending-compact', resolve(dataDir, 'pending-compact'));
6488
6548
  addJsonDir('data/loop', resolve(dataDir, 'loop'));
6489
6549
  addFile('data/custom-icons.json', resolve(dataDir, 'custom-icons.json'));
6490
6550
  addDirRecursive('data/custom-icons', resolve(dataDir, 'custom-icons'));
6491
6551
 
6552
+ // Screenshots, whiteboard captures, staged images
6553
+ addDirRecursive('data/images', resolve(dataDir, 'images'));
6554
+
6492
6555
  if (existsSync(CATEGORIES_DATA_DIR)) {
6493
6556
  for (const f of readdirSync(CATEGORIES_DATA_DIR)) {
6494
6557
  if (f.endsWith('.json')) addFile(`mcp-data/${f}`, resolve(CATEGORIES_DATA_DIR, f));
@@ -6518,6 +6581,18 @@ function buildBackupZipToFile(destPath) {
6518
6581
  }
6519
6582
  }
6520
6583
 
6584
+ // Installed skins (user-customizable themes)
6585
+ const skinsDir = join(__dirname, 'skins');
6586
+ if (existsSync(skinsDir)) {
6587
+ addDirRecursive('skins', skinsDir);
6588
+ }
6589
+
6590
+ // Global Claude Code settings (hooks, permissions)
6591
+ const home = process.env.USERPROFILE || process.env.HOME || '';
6592
+ if (home) {
6593
+ addFile('claude-settings.json', join(home, '.claude', 'settings.json'));
6594
+ }
6595
+
6521
6596
  const dbPath = getDbPath();
6522
6597
  if (existsSync(dbPath)) {
6523
6598
  try {
@@ -7548,9 +7623,9 @@ function ensureSynaBunPermissions(settings) {
7548
7623
  if (!settings.permissions) settings.permissions = {};
7549
7624
  if (!Array.isArray(settings.permissions.allow)) settings.permissions.allow = [];
7550
7625
 
7551
- // Plan files now write to data/plans/ (project-local) instead of ~/.claude/plans/.
7552
- // No special Write permission needed — data/ is within the project root.
7553
- // Clean up legacy rule if present from older installations.
7626
+ // Plan files write to data/plans/ (project-local) to avoid ~/.claude/ sensitive-file
7627
+ // permission prompts. No Write permission rule needed — data/ is within project root.
7628
+ // Clean up any stale Write(~/.claude/plans/*) rules from older installations.
7554
7629
  const home = process.env.USERPROFILE || process.env.HOME || '';
7555
7630
  const legacyPlanRule = `Write(${join(home, '.claude', 'plans', '*')})`;
7556
7631
  const legacyIdx = settings.permissions.allow.indexOf(legacyPlanRule);
@@ -8625,9 +8700,8 @@ app.put('/api/greeting/config/:project', (req, res) => {
8625
8700
  // Supports ?format=claude (default) | cursor | generic
8626
8701
  app.get('/api/claude-code/ruleset', (req, res) => {
8627
8702
  try {
8628
- const claudeMdPath = resolve(PROJECT_ROOT, 'CLAUDE.md');
8629
8703
  const templatePath = resolve(__dirname, 'templates', 'CLAUDE-template.md');
8630
- const sourcePath = existsSync(claudeMdPath) ? claudeMdPath : templatePath;
8704
+ const sourcePath = templatePath;
8631
8705
  if (!existsSync(sourcePath)) {
8632
8706
  return res.status(404).json({ error: 'CLAUDE.md template not found' });
8633
8707
  }
@@ -66,7 +66,9 @@ Never store directly in parent categories.
66
66
 
67
67
  ### Plan Mode (MANDATORY)
68
68
 
69
- When in plan mode and you need clarification or have questions:
69
+ **CRITICAL**: Plan mode = research and planning ONLY. Do NOT use Edit, Write, or NotebookEdit. Read files, search code, investigate — then present the plan. Do NOT implement until the user approves and you exit plan mode.
70
+
71
+ When you need clarification or have questions:
70
72
  - **ALWAYS** use `AskUserQuestion` to present options — never write questions as plain text
71
73
  - Load the tool via `ToolSearch` first if its schema is not yet available
72
74
  - Structure as distinct choices (2-4 options per question, max 4 questions)
@@ -161,6 +163,12 @@ Tools: remember, recall, forget, restore, reflect, memories, sync, category_*
161
163
 
162
164
  ---
163
165
 
166
+ ## Plan Files
167
+
168
+ When in plan mode, write plan files to the project's `data/plans/` directory — **NOT** `~/.claude/plans/`. The `~/.claude/` directory is treated as sensitive by Claude Code, causing repeated permission prompts that don't persist across context compactions. The `data/plans/` directory is within the project root and requires no special permissions.
169
+
170
+ ---
171
+
164
172
  ## Hook System
165
173
 
166
174
  7 Claude Code hooks in `hooks/claude-code/`, registered in `~/.claude/settings.json`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synabun",
3
- "version": "2026.3.308",
3
+ "version": "2026.3.310",
4
4
  "description": "Persistent vector memory for AI assistants",
5
5
  "license": "Apache-2.0",
6
6
  "author": "The SynaBun Authors",
@@ -16,6 +16,7 @@
16
16
  "files": [
17
17
  "setup.js",
18
18
  "postinstall.js",
19
+ "preuninstall.js",
19
20
  "mcp-server/package.json",
20
21
  "mcp-server/dist/",
21
22
  "mcp-server/tsconfig.json",
@@ -40,7 +41,8 @@
40
41
  ],
41
42
  "scripts": {
42
43
  "start": "node setup.js",
43
- "postinstall": "node postinstall.js"
44
+ "postinstall": "node postinstall.js",
45
+ "preuninstall": "node preuninstall.js"
44
46
  },
45
47
  "keywords": [
46
48
  "claude",
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SynaBun — NPM preuninstall script
5
+ *
6
+ * Runs before `npm uninstall synabun`.
7
+ * Removes nested node_modules that cause EPERM/deep-path errors on Windows.
8
+ * Graceful — if cleanup fails, don't block the uninstall.
9
+ */
10
+
11
+ import { rmSync } from 'node:fs';
12
+ import { resolve, dirname } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const __dirname = dirname(fileURLToPath(import.meta.url));
16
+
17
+ const dirs = [
18
+ resolve(__dirname, 'neural-interface', 'node_modules'),
19
+ resolve(__dirname, 'mcp-server', 'node_modules'),
20
+ ];
21
+
22
+ for (const dir of dirs) {
23
+ try {
24
+ rmSync(dir, { recursive: true, force: true });
25
+ } catch {
26
+ // Non-fatal — npm will handle remaining cleanup
27
+ }
28
+ }
package/setup.js CHANGED
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import { execSync, spawn, exec } from 'node:child_process';
16
- import { existsSync, readFileSync } from 'node:fs';
16
+ import { existsSync, readFileSync, cpSync } from 'node:fs';
17
17
  import { resolve, dirname } from 'node:path';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { platform } from 'node:os';
@@ -37,6 +37,45 @@ function warn(msg) { console.log(` ${c.yellow}!${c.reset} ${msg}`); }
37
37
  function fail(msg) { console.log(` ${c.red}\u2717${c.reset} ${msg}`); }
38
38
  function info(msg) { console.log(` ${c.cyan}\u2192${c.reset} ${msg}`); }
39
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
+
40
79
  // ── Phase 1: Prerequisite checks ──
41
80
 
42
81
  function checkNodeVersion() {
@@ -209,6 +248,52 @@ function startServer() {
209
248
  // ── Main ──
210
249
 
211
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 ──
212
297
  console.log('');
213
298
  console.log(` ${c.cyan}╔═══════════════════════════════════╗${c.reset}`);
214
299
  console.log(` ${c.cyan}║${c.reset} ${c.cyan}║${c.reset}`);