@cephalization/phoenix-insight 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.
Files changed (54) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +620 -0
  3. package/dist/agent/index.js +230 -0
  4. package/dist/cli.js +640 -0
  5. package/dist/commands/index.js +2 -0
  6. package/dist/commands/px-fetch-more-spans.js +98 -0
  7. package/dist/commands/px-fetch-more-trace.js +110 -0
  8. package/dist/config/index.js +165 -0
  9. package/dist/config/loader.js +141 -0
  10. package/dist/config/schema.js +53 -0
  11. package/dist/index.js +1 -0
  12. package/dist/modes/index.js +17 -0
  13. package/dist/modes/local.js +134 -0
  14. package/dist/modes/sandbox.js +121 -0
  15. package/dist/modes/types.js +1 -0
  16. package/dist/observability/index.js +65 -0
  17. package/dist/progress.js +209 -0
  18. package/dist/prompts/index.js +1 -0
  19. package/dist/prompts/system.js +30 -0
  20. package/dist/snapshot/client.js +74 -0
  21. package/dist/snapshot/context.js +332 -0
  22. package/dist/snapshot/datasets.js +68 -0
  23. package/dist/snapshot/experiments.js +135 -0
  24. package/dist/snapshot/index.js +262 -0
  25. package/dist/snapshot/projects.js +44 -0
  26. package/dist/snapshot/prompts.js +199 -0
  27. package/dist/snapshot/spans.js +80 -0
  28. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  29. package/package.json +75 -0
  30. package/src/agent/index.ts +323 -0
  31. package/src/cli.ts +782 -0
  32. package/src/commands/index.ts +8 -0
  33. package/src/commands/px-fetch-more-spans.ts +174 -0
  34. package/src/commands/px-fetch-more-trace.ts +183 -0
  35. package/src/config/index.ts +225 -0
  36. package/src/config/loader.ts +173 -0
  37. package/src/config/schema.ts +66 -0
  38. package/src/index.ts +1 -0
  39. package/src/modes/index.ts +21 -0
  40. package/src/modes/local.ts +163 -0
  41. package/src/modes/sandbox.ts +144 -0
  42. package/src/modes/types.ts +31 -0
  43. package/src/observability/index.ts +90 -0
  44. package/src/progress.ts +239 -0
  45. package/src/prompts/index.ts +1 -0
  46. package/src/prompts/system.ts +31 -0
  47. package/src/snapshot/client.ts +129 -0
  48. package/src/snapshot/context.ts +462 -0
  49. package/src/snapshot/datasets.ts +132 -0
  50. package/src/snapshot/experiments.ts +246 -0
  51. package/src/snapshot/index.ts +403 -0
  52. package/src/snapshot/projects.ts +58 -0
  53. package/src/snapshot/prompts.ts +267 -0
  54. package/src/snapshot/spans.ts +142 -0
