memobank-cli 0.2.0 → 0.4.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 (70) hide show
  1. package/dist/cli.js +95 -12
  2. package/dist/cli.js.map +1 -1
  3. package/dist/commands/capture.d.ts +1 -0
  4. package/dist/commands/capture.d.ts.map +1 -1
  5. package/dist/commands/capture.js +14 -4
  6. package/dist/commands/capture.js.map +1 -1
  7. package/dist/commands/install.d.ts +1 -0
  8. package/dist/commands/install.d.ts.map +1 -1
  9. package/dist/commands/install.js +56 -4
  10. package/dist/commands/install.js.map +1 -1
  11. package/dist/commands/onboarding.d.ts +8 -7
  12. package/dist/commands/onboarding.d.ts.map +1 -1
  13. package/dist/commands/onboarding.js +336 -343
  14. package/dist/commands/onboarding.js.map +1 -1
  15. package/dist/commands/recall.d.ts +4 -2
  16. package/dist/commands/recall.d.ts.map +1 -1
  17. package/dist/commands/recall.js +20 -29
  18. package/dist/commands/recall.js.map +1 -1
  19. package/dist/commands/scan.d.ts +27 -0
  20. package/dist/commands/scan.d.ts.map +1 -0
  21. package/dist/commands/scan.js +147 -0
  22. package/dist/commands/scan.js.map +1 -0
  23. package/dist/commands/team.d.ts +18 -0
  24. package/dist/commands/team.d.ts.map +1 -0
  25. package/dist/commands/team.js +215 -0
  26. package/dist/commands/team.js.map +1 -0
  27. package/dist/config.d.ts.map +1 -1
  28. package/dist/config.js +2 -0
  29. package/dist/config.js.map +1 -1
  30. package/dist/core/embedding.d.ts +1 -1
  31. package/dist/core/embedding.d.ts.map +1 -1
  32. package/dist/core/embedding.js +13 -0
  33. package/dist/core/embedding.js.map +1 -1
  34. package/dist/core/reranker.d.ts +14 -0
  35. package/dist/core/reranker.d.ts.map +1 -0
  36. package/dist/core/reranker.js +64 -0
  37. package/dist/core/reranker.js.map +1 -0
  38. package/dist/core/retriever.d.ts +2 -5
  39. package/dist/core/retriever.d.ts.map +1 -1
  40. package/dist/core/retriever.js +51 -16
  41. package/dist/core/retriever.js.map +1 -1
  42. package/dist/core/sanitizer.d.ts +10 -0
  43. package/dist/core/sanitizer.d.ts.map +1 -1
  44. package/dist/core/sanitizer.js +57 -39
  45. package/dist/core/sanitizer.js.map +1 -1
  46. package/dist/core/store.d.ts +9 -19
  47. package/dist/core/store.d.ts.map +1 -1
  48. package/dist/core/store.js +69 -47
  49. package/dist/core/store.js.map +1 -1
  50. package/dist/engines/lancedb-engine.d.ts.map +1 -1
  51. package/dist/engines/lancedb-engine.js +2 -1
  52. package/dist/engines/lancedb-engine.js.map +1 -1
  53. package/dist/platforms/claude-code.d.ts.map +1 -1
  54. package/dist/platforms/claude-code.js +13 -0
  55. package/dist/platforms/claude-code.js.map +1 -1
  56. package/dist/platforms/gemini.d.ts +7 -0
  57. package/dist/platforms/gemini.d.ts.map +1 -0
  58. package/dist/platforms/gemini.js +87 -0
  59. package/dist/platforms/gemini.js.map +1 -0
  60. package/dist/platforms/qwen.d.ts +7 -0
  61. package/dist/platforms/qwen.d.ts.map +1 -0
  62. package/dist/platforms/qwen.js +87 -0
  63. package/dist/platforms/qwen.js.map +1 -0
  64. package/dist/types.d.ts +20 -0
  65. package/dist/types.d.ts.map +1 -1
  66. package/package.json +3 -2
  67. package/dist/commands/setup.d.ts +0 -9
  68. package/dist/commands/setup.d.ts.map +0 -1
  69. package/dist/commands/setup.js +0 -354
  70. package/dist/commands/setup.js.map +0 -1
@@ -1,8 +1,12 @@
1
1
  "use strict";
2
2
  /**
3
- * Interactive Onboarding Command
4
- * Unified setup flow with interactive menu selection
5
- * Replaces separate install and setup commands
3
+ * Onboarding command (memo init)
4
+ * 4-step interactive TUI setup wizard using Ink
5
+ *
6
+ * ink, ink-text-input, and ink-select-input are ESM-only packages that cannot be
7
+ * require()'d from a CommonJS bundle. All imports of those packages are done via
8
+ * a Function-constructor-based dynamic import() so TypeScript does not rewrite
9
+ * them to require() calls.
6
10
  */
7
11
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
12
  if (k2 === undefined) k2 = k;
@@ -41,380 +45,369 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
45
  exports.onboardingCommand = onboardingCommand;
