@kat-ai/cli 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,2712 @@
1
+ #!/usr/bin/env node
2
+ import * as path10 from 'path';
3
+ import path10__default, { resolve } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import * as fs9 from 'fs';
6
+ import { config } from 'dotenv';
7
+ import { Pinecone } from '@pinecone-database/pinecone';
8
+ import { parse } from 'csv-parse/sync';
9
+ import { generateObject, generateText } from 'ai';
10
+ import { openai } from '@ai-sdk/openai';
11
+ import { z } from 'zod';
12
+ import { resolveDefaultOpenAiChatModelId, progressiveIntrospect } from '@kat/core';
13
+ import natural from 'natural';
14
+ import { Command, InvalidArgumentError } from 'commander';
15
+ import * as readline from 'readline/promises';
16
+ import { stdout, stdin } from 'process';
17
+ import { readFile } from 'fs/promises';
18
+
19
+ var __defProp = Object.defineProperty;
20
+ var __getOwnPropNames = Object.getOwnPropertyNames;
21
+ var __esm = (fn, res) => function __init() {
22
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
23
+ };
24
+ var __export = (target, all) => {
25
+ for (var name in all)
26
+ __defProp(target, name, { get: all[name], enumerable: true });
27
+ };
28
+ var getFilename, getDirname, __dirname$1;
29
+ var init_esm_shims = __esm({
30
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.8_tsx@4.20.6_typescript@5.9.3_yaml@2.8.1/node_modules/tsup/assets/esm_shims.js"() {
31
+ getFilename = () => fileURLToPath(import.meta.url);
32
+ getDirname = () => path10__default.dirname(getFilename());
33
+ __dirname$1 = /* @__PURE__ */ getDirname();
34
+ }
35
+ });
36
+ function loadEnv(cwd = process.cwd()) {
37
+ for (const envPath of ENV_SEARCH_PATHS) {
38
+ const fullPath = path10.join(cwd, envPath);
39
+ if (fs9.existsSync(fullPath)) {
40
+ config({ path: fullPath });
41
+ return fullPath;
42
+ }
43
+ }
44
+ let current = cwd;
45
+ while (current !== path10.dirname(current)) {
46
+ for (const envFile of [".env.local", ".env"]) {
47
+ const envPath = path10.join(current, envFile);
48
+ if (fs9.existsSync(envPath)) {
49
+ config({ path: envPath });
50
+ return envPath;
51
+ }
52
+ }
53
+ current = path10.dirname(current);
54
+ }
55
+ return null;
56
+ }
57
+ function findDataDir(cwd = process.cwd()) {
58
+ const candidates = [
59
+ path10.join(cwd, "data"),
60
+ path10.join(cwd, "examples", "app", "data")
61
+ ];
62
+ for (const dir of candidates) {
63
+ if (fs9.existsSync(dir)) {
64
+ return dir;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ function findManifestsDir(cwd = process.cwd()) {
70
+ const candidates = [
71
+ path10.join(cwd, "composition", "manifests"),
72
+ path10.join(cwd, "examples", "app", "composition", "manifests")
73
+ ];
74
+ for (const dir of candidates) {
75
+ if (fs9.existsSync(dir)) {
76
+ return dir;
77
+ }
78
+ }
79
+ return null;
80
+ }
81
+ function requireEnv(vars) {
82
+ const missing = [];
83
+ const present = [];
84
+ for (const v of vars) {
85
+ if (process.env[v]) {
86
+ present.push(v);
87
+ } else {
88
+ missing.push(v);
89
+ }
90
+ }
91
+ return { missing, present };
92
+ }
93
+ function getVersion() {
94
+ try {
95
+ const pkgPath = path10.join(import.meta.dirname || __dirname$1, "..", "..", "package.json");
96
+ if (fs9.existsSync(pkgPath)) {
97
+ const pkg = JSON.parse(fs9.readFileSync(pkgPath, "utf-8"));
98
+ return pkg.version || "0.1.0";
99
+ }
100
+ } catch {
101
+ }
102
+ return "0.1.0";
103
+ }
104
+ var ENV_SEARCH_PATHS;
105
+ var init_config = __esm({
106
+ "src/utils/config.ts"() {
107
+ init_esm_shims();
108
+ ENV_SEARCH_PATHS = [
109
+ ".env.local",
110
+ ".env",
111
+ "examples/app/.env.local",
112
+ "examples/app/.env"
113
+ ];
114
+ }
115
+ });
116
+
117
+ // src/utils/colors.ts
118
+ function stripAnsi(text) {
119
+ return text.replace(/\x1b\[[0-9;]*m/g, "");
120
+ }
121
+ function drawBox(content, options = {}) {
122
+ const lines = Array.isArray(content) ? content : content.split("\n");
123
+ const padding = options.padding ?? 1;
124
+ const borderColor = options.borderColor ?? colors.cyan;
125
+ const contentWidth = Math.max(...lines.map((l) => stripAnsi(l).length));
126
+ const width = options.width ?? contentWidth + padding * 2;
127
+ const innerWidth = width;
128
+ const result = [];
129
+ if (options.title) {
130
+ const titleText = ` ${options.title} `;
131
+ const leftPad = Math.floor((innerWidth - titleText.length) / 2);
132
+ const rightPad = innerWidth - leftPad - titleText.length;
133
+ result.push(
134
+ borderColor(box.topLeft) + borderColor(box.horizontal.repeat(leftPad)) + colors.boldWhite(titleText) + borderColor(box.horizontal.repeat(rightPad)) + borderColor(box.topRight)
135
+ );
136
+ } else {
137
+ result.push(
138
+ borderColor(box.topLeft) + borderColor(box.horizontal.repeat(innerWidth)) + borderColor(box.topRight)
139
+ );
140
+ }
141
+ const paddingStr = " ".repeat(padding);
142
+ for (const line of lines) {
143
+ const strippedLen = stripAnsi(line).length;
144
+ const rightPadding = Math.max(0, innerWidth - strippedLen - padding * 2);
145
+ result.push(
146
+ borderColor(box.vertical) + paddingStr + line + " ".repeat(rightPadding) + paddingStr + borderColor(box.vertical)
147
+ );
148
+ }
149
+ result.push(
150
+ borderColor(box.bottomLeft) + borderColor(box.horizontal.repeat(innerWidth)) + borderColor(box.bottomRight)
151
+ );
152
+ return result.join("\n");
153
+ }
154
+ function printHeader(title, icon) {
155
+ const prefix = icon ? `${icon} ` : "";
156
+ console.log("");
157
+ console.log(colors.boldCyan(`${prefix}${title}`));
158
+ console.log(colors.cyan(box.horizontal.repeat(stripAnsi(title).length + (icon ? 2 : 0))));
159
+ }
160
+ function printSuccess(message) {
161
+ console.log(`${colors.green(icons.success)} ${message}`);
162
+ }
163
+ function printError(message) {
164
+ console.log(`${colors.red(icons.error)} ${message}`);
165
+ }
166
+ function printWarning(message) {
167
+ console.log(`${colors.yellow(icons.warning)} ${message}`);
168
+ }
169
+ function printInfo(message) {
170
+ console.log(`${colors.blue(icons.info)} ${message}`);
171
+ }
172
+ function printList(items, color = colors.cyan) {
173
+ for (const item of items) {
174
+ console.log(` ${color(icons.bullet)} ${item}`);
175
+ }
176
+ }
177
+ function printStep(current, total, message) {
178
+ const step = colors.gray(`[${current}/${total}]`);
179
+ console.log(`${step} ${message}`);
180
+ }
181
+ function menuOption(key, label, description) {
182
+ const keyPart = colors.boldCyan(`[${key}]`);
183
+ const labelPart = colors.white(label);
184
+ const descPart = description ? colors.gray(` - ${description}`) : "";
185
+ return ` ${keyPart} ${labelPart}${descPart}`;
186
+ }
187
+ function printBanner() {
188
+ const banner = `
189
+ ${colors.boldCyan("\u256D\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E")}
190
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 ")}${colors.boldCyan("\u2502")}
191
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u2588\u2588\u2551 \u2588\u2588\u2554\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557 ")}${colors.boldCyan("\u2502")}
192
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u2588\u2588\u2588\u2588\u2588\u2554\u255D \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.boldCyan("\u2502")}
193
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u2588\u2588\u2554\u2550\u2588\u2588\u2557 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.boldCyan("\u2502")}
194
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u2588\u2588\u2551 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D ")}${colors.boldCyan("\u2502")}
195
+ ${colors.boldCyan("\u2502")}${colors.brightMagenta(" \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D ")}${colors.boldCyan("\u2502")}
196
+ ${colors.boldCyan("\u2502")} ${colors.boldCyan("\u2502")}
197
+ ${colors.boldCyan("\u2502")} ${colors.gray("Knowledge Base Tools")} ${colors.dim("v0.1.0")} ${colors.boldCyan("\u2502")}
198
+ ${colors.boldCyan("\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F")}
199
+ `;
200
+ console.log(banner);
201
+ }
202
+ function divider(width = 50) {
203
+ console.log(colors.gray(box.horizontal.repeat(width)));
204
+ }
205
+ var RESET, BOLD, DIM, ITALIC, UNDERLINE, FG, BG, colors, icons, box, spinnerFrames, Spinner;
206
+ var init_colors = __esm({
207
+ "src/utils/colors.ts"() {
208
+ init_esm_shims();
209
+ RESET = "\x1B[0m";
210
+ BOLD = "\x1B[1m";
211
+ DIM = "\x1B[2m";
212
+ ITALIC = "\x1B[3m";
213
+ UNDERLINE = "\x1B[4m";
214
+ FG = {
215
+ black: "\x1B[30m",
216
+ red: "\x1B[31m",
217
+ green: "\x1B[32m",
218
+ yellow: "\x1B[33m",
219
+ blue: "\x1B[34m",
220
+ magenta: "\x1B[35m",
221
+ cyan: "\x1B[36m",
222
+ white: "\x1B[37m",
223
+ // Bright colors
224
+ brightBlack: "\x1B[90m",
225
+ brightRed: "\x1B[91m",
226
+ brightGreen: "\x1B[92m",
227
+ brightYellow: "\x1B[93m",
228
+ brightBlue: "\x1B[94m",
229
+ brightMagenta: "\x1B[95m",
230
+ brightCyan: "\x1B[96m",
231
+ brightWhite: "\x1B[97m"
232
+ };
233
+ BG = {
234
+ black: "\x1B[40m",
235
+ red: "\x1B[41m",
236
+ green: "\x1B[42m",
237
+ yellow: "\x1B[43m",
238
+ blue: "\x1B[44m",
239
+ magenta: "\x1B[45m",
240
+ cyan: "\x1B[46m",
241
+ white: "\x1B[47m"
242
+ };
243
+ colors = {
244
+ // Basic colors
245
+ red: (text) => `${FG.red}${text}${RESET}`,
246
+ green: (text) => `${FG.green}${text}${RESET}`,
247
+ yellow: (text) => `${FG.yellow}${text}${RESET}`,
248
+ blue: (text) => `${FG.blue}${text}${RESET}`,
249
+ magenta: (text) => `${FG.magenta}${text}${RESET}`,
250
+ cyan: (text) => `${FG.cyan}${text}${RESET}`,
251
+ white: (text) => `${FG.white}${text}${RESET}`,
252
+ gray: (text) => `${FG.brightBlack}${text}${RESET}`,
253
+ // Bright colors
254
+ brightRed: (text) => `${FG.brightRed}${text}${RESET}`,
255
+ brightGreen: (text) => `${FG.brightGreen}${text}${RESET}`,
256
+ brightYellow: (text) => `${FG.brightYellow}${text}${RESET}`,
257
+ brightBlue: (text) => `${FG.brightBlue}${text}${RESET}`,
258
+ brightMagenta: (text) => `${FG.brightMagenta}${text}${RESET}`,
259
+ brightCyan: (text) => `${FG.brightCyan}${text}${RESET}`,
260
+ // Styles
261
+ bold: (text) => `${BOLD}${text}${RESET}`,
262
+ dim: (text) => `${DIM}${text}${RESET}`,
263
+ italic: (text) => `${ITALIC}${text}${RESET}`,
264
+ underline: (text) => `${UNDERLINE}${text}${RESET}`,
265
+ // Combined styles
266
+ boldRed: (text) => `${BOLD}${FG.red}${text}${RESET}`,
267
+ boldGreen: (text) => `${BOLD}${FG.green}${text}${RESET}`,
268
+ boldYellow: (text) => `${BOLD}${FG.yellow}${text}${RESET}`,
269
+ boldBlue: (text) => `${BOLD}${FG.blue}${text}${RESET}`,
270
+ boldMagenta: (text) => `${BOLD}${FG.magenta}${text}${RESET}`,
271
+ boldCyan: (text) => `${BOLD}${FG.cyan}${text}${RESET}`,
272
+ boldWhite: (text) => `${BOLD}${FG.white}${text}${RESET}`,
273
+ // Semantic colors
274
+ success: (text) => `${FG.green}${text}${RESET}`,
275
+ error: (text) => `${FG.red}${text}${RESET}`,
276
+ warning: (text) => `${FG.yellow}${text}${RESET}`,
277
+ info: (text) => `${FG.cyan}${text}${RESET}`,
278
+ muted: (text) => `${FG.brightBlack}${text}${RESET}`,
279
+ // Highlight with background
280
+ highlight: (text) => `${BG.yellow}${FG.black}${text}${RESET}`,
281
+ badge: (text, color) => {
282
+ const bg = BG[color] || BG.blue;
283
+ return `${bg}${FG.white}${BOLD} ${text} ${RESET}`;
284
+ }
285
+ };
286
+ icons = {
287
+ success: "\u2714",
288
+ error: "\u2716",
289
+ warning: "\u26A0",
290
+ info: "\u2139",
291
+ question: "?",
292
+ pointer: "\u276F",
293
+ bullet: "\u25CF",
294
+ star: "\u2605",
295
+ arrow: "\u2192",
296
+ arrowRight: "\u2192",
297
+ arrowDown: "\u2193",
298
+ check: "\u2713",
299
+ cross: "\u2717",
300
+ dot: "\xB7",
301
+ ellipsis: "\u2026",
302
+ folder: "\u{1F4C1}",
303
+ file: "\u{1F4C4}",
304
+ gear: "\u2699",
305
+ rocket: "\u{1F680}",
306
+ sparkles: "\u2728",
307
+ tools: "\u{1F527}",
308
+ database: "\u{1F5C3}",
309
+ search: "\u{1F50D}",
310
+ lightning: "\u26A1",
311
+ package: "\u{1F4E6}"
312
+ };
313
+ box = {
314
+ topLeft: "\u256D",
315
+ topRight: "\u256E",
316
+ bottomLeft: "\u2570",
317
+ bottomRight: "\u256F",
318
+ horizontal: "\u2500",
319
+ vertical: "\u2502",
320
+ tee: "\u251C",
321
+ teeRight: "\u2524",
322
+ cross: "\u253C",
323
+ top: "\u252C",
324
+ bottom: "\u2534"
325
+ };
326
+ spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
327
+ Spinner = class {
328
+ frameIndex = 0;
329
+ interval = null;
330
+ text;
331
+ color;
332
+ constructor(text, color = colors.cyan) {
333
+ this.text = text;
334
+ this.color = color;
335
+ }
336
+ start() {
337
+ if (this.interval) return;
338
+ process.stdout.write("\x1B[?25l");
339
+ this.interval = setInterval(() => {
340
+ const frame = spinnerFrames[this.frameIndex];
341
+ process.stdout.write(`\r${this.color(frame)} ${this.text}`);
342
+ this.frameIndex = (this.frameIndex + 1) % spinnerFrames.length;
343
+ }, 80);
344
+ }
345
+ update(text) {
346
+ this.text = text;
347
+ }
348
+ stop(finalMessage) {
349
+ if (this.interval) {
350
+ clearInterval(this.interval);
351
+ this.interval = null;
352
+ }
353
+ process.stdout.write("\r\x1B[K");
354
+ process.stdout.write("\x1B[?25h");
355
+ if (finalMessage) {
356
+ console.log(finalMessage);
357
+ }
358
+ }
359
+ succeed(text) {
360
+ this.stop(`${colors.green(icons.success)} ${text || this.text}`);
361
+ }
362
+ fail(text) {
363
+ this.stop(`${colors.red(icons.error)} ${text || this.text}`);
364
+ }
365
+ warn(text) {
366
+ this.stop(`${colors.yellow(icons.warning)} ${text || this.text}`);
367
+ }
368
+ info(text) {
369
+ this.stop(`${colors.blue(icons.info)} ${text || this.text}`);
370
+ }
371
+ };
372
+ }
373
+ });
374
+
375
+ // src/lib/ingest.ts
376
+ var ingest_exports = {};
377
+ __export(ingest_exports, {
378
+ ingestData: () => ingestData
379
+ });
380
+ async function ingestData(options) {
381
+ const { outputMode, localOutputDir, dryRun, verbose, dataDir } = options;
382
+ const csvFiles = fs9.readdirSync(dataDir).filter((f) => f.endsWith(".csv"));
383
+ if (csvFiles.length === 0) {
384
+ printWarning("No CSV files found.");
385
+ return;
386
+ }
387
+ printInfo(`Found ${csvFiles.length} CSV file(s) to process.`);
388
+ let pinecone = null;
389
+ if ((outputMode === "pinecone" || outputMode === "both") && !dryRun) {
390
+ pinecone = new Pinecone({
391
+ apiKey: process.env.PINECONE_API_KEY
392
+ });
393
+ }
394
+ for (let i = 0; i < csvFiles.length; i++) {
395
+ const csvFile = csvFiles[i];
396
+ const csvPath = path10.join(dataDir, csvFile);
397
+ printStep(i + 1, csvFiles.length, `Processing ${csvFile}...`);
398
+ const records = await processCSV(csvPath);
399
+ if (verbose) {
400
+ printInfo(` Processed ${records.length} records`);
401
+ }
402
+ if (outputMode === "local" || outputMode === "both") {
403
+ const outputDir = localOutputDir || path10.join(dataDir, "ingested");
404
+ const kbName = path10.basename(csvFile, ".csv").toLowerCase().replace(/\s+/g, "_");
405
+ await writeLocalFiles(records, outputDir, kbName, dryRun);
406
+ }
407
+ if ((outputMode === "pinecone" || outputMode === "both") && pinecone && !dryRun) {
408
+ const assistantName = deriveAssistantName(csvFile);
409
+ await uploadToPinecone(pinecone, records, assistantName);
410
+ }
411
+ }
412
+ }
413
+ async function processCSV(csvPath) {
414
+ const content = fs9.readFileSync(csvPath, "utf-8");
415
+ const rows = parse(content, {
416
+ columns: true,
417
+ skip_empty_lines: true
418
+ });
419
+ const records = [];
420
+ for (let i = 0; i < rows.length; i++) {
421
+ const row = rows[i];
422
+ const markdown = rowToMarkdown(row, i + 1);
423
+ const metadata = extractMetadata(row, i + 1);
424
+ const filename = `row_${String(i + 1).padStart(4, "0")}.md`;
425
+ records.push({ filename, markdown, metadata });
426
+ }
427
+ return records;
428
+ }
429
+ function rowToMarkdown(row, rowNum) {
430
+ const lines = [];
431
+ const title = row.Question || row.Title || row.Name || `Record ${rowNum}`;
432
+ lines.push(`# ${title}
433
+ `);
434
+ for (const [key, value] of Object.entries(row)) {
435
+ if (value && key !== "Question" && key !== "Title" && key !== "Name") {
436
+ lines.push(`## ${key}
437
+ `);
438
+ lines.push(`${value}
439
+ `);
440
+ }
441
+ }
442
+ return lines.join("\n");
443
+ }
444
+ function extractMetadata(row, rowNum) {
445
+ return {
446
+ row_id: rowNum,
447
+ ...row
448
+ };
449
+ }
450
+ async function writeLocalFiles(records, outputDir, kbName, dryRun) {
451
+ const kbDir = path10.join(outputDir, kbName, "documents");
452
+ const metaDir = path10.join(outputDir, kbName, "metadata");
453
+ if (!dryRun) {
454
+ fs9.mkdirSync(kbDir, { recursive: true });
455
+ fs9.mkdirSync(metaDir, { recursive: true });
456
+ }
457
+ for (const record of records) {
458
+ if (!dryRun) {
459
+ fs9.writeFileSync(path10.join(kbDir, record.filename), record.markdown);
460
+ fs9.writeFileSync(
461
+ path10.join(metaDir, record.filename.replace(".md", ".json")),
462
+ JSON.stringify(record.metadata, null, 2)
463
+ );
464
+ }
465
+ }
466
+ if (!dryRun) {
467
+ const indexPath = path10.join(outputDir, kbName, "index.json");
468
+ fs9.writeFileSync(
469
+ indexPath,
470
+ JSON.stringify(
471
+ {
472
+ name: kbName,
473
+ recordCount: records.length,
474
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
475
+ },
476
+ null,
477
+ 2
478
+ )
479
+ );
480
+ }
481
+ printSuccess(` Written ${records.length} files to ${kbDir}`);
482
+ }
483
+ async function uploadToPinecone(pinecone, records, assistantName) {
484
+ const spinner = new Spinner(` Uploading to Pinecone assistant: ${assistantName}`, colors.blue);
485
+ spinner.start();
486
+ try {
487
+ let assistant;
488
+ try {
489
+ assistant = pinecone.assistant(assistantName);
490
+ } catch {
491
+ assistant = await pinecone.createAssistant({
492
+ name: assistantName,
493
+ instructions: `You are a helpful assistant for ${assistantName}.`
494
+ });
495
+ }
496
+ for (const record of records) {
497
+ const blob = new Blob([record.markdown], { type: "text/markdown" });
498
+ const file = new File([blob], record.filename, { type: "text/markdown" });
499
+ await assistant.uploadFile(file, {
500
+ metadata: record.metadata
501
+ });
502
+ }
503
+ spinner.succeed(` Uploaded ${records.length} files to ${assistantName}`);
504
+ } catch (error) {
505
+ spinner.fail(` Failed to upload to ${assistantName}`);
506
+ throw error;
507
+ }
508
+ }
509
+ function deriveAssistantName(csvFile) {
510
+ return path10.basename(csvFile, ".csv").toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
511
+ }
512
+ var init_ingest = __esm({
513
+ "src/lib/ingest.ts"() {
514
+ init_esm_shims();
515
+ init_colors();
516
+ }
517
+ });
518
+
519
+ // src/lib/compose.ts
520
+ var compose_exports = {};
521
+ __export(compose_exports, {
522
+ buildComposition: () => buildComposition
523
+ });
524
+ async function buildComposition(options) {
525
+ const { kbId, domain, description, assistantName, sourcePath, storageMode, dryRun } = options;
526
+ let sourceDataSample = "";
527
+ if (sourcePath) {
528
+ const resolvedPath = resolveSourcePath(sourcePath);
529
+ if (resolvedPath) {
530
+ sourceDataSample = await extractDataSample(resolvedPath);
531
+ printInfo(`Extracted sample from ${resolvedPath}`);
532
+ }
533
+ }
534
+ printStep(1, 3, "Generating manifest with AI...");
535
+ const manifest = await generateManifest({
536
+ kbId,
537
+ domain,
538
+ description,
539
+ assistantName,
540
+ sourceDataSample
541
+ });
542
+ printStep(2, 3, "Manifest generated");
543
+ console.log("\n" + colors.boldWhite("Generated Manifest:"));
544
+ console.log(colors.gray(JSON.stringify(manifest, null, 2)));
545
+ if (dryRun) {
546
+ return { manifest, saved: false };
547
+ }
548
+ printStep(3, 3, "Saving manifest...");
549
+ let savedToLocal = false;
550
+ let savedToPinecone = false;
551
+ let outputPath;
552
+ if (storageMode === "local" || storageMode === "both") {
553
+ outputPath = await saveManifestLocally(manifest);
554
+ savedToLocal = true;
555
+ }
556
+ if (storageMode === "pinecone" || storageMode === "both") {
557
+ await saveManifestToPinecone(manifest);
558
+ savedToPinecone = true;
559
+ }
560
+ return {
561
+ manifest,
562
+ saved: true,
563
+ savedToLocal,
564
+ savedToPinecone,
565
+ outputPath
566
+ };
567
+ }
568
+ async function generateManifest(params) {
569
+ const { kbId, domain, description, assistantName, sourceDataSample } = params;
570
+ const prompt = `Generate a KB manifest for a knowledge base with the following details:
571
+
572
+ KB ID: ${kbId}
573
+ Domain: ${domain || "general"}
574
+ Description: ${description || "A knowledge base"}
575
+ Assistant Name: ${assistantName}
576
+
577
+ ${sourceDataSample ? `Sample data from the source:
578
+ ${sourceDataSample}
579
+ ` : ""}
580
+
581
+ Create a comprehensive manifest that defines:
582
+ 1. What capabilities this KB provides
583
+ 2. What input slots it needs (variables like product_type, issue_description, etc.)
584
+ 3. What outputs it provides
585
+ 4. Slot definitions with descriptions, types, and example values
586
+ 5. Relevance hints (keywords that indicate this KB should be used)
587
+
588
+ The manifest should be practical and help route user queries to this KB when appropriate.`;
589
+ const { object } = await generateObject({
590
+ model: openai(resolveDefaultOpenAiChatModelId()),
591
+ schema: KBManifestSchema,
592
+ prompt
593
+ });
594
+ return object;
595
+ }
596
+ function resolveSourcePath(sourcePath) {
597
+ if (path10.isAbsolute(sourcePath) && fs9.existsSync(sourcePath)) {
598
+ return sourcePath;
599
+ }
600
+ const dataDir = findDataDir();
601
+ if (dataDir) {
602
+ const fullPath = path10.join(dataDir, sourcePath);
603
+ if (fs9.existsSync(fullPath)) {
604
+ return fullPath;
605
+ }
606
+ }
607
+ const cwdPath = path10.join(process.cwd(), sourcePath);
608
+ if (fs9.existsSync(cwdPath)) {
609
+ return cwdPath;
610
+ }
611
+ return null;
612
+ }
613
+ async function extractDataSample(sourcePath) {
614
+ const content = fs9.readFileSync(sourcePath, "utf-8");
615
+ if (sourcePath.endsWith(".csv")) {
616
+ const rows = parse(content, {
617
+ columns: true,
618
+ skip_empty_lines: true
619
+ });
620
+ const sample = rows.slice(0, 5);
621
+ return JSON.stringify(sample, null, 2);
622
+ }
623
+ return content.slice(0, 2e3);
624
+ }
625
+ async function saveManifestLocally(manifest) {
626
+ const manifestsDir = findManifestsDir() || path10.join(process.cwd(), "composition", "manifests");
627
+ fs9.mkdirSync(manifestsDir, { recursive: true });
628
+ const outputPath = path10.join(manifestsDir, `${manifest.id}.json`);
629
+ fs9.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
630
+ return outputPath;
631
+ }
632
+ async function saveManifestToPinecone(manifest) {
633
+ const { Pinecone: Pinecone4 } = await import('@pinecone-database/pinecone');
634
+ const pinecone = new Pinecone4({
635
+ apiKey: process.env.PINECONE_API_KEY
636
+ });
637
+ await pinecone.configureAssistant(manifest.assistantName, {
638
+ metadata: {
639
+ kbManifest: JSON.stringify(manifest)
640
+ }
641
+ });
642
+ }
643
+ var SlotDefinitionSchema, KBManifestSchema;
644
+ var init_compose = __esm({
645
+ "src/lib/compose.ts"() {
646
+ init_esm_shims();
647
+ init_colors();
648
+ init_config();
649
+ SlotDefinitionSchema = z.object({
650
+ name: z.string(),
651
+ type: z.enum(["string", "boolean", "enum"]),
652
+ description: z.string(),
653
+ prompt: z.string().optional(),
654
+ examples: z.array(z.string()).optional(),
655
+ enumValues: z.array(z.string()).optional()
656
+ });
657
+ KBManifestSchema = z.object({
658
+ id: z.string(),
659
+ assistantName: z.string(),
660
+ domain: z.string(),
661
+ description: z.string(),
662
+ capabilities: z.array(z.string()),
663
+ inputs: z.array(
664
+ z.object({
665
+ slot: z.string(),
666
+ required: z.boolean().optional(),
667
+ description: z.string().optional()
668
+ })
669
+ ),
670
+ outputs: z.array(z.string()),
671
+ slots: z.array(SlotDefinitionSchema),
672
+ relevance: z.object({
673
+ goalKeywords: z.array(z.string()).optional(),
674
+ slotTriggers: z.array(z.string()).optional(),
675
+ domains: z.array(z.string()).optional()
676
+ }).optional()
677
+ });
678
+ }
679
+ });
680
+
681
+ // src/lib/introspect.ts
682
+ var introspect_exports = {};
683
+ __export(introspect_exports, {
684
+ introspectAssistant: () => introspectAssistant
685
+ });
686
+ async function introspectAssistant(options) {
687
+ const { assistantName, kbId, domain, storageMode, dryRun, verbose } = options;
688
+ printHeader("Progressive Introspection", icons.search);
689
+ console.log(`${colors.cyan(icons.bullet)} Assistant: ${colors.white(assistantName)}`);
690
+ console.log(`${colors.cyan(icons.bullet)} KB ID: ${colors.white(kbId)}`);
691
+ if (domain) {
692
+ console.log(`${colors.cyan(icons.bullet)} Domain: ${colors.white(domain)}`);
693
+ }
694
+ console.log("");
695
+ const callbacks = buildCLICallbacks(verbose);
696
+ const configOverrides = {};
697
+ if (options.maxDepth !== void 0) configOverrides.maxDepth = options.maxDepth;
698
+ if (options.maxQuestions !== void 0) configOverrides.maxTotalQuestions = options.maxQuestions;
699
+ if (options.maxBranch !== void 0) configOverrides.maxBranchFactor = options.maxBranch;
700
+ if (options.maxRetries !== void 0) configOverrides.maxRetries = options.maxRetries;
701
+ if (options.llmModel !== void 0 && options.llmModel.trim() !== "") {
702
+ configOverrides.llmModel = options.llmModel.trim();
703
+ }
704
+ const result = await progressiveIntrospect({
705
+ assistantName,
706
+ kbId,
707
+ domain,
708
+ config: configOverrides,
709
+ callbacks
710
+ });
711
+ console.log("");
712
+ printHeader("Introspection Summary", icons.check);
713
+ console.log(`${colors.cyan(icons.bullet)} Questions asked: ${colors.white(String(result.stats.questionsAsked))}`);
714
+ console.log(`${colors.cyan(icons.bullet)} Categories discovered: ${colors.white(String(result.stats.categoriesDiscovered))}`);
715
+ console.log(`${colors.cyan(icons.bullet)} Entities discovered: ${colors.white(String(result.stats.entitiesDiscovered))}`);
716
+ console.log(`${colors.cyan(icons.bullet)} Source files found: ${colors.white(String(result.stats.sourceFilesFound))}`);
717
+ if (result.state.sourceFiles.size > 0) {
718
+ console.log("");
719
+ console.log(colors.boldCyan(" Indexed Documents:"));
720
+ for (const file of result.state.sourceFiles) {
721
+ console.log(` ${colors.green(icons.file)} ${colors.white(file)}`);
722
+ }
723
+ }
724
+ if (result.stats.usage) {
725
+ printUsageSummary(result.stats.usage);
726
+ }
727
+ console.log("");
728
+ printHeader("Generated Manifest", icons.file);
729
+ console.log(colors.gray(JSON.stringify(result.manifest, null, 2)));
730
+ const manifestObj = result.manifest;
731
+ const localOutputPath = storageMode === "local" || storageMode === "both" ? getLocalManifestOutputPath(manifestObj.id) : void 0;
732
+ if (dryRun) {
733
+ return {
734
+ manifest: result.manifest,
735
+ saved: false,
736
+ wouldSaveToLocal: storageMode === "local" || storageMode === "both",
737
+ wouldSaveToPinecone: storageMode === "pinecone" || storageMode === "both",
738
+ outputPath: localOutputPath,
739
+ stats: result.stats
740
+ };
741
+ }
742
+ let savedToLocal = false;
743
+ let savedToPinecone = false;
744
+ let outputPath = localOutputPath;
745
+ if (storageMode === "local" || storageMode === "both") {
746
+ outputPath = await saveManifestLocally2(result.manifest);
747
+ savedToLocal = true;
748
+ }
749
+ if (storageMode === "pinecone" || storageMode === "both") {
750
+ await saveManifestToPinecone2(result.manifest);
751
+ savedToPinecone = true;
752
+ }
753
+ return {
754
+ manifest: result.manifest,
755
+ saved: true,
756
+ savedToLocal,
757
+ savedToPinecone,
758
+ outputPath,
759
+ stats: result.stats
760
+ };
761
+ }
762
+ function buildCLICallbacks(verbose) {
763
+ let currentPhase = "";
764
+ let phaseSpinner = null;
765
+ return {
766
+ onPhaseStart: (phase, assistantName) => {
767
+ if (phaseSpinner) {
768
+ phaseSpinner.succeed(`${currentPhase} complete`);
769
+ }
770
+ const phaseLabels = {
771
+ seed_discovery: "Phase 1: Seed Discovery",
772
+ entity_expansion: "Phase 2: Entity Expansion",
773
+ relationship_discovery: "Phase 3: Relationship Discovery",
774
+ abstraction_refinement: "Phase 4: Manifest Inference",
775
+ enum_extraction: "Phase 5: Enum Extraction",
776
+ manifest_generation: "Phase 6: Manifest Synthesis"
777
+ };
778
+ currentPhase = phaseLabels[phase] || phase;
779
+ console.log("");
780
+ printHeader(currentPhase, icons.sparkles);
781
+ if (!verbose) {
782
+ phaseSpinner = new Spinner(`${currentPhase}...`, colors.blue);
783
+ phaseSpinner.start();
784
+ }
785
+ },
786
+ onPhaseProgress: (phase, message) => {
787
+ if (verbose) {
788
+ console.log(` ${colors.gray("\u21B3")} ${colors.gray(message)}`);
789
+ } else if (phaseSpinner) {
790
+ phaseSpinner.update(message);
791
+ }
792
+ },
793
+ onQuestion: (questionNum, totalBudget, question, objective) => {
794
+ if (verbose) {
795
+ console.log("");
796
+ const objectiveLabel = objective.replace(/_/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
797
+ printStep(questionNum, totalBudget, colors.cyan(objectiveLabel));
798
+ console.log(` ${colors.boldWhite("Question:")} ${colors.white(question)}`);
799
+ } else if (phaseSpinner) {
800
+ phaseSpinner.update(`${currentPhase} (Q${questionNum}/${totalBudget})...`);
801
+ }
802
+ },
803
+ onAnswer: (questionNum, answer) => {
804
+ if (verbose) {
805
+ console.log(` ${colors.green(icons.check)} ${colors.boldWhite("Answer Preview:")}`);
806
+ for (const line of formatAnswerPreview(answer)) {
807
+ console.log(` ${colors.white(line)}`);
808
+ }
809
+ console.log(` ${colors.gray(`(${answer.length} chars total)`)}`);
810
+ }
811
+ },
812
+ onValidation: (questionNum, validation) => {
813
+ if (verbose) {
814
+ const classColor = validation.isValid ? colors.green : colors.yellow;
815
+ console.log(` ${colors.gray("Validation:")} ${classColor(validation.classification)} (${(validation.confidence * 100).toFixed(0)}% confidence)`);
816
+ if (!validation.isValid && validation.suggestedRetryStrategy) {
817
+ console.log(` ${colors.gray("Retry strategy:")} ${colors.white(validation.suggestedRetryStrategy)}`);
818
+ }
819
+ if (validation.newInformation) {
820
+ console.log(` ${colors.gray("New information:")} ${colors.white(validation.newInformation)}`);
821
+ }
822
+ if (validation.extractedCategories.length > 0) {
823
+ console.log(` ${colors.gray("Categories:")} ${colors.white(validation.extractedCategories.join(", "))}`);
824
+ }
825
+ if (validation.extractedEntities.length > 0) {
826
+ console.log(` ${colors.gray("Entities:")} ${colors.white(validation.extractedEntities.slice(0, 5).join(", "))}${validation.extractedEntities.length > 5 ? "..." : ""}`);
827
+ }
828
+ }
829
+ },
830
+ onDiscovery: (categories, entities, sourceFiles) => {
831
+ if (verbose) {
832
+ console.log(
833
+ ` ${colors.gray("Discovery:")} ${colors.white(`${categories} categories, ${entities} entities, ${sourceFiles} source files`)}`
834
+ );
835
+ } else if (phaseSpinner) {
836
+ phaseSpinner.update(`${currentPhase} (${categories} categories, ${entities} entities)...`);
837
+ }
838
+ },
839
+ onManifestStep: (step, totalSteps, description) => {
840
+ if (verbose) {
841
+ printStep(step, totalSteps, description);
842
+ } else if (phaseSpinner) {
843
+ phaseSpinner.update(`Generating manifest (${step}/${totalSteps})...`);
844
+ }
845
+ },
846
+ onError: (assistantName, error) => {
847
+ if (phaseSpinner) {
848
+ phaseSpinner.fail(`Error: ${error}`);
849
+ phaseSpinner = null;
850
+ }
851
+ printWarning(`Error for ${assistantName}: ${error}`);
852
+ },
853
+ onComplete: (assistantName, manifest) => {
854
+ if (phaseSpinner) {
855
+ phaseSpinner.succeed("Manifest generation complete");
856
+ phaseSpinner = null;
857
+ }
858
+ }
859
+ };
860
+ }
861
+ function formatAnswerPreview(answer, maxLines = 8, maxChars = 1200) {
862
+ const cleaned = answer.trim();
863
+ if (!cleaned) {
864
+ return ["(empty response)"];
865
+ }
866
+ const lines = cleaned.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
867
+ const previewLines = [];
868
+ let consumed = 0;
869
+ for (const line of lines) {
870
+ if (previewLines.length >= maxLines || consumed >= maxChars) {
871
+ break;
872
+ }
873
+ const remaining = maxChars - consumed;
874
+ if (line.length <= remaining) {
875
+ previewLines.push(line);
876
+ consumed += line.length;
877
+ continue;
878
+ }
879
+ const clipped = `${line.slice(0, Math.max(0, remaining - 1)).trimEnd()}\u2026`;
880
+ previewLines.push(clipped);
881
+ consumed = maxChars;
882
+ break;
883
+ }
884
+ if (lines.length > previewLines.length || cleaned.length > maxChars) {
885
+ previewLines.push("\u2026");
886
+ }
887
+ return previewLines;
888
+ }
889
+ function formatCost(cost) {
890
+ if (cost < 0.01) {
891
+ return `$${cost.toFixed(4)}`;
892
+ }
893
+ return `$${cost.toFixed(2)}`;
894
+ }
895
+ function formatTokens(count) {
896
+ return count.toLocaleString();
897
+ }
898
+ function printUsageSummary(usage) {
899
+ console.log("");
900
+ printHeader("LLM Usage Summary", icons.sparkles);
901
+ console.log(`${colors.cyan(icons.bullet)} Total cost: ${colors.white(formatCost(usage.totalCost))}`);
902
+ console.log(`${colors.cyan(icons.bullet)} Total tokens: ${colors.white(formatTokens(usage.totalTokens.totalTokens))} (${formatTokens(usage.totalTokens.promptTokens)} in / ${formatTokens(usage.totalTokens.completionTokens)} out)`);
903
+ console.log(`${colors.cyan(icons.bullet)} Total LLM calls: ${colors.white(String(usage.entries.length))}`);
904
+ const providers = Object.entries(usage.byProvider);
905
+ if (providers.length > 0) {
906
+ console.log("");
907
+ console.log(colors.boldCyan(" By Provider:"));
908
+ for (const [provider, data] of providers) {
909
+ const costStr = data.cost > 0 ? ` (${formatCost(data.cost)})` : "";
910
+ console.log(` ${colors.gray(provider + ":")} ${colors.white(formatTokens(data.tokens.totalTokens))} tokens${costStr} [${data.calls} calls]`);
911
+ }
912
+ }
913
+ const phases = Object.entries(usage.byPhase);
914
+ if (phases.length > 0) {
915
+ const phaseLabels = {
916
+ seed_discovery: "Seed Discovery",
917
+ entity_expansion: "Entity Expansion",
918
+ relationship_discovery: "Relationship Discovery",
919
+ manifest_inference: "Manifest Inference",
920
+ manifest_generation: "Manifest Generation"
921
+ };
922
+ console.log("");
923
+ console.log(colors.boldCyan(" By Phase:"));
924
+ for (const [phase, data] of phases) {
925
+ const label = phaseLabels[phase] || phase;
926
+ const costStr = data.cost > 0 ? formatCost(data.cost) : "$0.00";
927
+ console.log(` ${colors.gray(label + ":")} ${colors.white(costStr)} (${data.calls} calls, ${formatTokens(data.tokens.totalTokens)} tokens)`);
928
+ }
929
+ }
930
+ }
931
+ async function saveManifestLocally2(manifest) {
932
+ const manifestObj = manifest;
933
+ const outputPath = getLocalManifestOutputPath(manifestObj.id);
934
+ fs9.mkdirSync(path10.dirname(outputPath), { recursive: true });
935
+ fs9.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
936
+ return outputPath;
937
+ }
938
+ function getLocalManifestOutputPath(manifestId) {
939
+ const manifestsDir = findManifestsDir() || path10.join(process.cwd(), "composition", "manifests");
940
+ return path10.join(manifestsDir, `${manifestId}.json`);
941
+ }
942
+ async function saveManifestToPinecone2(manifest) {
943
+ const pinecone = new Pinecone({
944
+ apiKey: process.env.PINECONE_API_KEY
945
+ });
946
+ const manifestObj = manifest;
947
+ await pinecone.configureAssistant(manifestObj.assistantName, {
948
+ metadata: {
949
+ kbManifest: JSON.stringify(manifest)
950
+ }
951
+ });
952
+ }
953
+ var init_introspect = __esm({
954
+ "src/lib/introspect.ts"() {
955
+ init_esm_shims();
956
+ init_colors();
957
+ init_config();
958
+ }
959
+ });
960
+
961
+ // src/lib/validate.ts
962
+ var validate_exports = {};
963
+ __export(validate_exports, {
964
+ validateData: () => validateData
965
+ });
966
+ async function validateData(options = {}) {
967
+ const dataDir = options.path || findDataDir();
968
+ const results = [];
969
+ if (!dataDir || !fs9.existsSync(dataDir)) {
970
+ return {
971
+ results: [
972
+ {
973
+ name: "Data Directory",
974
+ valid: false,
975
+ records: 0,
976
+ issues: [`Data directory not found: ${dataDir || "unknown"}`]
977
+ }
978
+ ],
979
+ allValid: false
980
+ };
981
+ }
982
+ const csvFiles = fs9.readdirSync(dataDir).filter((f) => f.endsWith(".csv"));
983
+ for (const csvFile of csvFiles) {
984
+ const csvPath = path10.join(dataDir, csvFile);
985
+ const result = validateCSV(csvPath, csvFile);
986
+ results.push(result);
987
+ }
988
+ const allValid = results.every((r) => r.valid);
989
+ return { results, allValid };
990
+ }
991
+ function validateCSV(csvPath, filename) {
992
+ const issues = [];
993
+ let records = 0;
994
+ try {
995
+ const content = fs9.readFileSync(csvPath, "utf-8");
996
+ const rows = parse(content, {
997
+ columns: true,
998
+ skip_empty_lines: true
999
+ });
1000
+ records = rows.length;
1001
+ if (records === 0) {
1002
+ issues.push("CSV file is empty");
1003
+ }
1004
+ const headers = Object.keys(rows[0] || {});
1005
+ if (headers.length === 0) {
1006
+ issues.push("CSV file has no columns");
1007
+ }
1008
+ const recommendedColumns = ["Question", "Answer", "Category", "Title", "Description"];
1009
+ const hasRecommended = recommendedColumns.some(
1010
+ (col) => headers.some((h) => h.toLowerCase() === col.toLowerCase())
1011
+ );
1012
+ if (!hasRecommended) {
1013
+ issues.push(
1014
+ `CSV has no standard columns. Found: ${headers.join(", ")}. Consider adding: ${recommendedColumns.join(", ")}`
1015
+ );
1016
+ }
1017
+ const emptyCheck = {};
1018
+ for (const row of rows) {
1019
+ for (const [key, value] of Object.entries(row)) {
1020
+ if (!value || value.trim() === "") {
1021
+ emptyCheck[key] = (emptyCheck[key] || 0) + 1;
1022
+ }
1023
+ }
1024
+ }
1025
+ for (const [col, count] of Object.entries(emptyCheck)) {
1026
+ if (count > records * 0.1) {
1027
+ issues.push(`Column "${col}" has ${count}/${records} empty values`);
1028
+ }
1029
+ }
1030
+ for (let i = 0; i < rows.length; i++) {
1031
+ const row = rows[i];
1032
+ for (const [key, value] of Object.entries(row)) {
1033
+ if (value && value.length > 5e4) {
1034
+ issues.push(`Row ${i + 1}, column "${key}" has very long content (${value.length} chars)`);
1035
+ }
1036
+ }
1037
+ }
1038
+ } catch (error) {
1039
+ issues.push(`Failed to parse CSV: ${error instanceof Error ? error.message : String(error)}`);
1040
+ }
1041
+ return {
1042
+ name: filename,
1043
+ valid: issues.length === 0,
1044
+ records,
1045
+ issues
1046
+ };
1047
+ }
1048
+ var init_validate = __esm({
1049
+ "src/lib/validate.ts"() {
1050
+ init_esm_shims();
1051
+ init_config();
1052
+ }
1053
+ });
1054
+
1055
+ // src/lib/topics.ts
1056
+ var topics_exports = {};
1057
+ __export(topics_exports, {
1058
+ testTopics: () => testTopics
1059
+ });
1060
+ async function testTopics(options) {
1061
+ const { paths, mode = "classic", topN, verbose, json } = options;
1062
+ const results = [];
1063
+ for (const inputPath of paths) {
1064
+ const resolvedPath = path10.resolve(inputPath);
1065
+ if (!fs9.existsSync(resolvedPath)) {
1066
+ console.log(colors.red(`Path not found: ${resolvedPath}`));
1067
+ continue;
1068
+ }
1069
+ const stat = fs9.statSync(resolvedPath);
1070
+ if (stat.isDirectory()) {
1071
+ const files = fs9.readdirSync(resolvedPath);
1072
+ for (const file of files) {
1073
+ const filePath = path10.join(resolvedPath, file);
1074
+ if (fs9.statSync(filePath).isFile()) {
1075
+ const result = await processFile(filePath, mode, topN, verbose);
1076
+ results.push(result);
1077
+ }
1078
+ }
1079
+ } else {
1080
+ const result = await processFile(resolvedPath, mode, topN, verbose);
1081
+ results.push(result);
1082
+ }
1083
+ }
1084
+ if (json) {
1085
+ console.log(JSON.stringify(results, null, 2));
1086
+ } else {
1087
+ printResults(results, verbose);
1088
+ }
1089
+ }
1090
+ async function processFile(filePath, mode, topN, verbose) {
1091
+ const content = fs9.readFileSync(filePath, "utf-8");
1092
+ const preview = verbose ? content.slice(0, 200) + "..." : void 0;
1093
+ let topics;
1094
+ switch (mode) {
1095
+ case "classic":
1096
+ topics = extractTopicsClassic(content, topN);
1097
+ break;
1098
+ case "slm":
1099
+ topics = await extractTopicsSLM(content, topN);
1100
+ break;
1101
+ case "hybrid":
1102
+ const classicTopics = extractTopicsClassic(content, topN);
1103
+ const slmTopics = await extractTopicsSLM(content, topN);
1104
+ topics = [.../* @__PURE__ */ new Set([...classicTopics, ...slmTopics])].slice(0, topN);
1105
+ break;
1106
+ default:
1107
+ topics = extractTopicsClassic(content, topN);
1108
+ }
1109
+ return {
1110
+ file: path10.basename(filePath),
1111
+ topics,
1112
+ method: mode,
1113
+ preview
1114
+ };
1115
+ }
1116
+ function extractTopicsClassic(content, topN) {
1117
+ const TfIdf = natural.TfIdf;
1118
+ const tfidf = new TfIdf();
1119
+ tfidf.addDocument(content);
1120
+ const terms = [];
1121
+ tfidf.listTerms(0).forEach((item) => {
1122
+ if (item.term.length > 2 && !isStopword(item.term)) {
1123
+ terms.push({ term: item.term, score: item.tfidf });
1124
+ }
1125
+ });
1126
+ terms.sort((a, b) => b.score - a.score);
1127
+ return terms.slice(0, topN).map((t) => t.term);
1128
+ }
1129
+ async function extractTopicsSLM(content, topN) {
1130
+ const truncatedContent = content.slice(0, 4e3);
1131
+ const { text } = await generateText({
1132
+ model: openai(resolveDefaultOpenAiChatModelId()),
1133
+ prompt: `Extract the top ${topN} most important topics from the following document.
1134
+ Return only the topics as a comma-separated list, without any additional text or explanation.
1135
+
1136
+ Document:
1137
+ ${truncatedContent}`
1138
+ });
1139
+ return text.split(",").map((t) => t.trim()).filter(Boolean).slice(0, topN);
1140
+ }
1141
+ function isStopword(word) {
1142
+ const stopwords = /* @__PURE__ */ new Set([
1143
+ "the",
1144
+ "a",
1145
+ "an",
1146
+ "and",
1147
+ "or",
1148
+ "but",
1149
+ "in",
1150
+ "on",
1151
+ "at",
1152
+ "to",
1153
+ "for",
1154
+ "of",
1155
+ "with",
1156
+ "by",
1157
+ "from",
1158
+ "is",
1159
+ "are",
1160
+ "was",
1161
+ "were",
1162
+ "be",
1163
+ "been",
1164
+ "being",
1165
+ "have",
1166
+ "has",
1167
+ "had",
1168
+ "do",
1169
+ "does",
1170
+ "did",
1171
+ "will",
1172
+ "would",
1173
+ "could",
1174
+ "should",
1175
+ "may",
1176
+ "might",
1177
+ "must",
1178
+ "shall",
1179
+ "can",
1180
+ "this",
1181
+ "that",
1182
+ "these",
1183
+ "those",
1184
+ "i",
1185
+ "you",
1186
+ "he",
1187
+ "she",
1188
+ "it",
1189
+ "we",
1190
+ "they",
1191
+ "what",
1192
+ "which",
1193
+ "who",
1194
+ "when",
1195
+ "where",
1196
+ "why",
1197
+ "how",
1198
+ "all",
1199
+ "each",
1200
+ "every",
1201
+ "both",
1202
+ "few",
1203
+ "more",
1204
+ "most",
1205
+ "other",
1206
+ "some",
1207
+ "such",
1208
+ "no",
1209
+ "nor",
1210
+ "not",
1211
+ "only",
1212
+ "own",
1213
+ "same",
1214
+ "so",
1215
+ "than",
1216
+ "too",
1217
+ "very",
1218
+ "just"
1219
+ ]);
1220
+ return stopwords.has(word.toLowerCase());
1221
+ }
1222
+ function printResults(results, verbose) {
1223
+ for (const result of results) {
1224
+ divider();
1225
+ console.log(colors.boldWhite(result.file));
1226
+ console.log(colors.gray(`Method: ${result.method}`));
1227
+ if (verbose && result.preview) {
1228
+ console.log(colors.gray("\nPreview:"));
1229
+ console.log(colors.dim(result.preview));
1230
+ }
1231
+ console.log(colors.cyan("\nTopics:"));
1232
+ for (const topic of result.topics) {
1233
+ console.log(` ${colors.green("\u2022")} ${topic}`);
1234
+ }
1235
+ }
1236
+ divider();
1237
+ }
1238
+ var init_topics = __esm({
1239
+ "src/lib/topics.ts"() {
1240
+ init_esm_shims();
1241
+ init_colors();
1242
+ }
1243
+ });
1244
+
1245
+ // src/lib/sync.ts
1246
+ var sync_exports = {};
1247
+ __export(sync_exports, {
1248
+ listManifests: () => listManifests,
1249
+ syncManifests: () => syncManifests
1250
+ });
1251
+ async function listManifests() {
1252
+ const local = [];
1253
+ const pinecone = [];
1254
+ const manifestsDir = findManifestsDir();
1255
+ if (manifestsDir && fs9.existsSync(manifestsDir)) {
1256
+ const files = fs9.readdirSync(manifestsDir).filter((f) => f.endsWith(".json"));
1257
+ for (const file of files) {
1258
+ try {
1259
+ const content = fs9.readFileSync(path10.join(manifestsDir, file), "utf-8");
1260
+ const manifest = JSON.parse(content);
1261
+ local.push({
1262
+ id: manifest.id || path10.basename(file, ".json"),
1263
+ assistantName: manifest.assistantName || "",
1264
+ source: "local"
1265
+ });
1266
+ } catch {
1267
+ }
1268
+ }
1269
+ }
1270
+ try {
1271
+ const pc = new Pinecone({
1272
+ apiKey: process.env.PINECONE_API_KEY
1273
+ });
1274
+ const assistants = await pc.listAssistants();
1275
+ for (const assistant of assistants.assistants || []) {
1276
+ try {
1277
+ const details = await pc.describeAssistant(assistant.name);
1278
+ if (details.metadata?.kbManifest) {
1279
+ const manifest = JSON.parse(details.metadata.kbManifest);
1280
+ pinecone.push({
1281
+ id: manifest.id || assistant.name,
1282
+ assistantName: assistant.name,
1283
+ source: "pinecone"
1284
+ });
1285
+ }
1286
+ } catch {
1287
+ }
1288
+ }
1289
+ } catch (error) {
1290
+ printInfo("Could not list Pinecone assistants");
1291
+ }
1292
+ return { local, pinecone };
1293
+ }
1294
+ async function syncManifests(direction, options = {}) {
1295
+ const { dryRun } = options;
1296
+ const errors = [];
1297
+ let synced = 0;
1298
+ const pc = new Pinecone({
1299
+ apiKey: process.env.PINECONE_API_KEY
1300
+ });
1301
+ const manifestsDir = findManifestsDir() || path10.join(process.cwd(), "composition", "manifests");
1302
+ if (direction === "toLocal" || direction === "both") {
1303
+ printStep(1, 2, "Downloading manifests from Pinecone...");
1304
+ try {
1305
+ const assistants = await pc.listAssistants();
1306
+ for (const assistant of assistants.assistants || []) {
1307
+ try {
1308
+ const details = await pc.describeAssistant(assistant.name);
1309
+ if (details.metadata?.kbManifest) {
1310
+ const manifest = JSON.parse(details.metadata.kbManifest);
1311
+ if (!dryRun) {
1312
+ fs9.mkdirSync(manifestsDir, { recursive: true });
1313
+ const outputPath = path10.join(manifestsDir, `${manifest.id || assistant.name}.json`);
1314
+ fs9.writeFileSync(outputPath, JSON.stringify(manifest, null, 2));
1315
+ }
1316
+ synced++;
1317
+ printInfo(` ${dryRun ? "Would sync" : "Synced"}: ${assistant.name}`);
1318
+ }
1319
+ } catch (error) {
1320
+ errors.push(`Failed to sync ${assistant.name}: ${error}`);
1321
+ }
1322
+ }
1323
+ } catch (error) {
1324
+ errors.push(`Failed to list assistants: ${error}`);
1325
+ }
1326
+ }
1327
+ if (direction === "toPinecone" || direction === "both") {
1328
+ printStep(direction === "both" ? 2 : 1, direction === "both" ? 2 : 1, "Uploading manifests to Pinecone...");
1329
+ if (fs9.existsSync(manifestsDir)) {
1330
+ const files = fs9.readdirSync(manifestsDir).filter((f) => f.endsWith(".json"));
1331
+ for (const file of files) {
1332
+ try {
1333
+ const content = fs9.readFileSync(path10.join(manifestsDir, file), "utf-8");
1334
+ const manifest = JSON.parse(content);
1335
+ if (manifest.assistantName) {
1336
+ if (!dryRun) {
1337
+ await pc.configureAssistant(manifest.assistantName, {
1338
+ metadata: {
1339
+ kbManifest: JSON.stringify(manifest)
1340
+ }
1341
+ });
1342
+ }
1343
+ synced++;
1344
+ printInfo(` ${dryRun ? "Would sync" : "Synced"}: ${manifest.id}`);
1345
+ }
1346
+ } catch (error) {
1347
+ errors.push(`Failed to sync ${file}: ${error}`);
1348
+ }
1349
+ }
1350
+ }
1351
+ }
1352
+ return { synced, errors };
1353
+ }
1354
+ var init_sync = __esm({
1355
+ "src/lib/sync.ts"() {
1356
+ init_esm_shims();
1357
+ init_config();
1358
+ init_colors();
1359
+ }
1360
+ });
1361
+
1362
+ // src/lib/generate.ts
1363
+ var generate_exports = {};
1364
+ __export(generate_exports, {
1365
+ generateApp: () => generateApp
1366
+ });
1367
+ function getTemplatePath(template) {
1368
+ const templateName = template === "vercel-ai" ? "app-vercel-ai" : "app";
1369
+ const possiblePaths = [
1370
+ // From packages/cli/dist (bundled)
1371
+ path10.resolve(import.meta.dirname || __dirname2, "..", "..", "..", "examples", templateName),
1372
+ // From packages/cli/src/lib (source)
1373
+ path10.resolve(import.meta.dirname || __dirname2, "..", "..", "..", "..", "examples", templateName),
1374
+ // From project root (when cwd is project root)
1375
+ path10.resolve(process.cwd(), "examples", templateName),
1376
+ // Up from cwd
1377
+ path10.resolve(process.cwd(), "..", "examples", templateName),
1378
+ path10.resolve(process.cwd(), "..", "..", "examples", templateName)
1379
+ ];
1380
+ for (const templatePath of possiblePaths) {
1381
+ if (fs9.existsSync(templatePath)) {
1382
+ return templatePath;
1383
+ }
1384
+ }
1385
+ return possiblePaths[0];
1386
+ }
1387
+ async function generateAgentConfig(options) {
1388
+ const { name, description, domain } = options;
1389
+ const prompt = `Generate a complete agent configuration for a knowledge-base powered assistant app.
1390
+
1391
+ App Name: ${name}
1392
+ Description: ${description}
1393
+ Domain: ${domain}
1394
+
1395
+ Create a JSON configuration that includes:
1396
+ 1. Appropriate system prompts for the domain
1397
+ 2. 4-6 realistic user goals/intents (with name and description)
1398
+ 3. 5-8 extraction slots relevant to the domain
1399
+ 4. Error messages appropriate for the domain
1400
+ 5. 5-7 example questions users might ask
1401
+ 6. Sensible default behaviors
1402
+
1403
+ The configuration should be practical and help the app understand and respond to user queries in the ${domain} domain.`;
1404
+ try {
1405
+ const { object } = await generateObject({
1406
+ model: openai(resolveDefaultOpenAiChatModelId()),
1407
+ schema: AgentConfigSchema,
1408
+ prompt
1409
+ });
1410
+ return object;
1411
+ } catch (error) {
1412
+ console.error("LLM generation failed, using defaults:", error instanceof Error ? error.message : error);
1413
+ return createDefaultConfig(name, description, domain);
1414
+ }
1415
+ }
1416
+ function createDefaultConfig(name, description, domain) {
1417
+ const kbId = `kb-${domain.toLowerCase().replace(/\s+/g, "-")}`;
1418
+ const defaultModel = resolveDefaultOpenAiChatModelId();
1419
+ return {
1420
+ name,
1421
+ description,
1422
+ domain,
1423
+ llm: {
1424
+ provider: "openai",
1425
+ models: {
1426
+ planning: defaultModel,
1427
+ extraction: defaultModel,
1428
+ synthesis: defaultModel,
1429
+ questionGeneration: defaultModel
1430
+ }
1431
+ },
1432
+ prompts: {
1433
+ plannerSystemRole: `an expert ${domain} assistant`,
1434
+ synthesisSystemRole: `a knowledgeable ${domain} specialist helping users with their questions`,
1435
+ planningInstructions: `Consider the user's needs in the ${domain} domain. Look for relevant information and examples.`,
1436
+ synthesisInstructions: `Provide clear, helpful responses. Reference specific sources when available.`,
1437
+ extractionInstructions: `Identify the topic, specific issue, and any relevant context from the user's query.`
1438
+ },
1439
+ goals: [
1440
+ { name: "get_information", description: "User wants to learn about a topic" },
1441
+ { name: "solve_problem", description: "User needs help solving a specific issue" },
1442
+ { name: "find_resource", description: "User is looking for a specific resource or reference" },
1443
+ { name: "compare_options", description: "User wants to compare different options or approaches" }
1444
+ ],
1445
+ messages: {
1446
+ allKBsFailed: `I apologize, but I encountered an issue accessing the information needed to answer your question. Please try again or rephrase your question.`,
1447
+ noRelevantInfo: `I couldn't find specific information about that topic in the available knowledge base. Could you rephrase your question or provide more context?`,
1448
+ synthesisError: `I encountered an error while preparing the answer. Please try asking your question again.`
1449
+ },
1450
+ slots: [
1451
+ {
1452
+ name: "topic",
1453
+ type: "string",
1454
+ required: true,
1455
+ description: "The main topic of the query",
1456
+ prompt: "What topic are you asking about?",
1457
+ examples: ["general inquiry", "specific issue", "how-to question"]
1458
+ },
1459
+ {
1460
+ name: "context",
1461
+ type: "string",
1462
+ required: false,
1463
+ description: "Additional context for the query",
1464
+ prompt: "Can you provide more context?",
1465
+ examples: ["background info", "related details"]
1466
+ }
1467
+ ],
1468
+ slotPriority: ["topic", "context"],
1469
+ defaultKbId: kbId,
1470
+ citations: {
1471
+ format: "brackets",
1472
+ labelPrefix: "S"
1473
+ },
1474
+ behavior: {
1475
+ maxDisambiguationAttempts: 3,
1476
+ fallbackStrategy: "use_first",
1477
+ enablePlanModification: true
1478
+ },
1479
+ quickQuestions: [
1480
+ `What can you help me with in ${domain}?`,
1481
+ `How do I get started?`,
1482
+ `Can you explain the basics?`,
1483
+ `What are common issues people face?`,
1484
+ `Where can I find more information?`
1485
+ ]
1486
+ };
1487
+ }
1488
+ function copyTemplate(src, dest) {
1489
+ if (!fs9.existsSync(src)) {
1490
+ throw new Error(`Template directory not found: ${src}`);
1491
+ }
1492
+ fs9.mkdirSync(dest, { recursive: true });
1493
+ const entries = fs9.readdirSync(src, { withFileTypes: true });
1494
+ for (const entry of entries) {
1495
+ const srcPath = path10.join(src, entry.name);
1496
+ const destPath = path10.join(dest, entry.name);
1497
+ if (EXCLUDED_PATHS.includes(entry.name)) {
1498
+ continue;
1499
+ }
1500
+ if (entry.isDirectory()) {
1501
+ copyTemplate(srcPath, destPath);
1502
+ } else {
1503
+ fs9.copyFileSync(srcPath, destPath);
1504
+ }
1505
+ }
1506
+ }
1507
+ function updatePackageJson(outputDir, name) {
1508
+ const packageJsonPath = path10.join(outputDir, "package.json");
1509
+ if (!fs9.existsSync(packageJsonPath)) {
1510
+ return;
1511
+ }
1512
+ const packageJson = JSON.parse(fs9.readFileSync(packageJsonPath, "utf-8"));
1513
+ const packageName = name.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9-]/g, "");
1514
+ packageJson.name = `@kat/${packageName}`;
1515
+ packageJson.description = `${name} - A KAT-powered assistant`;
1516
+ fs9.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + "\n");
1517
+ }
1518
+ function updateLayout(outputDir, name, description) {
1519
+ const layoutPath = path10.join(outputDir, "app", "layout.tsx");
1520
+ if (!fs9.existsSync(layoutPath)) {
1521
+ return;
1522
+ }
1523
+ let content = fs9.readFileSync(layoutPath, "utf-8");
1524
+ content = content.replace(
1525
+ /title:\s*['"][^'"]*['"]/,
1526
+ `title: '${name}'`
1527
+ );
1528
+ content = content.replace(
1529
+ /description:\s*['"][^'"]*['"]/,
1530
+ `description: '${description}'`
1531
+ );
1532
+ fs9.writeFileSync(layoutPath, content);
1533
+ }
1534
+ function createEnvExample(outputDir) {
1535
+ const envContent = `# OpenAI API Key (required)
1536
+ OPENAI_API_KEY=your-openai-api-key
1537
+
1538
+ # Pinecone API Key (required for KB access)
1539
+ PINECONE_API_KEY=your-pinecone-api-key
1540
+
1541
+ # Optional: Pinecone Environment
1542
+ # PINECONE_ENVIRONMENT=us-east-1
1543
+ `;
1544
+ fs9.writeFileSync(path10.join(outputDir, ".env.example"), envContent);
1545
+ }
1546
+ function createSampleManifest(outputDir, domain) {
1547
+ const manifestsDir = path10.join(outputDir, "composition", "manifests");
1548
+ fs9.mkdirSync(manifestsDir, { recursive: true });
1549
+ const kbId = `kb-${domain.toLowerCase().replace(/\s+/g, "-")}`;
1550
+ const sampleManifest = {
1551
+ id: kbId,
1552
+ assistantName: kbId,
1553
+ domain,
1554
+ description: `Knowledge base for ${domain}`,
1555
+ capabilities: [
1556
+ `Answer questions about ${domain}`,
1557
+ "Provide relevant information and guidance",
1558
+ "Reference source documents"
1559
+ ],
1560
+ inputs: [
1561
+ { slot: "topic", required: true, description: "The specific topic being asked about" },
1562
+ { slot: "context", required: false, description: "Additional context for the query" }
1563
+ ],
1564
+ outputs: [
1565
+ "Detailed answers with citations",
1566
+ "Relevant document references"
1567
+ ],
1568
+ slots: [
1569
+ {
1570
+ name: "topic",
1571
+ type: "string",
1572
+ description: "The main topic of the query"
1573
+ }
1574
+ ],
1575
+ relevance: {
1576
+ slotTriggers: ["topic"],
1577
+ domains: [domain]
1578
+ }
1579
+ };
1580
+ fs9.writeFileSync(
1581
+ path10.join(manifestsDir, `${kbId}.json`),
1582
+ JSON.stringify(sampleManifest, null, 2) + "\n"
1583
+ );
1584
+ }
1585
+ async function generateApp(options) {
1586
+ const { name, description, domain, template, outputDir } = options;
1587
+ const totalSteps = 5;
1588
+ const outputPath = path10.resolve(process.cwd(), outputDir);
1589
+ printStep(1, totalSteps, "Generating agent configuration with AI...");
1590
+ const config2 = await generateAgentConfig(options);
1591
+ printStep(2, totalSteps, `Copying ${template} template...`);
1592
+ const templatePath = getTemplatePath(template);
1593
+ copyTemplate(templatePath, outputPath);
1594
+ printStep(3, totalSteps, "Writing agent.config.json...");
1595
+ const configPath = path10.join(outputPath, "agent.config.json");
1596
+ fs9.writeFileSync(configPath, JSON.stringify(config2, null, 2) + "\n");
1597
+ printStep(4, totalSteps, "Customizing app files...");
1598
+ updatePackageJson(outputPath, name);
1599
+ updateLayout(outputPath, name, description);
1600
+ createEnvExample(outputPath);
1601
+ printStep(5, totalSteps, "Creating sample KB manifest...");
1602
+ createSampleManifest(outputPath, domain);
1603
+ printInfo(`Template: ${template === "vercel-ai" ? "Vercel AI SDK" : "Native Next.js"}`);
1604
+ return {
1605
+ success: true,
1606
+ outputPath,
1607
+ configPath
1608
+ };
1609
+ }
1610
+ var __dirname2, GoalSchema, SlotSchema, AgentConfigSchema, EXCLUDED_PATHS;
1611
+ var init_generate = __esm({
1612
+ "src/lib/generate.ts"() {
1613
+ init_esm_shims();
1614
+ init_colors();
1615
+ __dirname2 = import.meta.dirname || path10.dirname(new URL(import.meta.url).pathname);
1616
+ GoalSchema = z.object({
1617
+ name: z.string().describe("Snake_case identifier for the goal"),
1618
+ description: z.string().describe("Human-readable description of what the user wants")
1619
+ });
1620
+ SlotSchema = z.object({
1621
+ name: z.string().describe("Snake_case identifier for the slot"),
1622
+ type: z.string().describe("Data type of the slot: string, boolean, or number"),
1623
+ required: z.boolean().describe("Whether this slot is required"),
1624
+ description: z.string().describe("What this slot represents"),
1625
+ prompt: z.string().optional().describe("Question to ask the user to fill this slot"),
1626
+ examples: z.array(z.string()).optional().describe("Example values for this slot"),
1627
+ normalization: z.string().optional().describe("Normalization strategy")
1628
+ });
1629
+ AgentConfigSchema = z.object({
1630
+ name: z.string().describe("Display name of the app"),
1631
+ description: z.string().describe("Full description of what the app does"),
1632
+ domain: z.string().describe("Domain category"),
1633
+ llm: z.object({
1634
+ provider: z.string().default("openai"),
1635
+ models: z.object({
1636
+ planning: z.string().default("gpt-4o-mini"),
1637
+ extraction: z.string().default("gpt-4o-mini"),
1638
+ synthesis: z.string().default("gpt-4o-mini"),
1639
+ questionGeneration: z.string().default("gpt-4o-mini")
1640
+ })
1641
+ }),
1642
+ prompts: z.object({
1643
+ plannerSystemRole: z.string().describe("Role description for the planner"),
1644
+ synthesisSystemRole: z.string().describe("Role description for synthesis"),
1645
+ planningInstructions: z.string().describe("Instructions for planning"),
1646
+ synthesisInstructions: z.string().describe("Instructions for answer synthesis"),
1647
+ extractionInstructions: z.string().describe("Instructions for slot extraction")
1648
+ }),
1649
+ goals: z.array(GoalSchema).describe("Possible user intents/goals"),
1650
+ messages: z.object({
1651
+ allKBsFailed: z.string().describe("Message when KB access fails"),
1652
+ noRelevantInfo: z.string().describe("Message when no relevant info found"),
1653
+ synthesisError: z.string().describe("Message when synthesis fails")
1654
+ }),
1655
+ slots: z.array(SlotSchema).describe("Extraction slots for user queries"),
1656
+ slotPriority: z.array(z.string()).describe("Priority order for slot filling"),
1657
+ defaultKbId: z.string().describe("Default KB to use"),
1658
+ citations: z.object({
1659
+ format: z.string().default("brackets").describe("Citation format: brackets, footnotes, or inline"),
1660
+ labelPrefix: z.string().default("S")
1661
+ }),
1662
+ behavior: z.object({
1663
+ maxDisambiguationAttempts: z.number().default(3),
1664
+ fallbackStrategy: z.string().default("use_first").describe("Fallback strategy: use_first, ask_user, or none"),
1665
+ enablePlanModification: z.boolean().default(true)
1666
+ }),
1667
+ quickQuestions: z.array(z.string()).describe("Example questions for the UI")
1668
+ });
1669
+ EXCLUDED_PATHS = [
1670
+ "node_modules",
1671
+ ".next",
1672
+ ".git",
1673
+ "data",
1674
+ ".env.local",
1675
+ ".DS_Store",
1676
+ "pnpm-lock.yaml",
1677
+ "package-lock.json"
1678
+ ];
1679
+ }
1680
+ });
1681
+
1682
+ // src/index.ts
1683
+ init_esm_shims();
1684
+ init_config();
1685
+ init_colors();
1686
+
1687
+ // src/utils/prompts.ts
1688
+ init_esm_shims();
1689
+ init_colors();
1690
+ function createReadline() {
1691
+ return readline.createInterface({ input: stdin, output: stdout });
1692
+ }
1693
+ async function askQuestion(rl, question, defaultValue) {
1694
+ const defaultHint = defaultValue ? colors.gray(` [${defaultValue}]`) : "";
1695
+ const answer = await rl.question(`${colors.cyan(icons.pointer)} ${question}${defaultHint}: `);
1696
+ return answer.trim() || defaultValue || "";
1697
+ }
1698
+ async function askConfirm(rl, question, defaultValue = false) {
1699
+ const hint = defaultValue ? colors.gray(" [Y/n]") : colors.gray(" [y/N]");
1700
+ const answer = await rl.question(`${colors.cyan(icons.pointer)} ${question}${hint}: `);
1701
+ if (!answer.trim()) return defaultValue;
1702
+ return /^y(es)?$/i.test(answer.trim());
1703
+ }
1704
+ async function askSelect(rl, question, options) {
1705
+ console.log(`
1706
+ ${colors.cyan(icons.pointer)} ${question}`);
1707
+ for (const opt of options) {
1708
+ console.log(menuOption(opt.key, opt.label, opt.description));
1709
+ }
1710
+ const answer = await rl.question(`
1711
+ ${colors.cyan(icons.pointer)} Enter choice: `);
1712
+ return answer.trim().toLowerCase();
1713
+ }
1714
+
1715
+ // src/commands/ingest.ts
1716
+ init_esm_shims();
1717
+ init_colors();
1718
+ init_config();
1719
+ var ingestCommand = new Command("ingest").description("Ingest CSV data into Pinecone assistants").option("-o, --output <mode>", "Output mode: pinecone, local, or both", "pinecone").option("-d, --dir <path>", "Local output directory for ingested files").option("--dry-run", "Preview changes without making them", false).option("-i, --interactive", "Run in interactive mode", false).option("--verbose", "Show detailed output", false).action(async (options) => {
1720
+ if (options.interactive) {
1721
+ await runInteractive();
1722
+ } else {
1723
+ await runIngest(options);
1724
+ }
1725
+ });
1726
+ async function runInteractive() {
1727
+ printHeader("Data Ingestion", icons.database);
1728
+ console.log(colors.gray("Ingest CSV data into Pinecone assistants with topic extraction.\n"));
1729
+ const rl = createReadline();
1730
+ try {
1731
+ const dataDir = findDataDir();
1732
+ if (!dataDir) {
1733
+ printError("Data directory not found.");
1734
+ printInfo("Make sure you have CSV files in a data/ directory.");
1735
+ return;
1736
+ }
1737
+ const csvFiles = fs9.readdirSync(dataDir).filter((f) => f.endsWith(".csv"));
1738
+ if (csvFiles.length === 0) {
1739
+ printWarning("No CSV files found in data directory.");
1740
+ return;
1741
+ }
1742
+ console.log(colors.white("Available CSV files:"));
1743
+ printList(csvFiles.map((f) => `${f} ${colors.gray(`(${path10.join(dataDir, f)})`)}`));
1744
+ const outputModeChoice = await askSelect(rl, "Where should the data be written?", [
1745
+ { key: "1", label: "Pinecone", description: "Upload to Pinecone assistant (remote)" },
1746
+ { key: "2", label: "Local", description: "Write markdown files to disk" },
1747
+ { key: "3", label: "Both", description: "Pinecone + local files" }
1748
+ ]);
1749
+ const outputModeMap = {
1750
+ "1": "pinecone",
1751
+ "2": "local",
1752
+ "3": "both"
1753
+ };
1754
+ const output2 = outputModeMap[outputModeChoice] || "pinecone";
1755
+ let dir;
1756
+ if (output2 === "local" || output2 === "both") {
1757
+ const defaultDir = path10.join(dataDir, "ingested");
1758
+ dir = await askQuestion(rl, "Local output directory", defaultDir);
1759
+ }
1760
+ const dryRun = await askConfirm(rl, "Dry run (preview without making changes)?", false);
1761
+ const confirm = await askConfirm(rl, "Proceed with ingestion?", true);
1762
+ if (!confirm) {
1763
+ printInfo("Ingestion cancelled.");
1764
+ return;
1765
+ }
1766
+ await runIngest({ output: output2, dir, dryRun, interactive: true, verbose: false });
1767
+ } finally {
1768
+ rl.close();
1769
+ }
1770
+ }
1771
+ async function runIngest(options) {
1772
+ const { output: output2, dir, dryRun, verbose } = options;
1773
+ if (output2 === "pinecone" || output2 === "both") {
1774
+ const { missing } = requireEnv(["PINECONE_API_KEY"]);
1775
+ if (missing.length > 0) {
1776
+ printError(`Missing required environment variables: ${missing.join(", ")}`);
1777
+ printInfo("Set these in your .env.local file or environment.");
1778
+ process.exit(1);
1779
+ }
1780
+ }
1781
+ const dataDir = findDataDir();
1782
+ if (!dataDir) {
1783
+ printError("Data directory not found.");
1784
+ process.exit(1);
1785
+ }
1786
+ const spinner = new Spinner("Preparing ingestion...", colors.cyan);
1787
+ spinner.start();
1788
+ try {
1789
+ const ingestModule = await Promise.resolve().then(() => (init_ingest(), ingest_exports));
1790
+ spinner.stop();
1791
+ await ingestModule.ingestData({
1792
+ outputMode: output2,
1793
+ localOutputDir: dir,
1794
+ dryRun,
1795
+ verbose,
1796
+ dataDir
1797
+ });
1798
+ if (!dryRun) {
1799
+ printSuccess("Data ingestion completed successfully!");
1800
+ } else {
1801
+ printInfo("Dry run completed. No changes were made.");
1802
+ }
1803
+ } catch (error) {
1804
+ spinner.fail("Ingestion failed");
1805
+ printError(error instanceof Error ? error.message : String(error));
1806
+ process.exit(1);
1807
+ }
1808
+ }
1809
+
1810
+ // src/commands/compose.ts
1811
+ init_esm_shims();
1812
+ init_colors();
1813
+ init_config();
1814
+ var composeCommand = new Command("compose").description("Build KB manifests interactively with AI").option("--kb-id <id>", "KB identifier (e.g., kb_shipping)").option("--domain <domain>", "Domain name (e.g., returns, warranty)").option("--description <desc>", "Short description of the KB").option("--assistant <name>", "Pinecone assistant name").option("--source <path>", "Path to source CSV data (optional)").option("-s, --storage <mode>", "Storage mode: local, pinecone, or both", "local").option("--dry-run", "Preview manifest without saving", false).option("-i, --interactive", "Run in interactive mode", false).action(async (options) => {
1815
+ if (options.interactive || !options.kbId) {
1816
+ await runInteractive2(options);
1817
+ } else {
1818
+ await runCompose(options);
1819
+ }
1820
+ });
1821
+ async function runInteractive2(initialOptions = {}) {
1822
+ printHeader("Composition Builder", icons.tools);
1823
+ console.log(colors.gray("Build KB manifests from scratch or CSV data.\n"));
1824
+ const rl = createReadline();
1825
+ try {
1826
+ const kbId = initialOptions.kbId || await askQuestion(rl, "KB ID (e.g., kb_shipping)");
1827
+ if (!kbId) {
1828
+ printWarning("KB ID is required.");
1829
+ return;
1830
+ }
1831
+ const domain = initialOptions.domain || await askQuestion(rl, "Domain (e.g., returns, warranty)");
1832
+ const description = initialOptions.description || await askQuestion(rl, "Short description");
1833
+ const assistant = initialOptions.assistant || await askQuestion(rl, "Assistant name", kbId.replace(/_/g, "-"));
1834
+ const source = initialOptions.source || await askQuestion(
1835
+ rl,
1836
+ "Path to source data CSV (optional, press Enter to skip)"
1837
+ );
1838
+ const storageModeChoice = await askSelect(rl, "Where should the manifest be saved?", [
1839
+ { key: "1", label: "Local", description: "Save as JSON file in composition/manifests/" },
1840
+ { key: "2", label: "Pinecone", description: "Save to Pinecone assistant metadata" },
1841
+ { key: "3", label: "Both", description: "Save locally and to Pinecone" }
1842
+ ]);
1843
+ const storageModeMap = {
1844
+ "1": "local",
1845
+ "2": "pinecone",
1846
+ "3": "both"
1847
+ };
1848
+ const storage = storageModeMap[storageModeChoice] || "local";
1849
+ const dryRun = await askConfirm(rl, "Dry run (preview without saving)?", false);
1850
+ await runCompose({
1851
+ kbId,
1852
+ domain,
1853
+ description,
1854
+ assistant,
1855
+ source: source || void 0,
1856
+ storage,
1857
+ dryRun,
1858
+ interactive: true
1859
+ });
1860
+ } finally {
1861
+ rl.close();
1862
+ }
1863
+ }
1864
+ async function runCompose(options) {
1865
+ const { kbId, domain, description, assistant, source, storage, dryRun } = options;
1866
+ if (!kbId) {
1867
+ printError("KB ID is required. Use --kb-id or run in interactive mode.");
1868
+ process.exit(1);
1869
+ }
1870
+ const { missing } = requireEnv(["OPENAI_API_KEY"]);
1871
+ if (missing.length > 0) {
1872
+ printWarning(`Missing environment variables: ${missing.join(", ")}`);
1873
+ printInfo("AI-assisted composition may not work without these.");
1874
+ }
1875
+ if (storage === "pinecone" || storage === "both") {
1876
+ const { missing: pineconeMissing } = requireEnv(["PINECONE_API_KEY"]);
1877
+ if (pineconeMissing.length > 0) {
1878
+ printError("PINECONE_API_KEY is required for Pinecone storage.");
1879
+ process.exit(1);
1880
+ }
1881
+ }
1882
+ const spinner = new Spinner("Generating manifest with AI...", colors.magenta);
1883
+ spinner.start();
1884
+ try {
1885
+ const composeModule = await Promise.resolve().then(() => (init_compose(), compose_exports));
1886
+ spinner.stop();
1887
+ const result = await composeModule.buildComposition({
1888
+ kbId,
1889
+ domain: domain || "",
1890
+ description: description || "",
1891
+ assistantName: assistant || kbId.replace(/_/g, "-"),
1892
+ sourcePath: source,
1893
+ storageMode: storage,
1894
+ dryRun
1895
+ });
1896
+ if (result.saved) {
1897
+ if (result.savedToLocal) {
1898
+ printSuccess(`Manifest saved locally to ${result.outputPath}`);
1899
+ }
1900
+ if (result.savedToPinecone) {
1901
+ printSuccess("Manifest saved to Pinecone assistant");
1902
+ }
1903
+ } else if (dryRun) {
1904
+ printInfo("Dry run completed. No file was written.");
1905
+ }
1906
+ } catch (error) {
1907
+ spinner.fail("Manifest generation failed");
1908
+ printError(error instanceof Error ? error.message : String(error));
1909
+ process.exit(1);
1910
+ }
1911
+ }
1912
+
1913
+ // src/commands/introspect.ts
1914
+ init_esm_shims();
1915
+ init_colors();
1916
+ init_config();
1917
+ function parseStorageMode(value) {
1918
+ const normalized = value.toLowerCase().trim();
1919
+ if (normalized === "local" || normalized === "pinecone" || normalized === "both") {
1920
+ return normalized;
1921
+ }
1922
+ throw new InvalidArgumentError(
1923
+ `Invalid storage mode "${value}". Use one of: local, pinecone, both.`
1924
+ );
1925
+ }
1926
+ function parsePositiveInt(value, optionName) {
1927
+ const parsed = Number.parseInt(value, 10);
1928
+ if (!Number.isInteger(parsed) || parsed <= 0) {
1929
+ throw new InvalidArgumentError(`${optionName} must be a positive integer.`);
1930
+ }
1931
+ return parsed;
1932
+ }
1933
+ var introspectCommand = new Command("introspect").description("Generate manifest from existing Pinecone assistant").option("-a, --assistant <name>", "Pinecone assistant name").option("--kb-id <id>", "KB identifier (defaults to assistant name)").option("--domain <domain>", "Domain name (will be inferred if not provided)").option("-s, --storage <mode>", "Storage mode: local, pinecone, or both", parseStorageMode, "local").option("--dry-run", "Preview manifest without saving", false).option("--verbose", "Show detailed Q&A during introspection", false).option("-i, --interactive", "Run in interactive mode", false).option("--max-depth <n>", "Category drilling depth", (v) => parsePositiveInt(v, "--max-depth")).option("--max-questions <n>", "Total question budget", (v) => parsePositiveInt(v, "--max-questions")).option("--max-branch <n>", "Max expansions per discovered item", (v) => parsePositiveInt(v, "--max-branch")).option("--max-retries <n>", "Retries for generic responses", (v) => parsePositiveInt(v, "--max-retries")).option("--llm-model <id>", "OpenAI model for introspection (overrides KBS_INTROSPECTION_MODEL)").action(async (options) => {
1934
+ if (options.interactive || !options.assistant) {
1935
+ await runInteractive3(options);
1936
+ } else {
1937
+ await runIntrospect(options);
1938
+ }
1939
+ });
1940
+ async function runInteractive3(initialOptions = {}) {
1941
+ printHeader("Manifest Introspector", icons.search);
1942
+ console.log(colors.gray("Generate manifests by introspecting Pinecone assistants.\n"));
1943
+ const rl = createReadline();
1944
+ try {
1945
+ const assistant = initialOptions.assistant || await askQuestion(rl, "Pinecone assistant name");
1946
+ if (!assistant) {
1947
+ printWarning("Assistant name is required.");
1948
+ return;
1949
+ }
1950
+ const kbId = initialOptions.kbId || await askQuestion(rl, "KB ID", assistant);
1951
+ const domain = initialOptions.domain || await askQuestion(rl, "Domain (press Enter to infer)");
1952
+ const storageModeChoice = await askSelect(rl, "Where should the manifest be saved?", [
1953
+ { key: "1", label: "Local", description: "Save as JSON file in composition/manifests/" },
1954
+ { key: "2", label: "Pinecone", description: "Save to Pinecone assistant metadata" },
1955
+ { key: "3", label: "Both", description: "Save locally and to Pinecone" }
1956
+ ]);
1957
+ const storageModeMap = {
1958
+ "1": "local",
1959
+ "2": "pinecone",
1960
+ "3": "both"
1961
+ };
1962
+ const storage = storageModeMap[storageModeChoice] || "local";
1963
+ const dryRun = await askConfirm(rl, "Dry run (preview without saving)?", false);
1964
+ const verbose = await askConfirm(rl, "Verbose output (show Q&A)?", false);
1965
+ console.log("");
1966
+ printInfo("Starting introspection. This may take a few minutes...");
1967
+ await runIntrospect({
1968
+ assistant,
1969
+ kbId,
1970
+ domain: domain || void 0,
1971
+ storage,
1972
+ dryRun,
1973
+ verbose,
1974
+ interactive: true
1975
+ });
1976
+ } finally {
1977
+ rl.close();
1978
+ }
1979
+ }
1980
+ async function runIntrospect(options) {
1981
+ const { assistant, kbId, domain, storage, dryRun, verbose, maxDepth, maxQuestions, maxBranch, maxRetries, llmModel } = options;
1982
+ if (!assistant) {
1983
+ printError("Assistant name is required. Use --assistant or run in interactive mode.");
1984
+ process.exit(1);
1985
+ }
1986
+ const { missing } = requireEnv(["PINECONE_API_KEY", "OPENAI_API_KEY"]);
1987
+ if (missing.length > 0) {
1988
+ printError(`Missing required environment variables: ${missing.join(", ")}`);
1989
+ process.exit(1);
1990
+ }
1991
+ if (dryRun) {
1992
+ printInfo("Dry run enabled: introspection still queries Pinecone and OpenAI, but it does not write outputs.");
1993
+ } else {
1994
+ const saveTargets = storage === "both" ? "local manifest file and Pinecone assistant metadata" : storage === "local" ? "a local manifest file" : "Pinecone assistant metadata";
1995
+ printInfo(`Save target: ${saveTargets}.`);
1996
+ }
1997
+ const spinner = new Spinner("Introspecting assistant...", colors.blue);
1998
+ if (!verbose) {
1999
+ spinner.start();
2000
+ }
2001
+ try {
2002
+ const introspectModule = await Promise.resolve().then(() => (init_introspect(), introspect_exports));
2003
+ if (!verbose) {
2004
+ spinner.stop();
2005
+ }
2006
+ const result = await introspectModule.introspectAssistant({
2007
+ assistantName: assistant,
2008
+ kbId: kbId || assistant,
2009
+ domain,
2010
+ storageMode: storage,
2011
+ dryRun,
2012
+ verbose,
2013
+ maxDepth,
2014
+ maxQuestions,
2015
+ maxBranch,
2016
+ maxRetries,
2017
+ llmModel
2018
+ });
2019
+ if (result.saved) {
2020
+ if (result.savedToLocal) {
2021
+ printSuccess(`Manifest saved locally to ${result.outputPath}`);
2022
+ }
2023
+ if (result.savedToPinecone) {
2024
+ printSuccess("Manifest saved to Pinecone assistant");
2025
+ }
2026
+ } else if (dryRun) {
2027
+ printInfo("Dry run completed. No writes were performed.");
2028
+ if (result.wouldSaveToLocal && result.outputPath) {
2029
+ printInfo(`Would save local manifest to ${result.outputPath}`);
2030
+ }
2031
+ if (result.wouldSaveToPinecone) {
2032
+ printInfo("Would save manifest to Pinecone assistant metadata");
2033
+ }
2034
+ }
2035
+ } catch (error) {
2036
+ if (!verbose) {
2037
+ spinner.fail("Introspection failed");
2038
+ }
2039
+ printError(error instanceof Error ? error.message : String(error));
2040
+ process.exit(1);
2041
+ }
2042
+ }
2043
+
2044
+ // src/commands/validate.ts
2045
+ init_esm_shims();
2046
+ init_colors();
2047
+ init_config();
2048
+ var validateCommand = new Command("validate").description("Validate CSV data against KB definitions").option("-p, --path <path>", "Path to specific CSV file or directory").option("--json", "Output results as JSON", false).option("-i, --interactive", "Run in interactive mode", false).action(async (options) => {
2049
+ if (options.interactive) {
2050
+ await runInteractive4();
2051
+ } else {
2052
+ await runValidate(options);
2053
+ }
2054
+ });
2055
+ async function runInteractive4() {
2056
+ printHeader("Data Validator", icons.check);
2057
+ console.log(colors.gray("Validate CSV data against KB manifest definitions.\n"));
2058
+ const rl = createReadline();
2059
+ try {
2060
+ const dataDir = findDataDir();
2061
+ if (dataDir) {
2062
+ console.log(colors.gray(`Found data directory: ${dataDir}
2063
+ `));
2064
+ }
2065
+ const confirm = await askConfirm(rl, "Run validation?", true);
2066
+ if (!confirm) {
2067
+ return;
2068
+ }
2069
+ await runValidate({ json: false, interactive: true });
2070
+ } finally {
2071
+ rl.close();
2072
+ }
2073
+ }
2074
+ async function runValidate(options) {
2075
+ const { path: dataPath, json } = options;
2076
+ try {
2077
+ const validateModule = await Promise.resolve().then(() => (init_validate(), validate_exports));
2078
+ const result = await validateModule.validateData({ path: dataPath });
2079
+ if (json) {
2080
+ console.log(JSON.stringify(result, null, 2));
2081
+ return;
2082
+ }
2083
+ console.log("");
2084
+ divider();
2085
+ console.log(colors.boldWhite("Validation Summary"));
2086
+ divider();
2087
+ for (const kb of result.results) {
2088
+ const statusIcon = kb.valid ? colors.green(icons.success) : colors.red(icons.error);
2089
+ console.log(`${statusIcon} ${colors.white(kb.name)}: ${kb.records} records`);
2090
+ if (kb.issues.length > 0) {
2091
+ for (const issue of kb.issues) {
2092
+ console.log(` ${colors.yellow(icons.warning)} ${issue}`);
2093
+ }
2094
+ }
2095
+ }
2096
+ console.log("");
2097
+ if (result.allValid) {
2098
+ printSuccess("All data validates successfully!");
2099
+ } else {
2100
+ printWarning("Some validation issues found. Review output above.");
2101
+ process.exit(1);
2102
+ }
2103
+ } catch (error) {
2104
+ printError(error instanceof Error ? error.message : String(error));
2105
+ process.exit(1);
2106
+ }
2107
+ }
2108
+
2109
+ // src/commands/topics.ts
2110
+ init_esm_shims();
2111
+ init_colors();
2112
+ var topicsCommand = new Command("topics").description("Test topic extraction on files").argument("[paths...]", "Files or directories to process").option("-m, --mode <mode>", "Extraction mode: classic, slm, or hybrid").option("-n, --top-n <n>", "Number of topics per document", "5").option("--verbose", "Show document previews", false).option("--json", "Output results as JSON", false).option("-i, --interactive", "Run in interactive mode", false).action(async (paths, options) => {
2113
+ const fullOptions = { ...options, paths, topN: parseInt(options.topN, 10) || 5 };
2114
+ if (fullOptions.interactive || paths.length === 0) {
2115
+ await runInteractive5(fullOptions);
2116
+ } else {
2117
+ await runTopics(fullOptions);
2118
+ }
2119
+ });
2120
+ async function runInteractive5(initialOptions = {}) {
2121
+ printHeader("Topic Extraction Tester", icons.sparkles);
2122
+ console.log(colors.gray("Test topic extraction on files without full ingestion.\n"));
2123
+ const rl = createReadline();
2124
+ try {
2125
+ const inputPath = await askQuestion(rl, "File or directory path");
2126
+ if (!inputPath) {
2127
+ printWarning("Path is required.");
2128
+ return;
2129
+ }
2130
+ const modeChoice = await askSelect(rl, "Select extraction mode:", [
2131
+ { key: "1", label: "Classic (TF-IDF)", description: "Fast, keyword-based extraction" },
2132
+ { key: "2", label: "SLM (LLM)", description: "AI-powered semantic extraction" },
2133
+ { key: "3", label: "Hybrid", description: "Both methods combined" },
2134
+ { key: "4", label: "Default", description: "Use config settings" }
2135
+ ]);
2136
+ const modeMap = {
2137
+ "1": "classic",
2138
+ "2": "slm",
2139
+ "3": "hybrid",
2140
+ "4": void 0
2141
+ };
2142
+ const mode = modeMap[modeChoice];
2143
+ const topNStr = await askQuestion(rl, "Number of topics per document", "5");
2144
+ const topN = parseInt(topNStr, 10) || 5;
2145
+ const verbose = await askConfirm(rl, "Show document previews?", false);
2146
+ const json = await askConfirm(rl, "Output as JSON?", false);
2147
+ await runTopics({
2148
+ paths: [inputPath],
2149
+ mode,
2150
+ topN,
2151
+ verbose,
2152
+ json,
2153
+ interactive: true
2154
+ });
2155
+ } finally {
2156
+ rl.close();
2157
+ }
2158
+ }
2159
+ async function runTopics(options) {
2160
+ const { paths, mode, topN, verbose, json } = options;
2161
+ if (paths.length === 0) {
2162
+ printError("At least one path is required.");
2163
+ process.exit(1);
2164
+ }
2165
+ try {
2166
+ const topicsModule = await Promise.resolve().then(() => (init_topics(), topics_exports));
2167
+ await topicsModule.testTopics({
2168
+ paths,
2169
+ mode,
2170
+ topN,
2171
+ verbose,
2172
+ json
2173
+ });
2174
+ } catch (error) {
2175
+ printError(error instanceof Error ? error.message : String(error));
2176
+ process.exit(1);
2177
+ }
2178
+ }
2179
+
2180
+ // src/commands/sync.ts
2181
+ init_esm_shims();
2182
+ init_colors();
2183
+ init_config();
2184
+ var syncCommand = new Command("sync").description("Sync manifests between local files and Pinecone").option("-d, --direction <dir>", "Sync direction: toLocal, toPinecone, or both").option("--dry-run", "Preview changes without making them", false).option("-i, --interactive", "Run in interactive mode", false).action(async (options) => {
2185
+ if (options.interactive || !options.direction) {
2186
+ await runInteractive6(options);
2187
+ } else {
2188
+ await runSync(options);
2189
+ }
2190
+ });
2191
+ async function runInteractive6(initialOptions = {}) {
2192
+ printHeader("Manifest Sync", icons.gear);
2193
+ console.log(colors.gray("Sync manifests between local files and Pinecone assistant metadata.\n"));
2194
+ const { missing } = requireEnv(["PINECONE_API_KEY"]);
2195
+ if (missing.length > 0) {
2196
+ printError(`Missing required environment variables: ${missing.join(", ")}`);
2197
+ return;
2198
+ }
2199
+ const rl = createReadline();
2200
+ try {
2201
+ const syncModule = await Promise.resolve().then(() => (init_sync(), sync_exports));
2202
+ const manifests = await syncModule.listManifests();
2203
+ console.log(colors.white("Current manifests:"));
2204
+ console.log(
2205
+ ` ${colors.cyan("Local:")} ${manifests.local.length > 0 ? manifests.local.map((m) => m.id).join(", ") : colors.gray("(none)")}`
2206
+ );
2207
+ console.log(
2208
+ ` ${colors.blue("Pinecone:")} ${manifests.pinecone.length > 0 ? manifests.pinecone.map((m) => m.id).join(", ") : colors.gray("(none)")}`
2209
+ );
2210
+ const syncChoice = await askSelect(rl, "What would you like to do?", [
2211
+ { key: "1", label: "Download from Pinecone", description: "Save Pinecone manifests locally" },
2212
+ { key: "2", label: "Upload to Pinecone", description: "Save local manifests to Pinecone" },
2213
+ { key: "3", label: "Sync both ways", description: "Sync all manifests in both directions" },
2214
+ { key: "4", label: "Cancel", description: "Go back" }
2215
+ ]);
2216
+ if (syncChoice === "4") {
2217
+ printInfo("Cancelled.");
2218
+ return;
2219
+ }
2220
+ const directionMap = {
2221
+ "1": "toLocal",
2222
+ "2": "toPinecone",
2223
+ "3": "both"
2224
+ };
2225
+ const direction = directionMap[syncChoice];
2226
+ if (!direction) {
2227
+ return;
2228
+ }
2229
+ const dryRun = initialOptions.dryRun || await askConfirm(rl, "Dry run (preview without changes)?", false);
2230
+ const confirm = await askConfirm(rl, "Proceed with sync?", true);
2231
+ if (!confirm) {
2232
+ printInfo("Cancelled.");
2233
+ return;
2234
+ }
2235
+ await runSync({ direction, dryRun, interactive: true });
2236
+ } finally {
2237
+ rl.close();
2238
+ }
2239
+ }
2240
+ async function runSync(options) {
2241
+ const { direction, dryRun } = options;
2242
+ if (!direction) {
2243
+ printError("Sync direction is required. Use --direction or run in interactive mode.");
2244
+ process.exit(1);
2245
+ }
2246
+ const { missing } = requireEnv(["PINECONE_API_KEY"]);
2247
+ if (missing.length > 0) {
2248
+ printError(`Missing required environment variables: ${missing.join(", ")}`);
2249
+ process.exit(1);
2250
+ }
2251
+ try {
2252
+ const syncModule = await Promise.resolve().then(() => (init_sync(), sync_exports));
2253
+ const result = await syncModule.syncManifests(direction, { dryRun });
2254
+ if (dryRun) {
2255
+ printInfo(`Would sync ${result.synced} manifest(s).`);
2256
+ } else if (result.synced > 0) {
2257
+ printSuccess(`Synced ${result.synced} manifest(s)`);
2258
+ } else {
2259
+ printInfo("No manifests to sync.");
2260
+ }
2261
+ if (result.errors.length > 0) {
2262
+ for (const error of result.errors) {
2263
+ printError(error);
2264
+ }
2265
+ process.exit(1);
2266
+ }
2267
+ } catch (error) {
2268
+ printError(error instanceof Error ? error.message : String(error));
2269
+ process.exit(1);
2270
+ }
2271
+ }
2272
+
2273
+ // src/commands/generate.ts
2274
+ init_esm_shims();
2275
+ init_colors();
2276
+ init_config();
2277
+ var generateCommand = new Command("generate").description("Scaffold a new KAT app with guided questions").option("-n, --name <name>", "App name").option("-d, --description <desc>", "App description").option("--domain <domain>", "Domain (e.g., support, education, healthcare)").option("-t, --template <template>", "App template: native or vercel-ai", "native").option("-o, --output <path>", "Output directory", ".").option("-i, --interactive", "Run in interactive mode", false).action(async (options) => {
2278
+ if (options.interactive || !options.name) {
2279
+ await runInteractive7(options);
2280
+ } else {
2281
+ await runGenerate(options);
2282
+ }
2283
+ });
2284
+ async function runInteractive7(initialOptions = {}) {
2285
+ printHeader("App Generator", icons.rocket);
2286
+ console.log(colors.gray("Create a new KAT-powered Next.js app with guided setup.\n"));
2287
+ const rl = createReadline();
2288
+ try {
2289
+ const templateChoice = await askSelect(rl, "Which app template would you like to use?", [
2290
+ { key: "1", label: "Native Next.js", description: "Uses runAgent() with JSON responses" },
2291
+ { key: "2", label: "Vercel AI SDK", description: "Uses runAgentStreaming() with useChat hook" }
2292
+ ]);
2293
+ const template = templateChoice === "2" ? "vercel-ai" : "native";
2294
+ console.log("\n" + colors.boldWhite("Basic Information"));
2295
+ const name = initialOptions.name || await askQuestion(rl, "App name");
2296
+ if (!name) {
2297
+ printWarning("App name is required.");
2298
+ return;
2299
+ }
2300
+ const description = initialOptions.description || await askQuestion(
2301
+ rl,
2302
+ "App description",
2303
+ `A ${name} assistant powered by KAT`
2304
+ );
2305
+ const domain = initialOptions.domain || await askQuestion(
2306
+ rl,
2307
+ "Domain (e.g., support, education, healthcare)",
2308
+ "general"
2309
+ );
2310
+ const output2 = initialOptions.output || await askQuestion(
2311
+ rl,
2312
+ "Output directory",
2313
+ `./${name.toLowerCase().replace(/\s+/g, "-")}`
2314
+ );
2315
+ console.log("\n" + colors.boldWhite("Summary:"));
2316
+ console.log(` ${colors.cyan("Template:")} ${template === "vercel-ai" ? "Vercel AI SDK" : "Native Next.js"}`);
2317
+ console.log(` ${colors.cyan("Name:")} ${name}`);
2318
+ console.log(` ${colors.cyan("Description:")} ${description}`);
2319
+ console.log(` ${colors.cyan("Domain:")} ${domain}`);
2320
+ console.log(` ${colors.cyan("Output:")} ${output2}`);
2321
+ const proceed = await askConfirm(rl, "\nProceed with generation?", true);
2322
+ if (!proceed) {
2323
+ printInfo("Generation cancelled.");
2324
+ return;
2325
+ }
2326
+ await runGenerate({
2327
+ name,
2328
+ description,
2329
+ domain,
2330
+ template,
2331
+ output: output2,
2332
+ interactive: true
2333
+ });
2334
+ } finally {
2335
+ rl.close();
2336
+ }
2337
+ }
2338
+ async function runGenerate(options) {
2339
+ const { name, description, domain, template, output: output2 } = options;
2340
+ if (!name) {
2341
+ printError("App name is required. Use --name or run in interactive mode.");
2342
+ process.exit(1);
2343
+ }
2344
+ const { missing } = requireEnv(["OPENAI_API_KEY"]);
2345
+ if (missing.length > 0) {
2346
+ printWarning(`Missing environment variables: ${missing.join(", ")}`);
2347
+ printInfo("LLM-assisted generation may not work without these.");
2348
+ }
2349
+ const spinner = new Spinner("Generating app...", colors.magenta);
2350
+ spinner.start();
2351
+ try {
2352
+ const generateModule = await Promise.resolve().then(() => (init_generate(), generate_exports));
2353
+ spinner.stop();
2354
+ const result = await generateModule.generateApp({
2355
+ name,
2356
+ description: description || `A ${name} assistant`,
2357
+ domain: domain || "general",
2358
+ template: template || "native",
2359
+ outputDir: output2 || "."
2360
+ });
2361
+ if (result.success) {
2362
+ console.log("");
2363
+ printSuccess(`App generated successfully at ${colors.cyan(result.outputPath)}`);
2364
+ console.log("");
2365
+ console.log(colors.boldWhite("Next steps:"));
2366
+ console.log(` ${colors.gray("1.")} cd ${result.outputPath}`);
2367
+ console.log(` ${colors.gray("2.")} npm install`);
2368
+ console.log(` ${colors.gray("3.")} cp .env.example .env.local ${colors.gray("# Add your API keys")}`);
2369
+ console.log(` ${colors.gray("4.")} npm run dev`);
2370
+ console.log("");
2371
+ printInfo("Edit agent.config.json to customize your app behavior.");
2372
+ }
2373
+ } catch (error) {
2374
+ spinner.fail("App generation failed");
2375
+ printError(error instanceof Error ? error.message : String(error));
2376
+ process.exit(1);
2377
+ }
2378
+ }
2379
+
2380
+ // src/commands/eval.ts
2381
+ init_esm_shims();
2382
+ init_colors();
2383
+ async function loadEvalModule() {
2384
+ try {
2385
+ return await import('@kat/eval');
2386
+ } catch (error) {
2387
+ throw new Error(
2388
+ "Failed to load @kat/eval. Make sure it is installed:\n npm install @kat/eval"
2389
+ );
2390
+ }
2391
+ }
2392
+ var DEFAULT_BASELINE_SCENARIOS_PATH = "eval/baseline/naive-rag-baseline.json";
2393
+ var evalCommand = new Command().name("eval").description("Evaluate RAG system quality across introspection, retrieval, and agent layers").option("-l, --layer <layer>", "Eval layer: introspection, retrieval, agent, all", "all").option("-a, --assistant <name>", "Pinecone assistant name (required for introspection/retrieval)").option("-s, --scenarios <path>", "Path to scenarios JSON file").option("-b, --baseline", `Load canonical baseline fixtures from ./${DEFAULT_BASELINE_SCENARIOS_PATH}`).option("-e, --endpoint <url>", "Agent API endpoint (required for agent layer)").option("-o, --output <format>", "Output format: console, json", "console").option("-v, --verbose", "Show detailed output").option("-i, --interactive", "Run in interactive mode").action(async (options) => {
2394
+ try {
2395
+ await runEval(options);
2396
+ } catch (error) {
2397
+ printError(error instanceof Error ? error.message : String(error));
2398
+ process.exit(1);
2399
+ }
2400
+ });
2401
+ async function runEval(options) {
2402
+ const { layer, assistant, scenarios, baseline, endpoint, output: output2, verbose } = options;
2403
+ if ((layer === "introspection" || layer === "retrieval" || layer === "all") && !assistant) {
2404
+ throw new Error("--assistant is required for introspection and retrieval eval");
2405
+ }
2406
+ if (baseline && scenarios) {
2407
+ throw new Error("--baseline and --scenarios cannot be used together");
2408
+ }
2409
+ const evalModule = await loadEvalModule();
2410
+ const scenarioPath = resolveScenarioPath({ scenarios, baseline });
2411
+ let scenarioData = null;
2412
+ if (scenarioPath) {
2413
+ scenarioData = await loadScenarioBundle(scenarioPath);
2414
+ }
2415
+ if (baseline && output2 === "console" && scenarioPath) {
2416
+ console.log(colors.gray(`Using baseline fixtures: ${scenarioPath}`));
2417
+ }
2418
+ const hasAgentScenarios = Array.isArray(scenarioData?.scenarios) && scenarioData.scenarios.length > 0;
2419
+ if (layer === "agent" && !endpoint) {
2420
+ throw new Error("--endpoint is required for agent eval");
2421
+ }
2422
+ if (layer === "all" && hasAgentScenarios && !endpoint) {
2423
+ throw new Error(
2424
+ "--endpoint is required when running all layers with agent scenarios (use --layer introspection or --layer retrieval to skip agent eval)"
2425
+ );
2426
+ }
2427
+ const results = [];
2428
+ if (layer === "introspection" || layer === "all") {
2429
+ if (output2 === "console") {
2430
+ console.log("\n" + colors.cyan("\u2501".repeat(60)));
2431
+ console.log(colors.bold("Layer 1: Introspection Eval"));
2432
+ console.log(colors.cyan("\u2501".repeat(60)));
2433
+ }
2434
+ const manifestPath = resolve(process.cwd(), `composition/manifests/${assistant}.json`);
2435
+ let manifest;
2436
+ try {
2437
+ const content = await readFile(manifestPath, "utf-8");
2438
+ manifest = JSON.parse(content);
2439
+ } catch {
2440
+ if (output2 === "console") {
2441
+ console.log(colors.yellow(`\u26A0 No manifest found at ${manifestPath}`));
2442
+ console.log(colors.gray(' Run "kat introspect --assistant ' + assistant + '" first'));
2443
+ }
2444
+ manifest = { id: assistant, assistantName: assistant };
2445
+ }
2446
+ const config2 = {
2447
+ assistantName: assistant,
2448
+ manifest,
2449
+ groundTruth: scenarioData?.groundTruth || [],
2450
+ verbose
2451
+ };
2452
+ const result = await evalModule.evaluateIntrospection(config2);
2453
+ results.push({ layer: "introspection", result });
2454
+ if (output2 === "console") {
2455
+ printEvalResult("Introspection", result);
2456
+ }
2457
+ }
2458
+ if (layer === "retrieval" || layer === "all") {
2459
+ if (output2 === "console") {
2460
+ console.log("\n" + colors.cyan("\u2501".repeat(60)));
2461
+ console.log(colors.bold("Layer 2: Retrieval Eval"));
2462
+ console.log(colors.cyan("\u2501".repeat(60)));
2463
+ }
2464
+ const queries = scenarioData?.queries || [
2465
+ { query: "What topics can you help with?", expectedTopics: [] }
2466
+ ];
2467
+ const config2 = {
2468
+ assistantName: assistant,
2469
+ queries,
2470
+ verbose
2471
+ };
2472
+ const result = await evalModule.evaluateRetrieval(config2);
2473
+ results.push({ layer: "retrieval", result });
2474
+ if (output2 === "console") {
2475
+ printEvalResult("Retrieval", result);
2476
+ }
2477
+ }
2478
+ if (layer === "agent" || layer === "all") {
2479
+ const agentScenarios = scenarioData?.scenarios ? [...scenarioData.scenarios] : [];
2480
+ if (layer === "agent" || endpoint || agentScenarios.length > 0) {
2481
+ if (output2 === "console") {
2482
+ console.log("\n" + colors.cyan("\u2501".repeat(60)));
2483
+ console.log(colors.bold("Layer 3: Agent Eval"));
2484
+ console.log(colors.cyan("\u2501".repeat(60)));
2485
+ }
2486
+ if (agentScenarios.length === 0 && endpoint) {
2487
+ agentScenarios.push({
2488
+ name: "basic-test",
2489
+ initialQuery: "Hello, can you help me?",
2490
+ expectedOutcome: "answer",
2491
+ evaluation: { rubric: "Agent should provide a helpful response." }
2492
+ });
2493
+ }
2494
+ const config2 = {
2495
+ agentEndpoint: endpoint,
2496
+ scenarios: agentScenarios,
2497
+ verbose
2498
+ };
2499
+ const result = await evalModule.evaluateAgent(config2);
2500
+ results.push({ layer: "agent", result });
2501
+ if (output2 === "console") {
2502
+ printEvalResult("Agent", result);
2503
+ }
2504
+ } else if (output2 === "console") {
2505
+ console.log(colors.yellow("\n\u26A0 Skipping agent eval (no endpoint or scenarios provided)"));
2506
+ }
2507
+ }
2508
+ if (output2 === "json") {
2509
+ const jsonOutput = results.length === 1 ? results[0].result : results;
2510
+ console.log(JSON.stringify(jsonOutput, null, 2));
2511
+ }
2512
+ if (output2 === "console") {
2513
+ printSummary(results);
2514
+ }
2515
+ }
2516
+ function printEvalResult(layerName, result) {
2517
+ const status = result.passed ? colors.green("\u2713 PASSED") : colors.red("\u2717 FAILED");
2518
+ const score = result.overallScore;
2519
+ console.log(`
2520
+ ${status} - Overall Score: ${formatScore(score)}/100`);
2521
+ console.log(colors.gray(`Duration: ${result.duration}ms`));
2522
+ console.log("\nScores:");
2523
+ for (const [name, value] of Object.entries(result.scores)) {
2524
+ const formattedName = name.replace(/([A-Z])/g, " $1").replace(/^./, (s) => s.toUpperCase()).trim();
2525
+ const bar = createProgressBar(value, 20);
2526
+ console.log(` ${formattedName.padEnd(18)} ${bar} ${formatScore(value)}/100`);
2527
+ }
2528
+ if (result.evidence && result.evidence.length > 0) {
2529
+ console.log("\nKey Findings:");
2530
+ for (const evidence of result.evidence.slice(0, 4)) {
2531
+ const icon = evidence.score >= 70 ? colors.green("\u2713") : colors.yellow("\u26A0");
2532
+ console.log(` ${icon} ${evidence.reasoning}`);
2533
+ }
2534
+ }
2535
+ }
2536
+ function printSummary(results) {
2537
+ console.log("\n" + colors.cyan("\u2550".repeat(60)));
2538
+ console.log(colors.bold("EVAL SUMMARY"));
2539
+ console.log(colors.cyan("\u2550".repeat(60)));
2540
+ const allPassed = results.every((r) => r.result.passed);
2541
+ const avgScore = results.reduce((sum, r) => sum + r.result.overallScore, 0) / results.length;
2542
+ console.log("\n" + drawBox([
2543
+ `Overall: ${allPassed ? colors.green("PASSED") : colors.red("FAILED")}`,
2544
+ `Average Score: ${formatScore(avgScore)}/100`,
2545
+ "",
2546
+ ...results.map((r) => {
2547
+ const icon = r.result.passed ? "\u2713" : "\u2717";
2548
+ const color = r.result.passed ? colors.green : colors.red;
2549
+ return `${color(icon)} ${r.layer.charAt(0).toUpperCase() + r.layer.slice(1)}: ${r.result.overallScore}/100`;
2550
+ })
2551
+ ], { borderColor: allPassed ? colors.green : colors.red, padding: 1 }));
2552
+ console.log("");
2553
+ }
2554
+ function formatScore(score) {
2555
+ if (score >= 80) return colors.green(String(Math.round(score)));
2556
+ if (score >= 60) return colors.yellow(String(Math.round(score)));
2557
+ return colors.red(String(Math.round(score)));
2558
+ }
2559
+ function createProgressBar(value, width) {
2560
+ const filled = Math.round(value / 100 * width);
2561
+ const empty = width - filled;
2562
+ const filledChar = value >= 70 ? colors.green("\u2588") : value >= 50 ? colors.yellow("\u2588") : colors.red("\u2588");
2563
+ const emptyChar = colors.gray("\u2591");
2564
+ return "[" + filledChar.repeat(filled) + emptyChar.repeat(empty) + "]";
2565
+ }
2566
+ function resolveScenarioPath(options) {
2567
+ if (options.scenarios) {
2568
+ return resolve(process.cwd(), options.scenarios);
2569
+ }
2570
+ if (options.baseline) {
2571
+ return resolve(process.cwd(), DEFAULT_BASELINE_SCENARIOS_PATH);
2572
+ }
2573
+ return null;
2574
+ }
2575
+ async function loadScenarioBundle(scenarioPath) {
2576
+ try {
2577
+ const content = await readFile(scenarioPath, "utf-8");
2578
+ const parsed = JSON.parse(content);
2579
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2580
+ throw new Error("Scenario JSON must be an object");
2581
+ }
2582
+ return parsed;
2583
+ } catch (error) {
2584
+ throw new Error(`Failed to load scenarios from ${scenarioPath}: ${error}`);
2585
+ }
2586
+ }
2587
+
2588
+ // src/index.ts
2589
+ loadEnv();
2590
+ var program = new Command();
2591
+ program.name("kat").description("Knowledge Base Tools CLI - Manage Pinecone knowledge bases").version(getVersion(), "-v, --version", "Display version number").option("--no-color", "Disable colored output").option("--verbose", "Enable verbose output").hook("preAction", (thisCommand) => {
2592
+ if (thisCommand.opts().noColor) {
2593
+ process.env.NO_COLOR = "1";
2594
+ }
2595
+ if (thisCommand.opts().verbose) {
2596
+ process.env.KAT_VERBOSE = "1";
2597
+ }
2598
+ });
2599
+ program.addCommand(ingestCommand);
2600
+ program.addCommand(composeCommand);
2601
+ program.addCommand(introspectCommand);
2602
+ program.addCommand(validateCommand);
2603
+ program.addCommand(topicsCommand);
2604
+ program.addCommand(syncCommand);
2605
+ program.addCommand(generateCommand);
2606
+ program.addCommand(evalCommand);
2607
+ program.action(async () => {
2608
+ await runInteractiveMode();
2609
+ });
2610
+ async function runInteractiveMode() {
2611
+ printBanner();
2612
+ console.log(
2613
+ drawBox(
2614
+ [
2615
+ colors.white("Welcome to the KB Tools CLI!"),
2616
+ "",
2617
+ colors.gray("This tool helps you manage your knowledge bases:"),
2618
+ ` ${colors.cyan("\u2022")} Ingest data into Pinecone`,
2619
+ ` ${colors.cyan("\u2022")} Build and introspect manifests`,
2620
+ ` ${colors.cyan("\u2022")} Validate data and test topics`
2621
+ ],
2622
+ { borderColor: colors.cyan, padding: 2 }
2623
+ )
2624
+ );
2625
+ const rl = createReadline();
2626
+ try {
2627
+ await showMainMenu(rl);
2628
+ } finally {
2629
+ rl.close();
2630
+ }
2631
+ }
2632
+ var MENU_COMMANDS = [
2633
+ { key: "1", name: "Generate App", description: "Scaffold a new KAT app", icon: "\u{1F680}", command: "generate" },
2634
+ { key: "2", name: "Ingest Data", description: "Ingest CSV data into Pinecone", icon: "\u{1F5C3}", command: "ingest" },
2635
+ { key: "3", name: "Compose Manifest", description: "Build KB manifests with AI", icon: "\u{1F527}", command: "compose" },
2636
+ { key: "4", name: "Introspect Assistant", description: "Generate manifest from Pinecone", icon: "\u{1F50D}", command: "introspect" },
2637
+ { key: "5", name: "Eval Quality", description: "Evaluate RAG system quality", icon: "\u{1F4CA}", command: "eval" },
2638
+ { key: "6", name: "Validate Data", description: "Validate CSV against definitions", icon: "\u2713", command: "validate" },
2639
+ { key: "7", name: "Test Topics", description: "Test topic extraction", icon: "\u2728", command: "topics" },
2640
+ { key: "8", name: "Sync Manifests", description: "Sync manifests local \u2194 Pinecone", icon: "\u2699", command: "sync" }
2641
+ ];
2642
+ async function showMainMenu(rl) {
2643
+ const options = [
2644
+ ...MENU_COMMANDS.map((cmd) => ({
2645
+ key: cmd.key,
2646
+ label: `${cmd.icon} ${cmd.name}`,
2647
+ description: cmd.description
2648
+ })),
2649
+ { key: "h", label: "? Help", description: "Show help information" },
2650
+ { key: "q", label: "\u2717 Quit", description: "Exit the CLI" }
2651
+ ];
2652
+ const choice = await askSelect(rl, "What would you like to do?", options);
2653
+ await handleMenuChoice(choice, rl);
2654
+ }
2655
+ async function handleMenuChoice(choice, rl) {
2656
+ const keyToCommand = {
2657
+ "1": "generate",
2658
+ "2": "ingest",
2659
+ "3": "compose",
2660
+ "4": "introspect",
2661
+ "5": "eval",
2662
+ "6": "validate",
2663
+ "7": "topics",
2664
+ "8": "sync",
2665
+ "h": "help",
2666
+ "q": "quit",
2667
+ // Also accept command names directly
2668
+ generate: "generate",
2669
+ ingest: "ingest",
2670
+ compose: "compose",
2671
+ introspect: "introspect",
2672
+ eval: "eval",
2673
+ validate: "validate",
2674
+ topics: "topics",
2675
+ sync: "sync",
2676
+ help: "help",
2677
+ quit: "quit",
2678
+ exit: "quit"
2679
+ };
2680
+ const command = keyToCommand[choice.toLowerCase()] || choice;
2681
+ if (command === "quit") {
2682
+ console.log("\n" + colors.gray("Goodbye! \u{1F44B}") + "\n");
2683
+ process.exit(0);
2684
+ }
2685
+ if (command === "help") {
2686
+ program.outputHelp();
2687
+ return showMainMenu(rl);
2688
+ }
2689
+ const menuCmd = MENU_COMMANDS.find((c) => c.command === command);
2690
+ if (menuCmd) {
2691
+ console.log("");
2692
+ try {
2693
+ await program.parseAsync([process.argv[0], process.argv[1], menuCmd.command, "--interactive"]);
2694
+ } catch (error) {
2695
+ printError(error instanceof Error ? error.message : String(error));
2696
+ }
2697
+ console.log("");
2698
+ const continueChoice = await askConfirm(rl, "Return to main menu?", true);
2699
+ if (continueChoice) {
2700
+ return showMainMenu(rl);
2701
+ } else {
2702
+ console.log("\n" + colors.gray("Goodbye! \u{1F44B}") + "\n");
2703
+ process.exit(0);
2704
+ }
2705
+ } else {
2706
+ printError(`Unknown option: ${choice}`);
2707
+ return showMainMenu(rl);
2708
+ }
2709
+ }
2710
+ program.parse();
2711
+ //# sourceMappingURL=index.js.map
2712
+ //# sourceMappingURL=index.js.map