@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.
- package/LICENSE +21 -0
- package/README.md +402 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +97 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/detect.d.ts +30 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +212 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/extract.d.ts +6 -0
- package/dist/core/extract.d.ts.map +1 -0
- package/dist/core/extract.js +104 -0
- package/dist/core/extract.js.map +1 -0
- package/dist/core/inspect.d.ts +7 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +98 -0
- package/dist/core/inspect.js.map +1 -0
- package/dist/core/llm/client.d.ts +55 -0
- package/dist/core/llm/client.d.ts.map +1 -0
- package/dist/core/llm/client.js +199 -0
- package/dist/core/llm/client.js.map +1 -0
- package/dist/core/llm/detect-llm.d.ts +28 -0
- package/dist/core/llm/detect-llm.d.ts.map +1 -0
- package/dist/core/llm/detect-llm.js +212 -0
- package/dist/core/llm/detect-llm.js.map +1 -0
- package/dist/core/llm/extract-llm.d.ts +27 -0
- package/dist/core/llm/extract-llm.d.ts.map +1 -0
- package/dist/core/llm/extract-llm.js +151 -0
- package/dist/core/llm/extract-llm.js.map +1 -0
- package/dist/core/llm/prompts.d.ts +28 -0
- package/dist/core/llm/prompts.d.ts.map +1 -0
- package/dist/core/llm/prompts.js +172 -0
- package/dist/core/llm/prompts.js.map +1 -0
- package/dist/core/llm/providers.d.ts +34 -0
- package/dist/core/llm/providers.d.ts.map +1 -0
- package/dist/core/llm/providers.js +169 -0
- package/dist/core/llm/providers.js.map +1 -0
- package/dist/core/load.d.ts +10 -0
- package/dist/core/load.d.ts.map +1 -0
- package/dist/core/load.js +106 -0
- package/dist/core/load.js.map +1 -0
- package/dist/core/normalize.d.ts +30 -0
- package/dist/core/normalize.d.ts.map +1 -0
- package/dist/core/normalize.js +63 -0
- package/dist/core/normalize.js.map +1 -0
- package/dist/core/render.d.ts +10 -0
- package/dist/core/render.d.ts.map +1 -0
- package/dist/core/render.js +60 -0
- package/dist/core/render.js.map +1 -0
- package/dist/core/score.d.ts +27 -0
- package/dist/core/score.d.ts.map +1 -0
- package/dist/core/score.js +59 -0
- package/dist/core/score.js.map +1 -0
- package/dist/core/types.d.ts +162 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +6 -0
- package/dist/core/types.js.map +1 -0
- package/dist/interactive/index.d.ts +67 -0
- package/dist/interactive/index.d.ts.map +1 -0
- package/dist/interactive/index.js +794 -0
- package/dist/interactive/index.js.map +1 -0
- package/dist/interactive/settings.d.ts +36 -0
- package/dist/interactive/settings.d.ts.map +1 -0
- package/dist/interactive/settings.js +174 -0
- package/dist/interactive/settings.js.map +1 -0
- package/dist/interactive/wizard.d.ts +10 -0
- package/dist/interactive/wizard.d.ts.map +1 -0
- package/dist/interactive/wizard.js +249 -0
- package/dist/interactive/wizard.js.map +1 -0
- 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
|