@memograph/cli 0.1.4

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +402 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +97 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/core/detect.d.ts +30 -0
  8. package/dist/core/detect.d.ts.map +1 -0
  9. package/dist/core/detect.js +212 -0
  10. package/dist/core/detect.js.map +1 -0
  11. package/dist/core/extract.d.ts +6 -0
  12. package/dist/core/extract.d.ts.map +1 -0
  13. package/dist/core/extract.js +104 -0
  14. package/dist/core/extract.js.map +1 -0
  15. package/dist/core/inspect.d.ts +7 -0
  16. package/dist/core/inspect.d.ts.map +1 -0
  17. package/dist/core/inspect.js +98 -0
  18. package/dist/core/inspect.js.map +1 -0
  19. package/dist/core/llm/client.d.ts +55 -0
  20. package/dist/core/llm/client.d.ts.map +1 -0
  21. package/dist/core/llm/client.js +199 -0
  22. package/dist/core/llm/client.js.map +1 -0
  23. package/dist/core/llm/detect-llm.d.ts +28 -0
  24. package/dist/core/llm/detect-llm.d.ts.map +1 -0
  25. package/dist/core/llm/detect-llm.js +212 -0
  26. package/dist/core/llm/detect-llm.js.map +1 -0
  27. package/dist/core/llm/extract-llm.d.ts +27 -0
  28. package/dist/core/llm/extract-llm.d.ts.map +1 -0
  29. package/dist/core/llm/extract-llm.js +151 -0
  30. package/dist/core/llm/extract-llm.js.map +1 -0
  31. package/dist/core/llm/prompts.d.ts +28 -0
  32. package/dist/core/llm/prompts.d.ts.map +1 -0
  33. package/dist/core/llm/prompts.js +172 -0
  34. package/dist/core/llm/prompts.js.map +1 -0
  35. package/dist/core/llm/providers.d.ts +34 -0
  36. package/dist/core/llm/providers.d.ts.map +1 -0
  37. package/dist/core/llm/providers.js +169 -0
  38. package/dist/core/llm/providers.js.map +1 -0
  39. package/dist/core/load.d.ts +10 -0
  40. package/dist/core/load.d.ts.map +1 -0
  41. package/dist/core/load.js +106 -0
  42. package/dist/core/load.js.map +1 -0
  43. package/dist/core/normalize.d.ts +30 -0
  44. package/dist/core/normalize.d.ts.map +1 -0
  45. package/dist/core/normalize.js +63 -0
  46. package/dist/core/normalize.js.map +1 -0
  47. package/dist/core/render.d.ts +10 -0
  48. package/dist/core/render.d.ts.map +1 -0
  49. package/dist/core/render.js +60 -0
  50. package/dist/core/render.js.map +1 -0
  51. package/dist/core/score.d.ts +27 -0
  52. package/dist/core/score.d.ts.map +1 -0
  53. package/dist/core/score.js +59 -0
  54. package/dist/core/score.js.map +1 -0
  55. package/dist/core/types.d.ts +162 -0
  56. package/dist/core/types.d.ts.map +1 -0
  57. package/dist/core/types.js +6 -0
  58. package/dist/core/types.js.map +1 -0
  59. package/dist/interactive/index.d.ts +67 -0
  60. package/dist/interactive/index.d.ts.map +1 -0
  61. package/dist/interactive/index.js +794 -0
  62. package/dist/interactive/index.js.map +1 -0
  63. package/dist/interactive/settings.d.ts +36 -0
  64. package/dist/interactive/settings.d.ts.map +1 -0
  65. package/dist/interactive/settings.js +174 -0
  66. package/dist/interactive/settings.js.map +1 -0
  67. package/dist/interactive/wizard.d.ts +10 -0
  68. package/dist/interactive/wizard.d.ts.map +1 -0
  69. package/dist/interactive/wizard.js +249 -0
  70. package/dist/interactive/wizard.js.map +1 -0
  71. package/package.json +49 -0
