From d7eb0a78158a11c26a66439e1ec97110dcd89c8d Mon Sep 17 00:00:00 2001 From: Tony Powell Date: Wed, 21 Jan 2026 10:21:54 -0500 Subject: [PATCH] docs: Update README and CLI documentation to include requirements for Node.js and Anthropic API key --- .changeset/brave-pillows-sit.md | 5 +++ README.md | 16 +++++++++- packages/cli/README.md | 19 +++++++++++ packages/cli/src/cli.ts | 47 +++++++++++++++++++++++++++- packages/cli/src/server/ui.ts | 22 ++++++++++++- packages/cli/src/server/websocket.ts | 16 ++++++++++ 6 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 .changeset/brave-pillows-sit.md diff --git a/.changeset/brave-pillows-sit.md b/.changeset/brave-pillows-sit.md new file mode 100644 index 0000000..27aea1d --- /dev/null +++ b/.changeset/brave-pillows-sit.md @@ -0,0 +1,5 @@ +--- +"@cephalization/phoenix-insight": minor +--- + +fix: Forcefully shutdown keep-alive connections on close diff --git a/README.md b/README.md index 9c3b5e7..3b9c52c 100644 --- a/README.md +++ b/README.md @@ -50,12 +50,26 @@ phoenix-insight/ └── README.md # This file ``` +## Requirements + +- **Node.js v22 or newer** - Required for the CLI to run +- **Anthropic API key** - Required for the AI agent + +Set your Anthropic API key before running: + +```bash +export ANTHROPIC_API_KEY=sk-ant-api03-... +``` + +You can get an API key from [console.anthropic.com](https://console.anthropic.com/). + ## Development ### Prerequisites -- Node.js >= 18 (v24 recommended, see `.nvmrc`) +- Node.js v22 or newer - pnpm 9.15.0 (`corepack enable && corepack prepare pnpm@9.15.0 --activate`) +- Anthropic API key (see [Requirements](#requirements)) ### Setup diff --git a/packages/cli/README.md b/packages/cli/README.md index c344dbb..f51e91d 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -6,6 +6,25 @@ Phoenix Insight brings AI-powered analysis to your [Phoenix](https://github.com/ This filesystem-native approach provides transparency that traditional APIs can't match. Every query the agent runs is visible and reproducible. You can inspect the exact files it reads, copy its commands, and run them yourself. The data is just files, and the analysis is just bash, making AI-driven observability debuggable, auditable, and extensible with any tool in your Unix toolkit. +## Requirements + +- **Node.js v22 or newer** - Required for the CLI to run +- **Anthropic API key** - Required for the AI agent + +Set your Anthropic API key before running: + +```bash +export ANTHROPIC_API_KEY=sk-ant-api03-... +``` + +Or add it to your shell profile for persistence: + +```bash +echo 'export ANTHROPIC_API_KEY=sk-ant-api03-...' >> ~/.zshrc # or ~/.bashrc +``` + +You can get an API key from [console.anthropic.com](https://console.anthropic.com/). + ## Installation ```bash diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index ba1a6ed..42b68b6 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -94,6 +94,30 @@ function formatBashCommand(command: string): string { } } +/** + * Check if ANTHROPIC_API_KEY is set and provide a helpful error if not + */ +function ensureAnthropicApiKey(): void { + if (!process.env.ANTHROPIC_API_KEY) { + console.error( + "\nāŒ Error: Missing ANTHROPIC_API_KEY environment variable\n" + ); + console.error("The Anthropic API key is required to run the AI agent.\n"); + console.error("To fix this, set the environment variable:\n"); + console.error(" export ANTHROPIC_API_KEY=sk-ant-api03-...\n"); + console.error( + "Or add it to your shell profile (~/.zshrc, ~/.bashrc, etc.):\n" + ); + console.error( + " echo 'export ANTHROPIC_API_KEY=sk-ant-api03-...' >> ~/.zshrc\n" + ); + console.error( + "You can get an API key from: https://console.anthropic.com/\n" + ); + process.exit(1); + } +} + /** * Handle errors with appropriate exit codes and user-friendly messages */ @@ -448,6 +472,9 @@ program return; } + // Ensure Anthropic API key is available for agent execution + ensureAnthropicApiKey(); + // Initialize observability if trace is enabled in config if (config.trace) { initializeObservability({ @@ -622,6 +649,9 @@ async function runUIServer(options: { port?: number; open?: boolean; }): Promise { + // Ensure Anthropic API key is available for agent execution + ensureAnthropicApiKey(); + const config = getConfig(); const port = options.port ?? 6007; const shouldOpen = options.open !== false; // Default to opening browser @@ -735,14 +765,24 @@ async function runUIServer(options: { openBrowser(url); } - // Handle graceful shutdown + // Handle graceful shutdown with timeout let isShuttingDown = false; + const SHUTDOWN_TIMEOUT_MS = 3000; + const shutdown = async (signal: string) => { if (isShuttingDown) return; isShuttingDown = true; console.log(`\n\nšŸ“„ Received ${signal}, shutting down gracefully...`); + // Set up a timeout to force exit if graceful shutdown takes too long + const forceExitTimeout = setTimeout(() => { + console.log("ā±ļø Shutdown timeout reached, forcing exit..."); + wsServer.forceClose(); + uiServer.forceClose(); + process.exit(0); + }, SHUTDOWN_TIMEOUT_MS); + try { // Close WebSocket connections first await wsServer.close(); @@ -759,9 +799,11 @@ async function runUIServer(options: { // Shutdown observability if enabled await shutdownObservability(); + clearTimeout(forceExitTimeout); console.log("šŸ‘‹ Server stopped. Goodbye!"); process.exit(0); } catch (error) { + clearTimeout(forceExitTimeout); console.error("Error during shutdown:", error); process.exit(1); } @@ -780,6 +822,9 @@ async function runUIServer(options: { } async function runInteractiveMode(): Promise { + // Ensure Anthropic API key is available for agent execution + ensureAnthropicApiKey(); + const config = getConfig(); console.log("šŸš€ Phoenix Insight Interactive Mode"); diff --git a/packages/cli/src/server/ui.ts b/packages/cli/src/server/ui.ts index 5322e0e..811c7df 100644 --- a/packages/cli/src/server/ui.ts +++ b/packages/cli/src/server/ui.ts @@ -103,8 +103,10 @@ export interface UIServer { host: string; /** Path to the UI dist directory being served */ distPath: string; - /** Close the server */ + /** Close the server gracefully */ close(): Promise; + /** Force close all connections immediately */ + forceClose(): void; } // ============================================================================ @@ -189,6 +191,9 @@ export function createUIServer(options: UIServerOptions = {}): Promise } return new Promise((resolve, reject) => { + // Track active connections for force-close capability + const activeConnections = new Set(); + const httpServer = createServer((req, res) => { const urlPath = req.url ?? "/"; @@ -258,6 +263,14 @@ export function createUIServer(options: UIServerOptions = {}): Promise }); }); + // Track connections to enable force-close + httpServer.on("connection", (socket) => { + activeConnections.add(socket); + socket.on("close", () => { + activeConnections.delete(socket); + }); + }); + // Handle server errors httpServer.on("error", (err) => { reject(err); @@ -287,6 +300,13 @@ export function createUIServer(options: UIServerOptions = {}): Promise }); }); }, + forceClose(): void { + // Destroy all active connections immediately + for (const socket of activeConnections) { + socket.destroy(); + } + activeConnections.clear(); + }, }); }); }); diff --git a/packages/cli/src/server/websocket.ts b/packages/cli/src/server/websocket.ts index 7dc779f..e5fcbc7 100644 --- a/packages/cli/src/server/websocket.ts +++ b/packages/cli/src/server/websocket.ts @@ -271,6 +271,22 @@ export class PhoenixWebSocketServer { }); }); } + + /** + * Force terminate all WebSocket connections immediately. + * Use this when graceful close doesn't complete in time. + */ + forceClose(): void { + if (!this.wss) { + return; + } + + // Terminate all client connections immediately (no close handshake) + for (const client of this.clients) { + client.terminate(); + } + this.clients.clear(); + } } // ============================================================================