@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
@@ -0,0 +1,239 @@
1
+ /**
2
+ * Progress indicators for Phoenix Insight CLI
3
+ */
4
+ import ora, { type Ora } from "ora";
5
+
6
+ /**
7
+ * Progress indicator for snapshot operations
8
+ */
9
+ export class SnapshotProgress {
10
+ private spinner: Ora | null = null;
11
+ private enabled: boolean;
12
+ private currentPhase: string | null = null;
13
+ private totalSteps = 6;
14
+ private currentStep = 0;
15
+
16
+ constructor(enabled: boolean = true) {
17
+ this.enabled = enabled;
18
+ }
19
+
20
+ /**
21
+ * Start the progress indicator
22
+ */
23
+ start(message: string = "Creating Phoenix data snapshot") {
24
+ if (!this.enabled) return;
25
+
26
+ this.currentStep = 0;
27
+ this.spinner = ora({
28
+ text: message,
29
+ spinner: "dots",
30
+ color: "blue",
31
+ }).start();
32
+ }
33
+
34
+ /**
35
+ * Update progress with a new phase
36
+ */
37
+ update(phase: string, detail?: string) {
38
+ if (!this.enabled || !this.spinner) return;
39
+
40
+ this.currentStep++;
41
+ this.currentPhase = phase;
42
+
43
+ const progress = Math.round((this.currentStep / this.totalSteps) * 100);
44
+ const progressBar = this.createProgressBar(progress);
45
+
46
+ const message = detail
47
+ ? `${progressBar} ${phase}: ${detail}`
48
+ : `${progressBar} ${phase}`;
49
+
50
+ this.spinner.text = message;
51
+ }
52
+
53
+ /**
54
+ * Complete a phase successfully
55
+ */
56
+ succeed(message?: string) {
57
+ if (!this.enabled || !this.spinner) return;
58
+
59
+ const finalMessage =
60
+ message || `✓ ${this.currentPhase || "Snapshot"} complete`;
61
+ this.spinner.succeed(finalMessage);
62
+ this.spinner = null;
63
+ }
64
+
65
+ /**
66
+ * Fail with an error
67
+ */
68
+ fail(message?: string) {
69
+ if (!this.enabled || !this.spinner) return;
70
+
71
+ const finalMessage =
72
+ message || `✗ ${this.currentPhase || "Snapshot"} failed`;
73
+ this.spinner.fail(finalMessage);
74
+ this.spinner = null;
75
+ }
76
+
77
+ /**
78
+ * Stop without success/fail status
79
+ */
80
+ stop() {
81
+ if (!this.enabled || !this.spinner) return;
82
+
83
+ this.spinner.stop();
84
+ this.spinner = null;
85
+ }
86
+
87
+ /**
88
+ * Create a progress bar string
89
+ */
90
+ private createProgressBar(percentage: number): string {
91
+ const width = 20;
92
+ const filled = Math.round((percentage / 100) * width);
93
+ const empty = width - filled;
94
+
95
+ const bar = "█".repeat(filled) + "░".repeat(empty);
96
+ return `[${bar}] ${percentage}%`;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Progress indicator for agent thinking
102
+ */
103
+ export class AgentProgress {
104
+ private spinner: Ora | null = null;
105
+ private enabled: boolean;
106
+ private stepCount = 0;
107
+ private currentTool: string | null = null;
108
+
109
+ constructor(enabled: boolean = true) {
110
+ this.enabled = enabled;
111
+ }
112
+
113
+ /**
114
+ * Start thinking indicator
115
+ */
116
+ startThinking() {
117
+ if (!this.enabled) return;
118
+
119
+ this.stepCount = 0;
120
+ this.currentTool = null;
121
+ this.spinner = ora({
122
+ text: "🤔 Analyzing...",
123
+ spinner: "dots",
124
+ color: "cyan",
125
+ }).start();
126
+ }
127
+
128
+ /**
129
+ * Update with current tool usage
130
+ */
131
+ updateTool(toolName: string, detail?: string) {
132
+ if (!this.enabled || !this.spinner) return;
133
+
134
+ this.stepCount++;
135
+ this.currentTool = toolName;
136
+
137
+ // Map tool names to more user-friendly descriptions
138
+ const friendlyNames: Record<string, string> = {
139
+ bash: "Running command",
140
+ px_fetch_more_spans: "Fetching additional spans",
141
+ px_fetch_more_trace: "Fetching trace details",
142
+ };
143
+
144
+ const displayName = friendlyNames[toolName] || toolName;
145
+ const message = detail
146
+ ? `🔧 ${displayName}: ${detail}`
147
+ : `🔧 ${displayName} (step ${this.stepCount})`;
148
+
149
+ this.spinner.text = message;
150
+ }
151
+
152
+ /**
153
+ * Update with tool result
154
+ */
155
+ updateToolResult(toolName: string, success: boolean = true) {
156
+ if (!this.enabled || !this.spinner) return;
157
+
158
+ const icon = success ? "✓" : "✗";
159
+ const status = success ? "completed" : "failed";
160
+
161
+ // More informative messages for each tool
162
+ const toolMessages: Record<string, string> = {
163
+ bash: "Command executed",
164
+ px_fetch_more_spans: "Additional spans fetched",
165
+ px_fetch_more_trace: "Trace details fetched",
166
+ };
167
+
168
+ const baseMessage = toolMessages[toolName] || `Tool ${toolName}`;
169
+ const message = `${icon} ${baseMessage} ${status}`;
170
+
171
+ // Brief flash of the result before moving on
172
+ this.spinner.text = message;
173
+ }
174
+
175
+ /**
176
+ * Show progress for a specific action
177
+ */
178
+ updateAction(action: string) {
179
+ if (!this.enabled || !this.spinner) return;
180
+
181
+ this.spinner.text = `🔍 ${action}...`;
182
+ }
183
+
184
+ /**
185
+ * Stop the thinking indicator
186
+ */
187
+ stop() {
188
+ if (!this.enabled || !this.spinner) return;
189
+
190
+ this.spinner.stop();
191
+ this.spinner = null;
192
+ }
193
+
194
+ /**
195
+ * Complete with a success message
196
+ */
197
+ succeed(message: string = "✨ Analysis complete") {
198
+ if (!this.enabled || !this.spinner) return;
199
+
200
+ this.spinner.succeed(message);
201
+ this.spinner = null;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Simple progress logger for when spinners aren't appropriate
207
+ */
208
+ export class SimpleProgress {
209
+ private enabled: boolean;
210
+
211
+ constructor(enabled: boolean = true) {
212
+ this.enabled = enabled;
213
+ }
214
+
215
+ log(message: string) {
216
+ if (!this.enabled) return;
217
+ console.log(`[Phoenix Insight] ${message}`);
218
+ }
219
+
220
+ info(message: string) {
221
+ if (!this.enabled) return;
222
+ console.log(`ℹ️ ${message}`);
223
+ }
224
+
225
+ success(message: string) {
226
+ if (!this.enabled) return;
227
+ console.log(`✅ ${message}`);
228
+ }
229
+
230
+ warning(message: string) {
231
+ if (!this.enabled) return;
232
+ console.log(`⚠️ ${message}`);
233
+ }
234
+
235
+ error(message: string) {
236
+ if (!this.enabled) return;
237
+ console.log(`❌ ${message}`);
238
+ }
239
+ }
@@ -0,0 +1 @@
1
+ export { INSIGHT_SYSTEM_PROMPT } from "./system.js";
@@ -0,0 +1,31 @@
1
+ /**
2
+ * System prompt for the Phoenix Insight AI agent
3
+ * This prompt teaches the agent about the filesystem layout and available commands
4
+ */
5
+
6
+ export const INSIGHT_SYSTEM_PROMPT = `You are an expert at analyzing Phoenix observability data.
7
+
8
+ **START by reading /phoenix/_context.md** - it contains a summary of what's available.
9
+
10
+ You have access to a bash shell with Phoenix data organized as files:
11
+
12
+ /phoenix/
13
+ _context.md - READ THIS FIRST: summary of available data
14
+ /projects/{name}/spans/ - Span data (JSONL format, may be sampled)
15
+ /datasets/ - Datasets and examples
16
+ /experiments/ - Experiment runs and results
17
+ /prompts/ - Prompt templates and versions
18
+
19
+ Use commands like:
20
+ - cat, head, tail: Read file contents
21
+ - grep: Search for patterns
22
+ - jq: Query and transform JSON/JSONL
23
+ - ls, find: Navigate and discover data
24
+ - sort, uniq, wc: Aggregate and count
25
+ - awk: Complex text processing
26
+
27
+ If you need MORE data than what's in the snapshot:
28
+ - px-fetch-more spans --project <name> --limit 500
29
+ - px-fetch-more trace --trace-id <id>
30
+
31
+ This is a READ-ONLY snapshot. Start with _context.md, then explore to answer the question.`;
@@ -0,0 +1,129 @@
1
+ import { createClient, type PhoenixClient } from "@arizeai/phoenix-client";
2
+
3
+ export class PhoenixClientError extends Error {
4
+ public code:
5
+ | "NETWORK_ERROR"
6
+ | "AUTH_ERROR"
7
+ | "INVALID_RESPONSE"
8
+ | "UNKNOWN_ERROR";
9
+ public originalError?: unknown;
10
+
11
+ constructor(
12
+ message: string,
13
+ code: "NETWORK_ERROR" | "AUTH_ERROR" | "INVALID_RESPONSE" | "UNKNOWN_ERROR",
14
+ originalError?: unknown
15
+ ) {
16
+ super(message);
17
+ this.name = "PhoenixClientError";
18
+ this.code = code;
19
+ this.originalError = originalError;
20
+ }
21
+ }
22
+
23
+ export interface PhoenixClientConfig {
24
+ baseURL?: string;
25
+ apiKey?: string;
26
+ }
27
+
28
+ /**
29
+ * Creates a wrapped Phoenix client with error handling
30
+ */
31
+ export function createPhoenixClient(
32
+ config: PhoenixClientConfig = {}
33
+ ): PhoenixClient {
34
+ const headers: Record<string, string> = {};
35
+
36
+ if (config.apiKey) {
37
+ headers["api_key"] = config.apiKey;
38
+ }
39
+
40
+ const clientOptions: Parameters<typeof createClient>[0] = {
41
+ options: {
42
+ baseUrl: config.baseURL,
43
+ headers: Object.keys(headers).length > 0 ? headers : undefined,
44
+ },
45
+ };
46
+
47
+ return createClient(clientOptions);
48
+ }
49
+
50
+ /**
51
+ * Wraps an async operation with standardized error handling
52
+ */
53
+ export async function withErrorHandling<T>(
54
+ operation: () => Promise<T>,
55
+ context: string
56
+ ): Promise<T> {
57
+ try {
58
+ return await operation();
59
+ } catch (error) {
60
+ // Network errors
61
+ if (error instanceof TypeError && error.message.includes("fetch")) {
62
+ throw new PhoenixClientError(
63
+ `Network error during ${context}: Unable to connect to Phoenix server`,
64
+ "NETWORK_ERROR",
65
+ error
66
+ );
67
+ }
68
+
69
+ // HTTP errors from the middleware
70
+ if (error instanceof Error && error.message.includes(": ")) {
71
+ const parts = error.message.split(": ", 2);
72
+ if (parts.length === 2 && parts[1]) {
73
+ const [url, statusInfo] = parts;
74
+ const statusParts = statusInfo.split(" ");
75
+ const statusCode = statusParts[0];
76
+ const statusText = statusParts.slice(1).join(" ");
77
+
78
+ if (statusCode === "401" || statusCode === "403") {
79
+ throw new PhoenixClientError(
80
+ `Authentication error during ${context}: ${statusText}`,
81
+ "AUTH_ERROR",
82
+ error
83
+ );
84
+ }
85
+
86
+ if (statusCode && statusCode.startsWith("4")) {
87
+ throw new PhoenixClientError(
88
+ `Client error during ${context}: ${statusCode} ${statusText}`,
89
+ "INVALID_RESPONSE",
90
+ error
91
+ );
92
+ }
93
+
94
+ if (statusCode && statusCode.startsWith("5")) {
95
+ throw new PhoenixClientError(
96
+ `Server error during ${context}: ${statusCode} ${statusText}`,
97
+ "NETWORK_ERROR",
98
+ error
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ // Unknown errors
105
+ throw new PhoenixClientError(
106
+ `Unexpected error during ${context}: ${error instanceof Error ? error.message : String(error)}`,
107
+ "UNKNOWN_ERROR",
108
+ error
109
+ );
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Helper to safely extract data from API responses
115
+ */
116
+ export function extractData<T>(response: { data?: T; error?: unknown }): T {
117
+ if (response.error) {
118
+ throw response.error;
119
+ }
120
+
121
+ if (!response.data) {
122
+ throw new PhoenixClientError(
123
+ "Invalid API response: missing data",
124
+ "INVALID_RESPONSE"
125
+ );
126
+ }
127
+
128
+ return response.data;
129
+ }