@@ -0,0 +1,794 @@
1
+ "use strict";
2
+ /**
3
+ * Interactive CLI menu system for Memograph
4
+ * Uses Node.js built-in readline with arrow key navigation
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.flushStdout = flushStdout;
41
+ exports.drainStdout = drainStdout;
42
+ exports.createRL = createRL;
43
+ exports.createTerminalRL = createTerminalRL;
44
+ exports.waitForEnterOrCtrlC = waitForEnterOrCtrlC;
45
+ exports.resetStdinState = resetStdinState;
46
+ exports.ensureStdinReady = ensureStdinReady;
47
+ exports.ask = ask;
48
+ exports.askMasked = askMasked;
49
+ exports.selectMenu = selectMenu;
50
+ exports.displaySettings = displaySettings;
51
+ exports.runInteractiveMode = runInteractiveMode;
52
+ const readline = __importStar(require("readline"));
53
+ const inspect_js_1 = require("../core/inspect.js");
54
+ const load_js_1 = require("../core/load.js");
55
+ const render_js_1 = require("../core/render.js");
56
+ const providers_js_1 = require("../core/llm/providers.js");
57
+ const wizard_js_1 = require("./wizard.js");
58
+ const settings_js_1 = require("./settings.js");
59
+ const CLOUD_PROVIDERS = [
60
+ 'openai',
61
+ 'anthropic',
62
+ 'gemini',
63
+ 'mistral',
64
+ 'cohere',
65
+ 'xai',
66
+ 'perplexity',
67
+ ];
68
+ const OTHER_PROVIDER_GROUPS = [
69
+ { key: 'aggregator', label: 'Aggregators (OpenAI-compatible)' },
70
+ { key: 'local', label: 'Local / Self-hosted' },
71
+ { key: 'custom', label: 'Custom (OpenAI-compatible)' },
72
+ ];
73
+ function getOtherProviderOptions(group) {
74
+ if (group === 'custom') {
75
+ return ['openai_compatible'];
76
+ }
77
+ return (0, providers_js_1.getProvidersByCategory)(group === 'aggregator' ? 'aggregator' : 'local');
78
+ }
79
+ async function selectProvider() {
80
+ const cloudProviderOptions = CLOUD_PROVIDERS.map((p) => providers_js_1.PROVIDERS[p].label);
81
+ while (true) {
82
+ const providerChoice = await selectMenu('Select Cloud Provider', [
83
+ ...cloudProviderOptions,
84
+ 'Others',
85
+ ]);
86
+ if (providerChoice < CLOUD_PROVIDERS.length) {
87
+ return CLOUD_PROVIDERS[providerChoice];
88
+ }
89
+ const otherChoice = await selectMenu('Select Category', [...OTHER_PROVIDER_GROUPS.map((group) => group.label), 'Back']);
90
+ if (otherChoice === OTHER_PROVIDER_GROUPS.length) {
91
+ console.clear();
92
+ continue;
93
+ }
94
+ const otherGroup = OTHER_PROVIDER_GROUPS[otherChoice].key;
95
+ const providersInGroup = getOtherProviderOptions(otherGroup);
96
+ const providerLabels = providersInGroup.map((p) => providers_js_1.PROVIDERS[p].label);
97
+ const groupedChoice = await selectMenu('Select Provider', providerLabels);
98
+ return providersInGroup[groupedChoice];
99
+ }
100
+ }
101
+ const BANNER_COLOR_ON = '\x1b[97m';
102
+ const BANNER_COLOR_RESET = '\x1b[0m';
103
+ const BANNER_BLOCK_FULL = `${BANNER_COLOR_ON}█${BANNER_COLOR_RESET}`;
104
+ const BANNER_BLOCK_TOP = `${BANNER_COLOR_ON}▀${BANNER_COLOR_RESET}`;
105
+ const BANNER_BLOCK_BOTTOM = `${BANNER_COLOR_ON}▄${BANNER_COLOR_RESET}`;
106
+ const BANNER_FONT_WIDTH = 5;
107
+ const BANNER_FONT_HEIGHT = 5;
108
+ const BANNER_LETTER_SPACING = 1;
109
+ const BANNER_FONT = {
110
+ M: ['X X', 'XX XX', 'X X X', 'X X', 'X X'],
111
+ E: ['XXXX', 'X', 'XXXX', 'X', 'XXXX'],
112
+ O: ['XXXXX', 'X X', 'X X', 'X X', 'XXXXX'],
113
+ G: ['XXXX', 'X', 'X XXX', 'X X', 'XXXX'],
114
+ R: ['XXXX', 'X X', 'XXXX', 'X X', 'X X'],
115
+ A: [' XXX ', 'X X', 'XXXX', 'X X', 'X X'],
116
+ P: ['XXXX', 'X X', 'XXXX', 'X', 'X'],
117
+ H: ['X X', 'X X', 'XXXX', 'X X', 'X X'],
118
+ C: [' XXXX', 'X', 'X', 'X', ' XXXX'],
119
+ L: ['X', 'X', 'X', 'X', 'XXXX'],
120
+ I: ['XXXX', ' X', ' X', ' X', 'XXXX'],
121
+ ' ': ['', '', '', '', ''],
122
+ };
123
+ const BANNER_LOGO_TEXT = 'MEMOGRAPH CLI';
124
+ const BANNER_TAGLINE = 'Analyze conversation transcripts for memory drift';
125
+ function buildWordPattern(word) {
126
+ const rows = Array.from({ length: BANNER_FONT_HEIGHT }, () => '');
127
+ for (const char of word) {
128
+ const glyph = BANNER_FONT[char] || BANNER_FONT[' '] || ['', '', '', '', ''];
129
+ for (let i = 0; i < BANNER_FONT_HEIGHT; i += 1) {
130
+ const row = glyph[i] ?? '';
131
+ rows[i] += row.padEnd(BANNER_FONT_WIDTH, ' ') + ' '.repeat(BANNER_LETTER_SPACING);
132
+ }
133
+ }
134
+ return rows.map((row) => row.trimEnd());
135
+ }
136
+ function renderPixelLines(patternLines) {
137
+ const width = Math.max(...patternLines.map((line) => line.length), 0);
138
+ const padded = patternLines.map((line) => line.padEnd(width, ' '));
139
+ if (padded.length % 2 !== 0) {
140
+ padded.push(''.padEnd(width, ' '));
141
+ }
142
+ const rendered = [];
143
+ for (let row = 0; row < padded.length; row += 2) {
144
+ const top = padded[row];
145
+ const bottom = padded[row + 1];
146
+ let line = '';
147
+ for (let col = 0; col < width; col += 1) {
148
+ const topOn = top[col] === 'X';
149
+ const bottomOn = bottom[col] === 'X';
150
+ if (topOn && bottomOn) {
151
+ line += BANNER_BLOCK_FULL;
152
+ }
153
+ else if (topOn) {
154
+ line += BANNER_BLOCK_TOP;
155
+ }
156
+ else if (bottomOn) {
157
+ line += BANNER_BLOCK_BOTTOM;
158
+ }
159
+ else {
160
+ line += ' ';
161
+ }
162
+ }
163
+ rendered.push(line);
164
+ }
165
+ return rendered;
166
+ }
167
+ const BANNER_LOGO_PATTERN = buildWordPattern(BANNER_LOGO_TEXT);
168
+ const BANNER_LOGO_LINES = renderPixelLines(BANNER_LOGO_PATTERN);
169
+ function renderBanner(trailingNewline = false) {
170
+ console.log('');
171
+ BANNER_LOGO_LINES.forEach((line) => {
172
+ console.log(line);
173
+ });
174
+ console.log('');
175
+ console.log(` ${BANNER_TAGLINE}`);
176
+ if (trailingNewline) {
177
+ console.log('');
178
+ }
179
+ }
180
+ // Default settings are now imported from settings.ts
181
+ /**
182
+ * Ensure stdout is fully flushed before reading input
183
+ */
184
+ function flushStdout() {
185
+ return new Promise((resolve) => {
186
+ // Write a newline to ensure buffer is flushed
187
+ // If this returns false, the buffer is full and we need to wait for drain
188
+ const needsDrain = !process.stdout.write('');
189
+ if (needsDrain) {
190
+ process.stdout.once('drain', () => {
191
+ // Even after drain, give a moment for the OS to process
192
+ setTimeout(resolve, 10);
193
+ });
194
+ }
195
+ else {
196
+ // Buffer not full, but still give time for previous writes
197
+ setTimeout(resolve, 10);
198
+ }
199
+ });
200
+ }
201
+ /**
202
+ * Drain stdout completely and wait for all writes to finish
203
+ */
204
+ function drainStdout() {
205
+ return new Promise((resolve) => {
206
+ // Force a write that will trigger drain if needed
207
+ if (process.stdout.write('\n')) {
208
+ // Write succeeded immediately, wait a bit for OS buffer
209
+ setImmediate(() => resolve());
210
+ }
211
+ else {
212
+ // Buffer full, wait for drain event
213
+ process.stdout.once('drain', () => {
214
+ setImmediate(() => resolve());
215
+ });
216
+ }
217
+ });
218
+ }
219
+ /**
220
+ * Create a readline interface
221
+ */
222
+ function createRL() {
223
+ return readline.createInterface({
224
+ input: process.stdin,
225
+ output: process.stdout,
226
+ });
227
+ }
228
+ /**
229
+ * Create a readline interface optimized for interactive terminals
230
+ */
231
+ function createTerminalRL() {
232
+ return readline.createInterface({
233
+ input: process.stdin,
234
+ output: process.stdout,
235
+ terminal: true,
236
+ });
237
+ }
238
+ /**
239
+ * Wait for Enter (return to menu) or Ctrl+C (exit)
240
+ * Used for the final analysis prompt to avoid readline issues
241
+ */
242
+ function waitForEnterOrCtrlC(message) {
243
+ return new Promise((resolve) => {
244
+ const stdin = process.stdin;
245
+ const wasRaw = stdin.isRaw;
246
+ console.log(message);
247
+ if (stdin.setRawMode) {
248
+ stdin.setRawMode(true);
249
+ }
250
+ stdin.resume();
251
+ const cleanup = () => {
252
+ stdin.removeListener('data', onData);
253
+ if (stdin.setRawMode) {
254
+ stdin.setRawMode(false);
255
+ }
256
+ if (wasRaw === false) {
257
+ stdin.pause();
258
+ }
259
+ };
260
+ const onData = (chunk) => {
261
+ const key = chunk?.[0];
262
+ if (key === 3) {
263
+ cleanup();
264
+ console.log('\n👋 Goodbye!\n');
265
+ process.exit(0);
266
+ }
267
+ if (key === 13 || key === 10) {
268
+ cleanup();
269
+ resolve();
270
+ }
271
+ };
272
+ stdin.on('data', onData);
273
+ });
274
+ }
275
+ /**
276
+ * Reset stdin to canonical mode and clear listeners after raw input usage
277
+ */
278
+ function resetStdinState() {
279
+ const stdin = process.stdin;
280
+ stdin.removeAllListeners('data');
281
+ stdin.removeAllListeners('keypress');
282
+ if (stdin.setRawMode) {
283
+ stdin.setRawMode(false);
284
+ }
285
+ stdin.pause();
286
+ stdin.resume();
287
+ }
288
+ /**
289
+ * Ensure stdin is ready for readline input
290
+ * Call this before using readline after selectMenu
291
+ */
292
+ async function ensureStdinReady() {
293
+ // Drain stdout completely first
294
+ await drainStdout();
295
+ // Pause stdin to reset its state
296
+ process.stdin.pause();
297
+ // Clear any existing stdin listeners
298
+ process.stdin.removeAllListeners('data');
299
+ process.stdin.removeAllListeners('keypress');
300
+ // Ensure no raw mode
301
+ if (process.stdin.setRawMode) {
302
+ process.stdin.setRawMode(false);
303
+ }
304
+ // Resume stdin in canonical mode
305
+ process.stdin.resume();
306
+ // Longer delay for terminal stabilization after heavy output
307
+ // Increased from 100ms to 200ms for terminals with slower buffers
308
+ return new Promise((resolve) => setTimeout(resolve, 200));
309
+ }
310
+ /**
311
+ * Ask a question and get user input (for text input)
312
+ */
313
+ function ask(rl, question) {
314
+ return new Promise((resolve, reject) => {
315
+ let resolved = false;
316
+ const sigintHandler = () => {
317
+ if (resolved)
318
+ return;
319
+ resolved = true;
320
+ process.removeListener('SIGINT', sigintHandler);
321
+ rl.removeListener('SIGINT', sigintHandler);
322
+ rl.close();
323
+ console.log('\n👋 Goodbye!\n');
324
+ process.exit(0);
325
+ };
326
+ process.on('SIGINT', sigintHandler);
327
+ rl.on('SIGINT', sigintHandler);
328
+ rl.question(question, (answer) => {
329
+ if (resolved)
330
+ return;
331
+ resolved = true;
332
+ process.removeListener('SIGINT', sigintHandler);
333
+ rl.removeListener('SIGINT', sigintHandler);
334
+ rl.close();
335
+ resolve(answer.trim());
336
+ });
337
+ // Ensure rl properly emits events
338
+ rl.resume();
339
+ });
340
+ }
341
+ /**
342
+ * Ask a question and mask input (for sensitive text like API keys)
343
+ */
344
+ function askMasked(question) {
345
+ return new Promise((resolve) => {
346
+ const stdin = process.stdin;
347
+ const stdout = process.stdout;
348
+ let value = '';
349
+ const wasRaw = stdin.isRaw;
350
+ stdout.write(question);
351
+ if (stdin.setRawMode) {
352
+ stdin.setRawMode(true);
353
+ }
354
+ stdin.resume();
355
+ const cleanup = () => {
356
+ stdin.removeListener('data', onData);
357
+ if (stdin.setRawMode) {
358
+ stdin.setRawMode(wasRaw ?? false);
359
+ }
360
+ stdin.pause();
361
+ };
362
+ const onData = (chunk) => {
363
+ const char = chunk.toString('utf8');
364
+ // Enter
365
+ if (chunk[0] === 13 || chunk[0] === 10) {
366
+ stdout.write('\n');
367
+ cleanup();
368
+ resolve(value.trim());
369
+ return;
370
+ }
371
+ // Ctrl+C
372
+ if (chunk[0] === 3) {
373
+ cleanup();
374
+ console.log('\n👋 Goodbye!\n');
375
+ process.exit(0);
376
+ return;
377
+ }
378
+ // Backspace
379
+ if (chunk[0] === 127 || chunk[0] === 8) {
380
+ if (value.length > 0) {
381
+ value = value.slice(0, -1);
382
+ stdout.write('\b \b');
383
+ }
384
+ return;
385
+ }
386
+ if (char) {
387
+ for (const ch of char) {
388
+ value += ch;
389
+ stdout.write('*');
390
+ }
391
+ }
392
+ };
393
+ stdin.on('data', onData);
394
+ });
395
+ }
396
+ /**
397
+ * Show an interactive select menu with arrow keys
398
+ */
399
+ async function selectMenu(title, options, selectedIndex = 0, showBannerText = false) {
400
+ // Save original settings
401
+ const stdin = process.stdin;
402
+ const stdout = process.stdout;
403
+ const isRaw = stdin.isRaw;
404
+ const wasMuted = stdout._muted;
405
+ // Enable raw mode for arrow key detection
406
+ stdin.setRawMode(true);
407
+ stdin.resume();
408
+ stdout._muted = false;
409
+ let resolved = false;
410
+ const render = () => {
411
+ console.clear();
412
+ // Show banner if requested
413
+ if (showBannerText) {
414
+ renderBanner();
415
+ }
416
+ console.log(`\n╭─ ${title} ${'─'.repeat(50)}`);
417
+ console.log('│'.padEnd(60) + '│');
418
+ options.forEach((opt, idx) => {
419
+ const isSelected = idx === selectedIndex;
420
+ const prefix = isSelected ? '▸' : ' ';
421
+ const line = isSelected ? `\x1b[36m${opt}\x1b[0m` : opt; // Cyan for selected
422
+ console.log(`│ ${prefix} ${line.padEnd(50)}│`);
423
+ });
424
+ console.log('│'.padEnd(60) + '│');
425
+ console.log('╰' + '─'.repeat(60));
426
+ console.log('\n Use ↑/↓ arrows to move, Enter to select, Ctrl+C to exit');
427
+ };
428
+ render();
429
+ const cleanup = () => {
430
+ if (resolved)
431
+ return;
432
+ resolved = true;
433
+ // Clean up event listeners first
434
+ stdin.removeAllListeners('data');
435
+ // Restore original settings
436
+ stdin.setRawMode(isRaw);
437
+ stdout._muted = wasMuted;
438
+ };
439
+ return new Promise((resolve) => {
440
+ stdin.on('data', (key) => {
441
+ const str = key.toString();
442
+ // Arrow up (↑)
443
+ if (key[0] === 27 && key[1] === 91 && key[2] === 65) {
444
+ selectedIndex = (selectedIndex - 1 + options.length) % options.length;
445
+ render();
446
+ return;
447
+ }
448
+ // Arrow down (↓)
449
+ if (key[0] === 27 && key[1] === 91 && key[2] === 66) {
450
+ selectedIndex = (selectedIndex + 1) % options.length;
451
+ render();
452
+ return;
453
+ }
454
+ // Enter key
455
+ if (key[0] === 13) {
456
+ cleanup();
457
+ resolve(selectedIndex);
458
+ return;
459
+ }
460
+ // Ctrl+C - graceful exit
461
+ if (key[0] === 3) {
462
+ cleanup();
463
+ console.log('\n👋 Goodbye!\n');
464
+ process.exit(0);
465
+ return;
466
+ }
467
+ // Escape - cancel only
468
+ if (key[0] === 27) {
469
+ cleanup();
470
+ console.log('\n❌ Cancelled');
471
+ process.exit(1);
472
+ return;
473
+ }
474
+ });
475
+ });
476
+ }
477
+ /**
478
+ * Show welcome banner
479
+ */
480
+ function showBanner() {
481
+ renderBanner(true);
482
+ }
483
+ /**
484
+ * Check configuration and prompt wizard if needed
485
+ */
486
+ async function checkAndPromptWizard(settings) {
487
+ const status = (0, settings_js_1.getConfigStatus)(settings);
488
+ if (status.configured) {
489
+ return true;
490
+ }
491
+ console.log(`\n❌ ${status.message}`);
492
+ console.log('\nAI model is not configured yet.');
493
+ // Ensure stdin is active and in the correct mode
494
+ await ensureStdinReady();
495
+ const rl = createRL();
496
+ const response = await ask(rl, 'Run Setup Wizard now? (Y/n): ');
497
+ rl.close();
498
+ if (response.toLowerCase() === 'y' || response === '') {
499
+ console.clear();
500
+ const newSettings = await (0, wizard_js_1.runSetupWizard)(settings);
501
+ Object.assign(settings, newSettings);
502
+ (0, settings_js_1.saveSettings)(settings);
503
+ // Check again
504
+ return (0, settings_js_1.isLLMConfigured)(settings);
505
+ }
506
+ return false;
507
+ }
508
+ /**
509
+ * Display current settings
510
+ */
511
+ function displaySettings(settings) {
512
+ const maskedKey = settings.llm.apiKey
513
+ ? settings.llm.apiKey.substring(0, 8) + '••••••••••••'
514
+ : '(not set)';
515
+ console.log('\n╭─ Current Settings ─────────────────────────────────────╮');
516
+ console.log('│ │');
517
+ console.log(`│ ◆ LLM Provider: ${settings.llm.provider.padEnd(20)}│`);
518
+ console.log(`│ ◆ LLM Model: ${settings.llm.model.padEnd(20)}│`);
519
+ console.log(`│ ◆ Temperature: ${String(settings.llm.temperature).padEnd(20)}│`);
520
+ console.log(`│ ◆ Max Tokens: ${String(settings.llm.maxTokens).padEnd(20)}│`);
521
+ console.log(`│ ◆ Base URL: ${(settings.llm.baseUrl || '(default)').padEnd(20)}│`);
522
+ console.log(`│ ◆ API Key: ${maskedKey.padEnd(20)}│`);
523
+ console.log('│ │');
524
+ console.log('╰──────────────────────────────────────────────────────╯\n');
525
+ }
526
+ /**
527
+ * Settings menu
528
+ */
529
+ async function settingsMenu(settings) {
530
+ const options = [
531
+ 'Quick Setup (Wizard)',
532
+ 'Change LLM Provider',
533
+ 'Change LLM Model',
534
+ 'Change Temperature',
535
+ 'Change Max Tokens',
536
+ 'Change Base URL',
537
+ 'Set/Update API Key',
538
+ 'Show raw config',
539
+ 'Back to main menu',
540
+ ];
541
+ while (true) {
542
+ const choice = await selectMenu('Settings', options);
543
+ switch (choice) {
544
+ case 0: // Quick setup wizard
545
+ {
546
+ console.clear();
547
+ const newSettings = await (0, wizard_js_1.runSetupWizard)(settings);
548
+ Object.assign(settings, newSettings);
549
+ (0, settings_js_1.saveSettings)(settings);
550
+ console.clear();
551
+ displaySettings(settings);
552
+ await ensureStdinReady();
553
+ await ask(createRL(), '\nPress Enter to continue...');
554
+ }
555
+ break;
556
+ case 1: // Change provider
557
+ {
558
+ const selectedProvider = await selectProvider();
559
+ settings.llm.provider = selectedProvider;
560
+ const providerInfo = (0, providers_js_1.getProviderInfo)(selectedProvider);
561
+ if (providerInfo?.defaultBaseUrl) {
562
+ settings.llm.baseUrl = providerInfo.defaultBaseUrl;
563
+ }
564
+ (0, settings_js_1.saveSettings)(settings);
565
+ console.log('\n✓ Provider updated to', settings.llm.provider);
566
+ await ensureStdinReady();
567
+ await ask(createRL(), '\nPress Enter to continue...');
568
+ }
569
+ break;
570
+ case 2: // Change model
571
+ {
572
+ const models = settings.llm.provider === 'openai'
573
+ ? ['gpt-4o-mini (recommended)', 'gpt-4o', 'gpt-3.5-turbo', 'Custom...']
574
+ : ['claude-3-5-sonnet-20241022 (recommended)', 'claude-3-5-haiku-20241022', 'Custom...'];
575
+ const modelChoice = await selectMenu('Select LLM Model', models);
576
+ const selected = models[modelChoice];
577
+ if (selected === 'Custom...') {
578
+ await ensureStdinReady();
579
+ const customModel = await ask(createRL(), 'Enter custom model name: ');
580
+ settings.llm.model = customModel;
581
+ }
582
+ else {
583
+ settings.llm.model = selected.split(' ')[0];
584
+ }
585
+ (0, settings_js_1.saveSettings)(settings);
586
+ console.log('\n✓ Model updated to', settings.llm.model);
587
+ await ensureStdinReady();
588
+ await ask(createRL(), '\nPress Enter to continue...');
589
+ }
590
+ break;
591
+ case 3: // Change temperature
592
+ {
593
+ await ensureStdinReady();
594
+ const temp = await ask(createRL(), 'Enter temperature (0.0-1.0, default 0.3): ');
595
+ if (temp) {
596
+ const tempVal = parseFloat(temp);
597
+ if (!isNaN(tempVal) && tempVal >= 0 && tempVal <= 1) {
598
+ settings.llm.temperature = tempVal;
599
+ (0, settings_js_1.saveSettings)(settings);
600
+ console.log('\n✓ Temperature updated to', tempVal);
601
+ }
602
+ else {
603
+ console.log('\n❌ Invalid temperature. Must be between 0.0 and 1.0');
604
+ }
605
+ }
606
+ await ensureStdinReady();
607
+ await ask(createRL(), '\nPress Enter to continue...');
608
+ }
609
+ break;
610
+ case 4: // Change max tokens
611
+ {
612
+ await ensureStdinReady();
613
+ const maxTokens = await ask(createRL(), 'Enter max tokens (default 4096): ');
614
+ if (maxTokens) {
615
+ const tokensVal = parseInt(maxTokens, 10);
616
+ if (!isNaN(tokensVal) && tokensVal > 0) {
617
+ settings.llm.maxTokens = tokensVal;
618
+ (0, settings_js_1.saveSettings)(settings);
619
+ console.log('\n✓ Max tokens updated to', tokensVal);
620
+ }
621
+ else {
622
+ console.log('\n❌ Invalid value. Must be a positive number');
623
+ }
624
+ }
625
+ await ensureStdinReady();
626
+ await ask(createRL(), '\nPress Enter to continue...');
627
+ }
628
+ break;
629
+ case 5: // Change base URL
630
+ {
631
+ await ensureStdinReady();
632
+ const baseUrl = await ask(createRL(), 'Enter base URL (or press Enter to clear): ');
633
+ if (baseUrl) {
634
+ settings.llm.baseUrl = baseUrl;
635
+ }
636
+ else {
637
+ settings.llm.baseUrl = undefined;
638
+ }
639
+ (0, settings_js_1.saveSettings)(settings);
640
+ console.log('\n✓ Base URL updated');
641
+ await ensureStdinReady();
642
+ await ask(createRL(), '\nPress Enter to continue...');
643
+ }
644
+ break;
645
+ case 6: // Set API key
646
+ {
647
+ const currentKey = settings.llm.apiKey;
648
+ if (currentKey) {
649
+ console.log('\nCurrent API key:', currentKey.substring(0, 8) + '••••••••••');
650
+ await ensureStdinReady();
651
+ const confirm = await ask(createRL(), 'Update API key? (y/N): ');
652
+ if (confirm.toLowerCase() !== 'y') {
653
+ console.clear();
654
+ displaySettings(settings);
655
+ break;
656
+ }
657
+ }
658
+ await ensureStdinReady();
659
+ const apiKey = await askMasked(`Enter ${settings.llm.provider.toUpperCase()} API key: `);
660
+ if (apiKey) {
661
+ settings.llm.apiKey = apiKey;
662
+ (0, settings_js_1.saveSettings)(settings);
663
+ console.log('\n✓ API key updated');
664
+ }
665
+ await ensureStdinReady();
666
+ await ask(createRL(), '\nPress Enter to continue...');
667
+ }
668
+ break;
669
+ case 7: // Show raw config
670
+ {
671
+ const displayConfig = { ...settings };
672
+ if (displayConfig.llm.apiKey) {
673
+ displayConfig.llm.apiKey = displayConfig.llm.apiKey.substring(0, 8) + '••••••••••';
674
+ }
675
+ console.log('\nRaw configuration:');
676
+ console.log(JSON.stringify(displayConfig, null, 2));
677
+ await ensureStdinReady();
678
+ await ask(createRL(), '\nPress Enter to continue...');
679
+ }
680
+ break;
681
+ case 8: // Back
682
+ console.clear();
683
+ return true;
684
+ }
685
+ console.clear();
686
+ displaySettings(settings);
687
+ }
688
+ }
689
+ /**
690
+ * Inspect transcript interactively
691
+ */
692
+ async function inspectTranscriptInteractive(settings) {
693
+ // Check configuration first
694
+ const isReady = await checkAndPromptWizard(settings);
695
+ if (!isReady) {
696
+ console.log('\n❌ Cannot proceed: LLM is not configured');
697
+ await flushStdout();
698
+ await ensureStdinReady();
699
+ const rl = createRL();
700
+ await ask(rl, '\nPress Enter to return to main menu (or Ctrl+C to exit)...');
701
+ rl.close();
702
+ return;
703
+ }
704
+ try {
705
+ // Get transcript path
706
+ await ensureStdinReady();
707
+ const path = await ask(createRL(), 'Enter path to transcript file: ');
708
+ // Get output format
709
+ const formatChoice = await selectMenu('Output Format', ['Text (human-readable)', 'JSON (machine-readable)']);
710
+ const asJson = formatChoice === 1;
711
+ console.log('\n✓ Loading transcript...');
712
+ const transcript = await (0, load_js_1.loadTranscript)(path);
713
+ console.log(`✓ Loaded transcript with ${transcript.messages.length} messages`);
714
+ console.log('✓ Extracting facts using LLM...');
715
+ const config = {
716
+ max_messages: 2000,
717
+ llm: settings.llm,
718
+ };
719
+ const result = await (0, inspect_js_1.inspectTranscript)(transcript, config);
720
+ console.log('✓ Analysis complete!\n');
721
+ const output = asJson ? (0, render_js_1.renderJsonReport)(result) : (0, render_js_1.renderTextReport)(result);
722
+ // Use direct write with proper async handling instead of console.log
723
+ await new Promise((resolve) => {
724
+ if (process.stdout.write(output + '\n')) {
725
+ // Write successful, resolve immediately
726
+ setImmediate(resolve);
727
+ }
728
+ else {
729
+ // Buffer full, wait for drain
730
+ process.stdout.once('drain', () => setImmediate(resolve));
731
+ }
732
+ });
733
+ if (process.stdin.setRawMode) {
734
+ process.stdin.setRawMode(false);
735
+ }
736
+ process.stdin.resume();
737
+ await new Promise((resolve) => setTimeout(resolve, 50));
738
+ await waitForEnterOrCtrlC('\nPress Enter to return to main menu (or Ctrl+C to exit)...');
739
+ resetStdinState();
740
+ await ensureStdinReady();
741
+ }
742
+ catch (error) {
743
+ const errorMsg = '\n❌ Error: ' + (error instanceof Error ? error.message : 'Unknown error');
744
+ // Use direct write with proper async handling
745
+ await new Promise((resolve) => {
746
+ if (process.stdout.write(errorMsg + '\n')) {
747
+ setImmediate(resolve);
748
+ }
749
+ else {
750
+ process.stdout.once('drain', () => setImmediate(resolve));
751
+ }
752
+ });
753
+ if (process.stdin.setRawMode) {
754
+ process.stdin.setRawMode(false);
755
+ }
756
+ process.stdin.resume();
757
+ await new Promise((resolve) => setTimeout(resolve, 50));
758
+ await waitForEnterOrCtrlC('\nPress Enter to return to main menu (or Ctrl+C to exit)...');
759
+ resetStdinState();
760
+ await ensureStdinReady();
761
+ }
762
+ }
763
+ /**
764
+ * Main menu
765
+ */
766
+ async function runInteractiveMode(settings) {
767
+ // Load settings from config file if not provided
768
+ if (!settings) {
769
+ settings = (0, settings_js_1.loadSettings)();
770
+ }
771
+ const options = [
772
+ 'Inspect a transcript',
773
+ 'Manage settings',
774
+ 'Exit',
775
+ ];
776
+ while (true) {
777
+ const choice = await selectMenu('Main Menu', options, 0, true);
778
+ switch (choice) {
779
+ case 0:
780
+ console.clear();
781
+ await inspectTranscriptInteractive(settings);
782
+ break;
783
+ case 1:
784
+ console.clear();
785
+ displaySettings(settings);
786
+ await settingsMenu(settings);
787
+ break;
788
+ case 2:
789
+ console.log('\n👋 Goodbye!\n');
790
+ process.exit(0);
791
+ }
792
+ }
793
+ }
794
+ //# sourceMappingURL=index.js.map