package/dist/cli.js ADDED
@@ -0,0 +1,640 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import * as readline from "node:readline";
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
6
+ import * as os from "node:os";
7
+ import { createSandboxMode, createLocalMode } from "./modes/index.js";
8
+ import { createInsightAgent, runOneShotQuery } from "./agent/index.js";
9
+ import { createSnapshot, createIncrementalSnapshot, createPhoenixClient, PhoenixClientError, } from "./snapshot/index.js";
10
+ import { AgentProgress } from "./progress.js";
11
+ import { initializeObservability, shutdownObservability, } from "./observability/index.js";
12
+ import { initializeConfig, getConfig } from "./config/index.js";
13
+ // Version will be read from package.json during build
14
+ const VERSION = "0.0.1";
15
+ const program = new Command();
16
+ /**
17
+ * Format bash command for display in progress indicator
18
+ */
19
+ function formatBashCommand(command) {
20
+ if (!command)
21
+ return "";
22
+ // Split by newline and get first line
23
+ const lines = command.split("\n");
24
+ const firstLine = lines[0]?.trim() || "";
25
+ // Check for pipeline first (3+ commands)
26
+ if (firstLine.includes(" | ") && firstLine.split(" | ").length > 2) {
27
+ const parts = firstLine.split(" | ");
28
+ const firstCmd = parts[0]?.split(" ")[0] || "";
29
+ const lastCmd = parts[parts.length - 1]?.split(" ")[0] || "";
30
+ return `${firstCmd} | ... | ${lastCmd}`;
31
+ }
32
+ // Common command patterns to display nicely
33
+ if (firstLine.startsWith("cat ")) {
34
+ const file = firstLine.substring(4).trim();
35
+ return `cat ${file}`;
36
+ }
37
+ else if (firstLine.startsWith("grep ")) {
38
+ // Extract pattern and file/directory
39
+ const match = firstLine.match(/grep\s+(?:-[^\s]+\s+)*['"]?([^'"]+)['"]?\s+(.+)/);
40
+ if (match && match[1] && match[2]) {
41
+ return `grep "${match[1]}" in ${match[2]}`;
42
+ }
43
+ return firstLine.substring(0, 60) + (firstLine.length > 60 ? "..." : "");
44
+ }
45
+ else if (firstLine.startsWith("find ")) {
46
+ const match = firstLine.match(/find\s+([^\s]+)(?:\s+-name\s+['"]?([^'"]+)['"]?)?/);
47
+ if (match && match[1]) {
48
+ return match[2]
49
+ ? `find "${match[2]}" in ${match[1]}`
50
+ : `find in ${match[1]}`;
51
+ }
52
+ return firstLine.substring(0, 60) + (firstLine.length > 60 ? "..." : "");
53
+ }
54
+ else if (firstLine.startsWith("ls ")) {
55
+ const path = firstLine.substring(3).trim();
56
+ return path ? `ls ${path}` : "ls";
57
+ }
58
+ else if (firstLine.startsWith("ls")) {
59
+ return "ls";
60
+ }
61
+ else if (firstLine.startsWith("jq ")) {
62
+ return `jq processing JSON data`;
63
+ }
64
+ else if (firstLine.startsWith("head ") || firstLine.startsWith("tail ")) {
65
+ const cmd = firstLine.split(" ")[0];
66
+ const fileMatch = firstLine.match(/(?:head|tail)\s+(?:-[^\s]+\s+)*(.+)/);
67
+ if (fileMatch && fileMatch[1]) {
68
+ return `${cmd} ${fileMatch[1]}`;
69
+ }
70
+ return firstLine.substring(0, 60) + (firstLine.length > 60 ? "..." : "");
71
+ }
72
+ else {
73
+ // For other commands, show up to 80 characters
74
+ return firstLine.substring(0, 80) + (firstLine.length > 80 ? "..." : "");
75
+ }
76
+ }
77
+ /**
78
+ * Handle errors with appropriate exit codes and user-friendly messages
79
+ */
80
+ function handleError(error, context) {
81
+ console.error(`\n❌ Error ${context}:`);
82
+ if (error instanceof PhoenixClientError) {
83
+ switch (error.code) {
84
+ case "NETWORK_ERROR":
85
+ console.error("\n🌐 Network Error: Unable to connect to Phoenix server");
86
+ console.error(` Make sure Phoenix is running and accessible`);
87
+ console.error(` You can specify a different URL with --base-url`);
88
+ break;
89
+ case "AUTH_ERROR":
90
+ console.error("\n🔒 Authentication Error: Invalid or missing API key");
91
+ console.error(` Set the PHOENIX_API_KEY environment variable or use --api-key`);
92
+ break;
93
+ case "INVALID_RESPONSE":
94
+ console.error("\n⚠️ Invalid Response: Phoenix returned unexpected data");
95
+ console.error(` This might be a version compatibility issue`);
96
+ break;
97
+ default:
98
+ console.error("\n❓ Phoenix Client Error:", error.message);
99
+ }
100
+ if (error.originalError && process.env.DEBUG) {
101
+ console.error("\nOriginal error:", error.originalError);
102
+ }
103
+ }
104
+ else if (error instanceof Error) {
105
+ // Check for specific error patterns
106
+ if (error.message.includes("ENOENT")) {
107
+ console.error("\n📁 File System Error: Required file or directory not found");
108
+ console.error(` ${error.message}`);
109
+ }
110
+ else if (error.message.includes("EACCES") ||
111
+ error.message.includes("EPERM")) {
112
+ console.error("\n🚫 Permission Error: Insufficient permissions");
113
+ console.error(` ${error.message}`);
114
+ if (error.message.includes(".phoenix-insight")) {
115
+ console.error(` Try running with appropriate permissions or check ~/.phoenix-insight/`);
116
+ }
117
+ }
118
+ else if (error.message.includes("rate limit") ||
119
+ error.message.includes("429")) {
120
+ console.error("\n⏱️ Rate Limit Error: Too many requests to Phoenix");
121
+ console.error(` Please wait a moment and try again`);
122
+ }
123
+ else if (error.message.includes("timeout")) {
124
+ console.error("\n⏰ Timeout Error: Request took too long");
125
+ console.error(` The Phoenix server might be slow or unresponsive`);
126
+ }
127
+ else {
128
+ console.error(`\n${error.message}`);
129
+ }
130
+ if (error.stack && process.env.DEBUG) {
131
+ console.error("\nStack trace:", error.stack);
132
+ }
133
+ }
134
+ else {
135
+ console.error("\nUnexpected error:", error);
136
+ }
137
+ console.error("\n💡 Tips:");
138
+ console.error(" • Run with DEBUG=1 for more detailed error information");
139
+ console.error(" • Check your Phoenix connection with: phoenix-insight snapshot --base-url <url>");
140
+ console.error(" • Use --help to see all available options");
141
+ process.exit(1);
142
+ }
143
+ program
144
+ .name("phoenix-insight")
145
+ .description("A CLI for Phoenix data analysis with AI agents")
146
+ .version(VERSION)
147
+ .usage("[options] [query]")
148
+ .option("--config <path>", "Path to config file (default: ~/.phoenix-insight/config.json, or set PHOENIX_INSIGHT_CONFIG env var)")
149
+ .addHelpText("after", `
150
+ Configuration:
151
+ Config values are loaded with the following priority (highest to lowest):
152
+ 1. CLI arguments (e.g., --base-url)
153
+ 2. Environment variables (e.g., PHOENIX_BASE_URL)
154
+ 3. Config file (~/.phoenix-insight/config.json)
155
+
156
+ Use --config to specify a custom config file path.
157
+ Set PHOENIX_INSIGHT_CONFIG env var to override the default config location.
158
+
159
+ Examples:
160
+ $ phoenix-insight # Start interactive mode
161
+ $ phoenix-insight "What are the slowest traces?" # Single query (sandbox mode)
162
+ $ phoenix-insight --interactive # Explicitly start interactive mode
163
+ $ phoenix-insight --local "Show me error patterns" # Local mode with persistence
164
+ $ phoenix-insight --local --stream "Analyze recent experiments" # Local mode with streaming
165
+ $ phoenix-insight --config ./my-config.json "Analyze traces" # Use custom config file
166
+ $ phoenix-insight help # Show this help message
167
+ `)
168
+ .hook("preAction", async (thisCommand) => {
169
+ // Get all options from the root command
170
+ const opts = thisCommand.opts();
171
+ // Build CLI args from commander options
172
+ const cliArgs = {
173
+ config: opts.config,
174
+ baseUrl: opts.baseUrl,
175
+ apiKey: opts.apiKey,
176
+ limit: opts.limit,
177
+ stream: opts.stream,
178
+ local: opts.local,
179
+ refresh: opts.refresh,
180
+ trace: opts.trace,
181
+ };
182
+ // Initialize config singleton before any command runs
183
+ await initializeConfig(cliArgs);
184
+ });
185
+ program
186
+ .command("snapshot")
187
+ .description("Create a snapshot of Phoenix data")
188
+ .action(async () => {
189
+ const config = getConfig();
190
+ // Initialize observability if trace is enabled in config
191
+ if (config.trace) {
192
+ initializeObservability({
193
+ enabled: true,
194
+ baseUrl: config.baseUrl,
195
+ apiKey: config.apiKey,
196
+ projectName: "phoenix-insight-snapshot",
197
+ debug: !!process.env.DEBUG,
198
+ });
199
+ }
200
+ try {
201
+ // Determine the execution mode
202
+ const mode = await createLocalMode();
203
+ // Create snapshot with config values
204
+ const snapshotOptions = {
205
+ baseURL: config.baseUrl,
206
+ apiKey: config.apiKey,
207
+ spansPerProject: config.limit,
208
+ showProgress: true,
209
+ };
210
+ await createSnapshot(mode, snapshotOptions);
211
+ // Cleanup
212
+ await mode.cleanup();
213
+ // Shutdown observability if enabled
214
+ await shutdownObservability();
215
+ }
216
+ catch (error) {
217
+ handleError(error, "creating snapshot");
218
+ }
219
+ });
220
+ program
221
+ .command("help")
222
+ .description("Show help information")
223
+ .action(() => {
224
+ program.outputHelp();
225
+ });
226
+ program
227
+ .command("prune")
228
+ .description("Delete the local snapshot directory (~/.phoenix-insight/)")
229
+ .option("--dry-run", "Show what would be deleted without actually deleting")
230
+ .action(async (options) => {
231
+ const snapshotDir = path.join(os.homedir(), ".phoenix-insight");
232
+ try {
233
+ // Check if the directory exists
234
+ const stats = await fs.stat(snapshotDir).catch(() => null);
235
+ if (!stats) {
236
+ console.log("📁 No local snapshot directory found. Nothing to prune.");
237
+ return;
238
+ }
239
+ if (options.dryRun) {
240
+ console.log("🔍 Dry run mode - would delete:");
241
+ console.log(` ${snapshotDir}`);
242
+ // Show size and count of snapshots
243
+ const snapshots = await fs
244
+ .readdir(path.join(snapshotDir, "snapshots"))
245
+ .catch(() => []);
246
+ console.log(` 📊 Contains ${snapshots.length} snapshot(s)`);
247
+ return;
248
+ }
249
+ // Ask for confirmation
250
+ const rl = readline.createInterface({
251
+ input: process.stdin,
252
+ output: process.stdout,
253
+ });
254
+ const answer = await new Promise((resolve) => {
255
+ rl.question(`⚠️ This will delete all local snapshots at:\n ${snapshotDir}\n\n Are you sure? (yes/no): `, resolve);
256
+ });
257
+ rl.close();
258
+ if (answer.toLowerCase() !== "yes" && answer.toLowerCase() !== "y") {
259
+ console.log("❌ Prune cancelled.");
260
+ return;
261
+ }
262
+ // Delete the directory
263
+ await fs.rm(snapshotDir, { recursive: true, force: true });
264
+ console.log("✅ Local snapshot directory deleted successfully!");
265
+ }
266
+ catch (error) {
267
+ console.error("❌ Error pruning snapshots:");
268
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
269
+ process.exit(1);
270
+ }
271
+ });
272
+ program
273
+ .argument("[query]", "Query to run against Phoenix data")
274
+ .option("--sandbox", "Run in sandbox mode with in-memory filesystem (default)")
275
+ .option("--local", "Run in local mode with real filesystem")
276
+ .option("--base-url <url>", "Phoenix base URL")
277
+ .option("--api-key <key>", "Phoenix API key")
278
+ .option("--refresh", "Force refresh of snapshot data")
279
+ .option("--limit <number>", "Limit number of spans to fetch", parseInt)
280
+ .option("--stream [true|false]", "Stream agent responses", (v) => ["f", "false"].includes(v.toLowerCase()) ? false : true)
281
+ .option("-i, --interactive", "Run in interactive mode (REPL)")
282
+ .option("--trace", "Enable tracing of the agent to Phoenix")
283
+ .action(async (query, options) => {
284
+ const config = getConfig();
285
+ // If interactive mode is requested, ignore query argument
286
+ if (options.interactive) {
287
+ await runInteractiveMode();
288
+ return;
289
+ }
290
+ // If no query is provided and no specific flag, start interactive mode
291
+ if (!query && !options.help) {
292
+ await runInteractiveMode();
293
+ return;
294
+ }
295
+ // Initialize observability if trace is enabled in config
296
+ if (config.trace) {
297
+ initializeObservability({
298
+ enabled: true,
299
+ baseUrl: config.baseUrl,
300
+ apiKey: config.apiKey,
301
+ projectName: "phoenix-insight",
302
+ debug: !!process.env.DEBUG,
303
+ });
304
+ }
305
+ try {
306
+ // Determine the execution mode
307
+ const mode = config.mode === "local" ? await createLocalMode() : createSandboxMode();
308
+ // Create Phoenix client
309
+ const client = createPhoenixClient({
310
+ baseURL: config.baseUrl,
311
+ apiKey: config.apiKey,
312
+ });
313
+ // Create or update snapshot
314
+ const snapshotOptions = {
315
+ baseURL: config.baseUrl,
316
+ apiKey: config.apiKey,
317
+ spansPerProject: config.limit,
318
+ showProgress: true,
319
+ };
320
+ if (config.refresh || config.mode !== "local") {
321
+ // For sandbox mode (default) or when refresh is requested, always create a fresh snapshot
322
+ await createSnapshot(mode, snapshotOptions);
323
+ }
324
+ else {
325
+ // For local mode without refresh, try incremental update
326
+ await createIncrementalSnapshot(mode, snapshotOptions);
327
+ }
328
+ // Create agent configuration
329
+ const agentConfig = {
330
+ mode,
331
+ client,
332
+ maxSteps: 25,
333
+ };
334
+ // Execute the query
335
+ const agentProgress = new AgentProgress(!config.stream);
336
+ agentProgress.startThinking();
337
+ if (config.stream) {
338
+ // Stream mode
339
+ const result = (await runOneShotQuery(agentConfig, query, {
340
+ stream: true,
341
+ onStepFinish: (step) => {
342
+ // Show tool usage even in stream mode
343
+ if (step.toolCalls?.length) {
344
+ step.toolCalls.forEach((toolCall) => {
345
+ const toolName = toolCall.toolName;
346
+ if (toolName === "bash") {
347
+ // Extract bash command for better visibility
348
+ const command = toolCall.args?.command || "";
349
+ const formattedCmd = formatBashCommand(command);
350
+ agentProgress.updateTool(toolName, formattedCmd);
351
+ }
352
+ else {
353
+ agentProgress.updateTool(toolName);
354
+ }
355
+ console.log();
356
+ });
357
+ }
358
+ // Show tool results
359
+ if (step.toolResults?.length) {
360
+ step.toolResults.forEach((toolResult) => {
361
+ agentProgress.updateToolResult(toolResult.toolName, !toolResult.isError);
362
+ });
363
+ console.log();
364
+ }
365
+ },
366
+ })); // Type assertion needed due to union type
367
+ // Stop progress before streaming
368
+ agentProgress.stop();
369
+ // Handle streaming response
370
+ console.log("\n✨ Answer:\n");
371
+ for await (const chunk of result.textStream) {
372
+ process.stdout.write(chunk);
373
+ }
374
+ console.log(); // Final newline
375
+ // Wait for full response to complete
376
+ await result.response;
377
+ }
378
+ else {
379
+ // Non-streaming mode
380
+ const result = (await runOneShotQuery(agentConfig, query, {
381
+ onStepFinish: (step) => {
382
+ // Show tool usage
383
+ if (step.toolCalls?.length) {
384
+ step.toolCalls.forEach((toolCall) => {
385
+ const toolName = toolCall.toolName;
386
+ if (toolName === "bash") {
387
+ // Extract bash command for better visibility
388
+ const command = toolCall.args?.command || "";
389
+ const formattedCmd = formatBashCommand(command);
390
+ agentProgress.updateTool(toolName, formattedCmd);
391
+ }
392
+ else {
393
+ agentProgress.updateTool(toolName);
394
+ }
395
+ });
396
+ }
397
+ // Show tool results
398
+ if (step.toolResults?.length) {
399
+ step.toolResults.forEach((toolResult) => {
400
+ agentProgress.updateToolResult(toolResult.toolName, !toolResult.isError);
401
+ });
402
+ }
403
+ },
404
+ })); // Type assertion needed due to union type
405
+ // Stop progress and display the final answer
406
+ agentProgress.succeed();
407
+ console.log("\n✨ Answer:\n");
408
+ console.log(result.text);
409
+ }
410
+ // Cleanup
411
+ await mode.cleanup();
412
+ console.log("\n✅ Done!");
413
+ }
414
+ catch (error) {
415
+ handleError(error, "executing query");
416
+ }
417
+ finally {
418
+ // Shutdown observability if enabled
419
+ await shutdownObservability();
420
+ }
421
+ });
422
+ async function runInteractiveMode() {
423
+ const config = getConfig();
424
+ console.log("🚀 Phoenix Insight Interactive Mode");
425
+ console.log("Type your queries below. Type 'help' for available commands or 'exit' to quit.\n");
426
+ // Prevent the process from exiting on unhandled promise rejections
427
+ process.on("unhandledRejection", (reason, promise) => {
428
+ console.error("\n⚠️ Unhandled promise rejection:", reason);
429
+ console.error("The interactive mode will continue. You can try another query.");
430
+ });
431
+ // Initialize observability if trace is enabled in config
432
+ if (config.trace) {
433
+ initializeObservability({
434
+ enabled: true,
435
+ baseUrl: config.baseUrl,
436
+ apiKey: config.apiKey,
437
+ projectName: "phoenix-insight",
438
+ debug: !!process.env.DEBUG,
439
+ });
440
+ }
441
+ // Setup mode and snapshot once for the session
442
+ let mode;
443
+ let agent;
444
+ try {
445
+ // Determine the execution mode
446
+ mode =
447
+ config.mode === "local" ? await createLocalMode() : createSandboxMode();
448
+ // Create Phoenix client
449
+ const client = createPhoenixClient({
450
+ baseURL: config.baseUrl,
451
+ apiKey: config.apiKey,
452
+ });
453
+ // Create or update snapshot
454
+ const snapshotOptions = {
455
+ baseURL: config.baseUrl,
456
+ apiKey: config.apiKey,
457
+ spansPerProject: config.limit,
458
+ showProgress: true,
459
+ };
460
+ if (config.refresh || config.mode !== "local") {
461
+ await createSnapshot(mode, snapshotOptions);
462
+ }
463
+ else {
464
+ await createIncrementalSnapshot(mode, snapshotOptions);
465
+ }
466
+ console.log("\n✅ Snapshot ready. You can now ask questions about your Phoenix data.\n");
467
+ // Create agent configuration
468
+ const agentConfig = {
469
+ mode,
470
+ client,
471
+ maxSteps: 25,
472
+ };
473
+ // Create reusable agent
474
+ agent = await createInsightAgent(agentConfig);
475
+ // Setup readline interface
476
+ const rl = readline.createInterface({
477
+ input: process.stdin,
478
+ output: process.stdout,
479
+ prompt: "phoenix> ",
480
+ terminal: true, // Ensure terminal mode for better compatibility
481
+ });
482
+ let userExited = false;
483
+ // Handle SIGINT (Ctrl+C) gracefully
484
+ rl.on("SIGINT", () => {
485
+ if (userExited) {
486
+ process.exit(0);
487
+ }
488
+ console.log('\n\nUse "exit" to quit or press Ctrl+C again to force exit.');
489
+ userExited = true;
490
+ rl.prompt();
491
+ });
492
+ // Helper function to process a single query
493
+ const processQuery = async (query) => {
494
+ if (query === "exit" || query === "quit") {
495
+ return true; // Signal to exit
496
+ }
497
+ if (query === "help") {
498
+ console.log("\n📖 Interactive Mode Commands:");
499
+ console.log(" help - Show this help message");
500
+ console.log(" exit, quit - Exit interactive mode");
501
+ console.log(" px-fetch-more - Fetch additional data (e.g., px-fetch-more spans --project <name> --limit <n>)");
502
+ console.log("\n💡 Usage Tips:");
503
+ console.log(" • Ask natural language questions about your Phoenix data");
504
+ console.log(" • The agent has access to bash commands to analyze the data");
505
+ console.log(" • Use px-fetch-more commands to get additional data on-demand");
506
+ console.log("\n🔧 Options (set when starting phoenix-insight):");
507
+ console.log(" --local - Use local mode with persistent storage");
508
+ console.log(" --stream - Stream agent responses in real-time");
509
+ console.log(" --refresh - Force fresh snapshot data");
510
+ console.log(" --limit <n> - Set max spans per project");
511
+ console.log(" --trace - Enable observability tracing");
512
+ return false;
513
+ }
514
+ if (query === "") {
515
+ return false;
516
+ }
517
+ try {
518
+ const agentProgress = new AgentProgress(!config.stream);
519
+ agentProgress.startThinking();
520
+ if (config.stream) {
521
+ // Stream mode
522
+ const result = await agent.stream(query, {
523
+ onStepFinish: (step) => {
524
+ // Show tool usage even in stream mode
525
+ if (step.toolCalls?.length) {
526
+ step.toolCalls.forEach((toolCall) => {
527
+ const toolName = toolCall.toolName;
528
+ if (toolName === "bash") {
529
+ // Extract bash command for better visibility
530
+ const command = toolCall.args?.command || "";
531
+ const shortCmd = command.split("\n")[0].substring(0, 50);
532
+ agentProgress.updateTool(toolName, shortCmd + (command.length > 50 ? "..." : ""));
533
+ }
534
+ else {
535
+ agentProgress.updateTool(toolName);
536
+ }
537
+ });
538
+ }
539
+ // Show tool results
540
+ if (step.toolResults?.length) {
541
+ step.toolResults.forEach((toolResult) => {
542
+ agentProgress.updateToolResult(toolResult.toolName, !toolResult.isError);
543
+ });
544
+ }
545
+ },
546
+ });
547
+ // Stop progress before streaming
548
+ agentProgress.stop();
549
+ // Handle streaming response
550
+ console.log("\n✨ Answer:\n");
551
+ for await (const chunk of result.textStream) {
552
+ process.stdout.write(chunk);
553
+ }
554
+ console.log(); // Final newline
555
+ // Wait for full response to complete
556
+ await result.response;
557
+ }
558
+ else {
559
+ // Non-streaming mode
560
+ const result = await agent.generate(query, {
561
+ onStepFinish: (step) => {
562
+ // Show tool usage
563
+ if (step.toolCalls?.length) {
564
+ step.toolCalls.forEach((toolCall) => {
565
+ const toolName = toolCall.toolName;
566
+ if (toolName === "bash") {
567
+ // Extract bash command for better visibility
568
+ const command = toolCall.args?.command || "";
569
+ const shortCmd = command.split("\n")[0].substring(0, 50);
570
+ agentProgress.updateTool(toolName, shortCmd + (command.length > 50 ? "..." : ""));
571
+ }
572
+ else {
573
+ agentProgress.updateTool(toolName);
574
+ }
575
+ });
576
+ }
577
+ // Show tool results
578
+ if (step.toolResults?.length) {
579
+ step.toolResults.forEach((toolResult) => {
580
+ agentProgress.updateToolResult(toolResult.toolName, !toolResult.isError);
581
+ });
582
+ }
583
+ },
584
+ });
585
+ // Stop progress and display the final answer
586
+ agentProgress.succeed();
587
+ console.log("\n✨ Answer:\n");
588
+ console.log(result.text);
589
+ }
590
+ console.log("\n" + "─".repeat(50) + "\n");
591
+ }
592
+ catch (error) {
593
+ console.error("\n❌ Query Error:");
594
+ if (error instanceof PhoenixClientError) {
595
+ console.error(` ${error.message}`);
596
+ }
597
+ else if (error instanceof Error) {
598
+ console.error(` ${error.message}`);
599
+ }
600
+ else {
601
+ console.error(` ${String(error)}`);
602
+ }
603
+ console.error(" You can try again with a different query\n");
604
+ }
605
+ return false;
606
+ };
607
+ // Use event-based approach instead of async iterator to prevent
608
+ // premature exit when ora/spinners interact with stdin
609
+ await new Promise((resolve) => {
610
+ rl.on("line", async (line) => {
611
+ const query = line.trim();
612
+ // Pause readline while processing to prevent queuing
613
+ rl.pause();
614
+ const shouldExit = await processQuery(query);
615
+ if (shouldExit) {
616
+ rl.close();
617
+ }
618
+ else {
619
+ // Resume and show prompt for next input
620
+ rl.resume();
621
+ rl.prompt();
622
+ }
623
+ });
624
+ rl.on("close", () => {
625
+ resolve();
626
+ });
627
+ // Show initial prompt
628
+ rl.prompt();
629
+ });
630
+ console.log("\n👋 Goodbye!");
631
+ // Cleanup
632
+ await mode.cleanup();
633
+ // Shutdown observability if enabled
634
+ await shutdownObservability();
635
+ }
636
+ catch (error) {
637
+ handleError(error, "setting up interactive mode");
638
+ }
639
+ }
640
+ program.parse();
@@ -0,0 +1,2 @@
1
+ export { fetchMoreSpans, } from "./px-fetch-more-spans.js";
2
+ export { fetchMoreTrace, } from "./px-fetch-more-trace.js";