@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,262 @@
1
+ // Export all snapshot modules
2
+ export { createPhoenixClient, PhoenixClientError, } from "./client.js";
3
+ export { fetchProjects } from "./projects.js";
4
+ export { snapshotSpans } from "./spans.js";
5
+ export { fetchDatasets } from "./datasets.js";
6
+ export { fetchExperiments } from "./experiments.js";
7
+ export { fetchPrompts } from "./prompts.js";
8
+ export { generateContext } from "./context.js";
9
+ import { createPhoenixClient, PhoenixClientError, } from "./client.js";
10
+ import { fetchProjects } from "./projects.js";
11
+ import { snapshotSpans } from "./spans.js";
12
+ import { fetchDatasets } from "./datasets.js";
13
+ import { fetchExperiments } from "./experiments.js";
14
+ import { fetchPrompts } from "./prompts.js";
15
+ import { generateContext } from "./context.js";
16
+ import { SnapshotProgress } from "../progress.js";
17
+ /**
18
+ * Orchestrates all data fetchers to create a complete Phoenix snapshot
19
+ * @param mode - The execution mode (sandbox or local)
20
+ * @param options - Snapshot options including server URL and limits
21
+ */
22
+ export async function createSnapshot(mode, options) {
23
+ const { baseURL, apiKey, spansPerProject = 1000, startTime, endTime, showProgress = false, } = options;
24
+ // Create progress indicator
25
+ const progress = new SnapshotProgress(showProgress);
26
+ progress.start("Creating Phoenix data snapshot");
27
+ // Create Phoenix client
28
+ const clientConfig = {
29
+ baseURL,
30
+ apiKey,
31
+ };
32
+ const client = createPhoenixClient(clientConfig);
33
+ try {
34
+ // 1. Fetch projects first (required for spans)
35
+ progress.update("Fetching projects");
36
+ try {
37
+ await fetchProjects(client, mode);
38
+ }
39
+ catch (error) {
40
+ progress.fail("Failed to fetch projects");
41
+ throw new PhoenixClientError(`Failed to fetch projects: ${error instanceof Error ? error.message : String(error)}`, error instanceof PhoenixClientError ? error.code : "UNKNOWN_ERROR", error);
42
+ }
43
+ // 2. Fetch spans and other data in parallel
44
+ progress.update("Fetching all data", "spans, datasets, experiments, prompts");
45
+ const spansOptions = {
46
+ spansPerProject,
47
+ startTime,
48
+ endTime,
49
+ };
50
+ // Fetch all data types in parallel for better performance
51
+ const results = await Promise.allSettled([
52
+ snapshotSpans(client, mode, spansOptions),
53
+ fetchDatasets(client, mode),
54
+ fetchExperiments(client, mode),
55
+ fetchPrompts(client, mode),
56
+ ]);
57
+ // Check for failures and collect errors
58
+ const errors = [];
59
+ const dataTypes = ["spans", "datasets", "experiments", "prompts"];
60
+ results.forEach((result, index) => {
61
+ if (result.status === "rejected") {
62
+ errors.push({
63
+ type: dataTypes[index] || "unknown",
64
+ error: result.reason,
65
+ });
66
+ }
67
+ });
68
+ if (errors.length > 0) {
69
+ // Log individual errors
70
+ errors.forEach(({ type, error }) => {
71
+ console.error(`Warning: Failed to fetch ${type}:`, error instanceof Error ? error.message : String(error));
72
+ });
73
+ // If spans failed, that's critical - throw error
74
+ if (errors.some((e) => e.type === "spans")) {
75
+ progress.fail("Failed to fetch spans");
76
+ throw new PhoenixClientError(`Failed to fetch spans: ${errors.find((e) => e.type === "spans")?.error}`, "UNKNOWN_ERROR", errors);
77
+ }
78
+ // If all other data failed, throw error. If partial success, continue with warning
79
+ if (errors.length === 4) {
80
+ progress.fail("Failed to fetch all data");
81
+ throw new PhoenixClientError("Failed to fetch all data types", "UNKNOWN_ERROR", errors);
82
+ }
83
+ }
84
+ // 4. Generate context file
85
+ progress.update("Generating context");
86
+ await generateContext(mode, {
87
+ phoenixUrl: baseURL,
88
+ snapshotTime: new Date(),
89
+ spansPerProject,
90
+ });
91
+ // 5. Write metadata file
92
+ progress.update("Writing metadata");
93
+ const metadata = {
94
+ created_at: new Date().toISOString(),
95
+ phoenix_url: baseURL,
96
+ cursors: {
97
+ spans: {}, // TODO: Track span cursors when span fetching supports it
98
+ datasets: { last_fetch: new Date().toISOString() },
99
+ experiments: { last_fetch: new Date().toISOString() },
100
+ prompts: { last_fetch: new Date().toISOString() },
101
+ },
102
+ limits: {
103
+ spans_per_project: spansPerProject,
104
+ },
105
+ };
106
+ await mode.writeFile("/_meta/snapshot.json", JSON.stringify(metadata, null, 2));
107
+ progress.succeed("✅ Snapshot created successfully!");
108
+ }
109
+ catch (error) {
110
+ // Stop progress if not already stopped
111
+ progress.stop();
112
+ // Enhance error with context before rethrowing
113
+ if (error instanceof PhoenixClientError) {
114
+ throw error; // Already has good context
115
+ }
116
+ throw new PhoenixClientError(`Failed to create snapshot: ${error instanceof Error ? error.message : String(error)}`, "UNKNOWN_ERROR", error);
117
+ }
118
+ }
119
+ /**
120
+ * Loads existing snapshot metadata if available
121
+ * @param mode - The execution mode (sandbox or local)
122
+ * @returns The snapshot metadata or null if not found
123
+ */
124
+ export async function loadSnapshotMetadata(mode) {
125
+ try {
126
+ const result = await mode.exec("cat /phoenix/_meta/snapshot.json 2>/dev/null");
127
+ if (result.exitCode === 0) {
128
+ return JSON.parse(result.stdout);
129
+ }
130
+ }
131
+ catch (error) {
132
+ // File doesn't exist or parse error
133
+ }
134
+ return null;
135
+ }
136
+ /**
137
+ * Creates an incremental snapshot, fetching only new/updated data
138
+ * @param mode - The execution mode (sandbox or local)
139
+ * @param options - Snapshot options including server URL and limits
140
+ */
141
+ export async function createIncrementalSnapshot(mode, options) {
142
+ // Load existing metadata to get cursors
143
+ const existingMetadata = await loadSnapshotMetadata(mode);
144
+ if (!existingMetadata) {
145
+ // No existing snapshot, create a full one
146
+ await createSnapshot(mode, options);
147
+ return;
148
+ }
149
+ const { baseURL, apiKey, spansPerProject = 1000, showProgress = false, } = options;
150
+ // Create progress indicator
151
+ const progress = new SnapshotProgress(showProgress);
152
+ progress.start("Updating Phoenix data snapshot");
153
+ // Create Phoenix client
154
+ const clientConfig = {
155
+ baseURL,
156
+ apiKey,
157
+ };
158
+ const client = createPhoenixClient(clientConfig);
159
+ try {
160
+ // Show time since last snapshot
161
+ const lastSnapshotDate = new Date(existingMetadata.created_at);
162
+ const timeSince = formatTimeSince(lastSnapshotDate);
163
+ progress.update("Checking for updates", `last snapshot ${timeSince} ago`);
164
+ // For incremental updates, we'll need to:
165
+ // 1. Fetch projects (always fetch all as they're small)
166
+ progress.update("Updating projects");
167
+ await fetchProjects(client, mode);
168
+ // 2. Fetch spans and other data in parallel for better performance
169
+ progress.update("Fetching updates", "new spans and refreshing other data");
170
+ const spansOptions = {
171
+ spansPerProject,
172
+ // Use the last end time from previous snapshot as start time
173
+ startTime: existingMetadata.cursors.spans
174
+ ? Object.values(existingMetadata.cursors.spans)
175
+ .map((cursor) => cursor.last_end_time)
176
+ .filter(Boolean)
177
+ .sort()
178
+ .pop()
179
+ : undefined,
180
+ };
181
+ // For datasets/experiments/prompts, check if they've been updated
182
+ const datasetsLastFetch = existingMetadata.cursors.datasets?.last_fetch;
183
+ const experimentsLastFetch = existingMetadata.cursors.experiments?.last_fetch;
184
+ const promptsLastFetch = existingMetadata.cursors.prompts?.last_fetch;
185
+ // Fetch all data types in parallel
186
+ // For now, we'll refetch all as the API doesn't support filtering by updated_at
187
+ // In a future enhancement, we could check individual items for updates
188
+ const updateResults = await Promise.allSettled([
189
+ snapshotSpans(client, mode, spansOptions),
190
+ fetchDatasets(client, mode),
191
+ fetchExperiments(client, mode),
192
+ fetchPrompts(client, mode),
193
+ ]);
194
+ // Check for critical errors
195
+ const updateErrors = [];
196
+ const updateDataTypes = ["spans", "datasets", "experiments", "prompts"];
197
+ updateResults.forEach((result, index) => {
198
+ if (result.status === "rejected") {
199
+ updateErrors.push({
200
+ type: updateDataTypes[index] || "unknown",
201
+ error: result.reason,
202
+ });
203
+ }
204
+ });
205
+ if (updateErrors.length > 0) {
206
+ // Log individual errors
207
+ updateErrors.forEach(({ type, error }) => {
208
+ console.error(`Warning: Failed to update ${type}:`, error instanceof Error ? error.message : String(error));
209
+ });
210
+ }
211
+ // 4. Regenerate context with updated data
212
+ progress.update("Regenerating context");
213
+ await generateContext(mode, {
214
+ phoenixUrl: baseURL,
215
+ snapshotTime: new Date(),
216
+ spansPerProject,
217
+ });
218
+ // 5. Update metadata
219
+ progress.update("Updating metadata");
220
+ const updatedSpansCursors = existingMetadata.cursors.spans || {};
221
+ const metadata = {
222
+ created_at: new Date().toISOString(),
223
+ phoenix_url: baseURL,
224
+ cursors: {
225
+ spans: updatedSpansCursors,
226
+ datasets: { last_fetch: new Date().toISOString() },
227
+ experiments: { last_fetch: new Date().toISOString() },
228
+ prompts: { last_fetch: new Date().toISOString() },
229
+ },
230
+ limits: {
231
+ spans_per_project: spansPerProject,
232
+ },
233
+ };
234
+ await mode.writeFile("/_meta/snapshot.json", JSON.stringify(metadata, null, 2));
235
+ progress.succeed("✅ Incremental update complete!");
236
+ }
237
+ catch (error) {
238
+ // Stop progress if not already stopped
239
+ progress.stop();
240
+ // Enhance error with context before rethrowing
241
+ if (error instanceof PhoenixClientError) {
242
+ throw error; // Already has good context
243
+ }
244
+ throw new PhoenixClientError(`Failed to create incremental snapshot: ${error instanceof Error ? error.message : String(error)}`, "UNKNOWN_ERROR", error);
245
+ }
246
+ }
247
+ /**
248
+ * Format time since a date in human-readable format
249
+ */
250
+ function formatTimeSince(date) {
251
+ const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
252
+ if (seconds < 60)
253
+ return `${seconds}s`;
254
+ const minutes = Math.floor(seconds / 60);
255
+ if (minutes < 60)
256
+ return `${minutes}m`;
257
+ const hours = Math.floor(minutes / 60);
258
+ if (hours < 24)
259
+ return `${hours}h`;
260
+ const days = Math.floor(hours / 24);
261
+ return `${days}d`;
262
+ }
@@ -0,0 +1,44 @@
1
+ import { withErrorHandling } from "./client.js";
2
+ /**
3
+ * Converts an array of items to JSONL format (one JSON object per line)
4
+ */
5
+ function toJSONL(items) {
6
+ return items.map((item) => JSON.stringify(item)).join("\n");
7
+ }
8
+ /**
9
+ * Fetches all projects and writes them to the filesystem
10
+ * @param client - The Phoenix client instance
11
+ * @param mode - The execution mode (sandbox or local)
12
+ */
13
+ export async function fetchProjects(client, mode) {
14
+ // Fetch all projects with error handling
15
+ const projectsData = await withErrorHandling(async () => {
16
+ const response = await client.GET("/v1/projects", {
17
+ params: {
18
+ query: {
19
+ include_experiment_projects: false,
20
+ },
21
+ },
22
+ });
23
+ if (!response.data) {
24
+ throw new Error("No data returned from projects endpoint");
25
+ }
26
+ return response.data;
27
+ }, "fetching projects");
28
+ // Extract projects from the response
29
+ const projects = projectsData.data || [];
30
+ // Write projects list as JSONL to /phoenix/projects/index.jsonl
31
+ const projectsPath = "/phoenix/projects/index.jsonl";
32
+ await mode.writeFile(projectsPath, toJSONL(projects));
33
+ // For each project, create a metadata.json file
34
+ for (const project of projects) {
35
+ const projectDir = `/phoenix/projects/${project.name}`;
36
+ const metadataPath = `${projectDir}/metadata.json`;
37
+ // Write project metadata
38
+ await mode.writeFile(metadataPath, JSON.stringify(project, null, 2));
39
+ // Create empty spans directory (will be populated by snapshot-spans task)
40
+ const spansDir = `${projectDir}/spans`;
41
+ // Create directory by writing a placeholder that will be overwritten later
42
+ await mode.writeFile(`${spansDir}/.gitkeep`, "");
43
+ }
44
+ }
@@ -0,0 +1,199 @@
1
+ import { withErrorHandling, extractData } from "./client.js";
2
+ /**
3
+ * Converts an array to JSONL format
4
+ */
5
+ function toJSONL(items) {
6
+ if (items.length === 0) {
7
+ return "";
8
+ }
9
+ return items.map((item) => JSON.stringify(item)).join("\n");
10
+ }
11
+ /**
12
+ * Converts a prompt template to markdown format
13
+ */
14
+ function templateToMarkdown(template, templateFormat) {
15
+ // Handle string template
16
+ if (templateFormat === "STRING") {
17
+ if (typeof template === "string") {
18
+ return template;
19
+ }
20
+ // It might be wrapped in an object with type
21
+ if (template?.template && typeof template.template === "string") {
22
+ return template.template;
23
+ }
24
+ return JSON.stringify(template, null, 2);
25
+ }
26
+ // Handle chat template
27
+ const messages = template?.messages || [];
28
+ const lines = ["# Chat Template", ""];
29
+ for (const message of messages) {
30
+ lines.push(`## ${message.role}`);
31
+ lines.push("");
32
+ if (typeof message.content === "string") {
33
+ lines.push(message.content);
34
+ }
35
+ else if (Array.isArray(message.content)) {
36
+ // Handle multi-part content
37
+ for (const part of message.content) {
38
+ if (part.type === "text" && part.text) {
39
+ lines.push(part.text);
40
+ }
41
+ else {
42
+ // For non-text parts, show as JSON
43
+ lines.push("```json");
44
+ lines.push(JSON.stringify(part, null, 2));
45
+ lines.push("```");
46
+ }
47
+ }
48
+ }
49
+ else {
50
+ // If content is not string or array, show as JSON
51
+ lines.push("```json");
52
+ lines.push(JSON.stringify(message.content, null, 2));
53
+ lines.push("```");
54
+ }
55
+ lines.push("");
56
+ }
57
+ return lines.join("\n");
58
+ }
59
+ /**
60
+ * Creates a markdown representation of a prompt version
61
+ */
62
+ function createVersionMarkdown(version) {
63
+ const lines = [];
64
+ // Add metadata header
65
+ lines.push("---");
66
+ lines.push(`id: ${version.id}`);
67
+ if (version.model_name)
68
+ lines.push(`model_name: ${version.model_name}`);
69
+ if (version.model_provider)
70
+ lines.push(`model_provider: ${version.model_provider}`);
71
+ if (version.template_format)
72
+ lines.push(`template_format: ${version.template_format}`);
73
+ if (version.description)
74
+ lines.push(`description: ${version.description}`);
75
+ lines.push("---");
76
+ lines.push("");
77
+ // Add template content
78
+ if (version.template) {
79
+ lines.push(templateToMarkdown(version.template, version.template_format || "STRING"));
80
+ lines.push("");
81
+ }
82
+ // Add invocation parameters if present
83
+ if (version.invocation_parameters) {
84
+ lines.push("## Invocation Parameters");
85
+ lines.push("");
86
+ lines.push("```json");
87
+ lines.push(JSON.stringify(version.invocation_parameters, null, 2));
88
+ lines.push("```");
89
+ lines.push("");
90
+ }
91
+ // Add tools if present
92
+ if (version.tools) {
93
+ lines.push("## Tools");
94
+ lines.push("");
95
+ lines.push("```json");
96
+ lines.push(JSON.stringify(version.tools, null, 2));
97
+ lines.push("```");
98
+ lines.push("");
99
+ }
100
+ // Add response format if present
101
+ if (version.response_format) {
102
+ lines.push("## Response Format");
103
+ lines.push("");
104
+ lines.push("```json");
105
+ lines.push(JSON.stringify(version.response_format, null, 2));
106
+ lines.push("```");
107
+ lines.push("");
108
+ }
109
+ return lines.join("\n");
110
+ }
111
+ /**
112
+ * Fetches all prompts and their versions from Phoenix
113
+ */
114
+ export async function fetchPrompts(client, mode, options = {}) {
115
+ const { limit = 100 } = options;
116
+ // Fetch all prompts with pagination
117
+ const prompts = [];
118
+ let cursor = null;
119
+ while (prompts.length < limit) {
120
+ const query = {
121
+ limit: Math.min(limit - prompts.length, 100),
122
+ };
123
+ if (cursor) {
124
+ query.cursor = cursor;
125
+ }
126
+ const response = await withErrorHandling(() => client.GET("/v1/prompts", { params: { query } }), "fetching prompts");
127
+ const data = extractData(response);
128
+ prompts.push(...data.data);
129
+ cursor = data.next_cursor;
130
+ // Stop if no more data
131
+ if (!cursor || data.data.length === 0) {
132
+ break;
133
+ }
134
+ }
135
+ // Write prompts index
136
+ await mode.writeFile("/phoenix/prompts/index.jsonl", toJSONL(prompts));
137
+ // Fetch versions for each prompt
138
+ for (const prompt of prompts) {
139
+ const safePromptName = prompt.name.replace(/[^a-zA-Z0-9-_]/g, "_");
140
+ // Write prompt metadata
141
+ await mode.writeFile(`/phoenix/prompts/${safePromptName}/metadata.json`, JSON.stringify({
142
+ id: prompt.id,
143
+ name: prompt.name,
144
+ description: prompt.description || null,
145
+ metadata: prompt.metadata || {},
146
+ source_prompt_id: prompt.source_prompt_id || null,
147
+ snapshot_timestamp: new Date().toISOString(),
148
+ }, null, 2));
149
+ // Fetch all versions for this prompt
150
+ const versions = [];
151
+ let versionCursor = null;
152
+ while (true) {
153
+ const versionQuery = {
154
+ limit: 100,
155
+ };
156
+ if (versionCursor) {
157
+ versionQuery.cursor = versionCursor;
158
+ }
159
+ const versionsResponse = await withErrorHandling(() => client.GET("/v1/prompts/{prompt_identifier}/versions", {
160
+ params: {
161
+ path: { prompt_identifier: prompt.id },
162
+ query: versionQuery,
163
+ },
164
+ }), `fetching versions for prompt ${prompt.name}`);
165
+ const versionsData = extractData(versionsResponse);
166
+ versions.push(...versionsData.data);
167
+ versionCursor = versionsData.next_cursor;
168
+ // Stop if no more data
169
+ if (!versionCursor || versionsData.data.length === 0) {
170
+ break;
171
+ }
172
+ }
173
+ // Write versions index as JSONL
174
+ await mode.writeFile(`/phoenix/prompts/${safePromptName}/versions/index.jsonl`, toJSONL(versions));
175
+ // Write each version as markdown
176
+ for (const version of versions) {
177
+ const versionId = version.id.replace(/[^a-zA-Z0-9-_]/g, "_");
178
+ const markdownContent = createVersionMarkdown(version);
179
+ await mode.writeFile(`/phoenix/prompts/${safePromptName}/versions/${versionId}.md`, markdownContent);
180
+ }
181
+ // Fetch and save the latest version separately for convenience
182
+ try {
183
+ const latestResponse = await withErrorHandling(() => client.GET("/v1/prompts/{prompt_identifier}/latest", {
184
+ params: {
185
+ path: { prompt_identifier: prompt.id },
186
+ },
187
+ }), `fetching latest version for prompt ${prompt.name}`);
188
+ const latestData = extractData(latestResponse);
189
+ if (latestData.data) {
190
+ const latestMarkdownContent = createVersionMarkdown(latestData.data);
191
+ await mode.writeFile(`/phoenix/prompts/${safePromptName}/latest.md`, latestMarkdownContent);
192
+ }
193
+ }
194
+ catch (error) {
195
+ // If there's no latest version, that's okay - just skip it
196
+ console.warn(`No latest version available for prompt ${prompt.name}: ${error instanceof Error ? error.message : String(error)}`);
197
+ }
198
+ }
199
+ }
@@ -0,0 +1,80 @@
1
+ import { withErrorHandling } from "./client.js";
2
+ /**
3
+ * Fetches spans for all projects and writes them to the snapshot
4
+ *
5
+ * @param client - Phoenix client instance
6
+ * @param mode - Execution mode for file operations
7
+ * @param options - Options for filtering and limiting spans
8
+ */
9
+ export async function snapshotSpans(client, mode, options = {}) {
10
+ const { startTime, endTime, spansPerProject = 1000 } = options;
11
+ // Read projects index to get project names
12
+ const projectsIndexContent = await mode.exec("cat /phoenix/projects/index.jsonl");
13
+ if (!projectsIndexContent.stdout) {
14
+ // No projects, nothing to do
15
+ return;
16
+ }
17
+ const projectNames = projectsIndexContent.stdout
18
+ .trim()
19
+ .split("\n")
20
+ .filter((line) => line.length > 0)
21
+ .map((line) => {
22
+ const project = JSON.parse(line);
23
+ return project.name;
24
+ });
25
+ // Fetch spans for each project
26
+ for (const projectName of projectNames) {
27
+ await withErrorHandling(async () => {
28
+ const spans = [];
29
+ let cursor = null;
30
+ let totalFetched = 0;
31
+ // Paginate through spans until we reach the limit or no more data
32
+ while (totalFetched < spansPerProject) {
33
+ const query = {
34
+ limit: Math.min(100, spansPerProject - totalFetched), // Fetch in chunks of 100
35
+ };
36
+ if (cursor) {
37
+ query.cursor = cursor;
38
+ }
39
+ if (startTime) {
40
+ query.start_time =
41
+ startTime instanceof Date ? startTime.toISOString() : startTime;
42
+ }
43
+ if (endTime) {
44
+ query.end_time =
45
+ endTime instanceof Date ? endTime.toISOString() : endTime;
46
+ }
47
+ const response = await client.GET("/v1/projects/{project_identifier}/spans", {
48
+ params: {
49
+ path: {
50
+ project_identifier: projectName,
51
+ },
52
+ query,
53
+ },
54
+ });
55
+ if (response.error)
56
+ throw response.error;
57
+ const data = response.data?.data ?? [];
58
+ spans.push(...data);
59
+ totalFetched += data.length;
60
+ cursor = response.data?.next_cursor ?? null;
61
+ // Stop if there's no more data
62
+ if (!cursor || data.length === 0) {
63
+ break;
64
+ }
65
+ }
66
+ // Write spans to JSONL file
67
+ const jsonlContent = spans.map((span) => JSON.stringify(span)).join("\n");
68
+ await mode.writeFile(`/phoenix/projects/${projectName}/spans/index.jsonl`, jsonlContent);
69
+ // Write metadata about the spans snapshot
70
+ const metadata = {
71
+ project: projectName,
72
+ spanCount: spans.length,
73
+ startTime: startTime || null,
74
+ endTime: endTime || null,
75
+ snapshotTime: new Date().toISOString(),
76
+ };
77
+ await mode.writeFile(`/phoenix/projects/${projectName}/spans/metadata.json`, JSON.stringify(metadata, null, 2));
78
+ }, `fetching spans for project ${projectName}`);
79
+ }
80
+ }
@@ -0,0 +1 @@
1
+ {"root":["../src/cli.ts","../src/index.ts","../src/progress.ts","../src/agent/index.ts","../src/commands/index.ts","../src/commands/px-fetch-more-spans.ts","../src/commands/px-fetch-more-trace.ts","../src/config/index.ts","../src/config/loader.ts","../src/config/schema.ts","../src/modes/index.ts","../src/modes/local.ts","../src/modes/sandbox.ts","../src/modes/types.ts","../src/observability/index.ts","../src/prompts/index.ts","../src/prompts/system.ts","../src/snapshot/client.ts","../src/snapshot/context.ts","../src/snapshot/datasets.ts","../src/snapshot/experiments.ts","../src/snapshot/index.ts","../src/snapshot/projects.ts","../src/snapshot/prompts.ts","../src/snapshot/spans.ts"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@cephalization/phoenix-insight",
3
+ "version": "0.1.0",
4
+ "description": "A CLI for Arize AI Phoenix data analysis with AI agents",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "types": "./dist/index.d.ts",
9
+ "import": "./dist/index.js"
10
+ }
11
+ },
12
+ "bin": {
13
+ "phoenix-insight": "./dist/cli.js",
14
+ "pxi": "./dist/cli.js"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "src",
22
+ "package.json"
23
+ ],
24
+ "keywords": [
25
+ "phoenix",
26
+ "cli",
27
+ "insight",
28
+ "arize",
29
+ "llmops",
30
+ "ai"
31
+ ],
32
+ "author": "oss@arize.com",
33
+ "license": "Apache-2.0",
34
+ "homepage": "https://github.com/cephalization/phoenix-insight#readme",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/cephalization/phoenix-insight.git"
38
+ },
39
+ "bugs": {
40
+ "url": "https://github.com/cephalization/phoenix-insight/issues"
41
+ },
42
+ "dependencies": {
43
+ "@ai-sdk/anthropic": "^3.0.0",
44
+ "@arizeai/phoenix-client": "^5.6.1",
45
+ "@arizeai/phoenix-otel": "^0.3.4",
46
+ "@opentelemetry/api": "1.9.0",
47
+ "ai": "^6.0.0",
48
+ "bash-tool": "^1.3.0",
49
+ "commander": "^12.0.0",
50
+ "just-bash": "^2.2.0",
51
+ "ora": "^9.0.0",
52
+ "zod": "^4.3.5"
53
+ },
54
+ "devDependencies": {
55
+ "@changesets/cli": "^2.29.8",
56
+ "@types/node": "^18.19.0",
57
+ "rimraf": "^5.0.10",
58
+ "tsc-alias": "^1.8.11",
59
+ "tsx": "^4.21.0",
60
+ "typescript": "^5.8.2",
61
+ "vitest": "^2.1.9"
62
+ },
63
+ "engines": {
64
+ "node": ">=18"
65
+ },
66
+ "scripts": {
67
+ "clean": "rimraf dist tsconfig.esm.tsbuildinfo tsconfig.tsbuildinfo",
68
+ "prebuild": "pnpm run clean",
69
+ "build": "tsc --build tsconfig.esm.json",
70
+ "start": "node dist/cli.js",
71
+ "dev": "tsx src/cli.ts",
72
+ "typecheck": "tsc --noEmit",
73
+ "test": "vitest run --typecheck"
74
+ }
75
+ }