@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.
- package/README.md +195 -1
- package/dist/chunk-KEQDYZIE.js +237 -0
- package/dist/chunk-KEQDYZIE.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +3950 -642
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +108 -0
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -0
- package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js +154 -0
- package/dist/ui/assets/code-block-F6WJLWQG-BTdTzfvl.js.map +1 -0
- package/dist/ui/assets/index-CX8aDatf.css +1 -0
- package/dist/ui/assets/index-DjZuAW6Y.js +63 -0
- package/dist/ui/assets/index-DjZuAW6Y.js.map +1 -0
- package/dist/ui/assets/vendor-data-r1ZEkUds.js +40 -0
- package/dist/ui/assets/vendor-data-r1ZEkUds.js.map +1 -0
- package/dist/ui/assets/vendor-react-Cgg2GOmP.js +2 -0
- package/dist/ui/assets/vendor-react-Cgg2GOmP.js.map +1 -0
- package/dist/ui/assets/vendor-render-DoMl5bum.js +381 -0
- package/dist/ui/assets/vendor-render-DoMl5bum.js.map +1 -0
- package/dist/ui/assets/vendor-ui-Cg-YC4hK.js +46 -0
- package/dist/ui/assets/vendor-ui-Cg-YC4hK.js.map +1 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui/vite.svg +1 -0
- package/package.json +14 -15
- package/dist/agent/index.js +0 -230
- package/dist/commands/index.js +0 -2
- package/dist/commands/px-fetch-more-spans.js +0 -98
- package/dist/commands/px-fetch-more-trace.js +0 -110
- package/dist/config/index.js +0 -165
- package/dist/config/loader.js +0 -141
- package/dist/config/schema.js +0 -53
- package/dist/modes/index.js +0 -17
- package/dist/modes/local.js +0 -134
- package/dist/modes/sandbox.js +0 -121
- package/dist/modes/types.js +0 -1
- package/dist/observability/index.js +0 -65
- package/dist/progress.js +0 -209
- package/dist/prompts/index.js +0 -1
- package/dist/prompts/system.js +0 -30
- package/dist/snapshot/client.js +0 -74
- package/dist/snapshot/context.js +0 -441
- package/dist/snapshot/datasets.js +0 -68
- package/dist/snapshot/experiments.js +0 -135
- package/dist/snapshot/index.js +0 -262
- package/dist/snapshot/projects.js +0 -44
- package/dist/snapshot/prompts.js +0 -199
- package/dist/snapshot/spans.js +0 -104
- package/dist/snapshot/utils.js +0 -112
- package/dist/tsconfig.esm.tsbuildinfo +0 -1
- package/src/agent/index.ts +0 -323
- package/src/cli.ts +0 -854
- package/src/commands/index.ts +0 -8
- package/src/commands/px-fetch-more-spans.ts +0 -174
- package/src/commands/px-fetch-more-trace.ts +0 -183
- package/src/config/index.ts +0 -225
- package/src/config/loader.ts +0 -173
- package/src/config/schema.ts +0 -66
- package/src/index.ts +0 -1
- package/src/modes/index.ts +0 -21
- package/src/modes/local.ts +0 -163
- package/src/modes/sandbox.ts +0 -144
- package/src/modes/types.ts +0 -31
- package/src/observability/index.ts +0 -90
- package/src/progress.ts +0 -239
- package/src/prompts/index.ts +0 -1
- package/src/prompts/system.ts +0 -31
- package/src/snapshot/client.ts +0 -129
- package/src/snapshot/context.ts +0 -587
- package/src/snapshot/datasets.ts +0 -132
- package/src/snapshot/experiments.ts +0 -246
- package/src/snapshot/index.ts +0 -403
- package/src/snapshot/projects.ts +0 -58
- package/src/snapshot/prompts.ts +0 -267
- package/src/snapshot/spans.ts +0 -163
- package/src/snapshot/utils.ts +0 -140
package/src/snapshot/prompts.ts
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import type { PhoenixClient } from "@arizeai/phoenix-client";
|
|
2
|
-
import type { ExecutionMode } from "../modes/types.js";
|
|
3
|
-
import { withErrorHandling, extractData } from "./client.js";
|
|
4
|
-
|
|
5
|
-
interface FetchPromptsOptions {
|
|
6
|
-
limit?: number;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Converts an array to JSONL format
|
|
11
|
-
*/
|
|
12
|
-
function toJSONL(items: unknown[]): string {
|
|
13
|
-
if (items.length === 0) {
|
|
14
|
-
return "";
|
|
15
|
-
}
|
|
16
|
-
return items.map((item) => JSON.stringify(item)).join("\n");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Converts a prompt template to markdown format
|
|
21
|
-
*/
|
|
22
|
-
function templateToMarkdown(template: any, templateFormat: string): string {
|
|
23
|
-
// Handle string template
|
|
24
|
-
if (templateFormat === "STRING") {
|
|
25
|
-
if (typeof template === "string") {
|
|
26
|
-
return template;
|
|
27
|
-
}
|
|
28
|
-
// It might be wrapped in an object with type
|
|
29
|
-
if (template?.template && typeof template.template === "string") {
|
|
30
|
-
return template.template;
|
|
31
|
-
}
|
|
32
|
-
return JSON.stringify(template, null, 2);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Handle chat template
|
|
36
|
-
const messages = template?.messages || [];
|
|
37
|
-
const lines: string[] = ["# Chat Template", ""];
|
|
38
|
-
|
|
39
|
-
for (const message of messages) {
|
|
40
|
-
lines.push(`## ${message.role}`);
|
|
41
|
-
lines.push("");
|
|
42
|
-
|
|
43
|
-
if (typeof message.content === "string") {
|
|
44
|
-
lines.push(message.content);
|
|
45
|
-
} else if (Array.isArray(message.content)) {
|
|
46
|
-
// Handle multi-part content
|
|
47
|
-
for (const part of message.content) {
|
|
48
|
-
if (part.type === "text" && part.text) {
|
|
49
|
-
lines.push(part.text);
|
|
50
|
-
} else {
|
|
51
|
-
// For non-text parts, show as JSON
|
|
52
|
-
lines.push("```json");
|
|
53
|
-
lines.push(JSON.stringify(part, null, 2));
|
|
54
|
-
lines.push("```");
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
// If content is not string or array, show as JSON
|
|
59
|
-
lines.push("```json");
|
|
60
|
-
lines.push(JSON.stringify(message.content, null, 2));
|
|
61
|
-
lines.push("```");
|
|
62
|
-
}
|
|
63
|
-
lines.push("");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
return lines.join("\n");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Creates a markdown representation of a prompt version
|
|
71
|
-
*/
|
|
72
|
-
function createVersionMarkdown(version: any): string {
|
|
73
|
-
const lines: string[] = [];
|
|
74
|
-
|
|
75
|
-
// Add metadata header
|
|
76
|
-
lines.push("---");
|
|
77
|
-
lines.push(`id: ${version.id}`);
|
|
78
|
-
if (version.model_name) lines.push(`model_name: ${version.model_name}`);
|
|
79
|
-
if (version.model_provider)
|
|
80
|
-
lines.push(`model_provider: ${version.model_provider}`);
|
|
81
|
-
if (version.template_format)
|
|
82
|
-
lines.push(`template_format: ${version.template_format}`);
|
|
83
|
-
if (version.description) lines.push(`description: ${version.description}`);
|
|
84
|
-
lines.push("---");
|
|
85
|
-
lines.push("");
|
|
86
|
-
|
|
87
|
-
// Add template content
|
|
88
|
-
if (version.template) {
|
|
89
|
-
lines.push(
|
|
90
|
-
templateToMarkdown(version.template, version.template_format || "STRING")
|
|
91
|
-
);
|
|
92
|
-
lines.push("");
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Add invocation parameters if present
|
|
96
|
-
if (version.invocation_parameters) {
|
|
97
|
-
lines.push("## Invocation Parameters");
|
|
98
|
-
lines.push("");
|
|
99
|
-
lines.push("```json");
|
|
100
|
-
lines.push(JSON.stringify(version.invocation_parameters, null, 2));
|
|
101
|
-
lines.push("```");
|
|
102
|
-
lines.push("");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Add tools if present
|
|
106
|
-
if (version.tools) {
|
|
107
|
-
lines.push("## Tools");
|
|
108
|
-
lines.push("");
|
|
109
|
-
lines.push("```json");
|
|
110
|
-
lines.push(JSON.stringify(version.tools, null, 2));
|
|
111
|
-
lines.push("```");
|
|
112
|
-
lines.push("");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Add response format if present
|
|
116
|
-
if (version.response_format) {
|
|
117
|
-
lines.push("## Response Format");
|
|
118
|
-
lines.push("");
|
|
119
|
-
lines.push("```json");
|
|
120
|
-
lines.push(JSON.stringify(version.response_format, null, 2));
|
|
121
|
-
lines.push("```");
|
|
122
|
-
lines.push("");
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return lines.join("\n");
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Fetches all prompts and their versions from Phoenix
|
|
130
|
-
*/
|
|
131
|
-
export async function fetchPrompts(
|
|
132
|
-
client: PhoenixClient,
|
|
133
|
-
mode: ExecutionMode,
|
|
134
|
-
options: FetchPromptsOptions = {}
|
|
135
|
-
): Promise<void> {
|
|
136
|
-
const { limit = 100 } = options;
|
|
137
|
-
|
|
138
|
-
// Fetch all prompts with pagination
|
|
139
|
-
const prompts: any[] = [];
|
|
140
|
-
let cursor: string | null = null;
|
|
141
|
-
|
|
142
|
-
while (prompts.length < limit) {
|
|
143
|
-
const query: Record<string, unknown> = {
|
|
144
|
-
limit: Math.min(limit - prompts.length, 100),
|
|
145
|
-
};
|
|
146
|
-
if (cursor) {
|
|
147
|
-
query.cursor = cursor;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const response = await withErrorHandling(
|
|
151
|
-
() => client.GET("/v1/prompts", { params: { query } }),
|
|
152
|
-
"fetching prompts"
|
|
153
|
-
);
|
|
154
|
-
|
|
155
|
-
const data = extractData(response);
|
|
156
|
-
prompts.push(...data.data);
|
|
157
|
-
cursor = data.next_cursor;
|
|
158
|
-
|
|
159
|
-
// Stop if no more data
|
|
160
|
-
if (!cursor || data.data.length === 0) {
|
|
161
|
-
break;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Write prompts index
|
|
166
|
-
await mode.writeFile("/phoenix/prompts/index.jsonl", toJSONL(prompts));
|
|
167
|
-
|
|
168
|
-
// Fetch versions for each prompt
|
|
169
|
-
for (const prompt of prompts) {
|
|
170
|
-
const safePromptName = prompt.name.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
171
|
-
|
|
172
|
-
// Write prompt metadata
|
|
173
|
-
await mode.writeFile(
|
|
174
|
-
`/phoenix/prompts/${safePromptName}/metadata.json`,
|
|
175
|
-
JSON.stringify(
|
|
176
|
-
{
|
|
177
|
-
id: prompt.id,
|
|
178
|
-
name: prompt.name,
|
|
179
|
-
description: prompt.description || null,
|
|
180
|
-
metadata: prompt.metadata || {},
|
|
181
|
-
source_prompt_id: prompt.source_prompt_id || null,
|
|
182
|
-
snapshot_timestamp: new Date().toISOString(),
|
|
183
|
-
},
|
|
184
|
-
null,
|
|
185
|
-
2
|
|
186
|
-
)
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
// Fetch all versions for this prompt
|
|
190
|
-
const versions: any[] = [];
|
|
191
|
-
let versionCursor: string | null = null;
|
|
192
|
-
|
|
193
|
-
while (true) {
|
|
194
|
-
const versionQuery: Record<string, unknown> = {
|
|
195
|
-
limit: 100,
|
|
196
|
-
};
|
|
197
|
-
if (versionCursor) {
|
|
198
|
-
versionQuery.cursor = versionCursor;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const versionsResponse = await withErrorHandling(
|
|
202
|
-
() =>
|
|
203
|
-
client.GET("/v1/prompts/{prompt_identifier}/versions", {
|
|
204
|
-
params: {
|
|
205
|
-
path: { prompt_identifier: prompt.id },
|
|
206
|
-
query: versionQuery,
|
|
207
|
-
},
|
|
208
|
-
}),
|
|
209
|
-
`fetching versions for prompt ${prompt.name}`
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
const versionsData = extractData(versionsResponse);
|
|
213
|
-
versions.push(...versionsData.data);
|
|
214
|
-
versionCursor = versionsData.next_cursor;
|
|
215
|
-
|
|
216
|
-
// Stop if no more data
|
|
217
|
-
if (!versionCursor || versionsData.data.length === 0) {
|
|
218
|
-
break;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Write versions index as JSONL
|
|
223
|
-
await mode.writeFile(
|
|
224
|
-
`/phoenix/prompts/${safePromptName}/versions/index.jsonl`,
|
|
225
|
-
toJSONL(versions)
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
// Write each version as markdown
|
|
229
|
-
for (const version of versions) {
|
|
230
|
-
const versionId = version.id.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
231
|
-
const markdownContent = createVersionMarkdown(version);
|
|
232
|
-
|
|
233
|
-
await mode.writeFile(
|
|
234
|
-
`/phoenix/prompts/${safePromptName}/versions/${versionId}.md`,
|
|
235
|
-
markdownContent
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Fetch and save the latest version separately for convenience
|
|
240
|
-
try {
|
|
241
|
-
const latestResponse = await withErrorHandling(
|
|
242
|
-
() =>
|
|
243
|
-
client.GET("/v1/prompts/{prompt_identifier}/latest", {
|
|
244
|
-
params: {
|
|
245
|
-
path: { prompt_identifier: prompt.id },
|
|
246
|
-
},
|
|
247
|
-
}),
|
|
248
|
-
`fetching latest version for prompt ${prompt.name}`
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
const latestData = extractData(latestResponse);
|
|
252
|
-
if (latestData.data) {
|
|
253
|
-
const latestMarkdownContent = createVersionMarkdown(latestData.data);
|
|
254
|
-
|
|
255
|
-
await mode.writeFile(
|
|
256
|
-
`/phoenix/prompts/${safePromptName}/latest.md`,
|
|
257
|
-
latestMarkdownContent
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
} catch (error) {
|
|
261
|
-
// If there's no latest version, that's okay - just skip it
|
|
262
|
-
console.warn(
|
|
263
|
-
`No latest version available for prompt ${prompt.name}: ${error instanceof Error ? error.message : String(error)}`
|
|
264
|
-
);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
package/src/snapshot/spans.ts
DELETED
|
@@ -1,163 +0,0 @@
|
|
|
1
|
-
import type { PhoenixClient } from "@arizeai/phoenix-client";
|
|
2
|
-
import type { ExecutionMode } from "../modes/types.js";
|
|
3
|
-
import { withErrorHandling } from "./client.js";
|
|
4
|
-
|
|
5
|
-
export interface SnapshotSpansOptions {
|
|
6
|
-
/** Inclusive lower bound time for filtering spans */
|
|
7
|
-
startTime?: Date | string | null;
|
|
8
|
-
/** Exclusive upper bound time for filtering spans */
|
|
9
|
-
endTime?: Date | string | null;
|
|
10
|
-
/** Maximum number of spans to fetch per project (default: 1000) */
|
|
11
|
-
spansPerProject?: number;
|
|
12
|
-
/** Enable debug logging (default: uses DEBUG env var) */
|
|
13
|
-
debug?: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Debug logger that respects the debug flag or DEBUG environment variable
|
|
18
|
-
*/
|
|
19
|
-
function createDebugLogger(debug?: boolean) {
|
|
20
|
-
const isDebugEnabled = debug ?? !!process.env.DEBUG;
|
|
21
|
-
return {
|
|
22
|
-
log: (message: string) => {
|
|
23
|
-
if (isDebugEnabled) {
|
|
24
|
-
console.log(`[snapshotSpans] ${message}`);
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface SpanData {
|
|
31
|
-
id: string;
|
|
32
|
-
name: string;
|
|
33
|
-
context: {
|
|
34
|
-
trace_id: string;
|
|
35
|
-
span_id: string;
|
|
36
|
-
};
|
|
37
|
-
span_kind: string;
|
|
38
|
-
parent_id: string | null;
|
|
39
|
-
start_time: string;
|
|
40
|
-
end_time: string;
|
|
41
|
-
status_code: string;
|
|
42
|
-
status_message: string;
|
|
43
|
-
attributes: Record<string, unknown>;
|
|
44
|
-
events: Array<unknown>;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
interface ProjectMetadata {
|
|
48
|
-
name: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Fetches spans for all projects and writes them to the snapshot
|
|
53
|
-
*
|
|
54
|
-
* @param client - Phoenix client instance
|
|
55
|
-
* @param mode - Execution mode for file operations
|
|
56
|
-
* @param options - Options for filtering and limiting spans
|
|
57
|
-
*/
|
|
58
|
-
export async function snapshotSpans(
|
|
59
|
-
client: PhoenixClient,
|
|
60
|
-
mode: ExecutionMode,
|
|
61
|
-
options: SnapshotSpansOptions = {}
|
|
62
|
-
): Promise<void> {
|
|
63
|
-
const { startTime, endTime, spansPerProject = 1000, debug } = options;
|
|
64
|
-
const logger = createDebugLogger(debug);
|
|
65
|
-
|
|
66
|
-
// Read projects index to get project names
|
|
67
|
-
// Use relative path so it works with the cwd set by the execution mode
|
|
68
|
-
logger.log("Reading projects index from projects/index.jsonl");
|
|
69
|
-
const projectsIndexContent = await mode.exec("cat projects/index.jsonl");
|
|
70
|
-
if (!projectsIndexContent.stdout) {
|
|
71
|
-
// No projects, nothing to do
|
|
72
|
-
logger.log("No projects found in index, skipping span fetch");
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const projectNames = projectsIndexContent.stdout
|
|
77
|
-
.trim()
|
|
78
|
-
.split("\n")
|
|
79
|
-
.filter((line) => line.length > 0)
|
|
80
|
-
.map((line) => {
|
|
81
|
-
const project = JSON.parse(line) as ProjectMetadata;
|
|
82
|
-
return project.name;
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
logger.log(`Found ${projectNames.length} project(s): ${projectNames.join(", ")}`);
|
|
86
|
-
|
|
87
|
-
// Fetch spans for each project
|
|
88
|
-
for (const projectName of projectNames) {
|
|
89
|
-
await withErrorHandling(async () => {
|
|
90
|
-
logger.log(`Starting span fetch for project: ${projectName}`);
|
|
91
|
-
const spans: SpanData[] = [];
|
|
92
|
-
let cursor: string | null = null;
|
|
93
|
-
let totalFetched = 0;
|
|
94
|
-
|
|
95
|
-
// Paginate through spans until we reach the limit or no more data
|
|
96
|
-
while (totalFetched < spansPerProject) {
|
|
97
|
-
const query: Record<string, any> = {
|
|
98
|
-
limit: Math.min(100, spansPerProject - totalFetched), // Fetch in chunks of 100
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
if (cursor) {
|
|
102
|
-
query.cursor = cursor;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (startTime) {
|
|
106
|
-
query.start_time =
|
|
107
|
-
startTime instanceof Date ? startTime.toISOString() : startTime;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (endTime) {
|
|
111
|
-
query.end_time =
|
|
112
|
-
endTime instanceof Date ? endTime.toISOString() : endTime;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const response = await client.GET(
|
|
116
|
-
"/v1/projects/{project_identifier}/spans",
|
|
117
|
-
{
|
|
118
|
-
params: {
|
|
119
|
-
path: {
|
|
120
|
-
project_identifier: projectName,
|
|
121
|
-
},
|
|
122
|
-
query,
|
|
123
|
-
},
|
|
124
|
-
}
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
if (response.error) throw response.error;
|
|
128
|
-
|
|
129
|
-
const data = response.data?.data ?? [];
|
|
130
|
-
spans.push(...(data as SpanData[]));
|
|
131
|
-
totalFetched += data.length;
|
|
132
|
-
|
|
133
|
-
cursor = response.data?.next_cursor ?? null;
|
|
134
|
-
|
|
135
|
-
// Stop if there's no more data
|
|
136
|
-
if (!cursor || data.length === 0) {
|
|
137
|
-
break;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
logger.log(`Completed span fetch for project ${projectName}: ${spans.length} span(s) fetched`);
|
|
142
|
-
|
|
143
|
-
// Write spans to JSONL file
|
|
144
|
-
const spansFilePath = `/phoenix/projects/${projectName}/spans/index.jsonl`;
|
|
145
|
-
logger.log(`Writing spans to ${spansFilePath}`);
|
|
146
|
-
const jsonlContent = spans.map((span) => JSON.stringify(span)).join("\n");
|
|
147
|
-
await mode.writeFile(spansFilePath, jsonlContent);
|
|
148
|
-
|
|
149
|
-
// Write metadata about the spans snapshot
|
|
150
|
-
const metadataFilePath = `/phoenix/projects/${projectName}/spans/metadata.json`;
|
|
151
|
-
logger.log(`Writing metadata to ${metadataFilePath}`);
|
|
152
|
-
const metadata = {
|
|
153
|
-
project: projectName,
|
|
154
|
-
spanCount: spans.length,
|
|
155
|
-
startTime: startTime || null,
|
|
156
|
-
endTime: endTime || null,
|
|
157
|
-
snapshotTime: new Date().toISOString(),
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
await mode.writeFile(metadataFilePath, JSON.stringify(metadata, null, 2));
|
|
161
|
-
}, `fetching spans for project ${projectName}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
package/src/snapshot/utils.ts
DELETED
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Snapshot discovery utilities
|
|
3
|
-
*
|
|
4
|
-
* Functions for listing and finding snapshots in the local filesystem.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import * as fs from "node:fs/promises";
|
|
8
|
-
import * as path from "node:path";
|
|
9
|
-
import * as os from "node:os";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Information about a single snapshot
|
|
13
|
-
*/
|
|
14
|
-
export interface SnapshotInfo {
|
|
15
|
-
/** Absolute path to the snapshot directory (the 'phoenix' subdirectory) */
|
|
16
|
-
path: string;
|
|
17
|
-
/** Timestamp when the snapshot was created (from directory name) */
|
|
18
|
-
timestamp: Date;
|
|
19
|
-
/** Unique identifier for the snapshot (directory name) */
|
|
20
|
-
id: string;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get the base snapshots directory path
|
|
25
|
-
*/
|
|
26
|
-
export function getSnapshotsDir(): string {
|
|
27
|
-
return path.join(os.homedir(), ".phoenix-insight", "snapshots");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Parse a snapshot directory name to extract timestamp
|
|
32
|
-
*
|
|
33
|
-
* Directory names are in format: `<timestamp>-<random>` where timestamp is Date.now()
|
|
34
|
-
* Example: "1704067200000-abc123" -> Date(2024-01-01T00:00:00.000Z)
|
|
35
|
-
*
|
|
36
|
-
* @param dirName - The directory name to parse
|
|
37
|
-
* @returns The parsed timestamp as Date, or null if invalid
|
|
38
|
-
*/
|
|
39
|
-
function parseSnapshotDirName(dirName: string): Date | null {
|
|
40
|
-
// Format: <timestamp>-<random>
|
|
41
|
-
const match = dirName.match(/^(\d+)-[\w]+$/);
|
|
42
|
-
if (!match || !match[1]) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const timestamp = parseInt(match[1], 10);
|
|
47
|
-
if (isNaN(timestamp) || timestamp <= 0) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const date = new Date(timestamp);
|
|
52
|
-
// Validate the date is reasonable (between year 2000 and year 3000)
|
|
53
|
-
// Use UTC year to avoid timezone issues
|
|
54
|
-
const year = date.getUTCFullYear();
|
|
55
|
-
if (year < 2000 || year > 3000) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return date;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* List all available snapshots
|
|
64
|
-
*
|
|
65
|
-
* Scans the snapshots directory and returns information about each valid snapshot.
|
|
66
|
-
* Results are sorted by timestamp descending (most recent first).
|
|
67
|
-
*
|
|
68
|
-
* @returns Array of snapshot info objects, sorted by timestamp descending
|
|
69
|
-
*/
|
|
70
|
-
export async function listSnapshots(): Promise<SnapshotInfo[]> {
|
|
71
|
-
const snapshotsDir = getSnapshotsDir();
|
|
72
|
-
|
|
73
|
-
// Check if snapshots directory exists
|
|
74
|
-
try {
|
|
75
|
-
await fs.access(snapshotsDir);
|
|
76
|
-
} catch {
|
|
77
|
-
// Directory doesn't exist - return empty array
|
|
78
|
-
return [];
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// Read directory contents
|
|
82
|
-
let entries: string[];
|
|
83
|
-
try {
|
|
84
|
-
entries = await fs.readdir(snapshotsDir);
|
|
85
|
-
} catch {
|
|
86
|
-
// Cannot read directory - return empty array
|
|
87
|
-
return [];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Filter and parse valid snapshot directories
|
|
91
|
-
const snapshots: SnapshotInfo[] = [];
|
|
92
|
-
|
|
93
|
-
for (const entry of entries) {
|
|
94
|
-
const timestamp = parseSnapshotDirName(entry);
|
|
95
|
-
if (!timestamp) {
|
|
96
|
-
// Invalid directory name format - skip
|
|
97
|
-
continue;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const snapshotPath = path.join(snapshotsDir, entry, "phoenix");
|
|
101
|
-
|
|
102
|
-
// Verify the phoenix subdirectory exists
|
|
103
|
-
try {
|
|
104
|
-
const stat = await fs.stat(snapshotPath);
|
|
105
|
-
if (!stat.isDirectory()) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
} catch {
|
|
109
|
-
// Phoenix subdirectory doesn't exist or can't be accessed - skip
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
snapshots.push({
|
|
114
|
-
path: snapshotPath,
|
|
115
|
-
timestamp,
|
|
116
|
-
id: entry,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Sort by timestamp descending (most recent first)
|
|
121
|
-
snapshots.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
122
|
-
|
|
123
|
-
return snapshots;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Get the latest (most recent) snapshot
|
|
128
|
-
*
|
|
129
|
-
* @returns The most recent snapshot info, or null if no snapshots exist
|
|
130
|
-
*/
|
|
131
|
-
export async function getLatestSnapshot(): Promise<SnapshotInfo | null> {
|
|
132
|
-
const snapshots = await listSnapshots();
|
|
133
|
-
|
|
134
|
-
if (snapshots.length === 0) {
|
|
135
|
-
return null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// First element is the most recent due to descending sort
|
|
139
|
-
return snapshots[0] ?? null;
|
|
140
|
-
}
|