42
46
  const fs = __importStar(require("fs"));
43
47
  const path = __importStar(require("path"));
44
- const readline = __importStar(require("readline"));
45
48
  const child_process_1 = require("child_process");
46
- const util_1 = require("util");
47
49
  const store_1 = require("../core/store");
48
50
  const config_1 = require("../config");
49
- const import_1 = require("./import");
50
51
  const claude_code_1 = require("../platforms/claude-code");
51
52
  const codex_1 = require("../platforms/codex");
53
+ const gemini_1 = require("../platforms/gemini");
54
+ const qwen_1 = require("../platforms/qwen");
52
55
  const cursor_1 = require("../platforms/cursor");
53
- const execAsync = (0, util_1.promisify)(child_process_1.exec);
54
- /**
55
- * Create readline interface with arrow key support
56
- */
57
- function createReadline() {
58
- return readline.createInterface({
59
- input: process.stdin,
60
- output: process.stdout,
61
- terminal: true,
62
- });
63
- }
64
- /**
65
- * Show interactive menu with arrow key navigation
66
- */
67
- function showMenu(rl, title, items, defaultIndex) {
68
- return new Promise((resolve) => {
69
- let selectedIndex = defaultIndex || 0;
70
- let isSelecting = true;
71
- // Hide cursor
72
- process.stdout.write('\x1B[?25l');
73
- const render = () => {
74
- // Clear screen from current position
75
- readline.clearScreenDown(process.stdout);
76
- rl.write('\x1B[G'); // Move to beginning of line
77
- console.log(`\n${title}\n`);
78
- items.forEach((item, index) => {
79
- const isSelected = index === selectedIndex;
80
- const icon = isSelected ? '❯' : ' ';
81
- const check = isSelected ? '◉' : '◯';
82
- const style = isSelected ? '\x1B[36m' : '\x1B[90m'; // Cyan or Gray
83
- const disabled = item.disabled ? '\x1B[90m' : '';
84
- console.log(`${style}${icon} ${check} ${item.label}${item.description ? ` - ${item.description}` : ''}\x1B[39m`);
85
- });
86
- console.log('\n\x1B[90mUse ↑↓ arrows to navigate, Enter to select\x1B[39m');
87
- };
88
- const handleKeypress = (_, key) => {
89
- if (key.name === 'up' && selectedIndex > 0) {
90
- selectedIndex--;
91
- render();
92
- }
93
- else if (key.name === 'down' && selectedIndex < items.length - 1) {
94
- selectedIndex++;
95
- render();
96
- }
97
- else if (key.name === 'return' && !items[selectedIndex].disabled) {
98
- cleanup();
99
- resolve(selectedIndex);
100
- }
101
- else if (key.name === 'c' && key.ctrl) {
102
- cleanup();
103
- process.exit(0);
104
- }
105
- };
106
- const cleanup = () => {
107
- // Show cursor
108
- process.stdout.write('\x1B[?25h');
109
- rl.removeListener('keypress', handleKeypress);
110
- };
111
- rl.on('keypress', handleKeypress);
112
- render();
113
- });
114
- }
115
- /**
116
- * Show checkbox selection (multiple choice)
117
- */
118
- function showCheckbox(rl, title, items) {
119
- return new Promise((resolve) => {
120
- let selectedIndex = 0;
121
- const selected = items.map((i) => i.selected || false);
122
- process.stdout.write('\x1B[?25l');
123
- const render = () => {
124
- readline.clearScreenDown(process.stdout);
125
- rl.write('\x1B[G');
126
- console.log(`\n${title}\n`);
127
- items.forEach((item, index) => {
128
- const isSelected = index === selectedIndex;
129
- const icon = isSelected ? '❯' : ' ';
130
- const check = selected[index] ? '◉' : '◯';
131
- const style = isSelected ? '\x1B[36m' : '\x1B[90m';
132
- console.log(`${style}${icon} [${check}] ${item.label}\x1B[39m`);
133
- });
134
- console.log('\n\x1B[90m↑↓ navigate, Space to toggle, Enter to confirm\x1B[39m');
135
- };
136
- const handleKeypress = (_, key) => {
137
- if (key.name === 'up' && selectedIndex > 0) {
138
- selectedIndex--;
139
- render();
140
- }
141
- else if (key.name === 'down' && selectedIndex < items.length - 1) {
142
- selectedIndex++;
143
- render();
144
- }
145
- else if (key.name === 'space') {
146
- selected[selectedIndex] = !selected[selectedIndex];
147
- render();
148
- }
149
- else if (key.name === 'return') {
150
- cleanup();
151
- resolve(items.filter((_, i) => selected[i]).map((i) => i.value));
152
- }
153
- else if (key.ctrl && key.name === 'c') {
154
- cleanup();
155
- process.exit(0);
156
- }
157
- };
158
- const cleanup = () => {
159
- process.stdout.write('\x1B[?25h');
160
- rl.removeListener('keypress', handleKeypress);
161
- };
162
- rl.on('keypress', handleKeypress);
163
- render();
164
- });
165
- }
166
- /**
167
- * Detect project name
168
- */
169
- async function detectProjectName(cwd) {
56
+ const team_1 = require("./team");
57
+ /** Detect git repo name from cwd */
58
+ function detectProjectName() {
170
59
  try {
171
- const { stdout } = await execAsync('git rev-parse --show-toplevel', { cwd });
172
- return path.basename(stdout.trim());
60
+ const result = (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
61
+ encoding: 'utf-8', stdio: 'pipe',
62
+ }).trim();
63
+ return path.basename(result);
173
64
  }
174
65
  catch {
175
- return path.basename(cwd);
66
+ return path.basename(process.cwd());
176
67
  }
177
68
  }
