@cephalization/phoenix-insight 1.0.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.
@@ -1,141 +0,0 @@
1
- import { exec as execCallback } from "node:child_process";
2
- import { promisify } from "node:util";
3
- import * as fs from "node:fs/promises";
4
- import * as path from "node:path";
5
- import * as os from "node:os";
6
- import { tool } from "ai";
7
- import { z } from "zod";
8
- const execAsync = promisify(execCallback);
9
- /**
10
- * Local execution mode using real bash and persistent filesystem
11
- * - Real bash execution via child_process
12
- * - Persistent storage in ~/.phoenix-insight/
13
- * - Full system access
14
- */
15
- export class LocalMode {
16
- workDir;
17
- toolCreated = false;
18
- bashToolPromise = null;
19
- constructor() {
20
- // Create a timestamped directory for this snapshot
21
- // Add a small random component to ensure uniqueness even if created at the same millisecond
22
- const timestamp = Date.now().toString() + "-" + Math.random().toString(36).substring(7);
23
- this.workDir = path.join(os.homedir(), ".phoenix-insight", "snapshots", timestamp, "phoenix");
24
- }
25
- /**
26
- * Get the absolute root path of the Phoenix snapshot directory
27
- * For local mode, this is the actual filesystem path
28
- */
29
- getSnapshotRoot() {
30
- return this.workDir;
31
- }
32
- /**
33
- * Initialize the working directory
34
- */
35
- async init() {
36
- try {
37
- // Create the directory structure if it doesn't exist
38
- await fs.mkdir(this.workDir, { recursive: true });
39
- }
40
- catch (error) {
41
- throw new Error(`Failed to initialize local mode directory at ${this.workDir}: ${error instanceof Error ? error.message : String(error)}`);
42
- }
43
- }
44
- async writeFile(filePath, content) {
45
- await this.init();
46
- // Ensure the path is relative to phoenix root
47
- const cleanPath = filePath.startsWith("/phoenix")
48
- ? filePath.substring(8) // Remove /phoenix prefix
49
- : filePath.startsWith("/")
50
- ? filePath.substring(1) // Remove leading slash
51
- : filePath;
52
- const fullPath = path.join(this.workDir, cleanPath);
53
- // Create parent directories if they don't exist
54
- const dirname = path.dirname(fullPath);
55
- await fs.mkdir(dirname, { recursive: true });
56
- // Write the file
57
- await fs.writeFile(fullPath, content, "utf-8");
58
- }
59
- async exec(command) {
60
- await this.init();
61
- try {
62
- // Execute the command in the phoenix directory with a timeout
63
- const { stdout, stderr } = await execAsync(command, {
64
- cwd: this.workDir,
65
- shell: "/bin/bash",
66
- encoding: "utf-8",
67
- timeout: 60000, // 60 second timeout for bash commands
68
- });
69
- return {
70
- stdout: stdout || "",
71
- stderr: stderr || "",
72
- exitCode: 0,
73
- };
74
- }
75
- catch (error) {
76
- // Handle command execution errors
77
- if (error.code !== undefined) {
78
- return {
79
- stdout: error.stdout || "",
80
- stderr: error.stderr || error.message || "Command failed",
81
- exitCode: error.code || 1,
82
- };
83
- }
84
- // Handle other errors
85
- return {
86
- stdout: "",
87
- stderr: error.message || "Unknown error",
88
- exitCode: 1,
89
- };
90
- }
91
- }
92
- async getBashTool() {
93
- // Only create the tool once and cache it
94
- if (!this.bashToolPromise) {
95
- this.bashToolPromise = this.createBashTool();
96
- }
97
- return this.bashToolPromise;
98
- }
99
- async createBashTool() {
100
- // We can't use bash-tool directly for local mode since it's designed for just-bash
101
- // Instead, we'll create a tool using the AI SDK's tool function
102
- // This ensures compatibility with the AI SDK
103
- // Return a bash tool that executes real bash commands
104
- return tool({
105
- description: "Execute bash commands in the local filesystem",
106
- inputSchema: z.object({
107
- command: z.string().describe("The bash command to execute"),
108
- }),
109
- execute: async ({ command }) => {
110
- const result = await this.exec(command);
111
- // Return result in a format similar to bash-tool
112
- if (result.exitCode !== 0) {
113
- // Include error details in the response
114
- return {
115
- success: false,
116
- stdout: result.stdout,
117
- stderr: result.stderr,
118
- exitCode: result.exitCode,
119
- error: `Command failed with exit code ${result.exitCode}`,
120
- };
121
- }
122
- return {
123
- success: true,
124
- stdout: result.stdout,
125
- stderr: result.stderr,
126
- exitCode: result.exitCode,
127
- };
128
- },
129
- });
130
- }
131
- async cleanup() {
132
- // Optional: Clean up old snapshots
133
- // For now, we'll keep all snapshots for user reference
134
- // Users can manually clean ~/.phoenix-insight/ if needed
135
- // We could implement logic to:
136
- // 1. Keep only the last N snapshots
137
- // 2. Delete snapshots older than X days
138
- // 3. Provide a separate cleanup command
139
- // For this implementation, we do nothing
140
- }
141
- }
@@ -1,129 +0,0 @@
1
- import { tool } from "ai";
2
- import { z } from "zod";
3
- /**
4
- * Sandbox execution mode using just-bash for isolated execution
5
- * - In-memory filesystem
6
- * - Simulated bash commands
7
- * - No disk or network access
8
- */
9
- export class SandboxMode {
10
- bash; // Will be typed as Bash from just-bash
11
- initialized = false;
12
- bashToolPromise = null;
13
- snapshotRoot = "/phoenix/";
14
- constructor() {
15
- // We'll initialize in the init method since we need async imports
16
- }
17
- /**
18
- * Get the absolute root path of the Phoenix snapshot directory
19
- * For sandbox mode, this is always the virtual path "/phoenix/"
20
- */
21
- getSnapshotRoot() {
22
- return this.snapshotRoot;
23
- }
24
- async init() {
25
- if (this.initialized)
26
- return;
27
- try {
28
- // Dynamic imports for ESM modules
29
- const { Bash } = await import("just-bash");
30
- // Initialize just-bash with /phoenix as the working directory
31
- this.bash = new Bash({ cwd: "/phoenix" });
32
- this.initialized = true;
33
- }
34
- catch (error) {
35
- throw new Error(`Failed to initialize sandbox mode: ${error instanceof Error ? error.message : String(error)}`);
36
- }
37
- }
38
- async writeFile(path, content) {
39
- await this.init();
40
- try {
41
- // Ensure the path starts with /phoenix
42
- const fullPath = path.startsWith("/phoenix")
43
- ? path
44
- : `/phoenix${path.startsWith("/") ? "" : "/"}${path}`;
45
- // Create parent directories if they don't exist
46
- const dirname = fullPath.substring(0, fullPath.lastIndexOf("/"));
47
- if (dirname) {
48
- await this.bash.exec(`mkdir -p ${dirname}`);
49
- }
50
- // Write the file using just-bash's filesystem
51
- // We'll use the InMemoryFs directly for better performance
52
- this.bash.fs.writeFileSync(fullPath, content);
53
- }
54
- catch (error) {
55
- throw new Error(`Failed to write file ${path}: ${error instanceof Error ? error.message : String(error)}`);
56
- }
57
- }
58
- async exec(command) {
59
- await this.init();
60
- try {
61
- const result = await this.bash.exec(command);
62
- // just-bash returns a different structure, so we need to normalize it
63
- return {
64
- stdout: result.stdout || "",
65
- stderr: result.stderr || "",
66
- exitCode: result.exitCode || 0,
67
- };
68
- }
69
- catch (error) {
70
- // If the command fails, just-bash throws an error
71
- // Extract what we can from the error
72
- if (error && typeof error === "object" && "exitCode" in error) {
73
- return {
74
- stdout: error.stdout || "",
75
- stderr: error.stderr || error.toString(),
76
- exitCode: error.exitCode || 1,
77
- };
78
- }
79
- // Fallback for unexpected errors
80
- return {
81
- stdout: "",
82
- stderr: error?.toString() || "Unknown error",
83
- exitCode: 1,
84
- };
85
- }
86
- }
87
- async getBashTool() {
88
- // Only create the tool once and cache it
89
- if (!this.bashToolPromise) {
90
- this.bashToolPromise = this.createBashTool();
91
- }
92
- return this.bashToolPromise;
93
- }
94
- async createBashTool() {
95
- await this.init();
96
- // Create a bash tool compatible with the AI SDK
97
- // Similar to local mode, we'll create it directly using the tool function
98
- return tool({
99
- description: "Execute bash commands in the sandbox filesystem",
100
- inputSchema: z.object({
101
- command: z.string().describe("The bash command to execute"),
102
- }),
103
- execute: async ({ command }) => {
104
- const result = await this.exec(command);
105
- // Return result in a format similar to bash-tool
106
- if (result.exitCode !== 0) {
107
- // Include error details in the response
108
- return {
109
- success: false,
110
- stdout: result.stdout,
111
- stderr: result.stderr,
112
- exitCode: result.exitCode,
113
- error: `Command failed with exit code ${result.exitCode}`,
114
- };
115
- }
116
- return {
117
- success: true,
118
- stdout: result.stdout,
119
- stderr: result.stderr,
120
- exitCode: result.exitCode,
121
- };
122
- },
123
- });
124
- }
125
- async cleanup() {
126
- // No-op for in-memory mode - garbage collection will handle cleanup
127
- // We could optionally clear the filesystem here if needed
128
- }
129
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,65 +0,0 @@
1
- /**
2
- * Phoenix Insight observability configuration
3
- */
4
- import { register } from "@arizeai/phoenix-otel";
5
- import { DiagLogLevel } from "@opentelemetry/api";
6
- /**
7
- * Global tracer provider instance
8
- */
9
- let tracerProvider = null;
10
- /**
11
- * Check if observability is enabled
12
- */
13
- export function isObservabilityEnabled() {
14
- return tracerProvider !== null;
15
- }
16
- /**
17
- * Initialize observability for the Phoenix Insight agent
18
- */
19
- export function initializeObservability(options) {
20
- if (!options.enabled) {
21
- return;
22
- }
23
- // If already initialized, skip
24
- if (tracerProvider) {
25
- return;
26
- }
27
- try {
28
- // Configure the tracer provider
29
- tracerProvider = register({
30
- projectName: options.projectName || "phoenix-insight",
31
- url: options.baseUrl,
32
- apiKey: options.apiKey,
33
- batch: true,
34
- global: true,
35
- diagLogLevel: options.debug ? DiagLogLevel.DEBUG : undefined,
36
- });
37
- if (options.debug) {
38
- console.error("🔭 Observability enabled - traces will be sent to Phoenix");
39
- }
40
- }
41
- catch (error) {
42
- console.error("âš ī¸ Failed to initialize observability:", error);
43
- // Don't throw - observability should not break the main functionality
44
- }
45
- }
46
- /**
47
- * Shutdown observability and cleanup resources
48
- */
49
- export async function shutdownObservability() {
50
- if (tracerProvider) {
51
- try {
52
- await tracerProvider.shutdown();
53
- tracerProvider = null;
54
- }
55
- catch (error) {
56
- console.error("âš ī¸ Error shutting down observability:", error);
57
- }
58
- }
59
- }
60
- /**
61
- * Get the current tracer provider
62
- */
63
- export function getTracerProvider() {
64
- return tracerProvider;
65
- }
package/dist/progress.js DELETED
@@ -1,209 +0,0 @@
1
- /**
2
- * Progress indicators for Phoenix Insight CLI
3
- */
4
- import ora, {} from "ora";
5
- /**
6
- * Progress indicator for snapshot operations
7
- */
8
- export class SnapshotProgress {
9
- spinner = null;
10
- enabled;
11
- currentPhase = null;
12
- totalSteps = 6;
13
- currentStep = 0;
14
- constructor(enabled = true) {
15
- this.enabled = enabled;
16
- }
17
- /**
18
- * Start the progress indicator
19
- */
20
- start(message = "Creating Phoenix data snapshot") {
21
- if (!this.enabled)
22
- return;
23
- this.currentStep = 0;
24
- this.spinner = ora({
25
- text: message,
26
- spinner: "dots",
27
- color: "blue",
28
- }).start();
29
- }
30
- /**
31
- * Update progress with a new phase
32
- */
33
- update(phase, detail) {
34
- if (!this.enabled || !this.spinner)
35
- return;
36
- this.currentStep++;
37
- this.currentPhase = phase;
38
- const progress = Math.round((this.currentStep / this.totalSteps) * 100);
39
- const progressBar = this.createProgressBar(progress);
40
- const message = detail
41
- ? `${progressBar} ${phase}: ${detail}`
42
- : `${progressBar} ${phase}`;
43
- this.spinner.text = message;
44
- }
45
- /**
46
- * Complete a phase successfully
47
- */
48
- succeed(message) {
49
- if (!this.enabled || !this.spinner)
50
- return;
51
- const finalMessage = message || `✓ ${this.currentPhase || "Snapshot"} complete`;
52
- this.spinner.succeed(finalMessage);
53
- this.spinner = null;
54
- }
55
- /**
56
- * Fail with an error
57
- */
58
- fail(message) {
59
- if (!this.enabled || !this.spinner)
60
- return;
61
- const finalMessage = message || `✗ ${this.currentPhase || "Snapshot"} failed`;
62
- this.spinner.fail(finalMessage);
63
- this.spinner = null;
64
- }
65
- /**
66
- * Stop without success/fail status
67
- */
68
- stop() {
69
- if (!this.enabled || !this.spinner)
70
- return;
71
- this.spinner.stop();
72
- this.spinner = null;
73
- }
74
- /**
75
- * Create a progress bar string
76
- */
77
- createProgressBar(percentage) {
78
- const width = 20;
79
- const filled = Math.round((percentage / 100) * width);
80
- const empty = width - filled;
81
- const bar = "█".repeat(filled) + "░".repeat(empty);
82
- return `[${bar}] ${percentage}%`;
83
- }
84
- }
85
- /**
86
- * Progress indicator for agent thinking
87
- */
88
- export class AgentProgress {
89
- spinner = null;
90
- enabled;
91
- stepCount = 0;
92
- currentTool = null;
93
- constructor(enabled = true) {
94
- this.enabled = enabled;
95
- }
96
- /**
97
- * Start thinking indicator
98
- */
99
- startThinking() {
100
- if (!this.enabled)
101
- return;
102
- this.stepCount = 0;
103
- this.currentTool = null;
104
- this.spinner = ora({
105
- text: "🤔 Analyzing...",
106
- spinner: "dots",
107
- color: "cyan",
108
- }).start();
109
- }
110
- /**
111
- * Update with current tool usage
112
- */
113
- updateTool(toolName, detail) {
114
- if (!this.enabled || !this.spinner)
115
- return;
116
- this.stepCount++;
117
- this.currentTool = toolName;
118
- // Map tool names to more user-friendly descriptions
119
- const friendlyNames = {
120
- bash: "Running command",
121
- px_fetch_more_spans: "Fetching additional spans",
122
- px_fetch_more_trace: "Fetching trace details",
123
- };
124
- const displayName = friendlyNames[toolName] || toolName;
125
- const message = detail
126
- ? `🔧 ${displayName}: ${detail}`
127
- : `🔧 ${displayName} (step ${this.stepCount})`;
128
- this.spinner.text = message;
129
- }
130
- /**
131
- * Update with tool result
132
- */
133
- updateToolResult(toolName, success = true) {
134
- if (!this.enabled || !this.spinner)
135
- return;
136
- const icon = success ? "✓" : "✗";
137
- const status = success ? "completed" : "failed";
138
- // More informative messages for each tool
139
- const toolMessages = {
140
- bash: "Command executed",
141
- px_fetch_more_spans: "Additional spans fetched",
142
- px_fetch_more_trace: "Trace details fetched",
143
- };
144
- const baseMessage = toolMessages[toolName] || `Tool ${toolName}`;
145
- const message = `${icon} ${baseMessage} ${status}`;
146
- // Brief flash of the result before moving on
147
- this.spinner.text = message;
148
- }
149
- /**
150
- * Show progress for a specific action
151
- */
152
- updateAction(action) {
153
- if (!this.enabled || !this.spinner)
154
- return;
155
- this.spinner.text = `🔍 ${action}...`;
156
- }
157
- /**
158
- * Stop the thinking indicator
159
- */
160
- stop() {
161
- if (!this.enabled || !this.spinner)
162
- return;
163
- this.spinner.stop();
164
- this.spinner = null;
165
- }
166
- /**
167
- * Complete with a success message
168
- */
169
- succeed(message = "✨ Analysis complete") {
170
- if (!this.enabled || !this.spinner)
171
- return;
172
- this.spinner.succeed(message);
173
- this.spinner = null;
174
- }
175
- }
176
- /**
177
- * Simple progress logger for when spinners aren't appropriate
178
- */
179
- export class SimpleProgress {
180
- enabled;
181
- constructor(enabled = true) {
182
- this.enabled = enabled;
183
- }
184
- log(message) {
185
- if (!this.enabled)
186
- return;
187
- console.log(`[Phoenix Insight] ${message}`);
188
- }
189
- info(message) {
190
- if (!this.enabled)
191
- return;
192
- console.log(`â„šī¸ ${message}`);
193
- }
194
- success(message) {
195
- if (!this.enabled)
196
- return;
197
- console.log(`✅ ${message}`);
198
- }
199
- warning(message) {
200
- if (!this.enabled)
201
- return;
202
- console.log(`âš ī¸ ${message}`);
203
- }
204
- error(message) {
205
- if (!this.enabled)
206
- return;
207
- console.log(`❌ ${message}`);
208
- }
209
- }
@@ -1 +0,0 @@
1
- export { getInsightSystemPrompt } from "./system.js";
@@ -1,37 +0,0 @@
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
- * Generate the system prompt for the Phoenix Insight agent
7
- * @param snapshotRoot - The absolute path to the Phoenix snapshot root directory
8
- * @returns The system prompt string with the correct snapshot path
9
- */
10
- export function getInsightSystemPrompt(snapshotRoot) {
11
- return `You are an expert at analyzing Phoenix observability data.
12
-
13
- **START by reading ${snapshotRoot}/_context.md** - it contains a summary of what's available.
14
-
15
- You have access to a bash shell with Phoenix data organized as files:
16
-
17
- ${snapshotRoot}/
18
- _context.md - READ THIS FIRST: summary of available data
19
- /projects/{name}/spans/ - Span data (JSONL format, may be sampled)
20
- /datasets/ - Datasets and examples
21
- /experiments/ - Experiment runs and results
22
- /prompts/ - Prompt templates and versions
23
-
24
- Use commands like:
25
- - cat, head, tail: Read file contents
26
- - grep: Search for patterns
27
- - jq: Query and transform JSON/JSONL
28
- - ls, find: Navigate and discover data
29
- - sort, uniq, wc: Aggregate and count
30
- - awk: Complex text processing
31
-
32
- If you need MORE data than what's in the snapshot:
33
- - px-fetch-more spans --project <name> --limit 500
34
- - px-fetch-more trace --trace-id <id>
35
-
36
- This is a READ-ONLY snapshot. Start with _context.md, then explore to answer the question.`;
37
- }