@cephalization/phoenix-insight 0.4.0 → 1.0.2

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