178
- /**
179
- * Check if Ollama is available
180
- */
181
- async function checkOllama() {
182
- return new Promise((resolve) => {
183
- (0, child_process_1.exec)('ollama list', (error) => {
184
- resolve(!error);
185
- });
186
- });
69
+ /** Detect which platforms are installed */
70
+ function detectPlatforms() {
71
+ const home = process.env.HOME || process.env.USERPROFILE || '';
72
+ const isInPath = (cmd) => {
73
+ try {
74
+ (0, child_process_1.execSync)(`which ${cmd}`, { stdio: 'pipe' });
75
+ return true;
76
+ }
77
+ catch {
78
+ return false;
79
+ }
80
+ };
81
+ return [
82
+ {
83
+ label: 'Claude Code',
84
+ value: 'claude-code',
85
+ hint: fs.existsSync(path.join(home, '.claude', 'settings.json')) ? '✓ detected' : 'not found',
86
+ disabled: false,
87
+ },
88
+ {
89
+ label: 'Codex',
90
+ value: 'codex',
91
+ hint: isInPath('codex') ? '✓ detected' : 'not found',
92
+ },
93
+ {
94
+ label: 'Gemini CLI',
95
+ value: 'gemini',
96
+ hint: (0, gemini_1.detectGemini)() ? '✓ detected' : 'not found',
97
+ },
98
+ {
99
+ label: 'Qwen Code',
100
+ value: 'qwen',
101
+ hint: (0, qwen_1.detectQwen)() ? '✓ detected' : 'not found',
102
+ },
103
+ {
104
+ label: 'Cursor',
105
+ value: 'cursor',
106
+ hint: fs.existsSync(path.join(process.cwd(), '.cursor')) ? '✓ detected' : 'not found',
107
+ },
108
+ ];
187
109
  }
188
- /**
189
- * Main onboarding flow
190
- */
191
- async function onboardingCommand(repoPath) {
192
- const rl = createReadline();
193
- const cwd = process.cwd();
194
- const repoRoot = repoPath ? path.resolve(repoPath) : (0, store_1.findRepoRoot)(cwd);
195
- // Welcome screen
196
- console.clear();
197
- console.log('\n\x1B[36m');
198
- console.log('╔═══════════════════════════════════════════════════════════╗');
199
- console.log('║ ║');
200
- console.log('║ 🧠 Welcome to Memobank CLI Setup ║');
201
- console.log('║ ║');
202
- console.log('║ Persistent memory for AI coding sessions ║');
203
- console.log('║ ║');
204
- console.log('╚═══════════════════════════════════════════════════════════╝');
205
- console.log('\x1B[39m\n');
206
- // Detect project
207
- const projectName = await detectProjectName(cwd);
208
- console.log(`📁 Project: \x1B[36m${projectName}\x1B[39m`);
209
- console.log(`📂 Location: \x1B[90m${repoRoot}\x1B[39m\n`);
210
- // Initialize config if needed
211
- const configPath = path.join(repoRoot, 'meta', 'config.yaml');
212
- let config;
213
- if (!fs.existsSync(configPath)) {
214
- (0, config_1.initConfig)(repoRoot, projectName);
215
- config = (0, config_1.loadConfig)(repoRoot);
216
- console.log('✓ Created new configuration\n');
110
+ /** Get default-selected platform values (detected ones) */
111
+ function getDetectedPlatforms(items) {
112
+ return items.filter(i => i.hint?.includes('✓')).map(i => i.value);
113
+ }
114
+ async function runSetup(state, repoRoot) {
115
+ const summaryLines = [];
116
+ // 1. Init config
117
+ (0, config_1.initConfig)(repoRoot, state.projectName);
118
+ // 2. Create personal/ directory structure
119
+ const personalDir = (0, store_1.getPersonalDir)(repoRoot);
120
+ const TYPES = ['lesson', 'decision', 'workflow', 'architecture'];
121
+ for (const type of TYPES) {
122
+ fs.mkdirSync(path.join(personalDir, type), { recursive: true });
217
123
  }
218
- else {
219
- config = (0, config_1.loadConfig)(repoRoot);
220
- console.log('✓ Found existing configuration\n');
124
+ fs.mkdirSync(path.join(repoRoot, 'memory'), { recursive: true });
125
+ // 3. Migrate existing root-level memories
126
+ const { migrated, skipped } = (0, store_1.migrateToPersonal)(repoRoot);
127
+ if (migrated.length > 0) {
128
+ summaryLines.push(`Migrated ${migrated.length} existing memories to personal/`);
221
129
  }
222
- await new Promise((resolve) => setTimeout(resolve, 1000));
223
- // Main menu
224
- const mainMenuItems = [
225
- { label: 'Quick Setup', value: 'quick', description: 'Recommended for most users' },
226
- { label: 'Custom Setup', value: 'custom', description: 'Configure each option' },
227
- { label: 'Import Memories', value: 'import', description: 'From Claude Code, Gemini, etc.' },
228
- { label: 'Platform Setup', value: 'platforms', description: 'Configure AI tools' },
229
- { label: 'Embedding Setup', value: 'embedding', description: 'Vector search configuration' },
230
- { label: 'Exit', value: 'exit', description: 'Finish setup' },
231
- ];
232
- let exitSetup = false;
233
- while (!exitSetup) {
234
- const choice = await showMenu(rl, 'What would you like to do?', mainMenuItems);
235
- switch (mainMenuItems[choice].value) {
236
- case 'quick':
237
- await quickSetup(rl, repoRoot, config);
238
- break;
239
- case 'custom':
240
- await customSetup(rl, repoRoot, config);
130
+ if (skipped.length > 0) {
131
+ summaryLines.push(`Skipped ${skipped.length} files (conflict) — resolve manually`);
132
+ }
133
+ summaryLines.push(`Personal memories: ${personalDir}`);
134
+ // 4. Install platform adapters
135
+ for (const platform of state.platforms) {
136
+ switch (platform) {
137
+ case 'claude-code':
138
+ await (0, claude_code_1.installClaudeCode)(repoRoot);
241
139
  break;
242
- case 'import':
243
- await (0, import_1.importMemories)({ repo: repoRoot });
140
+ case 'codex':
141
+ await (0, codex_1.installCodex)(process.cwd());
244
142
  break;
245
- case 'platforms':
246
- await platformSetup(rl, repoRoot);
143
+ case 'gemini':
144
+ await (0, gemini_1.installGemini)();
247
145
  break;
248
- case 'embedding':
249
- await embeddingSetup(rl, config, repoRoot);
146
+ case 'qwen':
147
+ await (0, qwen_1.installQwen)();
250
148
  break;
251
- case 'exit':
252
- exitSetup = true;
149
+ case 'cursor':
150
+ await (0, cursor_1.installCursor)(process.cwd());
253
151
  break;
254
152
  }
255
153
  }
256
- // Summary
257
- showSummary(config);
258
- rl.close();
259
- }
260
- /**
261
- * Quick setup - automated recommended configuration
262
- */
263
- async function quickSetup(rl, repoRoot, config) {
264
- console.log('\n🚀 Quick Setup\n');
265
- // Check for Ollama
266
- const hasOllama = await checkOllama();
267
- if (hasOllama) {
268
- console.log('✓ Detected Ollama installation');
269
- config.embedding.engine = 'lancedb';
270
- config.embedding.provider = 'ollama';
271
- config.embedding.model = 'mxbai-embed-large';
272
- config.embedding.dimensions = 1024;
154
+ if (state.platforms.length > 0) {
155
+ summaryLines.push(`Platforms: ${state.platforms.join(', ')}`);
273
156
  }
274
- else {
275
- console.log('⊘ Ollama not found, using text search');
276
- config.embedding.engine = 'text';
157
+ // 5. Set up team repo if provided
158
+ if (state.teamRepo.trim()) {
159
+ try {
160
+ await (0, team_1.teamInit)(state.teamRepo.trim(), repoRoot);
161
+ summaryLines.push(`Team repo: linked`);
162
+ }
163
+ catch (e) {
164
+ summaryLines.push(`Team repo: setup failed — ${e.message}`);
165
+ }
277
166
  }
278
- (0, config_1.writeConfig)(repoRoot, config);
279
- // Install platforms
280
- console.log('\n📦 Installing platform integrations...\n');
281
- await (0, claude_code_1.installClaudeCode)(repoRoot);
282
- await (0, codex_1.installCodex)(process.cwd());
283
- await (0, cursor_1.installCursor)(process.cwd());
284
- console.log('\n✅ Quick Setup Complete!\n');
285
- await new Promise((resolve) => setTimeout(resolve, 1000));
286
- }
287
- /**
288
- * Custom setup - step by step configuration
289
- */
290
- async function customSetup(rl, repoRoot, config) {
291
- // Embedding choice
292
- const embeddingMenu = [
293
- { label: 'Ollama (Local, Free)', value: 'ollama', description: 'Recommended' },
294
- { label: 'OpenAI (Cloud)', value: 'openai' },
295
- { label: 'Text Only (No embeddings)', value: 'text' },
296
- ];
297
- const embeddingChoice = await showMenu(rl, 'Choose embedding provider:', embeddingMenu);
298
- switch (embeddingMenu[embeddingChoice].value) {
299
- case 'ollama':
300
- config.embedding.engine = 'lancedb';
167
+ // 6. Update engine config if lancedb
168
+ if (state.searchEngine === 'lancedb') {
169
+ const config = (0, config_1.loadConfig)(repoRoot);
170
+ config.embedding.engine = 'lancedb';
171
+ if (state.embeddingProvider === 'ollama') {
301
172
  config.embedding.provider = 'ollama';
173
+ config.embedding.base_url = state.embeddingUrl || 'http://localhost:11434';
302
174
  config.embedding.model = 'mxbai-embed-large';
303
175
  config.embedding.dimensions = 1024;
304
- break;
305
- case 'openai':
306
- config.embedding.engine = 'lancedb';
176
+ }
177
+ else if (state.embeddingProvider === 'openai') {
307
178
  config.embedding.provider = 'openai';
308
179
  config.embedding.model = 'text-embedding-3-small';
309
180
  config.embedding.dimensions = 1536;
310
- break;
311
- case 'text':
312
- config.embedding.engine = 'text';
313
- break;
314
- }
315
- // Platform selection
316
- const platformItems = [
317
- { label: 'Claude Code', value: 'claude', selected: true },
318
- { label: 'Cursor', value: 'cursor', selected: true },
319
- { label: 'Codex (AGENTS.md)', value: 'codex', selected: true },
320
- ];
321
- const selectedPlatforms = await showCheckbox(rl, 'Configure AI tools:', platformItems);
322
- console.log('\n📦 Installing selected platforms...\n');
323
- if (selectedPlatforms.includes('claude')) {
324
- await (0, claude_code_1.installClaudeCode)(repoRoot);
325
- }
326
- if (selectedPlatforms.includes('cursor')) {
327
- await (0, cursor_1.installCursor)(process.cwd());
328
- }
329
- if (selectedPlatforms.includes('codex')) {
330
- await (0, codex_1.installCodex)(process.cwd());
331
- }
332
- (0, config_1.writeConfig)(repoRoot, config);
333
- console.log('\n✅ Custom Setup Complete!\n');
334
- await new Promise((resolve) => setTimeout(resolve, 1000));
335
- }
336
- /**
337
- * Platform-specific setup
338
- */
339
- async function platformSetup(rl, repoRoot) {
340
- const platformItems = [
341
- { label: 'Claude Code', value: 'claude', selected: false },
342
- { label: 'Cursor', value: 'cursor', selected: false },
343
- { label: 'Codex (AGENTS.md)', value: 'codex', selected: false },
344
- ];
345
- const selected = await showCheckbox(rl, 'Select platforms to configure:', platformItems);
346
- console.log('\n📦 Installing...\n');
347
- if (selected.includes('claude')) {
348
- await (0, claude_code_1.installClaudeCode)(repoRoot);
349
- }
350
- if (selected.includes('cursor')) {
351
- await (0, cursor_1.installCursor)(process.cwd());
181
+ // Save API key to env file (not config.yaml for security)
182
+ if (state.embeddingApiKey.trim()) {
183
+ const envPath = path.join(repoRoot, '.env');
184
+ const envLine = `OPENAI_API_KEY=${state.embeddingApiKey.trim()}\n`;
185
+ const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
186
+ if (!existing.includes('OPENAI_API_KEY=')) {
187
+ fs.writeFileSync(envPath, existing + envLine, 'utf-8');
188
+ summaryLines.push(`OpenAI API key saved to ${envPath}`);
189
+ }
190
+ }
191
+ }
192
+ else if (state.embeddingProvider === 'jina') {
193
+ config.embedding.provider = 'jina';
194
+ config.embedding.model = 'jina-embeddings-v3';
195
+ config.embedding.dimensions = 1024;
196
+ if (state.embeddingApiKey.trim()) {
197
+ const envPath = path.join(repoRoot, '.env');
198
+ const envLine = `JINA_API_KEY=${state.embeddingApiKey.trim()}\n`;
199
+ const existing = fs.existsSync(envPath) ? fs.readFileSync(envPath, 'utf-8') : '';
200
+ if (!existing.includes('JINA_API_KEY=')) {
201
+ fs.writeFileSync(envPath, existing + envLine, 'utf-8');
202
+ summaryLines.push(`Jina API key saved to ${envPath}`);
203
+ }
204
+ }
205
+ }
206
+ (0, config_1.writeConfig)(repoRoot, config);
352
207
  }
353
- if (selected.includes('codex')) {
354
- await (0, codex_1.installCodex)(process.cwd());
208
+ if (state.enableReranker && state.rerankerProvider) {
209
+ const config = (0, config_1.loadConfig)(repoRoot);
210
+ config.reranker = {
211
+ enabled: true,
212
+ provider: state.rerankerProvider,
213
+ };
214
+ (0, config_1.writeConfig)(repoRoot, config);
215
+ const keyVar = state.rerankerProvider === 'jina' ? 'JINA_API_KEY' : 'COHERE_API_KEY';
216
+ summaryLines.push(`Reranker: ${state.rerankerProvider} (set ${keyVar} env var)`);
355
217
  }
356
- console.log('\n✅ Platform setup complete!\n');
357
- await new Promise((resolve) => setTimeout(resolve, 1000));
218
+ return summaryLines;
358
219
  }
359
- /**
360
- * Embedding setup
361
- */
362
- async function embeddingSetup(rl, config, repoRoot) {
363
- const embeddingMenu = [
364
- { label: 'Ollama (Local, Free)', value: 'ollama', description: 'Recommended' },
365
- { label: 'OpenAI (Cloud)', value: 'openai' },
366
- { label: 'Azure OpenAI', value: 'azure' },
367
- { label: 'Text Only', value: 'text', description: 'No embeddings' },
220
+ async function onboardingCommand() {
221
+ const repoRoot = (0, store_1.findRepoRoot)(process.cwd());
222
+ // Use Function constructor to bypass TypeScript's import() -> require() transform.
223
+ // ink, ink-text-input, ink-select-input are ESM-only packages that cannot be
224
+ // require()'d from a CommonJS bundle; this ensures Node uses its ESM loader.
225
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
226
+ const esmImport = new Function('specifier', 'return import(specifier)');
227
+ const reactMod = await esmImport('react');
228
+ const React = (reactMod.default ?? reactMod);
229
+ const { useState, useRef } = React;
230
+ const inkMod = await esmImport('ink');
231
+ const { render, Box, Text, useInput } = inkMod;
232
+ const inkTextInputMod = await esmImport('ink-text-input');
233
+ const TextInput = inkTextInputMod.default;
234
+ const inkSelectInputMod = await esmImport('ink-select-input');
235
+ const SelectInput = inkSelectInputMod.default;
236
+ const defaultName = detectProjectName();
237
+ const platformItems = detectPlatforms();
238
+ const detectedPlatforms = getDetectedPlatforms(platformItems);
239
+ const searchEngineItems = [
240
+ { label: 'Text (recommended, zero setup)', value: 'text' },
241
+ { label: 'Vector / LanceDB (better recall, requires Ollama or OpenAI)', value: 'lancedb' },
368
242
  ];
369
- const choice = await showMenu(rl, 'Choose embedding provider:', embeddingMenu);
370
- switch (embeddingMenu[choice].value) {
371
- case 'ollama':
372
- config.embedding.engine = 'lancedb';
373
- config.embedding.provider = 'ollama';
374
- config.embedding.model = 'mxbai-embed-large';
375
- config.embedding.dimensions = 1024;
376
- break;
377
- case 'openai':
378
- config.embedding.engine = 'lancedb';
379
- config.embedding.provider = 'openai';
380
- config.embedding.model = 'text-embedding-3-small';
381
- config.embedding.dimensions = 1536;
382
- break;
383
- case 'azure':
384
- config.embedding.engine = 'lancedb';
385
- config.embedding.provider = 'azure';
386
- config.embedding.model = 'text-embedding-ada-002';
387
- config.embedding.dimensions = 1536;
388
- break;
389
- case 'text':
390
- config.embedding.engine = 'text';
391
- break;
243
+ function InlineMultiSelect({ label, items, defaultSelected = [], onSubmit }) {
244
+ const [cursor, setCursor] = useState(0);
245
+ const [selected, setSelected] = useState(new Set(defaultSelected));
246
+ useInput((input, key) => {
247
+ if (key.upArrow) {
248
+ setCursor(c => Math.max(0, c - 1));
249
+ }
250
+ if (key.downArrow) {
251
+ setCursor(c => Math.min(items.length - 1, c + 1));
252
+ }
253
+ if (input === ' ') {
254
+ const item = items[cursor];
255
+ if (item && !item.disabled) {
256
+ setSelected(prev => {
257
+ const next = new Set(prev);
258
+ if (next.has(item.value)) {
259
+ next.delete(item.value);
260
+ }
261
+ else {
262
+ next.add(item.value);
263
+ }
264
+ return next;
265
+ });
266
+ }
267
+ }
268
+ if (key.return) {
269
+ setSelected(prev => { onSubmit([...prev]); return prev; });
270
+ }
271
+ });
272
+ return React.createElement(Box, { flexDirection: 'column', marginBottom: 1 }, React.createElement(Text, { bold: true }, label), React.createElement(Text, { dimColor: true }, ' (↑↓ navigate · Space toggle · Enter confirm)'), ...items.map((item, i) => React.createElement(Box, { key: item.value }, React.createElement(Text, { color: (i === cursor ? 'cyan' : undefined) }, ` ${selected.has(item.value) ? '◉' : '◯'} ${item.label}`, item.hint ? React.createElement(Text, { dimColor: true }, ` ${item.hint}`) : null))));
392
273
  }
393
- (0, config_1.writeConfig)(repoRoot, config);
394
- console.log('\n✅ Embedding configured!\n');
395
- await new Promise((resolve) => setTimeout(resolve, 1000));
396
- }
397
- /**
398
- * Show setup summary
399
- */
400
- function showSummary(config) {
401
- console.log('\n\x1B[36m╔═══════════════════════════════════════════════════════════╗\x1B[39m');
402
- console.log('\x1B[36m║\x1B[39m \x1B[36m║\x1B[39m');
403
- console.log('\x1B[36m║\x1B[39m \x1B[1m✅ Setup Complete!\x1B[22m \x1B[36m║\x1B[39m');
404
- console.log('\x1B[36m║\x1B[39m \x1B[36m║\x1B[39m');
405
- console.log('\x1B[36m╚═══════════════════════════════════════════════════════════╝\x1B[39m\n');
406
- console.log('Configuration:\n');
407
- console.log(` Embedding Engine: \x1B[36m${config.embedding.engine}\x1B[39m`);
408
- if (config.embedding.engine === 'lancedb') {
409
- console.log(` Provider: \x1B[90m${config.embedding.provider}\x1B[39m`);
410
- console.log(` Model: \x1B[90m${config.embedding.model}\x1B[39m`);
274
+ function OnboardingApp() {
275
+ const [state, setState] = useState({
276
+ step: 'project-name',
277
+ projectName: defaultName,
278
+ platforms: detectedPlatforms,
279
+ teamRepo: '',
280
+ searchEngine: 'text',
281
+ embeddingProvider: '',
282
+ embeddingUrl: 'http://localhost:11434',
283
+ embeddingApiKey: '',
284
+ enableReranker: false,
285
+ rerankerProvider: '',
286
+ });
287
+ const [nameInput, setNameInput] = useState(defaultName);
288
+ const [teamInput, setTeamInput] = useState('');
289
+ const [ollamaUrlInput, setOllamaUrlInput] = useState('http://localhost:11434');
290
+ const [openaiKeyInput, setOpenaiKeyInput] = useState('');
291
+ const [jinaKeyInput, setJinaKeyInput] = useState('');
292
+ const [done, setDone] = useState(false);
293
+ const [summary, setSummary] = useState([]);
294
+ // Prevent double-submission
295
+ const setupRunning = useRef(false);
296
+ if (done) {
297
+ return React.createElement(Box, { flexDirection: 'column', marginTop: 1 }, React.createElement(Text, { color: 'green', bold: true }, '✓ memobank initialized!'), ...summary.map((line, i) => React.createElement(Text, { key: i, dimColor: true }, ` ${line}`)), React.createElement(Text, { dimColor: true }, 'Run: memo recall "anything" to test'));
298
+ }
299
+ return React.createElement(Box, { flexDirection: 'column', padding: 1 }, React.createElement(Text, { bold: true, color: 'cyan' }, '🧠 Memobank Setup'), React.createElement(Text, null, ' '), state.step === 'project-name' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, null, 'Project name:'), React.createElement(TextInput, {
300
+ value: nameInput,
301
+ onChange: setNameInput,
302
+ onSubmit: (value) => {
303
+ setState(s => ({ ...s, step: 'platforms', projectName: value || defaultName }));
304
+ },
305
+ })) : null, state.step === 'platforms' ? React.createElement(InlineMultiSelect, {
306
+ label: 'Select platforms to integrate:',
307
+ items: platformItems,
308
+ defaultSelected: detectedPlatforms,
309
+ onSubmit: (selected) => {
310
+ setState(s => ({ ...s, step: 'team-repo', platforms: selected }));
311
+ },
312
+ }) : null, state.step === 'team-repo' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, null, 'Team memory repo ', React.createElement(Text, { dimColor: true }, '(optional — Enter to skip):')), React.createElement(TextInput, {
313
+ value: teamInput,
314
+ onChange: setTeamInput,
315
+ onSubmit: (value) => {
316
+ setState(s => ({ ...s, step: 'search-engine', teamRepo: value }));
317
+ },
318
+ })) : null, state.step === 'search-engine' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { bold: true }, 'Search engine:'), React.createElement(SelectInput, {
319
+ items: searchEngineItems,
320
+ onSelect: (item) => {
321
+ const engine = String(item.value);
322
+ if (engine === 'lancedb') {
323
+ setState(s => ({ ...s, step: 'embedding-provider', searchEngine: engine }));
324
+ }
325
+ else {
326
+ setState(s => ({ ...s, step: 'reranker', searchEngine: engine }));
327
+ }
328
+ },
329
+ })) : null, state.step === 'embedding-provider' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { bold: true }, 'Embedding provider:'), React.createElement(SelectInput, {
330
+ items: [
331
+ { label: 'Ollama (local, no API key needed)', value: 'ollama' },
332
+ { label: 'OpenAI (cloud, requires API key)', value: 'openai' },
333
+ { label: 'Jina AI (cloud, requires API key)', value: 'jina' },
334
+ ],
335
+ onSelect: (item) => {
336
+ const provider = String(item.value);
337
+ if (provider === 'ollama') {
338
+ setState(s => ({ ...s, step: 'ollama-url', embeddingProvider: provider }));
339
+ }
340
+ else if (provider === 'openai') {
341
+ setState(s => ({ ...s, step: 'openai-key', embeddingProvider: provider }));
342
+ }
343
+ else {
344
+ setState(s => ({ ...s, step: 'jina-key', embeddingProvider: provider }));
345
+ }
346
+ },
347
+ })) : null, state.step === 'ollama-url' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, null, 'Ollama base URL:'), React.createElement(Text, { dimColor: true }, ' (default: http://localhost:11434 — press Enter to confirm)'), React.createElement(TextInput, {
348
+ value: ollamaUrlInput,
349
+ onChange: setOllamaUrlInput,
350
+ onSubmit: (value) => {
351
+ setState(s => ({ ...s, step: 'reranker', embeddingUrl: value || 'http://localhost:11434' }));
352
+ },
353
+ })) : null, state.step === 'openai-key' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, null, 'OpenAI API key:'), React.createElement(Text, { dimColor: true }, ' (will be saved to .env — press Enter to skip and set OPENAI_API_KEY manually)'), React.createElement(TextInput, {
354
+ value: openaiKeyInput,
355
+ onChange: setOpenaiKeyInput,
356
+ onSubmit: (value) => {
357
+ setState(s => ({ ...s, step: 'reranker', embeddingApiKey: value }));
358
+ },
359
+ })) : null, state.step === 'jina-key' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, null, 'Jina API key:'), React.createElement(Text, { dimColor: true }, ' (will be saved to .env — press Enter to skip and set JINA_API_KEY manually)'), React.createElement(TextInput, {
360
+ value: jinaKeyInput,
361
+ onChange: setJinaKeyInput,
362
+ onSubmit: (value) => {
363
+ setState(s => ({ ...s, step: 'reranker', embeddingApiKey: value }));
364
+ },
365
+ })) : null, state.step === 'reranker' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { bold: true }, 'Enable reranker?'), React.createElement(Text, { dimColor: true }, ' Re-ranks results with AI for better precision (needs Jina or Cohere API key)'), React.createElement(SelectInput, {
366
+ items: [
367
+ { label: 'No', value: 'no' },
368
+ { label: 'Yes', value: 'yes' },
369
+ ],
370
+ onSelect: (item) => {
371
+ if (String(item.value) === 'yes') {
372
+ setState(s => ({ ...s, step: 'reranker-provider' }));
373
+ }
374
+ else {
375
+ if (setupRunning.current)
376
+ return;
377
+ setupRunning.current = true;
378
+ const finalState = { ...state, step: 'done', enableReranker: false };
379
+ setState(finalState);
380
+ runSetup(finalState, repoRoot).then(lines => {
381
+ setSummary(lines);
382
+ setDone(true);
383
+ }).catch((err) => {
384
+ setSummary([`Setup failed: ${err.message}`]);
385
+ setDone(true);
386
+ });
387
+ }
388
+ },
389
+ })) : null, state.step === 'reranker-provider' ? React.createElement(Box, { flexDirection: 'column' }, React.createElement(Text, { bold: true }, 'Reranker provider:'), React.createElement(SelectInput, {
390
+ items: [
391
+ { label: 'Jina AI (set JINA_API_KEY)', value: 'jina' },
392
+ { label: 'Cohere (set COHERE_API_KEY)', value: 'cohere' },
393
+ ],
394
+ onSelect: (item) => {
395
+ if (setupRunning.current)
396
+ return;
397
+ setupRunning.current = true;
398
+ const finalState = { ...state, step: 'done', enableReranker: true, rerankerProvider: String(item.value) };
399
+ setState(finalState);
400
+ runSetup(finalState, repoRoot).then(lines => {
401
+ setSummary(lines);
402
+ setDone(true);
403
+ }).catch((err) => {
404
+ setSummary([`Setup failed: ${err.message}`]);
405
+ setDone(true);
406
+ });
407
+ },
408
+ })) : null);
411
409
  }
412
- console.log(` Top K Results: \x1B[90m${config.memory.top_k}\x1B[39m`);
413
- console.log(` Token Budget: \x1B[90m${config.memory.token_budget}\x1B[39m\n`);
414
- console.log('Next steps:\n');
415
- console.log(' \x1B[36mmemo write lesson\x1B[39m Create your first memory');
416
- console.log(' \x1B[36mmemo recall "query"\x1B[39m Search memories');
417
- console.log(' \x1B[36mmemo lifecycle report\x1B[39m View memory statistics');
418
- console.log(' \x1B[36mmemo --help\x1B[39m See all commands\n');
410
+ const { waitUntilExit } = render(React.createElement(OnboardingApp));
411
+ await waitUntilExit();
419
412
  }
420
413
  //# sourceMappingURL=onboarding.js.map