diff --git a/AGENTS.md b/AGENTS.md
index 11ef1ea..a6413f9 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,8 +13,7 @@ The V8 isolate provides these bindings to LLM-generated code:
| `context` | The loaded context data |
| `llm_query(prompt, model?)` | Query sub-LLM |
| `llm_query_batched(prompts, model?)` | Batch query sub-LLMs |
-| `FINAL(answer)` | Return final answer |
-| `FINAL_VAR(varName)` | Return variable as final answer |
+| `giveFinalAnswer({ message, data? })` | Return final answer |
| `print(...)` | Console output |
## Architecture
@@ -30,7 +29,7 @@ The V8 isolate provides these bindings to LLM-generated code:
│ │ │ • llm_query() ──┐ │ │
│ │ │ • llm_query_batched() │ │
│ ▼ │ • print() / console │ │
-│ ┌─────────────┐ │ • FINAL() / FINAL_VAR() │ │
+│ ┌─────────────┐ │ • giveFinalAnswer() │ │
│ │ LLMClient │◀───┼──────────────────┘ │ │
│ │ (OpenAI) │ │ │ │
│ └─────────────┘ │ LLM-generated JS code runs here │ │
diff --git a/README.md b/README.md
index a2bff71..b163e37 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,14 @@ pnpm add rllm
npm install rllm
```
+## Demo
+
+RLLM analyzing a `node_modules` directory — the LLM writes JavaScript to parse dependencies, query sub-LLMs in parallel, and synthesize a final answer:
+
+
+
+Built with Gemini Flash 3. See the full interactive example in [`examples/node-modules-viz/`](./examples/node-modules-viz/).
+
## Quick Start
LLM writes JavaScript code that runs in a secure V8 isolate:
@@ -88,7 +96,7 @@ const findings = await llm_query_batched(
const summary = await llm_query(`Combine findings:\n${findings.join('\n')}`);
print(summary);
-FINAL(summary);
+giveFinalAnswer({ message: summary });
```
## API Reference
@@ -100,7 +108,7 @@ Create an RLLM instance with sensible defaults.
```typescript
const rlm = createRLLM({
model: 'gpt-4o-mini', // Model name
- provider: 'openai', // 'openai' | 'anthropic' | 'openrouter' | 'custom'
+ provider: 'openai', // 'openai' | 'anthropic' | 'gemini' | 'openrouter' | 'custom'
apiKey: process.env.KEY, // Optional, uses env vars by default
baseUrl: undefined, // Optional, required for 'custom' provider
verbose: true, // Enable logging
@@ -147,10 +155,44 @@ The V8 isolate provides these bindings to LLM-generated code:
| `context` | The loaded context data |
| `llm_query(prompt, model?)` | Query sub-LLM |
| `llm_query_batched(prompts, model?)` | Batch query sub-LLMs |
-| `FINAL(answer)` | Return final answer |
-| `FINAL_VAR(varName)` | Return variable as final answer |
+| `giveFinalAnswer({ message, data? })` | Return final answer |
| `print(...)` | Console output |
+### Real-time Events
+
+Subscribe to execution events for visualizations, debugging, or streaming UIs:
+
+```typescript
+const result = await rlm.completion("Analyze this data", {
+ context: myData,
+ onEvent: (event) => {
+ switch (event.type) {
+ case "iteration_start":
+ console.log(`Starting iteration ${event.iteration}`);
+ break;
+ case "llm_query_start":
+ console.log("LLM thinking...");
+ break;
+ case "code_execution_start":
+ console.log(`Executing:\n${event.code}`);
+ break;
+ case "final_answer":
+ console.log(`Answer: ${event.answer}`);
+ break;
+ }
+ }
+});
+```
+
+| Event Type | Description |
+|------------|-------------|
+| `iteration_start` | New iteration beginning |
+| `llm_query_start` | Main LLM query starting |
+| `llm_query_end` | Main LLM response received |
+| `code_execution_start` | V8 isolate executing code |
+| `code_execution_end` | Code execution finished |
+| `final_answer` | `giveFinalAnswer()` called with answer |
+
## Architecture
```
@@ -164,7 +206,7 @@ The V8 isolate provides these bindings to LLM-generated code:
│ │ │ • llm_query() ──┐ │ │
│ │ │ • llm_query_batched() │ │
│ ▼ │ • print() / console │ │
-│ ┌─────────────┐ │ • FINAL() / FINAL_VAR() │ │
+│ ┌─────────────┐ │ • giveFinalAnswer() │ │
│ │ LLMClient │◀───┼──────────────────┘ │ │
│ │ (OpenAI) │ │ │ │
│ └─────────────┘ │ LLM-generated JS code runs here │ │
diff --git a/RLM.gif b/RLM.gif
new file mode 100644
index 0000000..e93e845
Binary files /dev/null and b/RLM.gif differ
diff --git a/examples/node-modules-viz/README.md b/examples/node-modules-viz/README.md
new file mode 100644
index 0000000..69db8d9
--- /dev/null
+++ b/examples/node-modules-viz/README.md
@@ -0,0 +1,264 @@
+# node_modules Graph Analyzer
+
+Interactive 3D visualization of your `node_modules` dependencies with RLLM-powered queries. Watch in real-time as the AI traverses your dependency graph to answer questions about duplicates, sizes, and relationships.
+
+## Features
+
+- 📦 **Parse any node_modules** - Analyzes package.json files, calculates sizes, detects duplicates
+- 🎯 **Real-time Visualization** - 3D force-directed graph with live highlighting as RLLM explores
+- 🤖 **AI-Powered Queries** - Ask natural language questions about your dependencies
+- 🔍 **Proxy Hooks** - Tracks every property access via JavaScript Proxies for visualization
+
+## Quick Start
+
+```bash
+cd examples/node-modules-viz
+pnpm install
+pnpm start
+```
+
+Then open http://localhost:3000 in your browser.
+
+This runs both the backend (WebSocket + RLLM) and frontend (Vite + React) concurrently.
+
+This will:
+1. Parse the parent directory's `node_modules` (this repo)
+2. Show the 3D visualization
+3. Let you click example queries or type your own
+
+## Usage
+
+### Analyze a specific project
+
+```bash
+pnpm server --target=/path/to/your/project
+```
+
+### Run a custom query from CLI
+
+```bash
+pnpm server --query="Find the largest packages"
+```
+
+Or just use the interactive UI in the browser!
+
+### Combine both
+
+```bash
+pnpm server --target=~/projects/my-app --query="What brings in lodash?"
+```
+
+## Example Queries
+
+### Find Duplicates
+```
+Which packages have multiple versions installed?
+```
+
+### Dependency Chains
+```
+What brings in lodash? Show the dependency chain.
+```
+
+### Disk Space Analysis
+```
+How much disk space could I save by deduping?
+```
+
+### Size Analysis
+```
+Find the 5 largest packages and their total size.
+```
+
+### Dependency Relationships
+```
+What's the path from react to scheduler?
+```
+
+### Popularity Analysis
+```
+Which packages have the most dependents?
+```
+
+### License Audit
+```
+Find all MIT licensed packages.
+```
+
+### Circular Dependencies
+```
+Find circular dependencies in the graph.
+```
+
+### Dev vs Prod
+```
+How many packages are dev dependencies vs production?
+```
+
+### Version Conflicts
+```
+Show all packages where different versions are required by different dependents.
+```
+
+## How It Works
+
+### 1. Graph Parsing
+
+The parser walks your `node_modules` directory recursively:
+- Reads each `package.json`
+- Calculates disk size
+- Detects hoisting and nesting
+- Identifies duplicate versions
+
+### 2. Proxy Tracking
+
+The graph context is wrapped in recursive JavaScript Proxies:
+
+```typescript
+const trackedContext = createTrackedGraph(context, (event) => {
+ // When RLLM code accesses context.packages["lodash@4.17.21"]
+ // This callback fires and sends the event to the browser
+ server.sendAccessEvent(event);
+});
+```
+
+### 3. RLLM Execution
+
+The AI writes JavaScript code that runs in a V8 isolate:
+
+```javascript
+// Example: Find duplicates
+const duplicates = [];
+for (const [name, ids] of Object.entries(context.packagesByName)) {
+ if (ids.length > 1) {
+ const sizes = ids.map(id => context.packages[id].diskSize);
+ duplicates.push({ name, versions: ids, totalSize: sizes.reduce((a,b) => a+b) });
+ }
+}
+giveFinalAnswer({ message: JSON.stringify(duplicates, null, 2) });
+```
+
+Every property access (like `context.packages[id]`) triggers the Proxy, which emits an event to highlight that node in the visualization.
+
+### 4. Real-time Visualization
+
+The browser receives WebSocket events and highlights nodes as they're accessed:
+- **Blue nodes** = regular packages
+- **Red nodes** = duplicate versions
+- **Green pulse** = currently accessed by RLLM
+
+## Architecture
+
+```
+┌─────────────────┐
+│ node_modules/ │
+└────────┬────────┘
+ │ parse
+ ▼
+┌─────────────────┐
+│ Dependency Graph│
+└────────┬────────┘
+ │ wrap in Proxy
+ ▼
+┌─────────────────┐ ┌──────────────┐
+│ Tracked Context │─────▶│ RLLM Engine │
+└────────┬────────┘ └──────┬───────┘
+ │ │
+ │ access events │ query result
+ ▼ ▼
+┌─────────────────────────────────────┐
+│ WebSocket Server │
+└────────────┬────────────────────────┘
+ │
+ ▼
+┌─────────────────────────────────────┐
+│ Browser (force-graph-3d) │
+│ - 3D visualization │
+│ - Real-time highlighting │
+│ - Stats panel │
+└─────────────────────────────────────┘
+```
+
+## Graph Data Structure
+
+### Nodes
+```typescript
+{
+ id: "lodash@4.17.21",
+ name: "lodash",
+ version: "4.17.21",
+ diskSize: 1234567,
+ isDuplicate: false,
+ isHoisted: true,
+ dependents: ["package-a@1.0.0", "package-b@2.0.0"]
+}
+```
+
+### Edges
+```typescript
+{
+ source: "react@18.2.0",
+ target: "scheduler@0.23.0",
+ type: "prod",
+ versionRange: "^0.23.0"
+}
+```
+
+## Environment Variables
+
+Create a `.env` file:
+
+```bash
+OPENAI_API_KEY=your_key_here
+MODEL=gpt-4o-mini # or gpt-4o, gpt-4-turbo, etc.
+```
+
+## Tips
+
+- **Large projects**: Parsing may take a minute for huge `node_modules` (10k+ packages)
+- **Query complexity**: Start simple, let RLLM iterate to solve complex queries
+- **Visualization**: Click and drag nodes, scroll to zoom, the graph auto-rotates
+- **Results panel**: Shows at the bottom when query completes
+
+## Troubleshooting
+
+### No packages found
+Make sure the target directory has a `node_modules` folder:
+```bash
+ls /path/to/project/node_modules
+```
+
+### Browser doesn't open
+Manually navigate to: http://localhost:3000
+
+### WebSocket connection failed
+Check if port 3000 is available:
+```bash
+lsof -i :3000
+```
+
+### RLLM query fails
+Check your OpenAI API key is set:
+```bash
+echo $OPENAI_API_KEY
+```
+
+## Development
+
+```bash
+# Watch mode (auto-restart on changes)
+pnpm dev
+
+# Run with verbose logging
+pnpm start --query="Your query"
+```
+
+## Credits
+
+Built with:
+- [RLLM](https://github.com/code-rabi/rllm) - Recursive Language Models
+- [react-force-graph-3d](https://github.com/vasturiano/react-force-graph) - React 3D graph visualization
+- [Vite](https://vitejs.dev/) + [React](https://react.dev/) - Frontend framework
+- [ws](https://github.com/websockets/ws) - WebSocket server (port 4242)
+
+Inspired by the [Recursive Language Models paper](https://arxiv.org/abs/2512.24601).
diff --git a/examples/node-modules-viz/index.html b/examples/node-modules-viz/index.html
new file mode 100644
index 0000000..595f6d1
--- /dev/null
+++ b/examples/node-modules-viz/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ node_modules Graph Analyzer
+
+
+
+
+
+
diff --git a/examples/node-modules-viz/lib/build-graph.ts b/examples/node-modules-viz/lib/build-graph.ts
new file mode 100644
index 0000000..9285692
--- /dev/null
+++ b/examples/node-modules-viz/lib/build-graph.ts
@@ -0,0 +1,192 @@
+/**
+ * Build dependency graph from parsed node_modules
+ *
+ * Creates nodes and edges suitable for visualization and RLLM queries.
+ */
+
+import type { PackageInfo, ParsedNodeModules } from "./parse-node-modules.js";
+
+export interface GraphNode {
+ id: string; // "lodash@4.17.21"
+ name: string; // "lodash"
+ version: string; // "4.17.21"
+ diskSize: number; // bytes
+ license?: string;
+ description?: string;
+ isHoisted: boolean;
+ isDuplicate: boolean; // true if another version exists
+ duplicateOf?: string[]; // other version IDs
+ dependents: string[]; // packages that depend on this
+ dependencyCount: number; // total number of dependencies
+}
+
+export interface GraphEdge {
+ id: string; // "react@18.2.0->scheduler"
+ source: string; // "react@18.2.0"
+ target: string; // target package name (not full ID, as version may vary)
+ targetId?: string; // resolved target ID if found
+ versionRange: string; // "^0.23.0"
+ type: "prod" | "dev" | "peer" | "optional";
+}
+
+export interface DependencyGraph {
+ nodes: Map;
+ edges: GraphEdge[];
+ nodesByName: Map; // name -> [id1, id2, ...]
+ stats: {
+ totalPackages: number;
+ totalDuplicates: number;
+ totalSize: number;
+ largestPackage: { id: string; size: number };
+ mostDependents: { id: string; count: number };
+ };
+}
+
+/**
+ * Build graph from parsed node_modules
+ */
+export function buildGraph(parsed: ParsedNodeModules): DependencyGraph {
+ const nodes = new Map();
+ const edges: GraphEdge[] = [];
+ const nodesByName = new Map();
+
+ // Build nodes
+ for (const [id, pkg] of parsed.packages) {
+ const duplicateIds = parsed.duplicates.get(pkg.name);
+ const isDuplicate = duplicateIds !== undefined && duplicateIds.length > 1;
+
+ const node: GraphNode = {
+ id: pkg.id,
+ name: pkg.name,
+ version: pkg.version,
+ diskSize: pkg.diskSize,
+ license: pkg.license,
+ description: pkg.description,
+ isHoisted: pkg.isHoisted,
+ isDuplicate,
+ duplicateOf: isDuplicate ? duplicateIds.filter(did => did !== id) : undefined,
+ dependents: [],
+ dependencyCount: 0,
+ };
+
+ nodes.set(id, node);
+
+ // Index by name
+ if (!nodesByName.has(pkg.name)) {
+ nodesByName.set(pkg.name, []);
+ }
+ nodesByName.get(pkg.name)!.push(id);
+ }
+
+ // Build edges and count dependencies
+ for (const [id, pkg] of parsed.packages) {
+ const node = nodes.get(id)!;
+
+ // Process all dependency types
+ const depTypes: Array<[Record, "prod" | "dev" | "peer" | "optional"]> = [
+ [pkg.dependencies, "prod"],
+ [pkg.devDependencies, "dev"],
+ [pkg.peerDependencies, "peer"],
+ [pkg.optionalDependencies, "optional"],
+ ];
+
+ for (const [deps, type] of depTypes) {
+ for (const [depName, versionRange] of Object.entries(deps)) {
+ node.dependencyCount++;
+
+ // Try to resolve the target package
+ const targetIds = nodesByName.get(depName);
+ const targetId = targetIds?.[0]; // Use first match (could be smarter with semver)
+
+ const edge: GraphEdge = {
+ id: `${id}->${depName}`,
+ source: id,
+ target: depName,
+ targetId,
+ versionRange,
+ type,
+ };
+
+ edges.push(edge);
+
+ // Add to dependents list
+ if (targetId) {
+ const targetNode = nodes.get(targetId);
+ if (targetNode) {
+ targetNode.dependents.push(id);
+ }
+ }
+ }
+ }
+ }
+
+ // Calculate stats
+ let totalSize = 0;
+ let largestPackage = { id: "", size: 0 };
+ let mostDependents = { id: "", count: 0 };
+
+ for (const [id, node] of nodes) {
+ totalSize += node.diskSize;
+
+ if (node.diskSize > largestPackage.size) {
+ largestPackage = { id, size: node.diskSize };
+ }
+
+ if (node.dependents.length > mostDependents.count) {
+ mostDependents = { id, count: node.dependents.length };
+ }
+ }
+
+ const stats = {
+ totalPackages: nodes.size,
+ totalDuplicates: parsed.duplicates.size,
+ totalSize,
+ largestPackage,
+ mostDependents,
+ };
+
+ return {
+ nodes,
+ edges,
+ nodesByName,
+ stats,
+ };
+}
+
+/**
+ * Convert graph to JSON-serializable format for context
+ */
+export function graphToContext(graph: DependencyGraph) {
+ return {
+ packages: Object.fromEntries(graph.nodes),
+ edges: graph.edges,
+ packagesByName: Object.fromEntries(graph.nodesByName),
+ stats: graph.stats,
+ };
+}
+
+/**
+ * Convert graph to force-graph-3d format
+ */
+export function graphToForceGraph(graph: DependencyGraph) {
+ const nodes = Array.from(graph.nodes.values()).map(node => ({
+ id: node.id,
+ name: node.name,
+ version: node.version,
+ diskSize: node.diskSize,
+ isDuplicate: node.isDuplicate,
+ isHoisted: node.isHoisted,
+ dependents: node.dependents.length,
+ }));
+
+ const links = graph.edges
+ .filter(edge => edge.targetId) // Only include edges with resolved targets
+ .map(edge => ({
+ source: edge.source,
+ target: edge.targetId!,
+ type: edge.type,
+ versionRange: edge.versionRange,
+ }));
+
+ return { nodes, links };
+}
diff --git a/examples/node-modules-viz/lib/parse-node-modules.ts b/examples/node-modules-viz/lib/parse-node-modules.ts
new file mode 100644
index 0000000..1155444
--- /dev/null
+++ b/examples/node-modules-viz/lib/parse-node-modules.ts
@@ -0,0 +1,219 @@
+/**
+ * Parse node_modules directory into a structured graph
+ *
+ * Walks the node_modules tree, reads package.json files,
+ * calculates disk sizes, and detects duplicates.
+ */
+
+import { readFileSync, readdirSync, statSync, existsSync } from "node:fs";
+import { join, relative } from "node:path";
+
+export interface PackageInfo {
+ id: string; // "lodash@4.17.21"
+ name: string; // "lodash"
+ version: string; // "4.17.21"
+ path: string; // relative path from target root
+ diskSize: number; // bytes on disk
+ license?: string;
+ description?: string;
+ isHoisted: boolean; // true if at top-level node_modules
+ dependencies: Record; // prod deps
+ devDependencies: Record; // dev deps
+ peerDependencies: Record; // peer deps
+ optionalDependencies: Record; // optional deps
+}
+
+export interface ParsedNodeModules {
+ packages: Map;
+ duplicates: Map; // name -> [id1, id2, ...]
+ rootPath: string;
+}
+
+/**
+ * Calculate directory size recursively
+ */
+function getDirSize(dirPath: string): number {
+ let totalSize = 0;
+
+ try {
+ const items = readdirSync(dirPath);
+
+ for (const item of items) {
+ const itemPath = join(dirPath, item);
+ const stats = statSync(itemPath);
+
+ if (stats.isDirectory()) {
+ totalSize += getDirSize(itemPath);
+ } else {
+ totalSize += stats.size;
+ }
+ }
+ } catch (err) {
+ // Ignore permission errors, symlink issues, etc.
+ }
+
+ return totalSize;
+}
+
+/**
+ * Parse a single package.json file
+ */
+function parsePackageJson(pkgPath: string, relativePath: string, isHoisted: boolean): PackageInfo | null {
+ try {
+ const pkgJsonPath = join(pkgPath, "package.json");
+
+ if (!existsSync(pkgJsonPath)) {
+ return null;
+ }
+
+ const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
+ const name = pkgJson.name as string;
+ const version = pkgJson.version as string;
+
+ if (!name || !version) {
+ return null;
+ }
+
+ const id = `${name}@${version}`;
+ const diskSize = getDirSize(pkgPath);
+
+ return {
+ id,
+ name,
+ version,
+ path: relativePath,
+ diskSize,
+ license: pkgJson.license,
+ description: pkgJson.description,
+ isHoisted,
+ dependencies: pkgJson.dependencies || {},
+ devDependencies: pkgJson.devDependencies || {},
+ peerDependencies: pkgJson.peerDependencies || {},
+ optionalDependencies: pkgJson.optionalDependencies || {},
+ };
+ } catch (err) {
+ console.warn(`Failed to parse ${pkgPath}:`, err);
+ return null;
+ }
+}
+
+/**
+ * Recursively walk node_modules directory
+ */
+function walkNodeModules(
+ nodeModulesPath: string,
+ rootPath: string,
+ packages: Map,
+ isTopLevel: boolean = true
+): void {
+ if (!existsSync(nodeModulesPath)) {
+ return;
+ }
+
+ try {
+ const items = readdirSync(nodeModulesPath);
+
+ for (const item of items) {
+ // Skip hidden files and .bin
+ if (item.startsWith(".")) {
+ continue;
+ }
+
+ const itemPath = join(nodeModulesPath, item);
+ const stats = statSync(itemPath);
+
+ if (!stats.isDirectory()) {
+ continue;
+ }
+
+ // Handle scoped packages (@org/package)
+ if (item.startsWith("@")) {
+ const scopedItems = readdirSync(itemPath);
+ for (const scopedItem of scopedItems) {
+ const scopedPath = join(itemPath, scopedItem);
+ const scopedStats = statSync(scopedPath);
+
+ if (scopedStats.isDirectory()) {
+ const relativePath = relative(rootPath, scopedPath);
+ const pkgInfo = parsePackageJson(scopedPath, relativePath, isTopLevel);
+
+ if (pkgInfo) {
+ packages.set(pkgInfo.id, pkgInfo);
+
+ // Recursively check for nested node_modules
+ const nestedNodeModules = join(scopedPath, "node_modules");
+ if (existsSync(nestedNodeModules)) {
+ walkNodeModules(nestedNodeModules, rootPath, packages, false);
+ }
+ }
+ }
+ }
+ } else {
+ // Regular package
+ const relativePath = relative(rootPath, itemPath);
+ const pkgInfo = parsePackageJson(itemPath, relativePath, isTopLevel);
+
+ if (pkgInfo) {
+ packages.set(pkgInfo.id, pkgInfo);
+
+ // Recursively check for nested node_modules
+ const nestedNodeModules = join(itemPath, "node_modules");
+ if (existsSync(nestedNodeModules)) {
+ walkNodeModules(nestedNodeModules, rootPath, packages, false);
+ }
+ }
+ }
+ }
+ } catch (err) {
+ console.warn(`Failed to walk ${nodeModulesPath}:`, err);
+ }
+}
+
+/**
+ * Detect duplicate packages (same name, different versions)
+ */
+function detectDuplicates(packages: Map): Map {
+ const byName = new Map();
+
+ for (const [id, pkg] of packages) {
+ if (!byName.has(pkg.name)) {
+ byName.set(pkg.name, []);
+ }
+ byName.get(pkg.name)!.push(id);
+ }
+
+ // Filter to only packages with multiple versions
+ const duplicates = new Map();
+ for (const [name, ids] of byName) {
+ if (ids.length > 1) {
+ duplicates.set(name, ids);
+ }
+ }
+
+ return duplicates;
+}
+
+/**
+ * Parse node_modules directory
+ *
+ * @param targetPath - Path to project root (containing node_modules)
+ * @returns Parsed node_modules data
+ */
+export function parseNodeModules(targetPath: string): ParsedNodeModules {
+ const nodeModulesPath = join(targetPath, "node_modules");
+ const packages = new Map();
+
+ console.log(`Parsing node_modules at: ${nodeModulesPath}`);
+
+ walkNodeModules(nodeModulesPath, targetPath, packages);
+
+ const duplicates = detectDuplicates(packages);
+
+ console.log(`Found ${packages.size} packages, ${duplicates.size} with duplicates`);
+
+ return {
+ packages,
+ duplicates,
+ rootPath: targetPath,
+ };
+}
diff --git a/examples/node-modules-viz/lib/tracked-graph.ts b/examples/node-modules-viz/lib/tracked-graph.ts
new file mode 100644
index 0000000..b3a98af
--- /dev/null
+++ b/examples/node-modules-viz/lib/tracked-graph.ts
@@ -0,0 +1,176 @@
+/**
+ * Tracked Graph - Proxy wrapper for access tracking
+ *
+ * Wraps the graph context in recursive Proxies to emit events
+ * whenever the RLLM-generated code accesses properties.
+ * This enables real-time visualization of graph traversal.
+ */
+
+export interface AccessEvent {
+ type: "node_access" | "edge_access" | "property_access";
+ path: string[]; // e.g., ["packages", "lodash@4.17.21", "diskSize"]
+ nodeId?: string; // extracted node ID if applicable
+ timestamp: number;
+}
+
+export type AccessCallback = (event: AccessEvent) => void;
+
+/**
+ * Create a tracked graph that emits events on property access
+ */
+export function createTrackedGraph(
+ target: T,
+ onAccess: AccessCallback,
+ path: string[] = []
+): T {
+ return new Proxy(target, {
+ get(obj: any, prop: string | symbol): any {
+ // Skip symbol properties and internal properties
+ if (typeof prop === "symbol" || prop.startsWith("__")) {
+ return obj[prop];
+ }
+
+ const propStr = String(prop);
+ const currentPath = [...path, propStr];
+
+ // Emit access event
+ const event: AccessEvent = {
+ type: detectAccessType(currentPath),
+ path: currentPath,
+ nodeId: extractNodeId(currentPath),
+ timestamp: Date.now(),
+ };
+
+ onAccess(event);
+
+ const value = obj[prop];
+
+ // Recursively wrap objects and arrays
+ if (value !== null && typeof value === "object") {
+ // Don't wrap functions or special objects
+ if (typeof value === "function") {
+ return value;
+ }
+
+ return createTrackedGraph(value, onAccess, currentPath);
+ }
+
+ return value;
+ },
+
+ // Also track property enumeration (for Object.keys, for...in, etc.)
+ ownKeys(obj: any): (string | symbol)[] {
+ const keys = Reflect.ownKeys(obj);
+
+ // Emit enumeration event
+ onAccess({
+ type: "property_access",
+ path: [...path, "[keys]"],
+ timestamp: Date.now(),
+ });
+
+ return keys;
+ },
+
+ // Track has checks (if ("prop" in obj))
+ has(obj: any, prop: string | symbol): boolean {
+ if (typeof prop !== "symbol") {
+ onAccess({
+ type: "property_access",
+ path: [...path, `[has:${String(prop)}]`],
+ timestamp: Date.now(),
+ });
+ }
+ return prop in obj;
+ },
+ });
+}
+
+/**
+ * Detect the type of access based on the path
+ */
+function detectAccessType(path: string[]): AccessEvent["type"] {
+ if (path.length === 0) {
+ return "property_access";
+ }
+
+ // Check if accessing packages
+ if (path[0] === "packages" && path.length >= 2) {
+ return "node_access";
+ }
+
+ // Check if accessing edges
+ if (path[0] === "edges") {
+ return "edge_access";
+ }
+
+ return "property_access";
+}
+
+/**
+ * Extract node ID from access path if applicable
+ */
+function extractNodeId(path: string[]): string | undefined {
+ // Pattern: ["packages", "lodash@4.17.21", ...]
+ if (path[0] === "packages" && path.length >= 2) {
+ const nodeId = path[1];
+ // Check if it looks like a package ID (has @ and version)
+ if (nodeId && nodeId.includes("@")) {
+ return nodeId;
+ }
+ }
+
+ return undefined;
+}
+
+/**
+ * Debounce function to avoid flooding with too many events
+ */
+export function createDebouncedCallback(
+ callback: AccessCallback,
+ delayMs: number = 50
+): AccessCallback {
+ let timeout: NodeJS.Timeout | null = null;
+ let pendingEvents: AccessEvent[] = [];
+
+ return (event: AccessEvent) => {
+ pendingEvents.push(event);
+
+ if (timeout) {
+ clearTimeout(timeout);
+ }
+
+ timeout = setTimeout(() => {
+ // Deduplicate events by path
+ const uniqueEvents = new Map();
+ for (const evt of pendingEvents) {
+ const key = evt.path.join(".");
+ if (!uniqueEvents.has(key)) {
+ uniqueEvents.set(key, evt);
+ }
+ }
+
+ // Emit unique events
+ for (const evt of uniqueEvents.values()) {
+ callback(evt);
+ }
+
+ pendingEvents = [];
+ timeout = null;
+ }, delayMs);
+ };
+}
+
+/**
+ * Filter to only emit node/edge access events (ignore property access)
+ */
+export function createFilteredCallback(
+ callback: AccessCallback,
+ types: AccessEvent["type"][] = ["node_access", "edge_access"]
+): AccessCallback {
+ return (event: AccessEvent) => {
+ if (types.includes(event.type)) {
+ callback(event);
+ }
+ };
+}
diff --git a/examples/node-modules-viz/package.json b/examples/node-modules-viz/package.json
new file mode 100644
index 0000000..351e586
--- /dev/null
+++ b/examples/node-modules-viz/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "node-modules-viz",
+ "version": "1.0.0",
+ "description": "Interactive 3D visualization of node_modules dependencies with RLLM",
+ "type": "module",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "server": "node server.mjs",
+ "start": "./start.sh"
+ },
+ "dependencies": {
+ "@pnpm/lockfile-file": "^9.1.3",
+ "dotenv": "^16.4.5",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-force-graph-3d": "^1.26.0",
+ "rllm": "workspace:*",
+ "three": "^0.170.0",
+ "ws": "^8.16.0",
+ "zod": "^3.25.76"
+ },
+ "devDependencies": {
+ "@types/node": "^20.11.0",
+ "@types/react": "^18.2.0",
+ "@types/react-dom": "^18.2.0",
+ "@types/ws": "^8.5.10",
+ "@vitejs/plugin-react": "^4.2.0",
+ "concurrently": "^8.2.2",
+ "tsx": "^4.7.0",
+ "typescript": "^5.3.3",
+ "vite": "^5.0.0"
+ }
+}
diff --git a/examples/node-modules-viz/run.ts b/examples/node-modules-viz/run.ts
new file mode 100644
index 0000000..dfd7ef9
--- /dev/null
+++ b/examples/node-modules-viz/run.ts
@@ -0,0 +1,126 @@
+#!/usr/bin/env node
+/**
+ * node_modules Graph Analyzer - Main Entry Point
+ */
+
+import "dotenv/config";
+import { WebSocketServer, WebSocket } from "ws";
+import { parseNodeModules } from "./lib/parse-node-modules.js";
+import { buildGraph, graphToContext, graphToForceGraph } from "./lib/build-graph.js";
+import { createTrackedGraph, createFilteredCallback } from "./lib/tracked-graph.js";
+import { createRLLM } from "rllm";
+import { resolve } from "path";
+
+const PORT = 4242;
+
+// Parse command line arguments
+const args = process.argv.slice(2);
+const targetArg = args.find(arg => arg.startsWith("--target="));
+const targetPath = targetArg
+ ? targetArg.split("=")[1]!
+ : resolve(process.cwd(), "../../");
+
+console.log("🔍 node_modules Graph Analyzer");
+console.log(`Target: ${targetPath}\n`);
+
+// Step 1: Parse node_modules
+console.log("📦 Parsing node_modules...");
+const parsed = parseNodeModules(targetPath);
+
+if (parsed.packages.size === 0) {
+ console.error("❌ No packages found.");
+ process.exit(1);
+}
+
+// Step 2: Build graph
+console.log("🔗 Building dependency graph...");
+const graph = buildGraph(parsed);
+const forceGraphData = graphToForceGraph(graph);
+const context = graphToContext(graph);
+
+console.log(`📊 Found ${graph.stats.totalPackages} packages\n`);
+
+// Step 3: Create WebSocket server
+console.log(`🌐 Starting WebSocket server on port ${PORT}...`);
+
+const wss = new WebSocketServer({ port: PORT });
+const clients = new Set();
+
+// Initialize RLLM
+const rlm = createRLLM({
+ model: process.env.MODEL || "gpt-4o-mini",
+ provider: "openai",
+ verbose: true,
+});
+
+wss.on("listening", () => {
+ console.log(`🚀 WebSocket server running on ws://localhost:${PORT}`);
+ console.log("✨ Ready! Waiting for frontend to connect...\n");
+});
+
+wss.on("error", (err) => {
+ console.error("❌ Server error:", err);
+ process.exit(1);
+});
+
+wss.on("connection", (ws) => {
+ console.log("✅ Client connected");
+ clients.add(ws);
+
+ // Send graph data immediately
+ ws.send(JSON.stringify({
+ type: "graph_data",
+ data: { graph: forceGraphData, stats: graph.stats }
+ }));
+
+ ws.on("message", async (data) => {
+ try {
+ const msg = JSON.parse(data.toString());
+
+ if (msg.type === "run_query") {
+ console.log(`\n🤖 Running query: ${msg.query}`);
+ broadcast({ type: "status", status: "running" });
+
+ const trackedContext = createTrackedGraph(
+ context,
+ createFilteredCallback((event) => {
+ broadcast({ type: "access_event", data: event });
+ })
+ );
+
+ try {
+ const result = await rlm.completion(msg.query, { context: trackedContext });
+ console.log("✅ Query complete");
+ broadcast({ type: "result", data: result });
+ broadcast({ type: "status", status: "completed" });
+ } catch (err) {
+ console.error("❌ Query error:", err);
+ broadcast({ type: "status", status: "error", data: { error: String(err) } });
+ }
+ }
+ } catch (err) {
+ console.error("Failed to parse message:", err);
+ }
+ });
+
+ ws.on("close", () => {
+ console.log("Client disconnected");
+ clients.delete(ws);
+ });
+});
+
+function broadcast(msg: any) {
+ const data = JSON.stringify(msg);
+ for (const client of clients) {
+ if (client.readyState === WebSocket.OPEN) {
+ client.send(data);
+ }
+ }
+}
+
+// Keep process alive
+process.on("SIGINT", () => {
+ console.log("\n👋 Shutting down...");
+ wss.close();
+ process.exit(0);
+});
diff --git a/examples/node-modules-viz/server.mjs b/examples/node-modules-viz/server.mjs
new file mode 100644
index 0000000..ecf7d74
--- /dev/null
+++ b/examples/node-modules-viz/server.mjs
@@ -0,0 +1,293 @@
+/**
+ * node_modules Graph Analyzer - WebSocket Server
+ * Parses pnpm-lock.yaml and runs RLLM queries
+ */
+
+import 'dotenv/config';
+import { WebSocketServer } from 'ws';
+import { readWantedLockfile } from '@pnpm/lockfile-file';
+import { resolve } from 'path';
+import { createRLLM } from 'rllm';
+import { z } from 'zod';
+
+// Define Zod schema for the dependency context
+const PackageSchema = z.object({
+ id: z.string().describe('Package identifier in format "name@version"'),
+ name: z.string().describe('Package name'),
+ version: z.string().describe('Semver version'),
+ val: z.number().describe('Node size for visualization'),
+ color: z.string().describe('Node color for visualization'),
+});
+
+const DependencySchema = z.object({
+ from: z.string().describe('Source package id'),
+ to: z.string().describe('Target package id (dependency)'),
+});
+
+const StatsSchema = z.object({
+ totalPackages: z.number().describe('Total number of packages'),
+ totalLinks: z.number().describe('Total number of dependency links'),
+ directDeps: z.number().describe('Number of direct dependencies'),
+});
+
+const ContextSchema = z.object({
+ packages: z.record(z.string(), PackageSchema).describe('Map of package id to package info'),
+ dependencies: z.array(DependencySchema).describe('List of dependency relationships'),
+ stats: StatsSchema.describe('Summary statistics'),
+});
+
+const PORT = 4242;
+const TARGET = resolve(process.cwd(), '../../');
+
+console.log('🔍 node_modules Graph Analyzer');
+console.log(`Target: ${TARGET}\n`);
+
+// Check for API key based on provider
+const provider = process.env.PROVIDER || 'gemini';
+const apiKeyName = {
+ gemini: 'GEMINI_API_KEY or GOOGLE_API_KEY',
+ openai: 'OPENAI_API_KEY',
+ anthropic: 'ANTHROPIC_API_KEY',
+ openrouter: 'OPENROUTER_API_KEY',
+}[provider] || 'API key';
+
+const hasApiKey = provider === 'gemini'
+ ? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY)
+ : process.env[`${provider.toUpperCase()}_API_KEY`];
+
+if (!hasApiKey) {
+ console.warn(`⚠️ ${apiKeyName} not set - queries will not work`);
+}
+
+// Initialize RLLM
+const rlm = createRLLM({
+ model: process.env.MODEL || 'gemini-3-flash-preview',
+ provider: process.env.PROVIDER || 'gemini',
+ verbose: true,
+});
+
+// Parse pnpm lockfile
+console.log('📦 Parsing pnpm-lock.yaml...');
+
+const lockfile = await readWantedLockfile(TARGET, { ignoreIncompatible: true });
+
+if (!lockfile) {
+ console.error('❌ No pnpm-lock.yaml found');
+ process.exit(1);
+}
+
+// Build graph from lockfile
+const nodes = new Map();
+const links = [];
+
+// Get root dependencies from importers
+const rootImporter = lockfile.importers?.['.'];
+const rootDeps = {
+ ...(rootImporter?.dependencies || {}),
+ ...(rootImporter?.devDependencies || {})
+};
+
+// Add root node
+nodes.set('root', {
+ id: 'root',
+ name: 'root',
+ version: '',
+ val: 20,
+ color: '#ff6b6b'
+});
+
+// Process all packages from lockfile
+const packages = lockfile.packages || {};
+
+for (const [pkgPath, pkgInfo] of Object.entries(packages)) {
+ const match = pkgPath.match(/^\/?(.+)@([^@]+)$/);
+ if (!match) continue;
+
+ const [, name, version] = match;
+ const id = `${name}@${version}`;
+
+ if (nodes.has(id)) continue;
+
+ const isDirect = Object.values(rootDeps).some(dep => {
+ const depVersion = dep.version || dep;
+ return depVersion.includes(version) || depVersion === version;
+ });
+
+ nodes.set(id, {
+ id,
+ name,
+ version,
+ val: isDirect ? 10 : 5,
+ color: isDirect ? '#4ecdc4' : '#6c9bcf'
+ });
+
+ // Add edges for dependencies
+ const deps = pkgInfo.dependencies || {};
+ for (const [depName, depVersion] of Object.entries(deps)) {
+ const depId = `${depName}@${depVersion}`;
+ links.push({ source: id, target: depId });
+ }
+}
+
+// Add links from root to direct dependencies
+for (const [depName, depInfo] of Object.entries(rootDeps)) {
+ const depVersion = depInfo.version || depInfo;
+ const cleanVersion = depVersion.replace(/\(.*\)/, '');
+ const depId = `${depName}@${cleanVersion}`;
+
+ if (nodes.has(depId)) {
+ links.push({ source: 'root', target: depId });
+ }
+}
+
+// Filter links to only include existing nodes
+const validLinks = links.filter(link =>
+ nodes.has(link.source) && nodes.has(link.target)
+);
+
+const graphData = {
+ nodes: Array.from(nodes.values()),
+ links: validLinks
+};
+
+const stats = {
+ totalPackages: nodes.size,
+ totalLinks: validLinks.length,
+ directDeps: Object.keys(rootDeps).length
+};
+
+// Build context object for RLLM
+const rawContext = {
+ packages: Object.fromEntries(nodes),
+ dependencies: validLinks.map(l => ({ from: l.source, to: l.target })),
+ stats
+};
+
+// Create tracked context with Proxy to emit access events
+function createTrackedContext(target, onAccess, path = []) {
+ if (target === null || typeof target !== 'object') {
+ return target;
+ }
+
+ return new Proxy(target, {
+ get(obj, prop) {
+ if (typeof prop === 'symbol' || String(prop).startsWith('__')) {
+ return obj[prop];
+ }
+
+ const propStr = String(prop);
+ const currentPath = [...path, propStr];
+
+ // Emit access event for packages
+ if (currentPath[0] === 'packages' && currentPath.length >= 2) {
+ const nodeId = currentPath[1];
+ console.log(`📍 Accessing: ${nodeId}`);
+ onAccess({ type: 'node_access', nodeId, path: currentPath });
+ }
+
+ const value = obj[prop];
+
+ // Recursively wrap objects
+ if (value !== null && typeof value === 'object' && typeof value !== 'function') {
+ return createTrackedContext(value, onAccess, currentPath);
+ }
+
+ return value;
+ },
+
+ ownKeys(obj) {
+ console.log(`📍 Enumerating: ${path.join('.')}`);
+ return Reflect.ownKeys(obj);
+ }
+ });
+}
+
+console.log(`📊 Found ${stats.totalPackages} packages, ${stats.totalLinks} dependency links\n`);
+
+// Create WebSocket server
+console.log(`🌐 Starting WebSocket server on port ${PORT}...`);
+const wss = new WebSocketServer({ port: PORT });
+const clients = new Set();
+
+function broadcast(msg) {
+ const data = JSON.stringify(msg);
+ for (const client of clients) {
+ if (client.readyState === 1) { // WebSocket.OPEN
+ client.send(data);
+ }
+ }
+}
+
+wss.on('listening', () => {
+ console.log(`🚀 Server running at ws://localhost:${PORT}`);
+ console.log('✨ Ready! Open http://localhost:3000\n');
+});
+
+wss.on('error', (err) => {
+ console.error('❌ Server error:', err.message);
+});
+
+wss.on('connection', (ws) => {
+ console.log('✅ Client connected');
+ clients.add(ws);
+
+ // Send graph data
+ ws.send(JSON.stringify({
+ type: 'graph_data',
+ data: { graph: graphData, stats }
+ }));
+
+ ws.on('message', async (data) => {
+ try {
+ const msg = JSON.parse(data.toString());
+
+ if (msg.type === 'run_query') {
+ console.log(`\n🤖 Running query: ${msg.query}`);
+ broadcast({ type: 'status', status: 'running' });
+
+ // Create tracked context that emits access events
+ const trackedContext = createTrackedContext(rawContext, (event) => {
+ if (event.nodeId) {
+ broadcast({ type: 'access_event', data: event });
+ }
+ });
+
+ try {
+ const result = await rlm.completion(msg.query, {
+ context: trackedContext,
+ contextSchema: ContextSchema,
+ onEvent: (event) => {
+ // Broadcast RLLM events to frontend for the activity log
+ broadcast({ type: 'rllm_event', data: event });
+ }
+ });
+ console.log('✅ Query complete');
+ console.log('Result:', result);
+ broadcast({ type: 'result', data: result });
+ broadcast({ type: 'status', status: 'completed' });
+ } catch (err) {
+ console.error('❌ Query error:', err.message);
+ broadcast({
+ type: 'result',
+ data: { answer: { message: `Error: ${err.message}`, data: null } }
+ });
+ broadcast({ type: 'status', status: 'error' });
+ }
+ }
+ } catch (err) {
+ console.error('Failed to parse message:', err);
+ }
+ });
+
+ ws.on('close', () => {
+ console.log('Client disconnected');
+ clients.delete(ws);
+ });
+});
+
+// Keep alive
+process.on('SIGINT', () => {
+ console.log('\n👋 Shutting down...');
+ wss.close();
+ process.exit(0);
+});
diff --git a/examples/node-modules-viz/src/App.css b/examples/node-modules-viz/src/App.css
new file mode 100644
index 0000000..7fecbc7
--- /dev/null
+++ b/examples/node-modules-viz/src/App.css
@@ -0,0 +1,439 @@
+.app {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+.header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ height: 60px;
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border-bottom: 1px solid #30363d;
+ display: flex;
+ align-items: center;
+ padding: 0 20px;
+ z-index: 1000;
+ gap: 20px;
+}
+
+.header h1 {
+ font-size: 18px;
+ font-weight: 600;
+ color: #58a6ff;
+ margin-right: auto;
+}
+
+.status {
+ padding: 6px 12px;
+ border-radius: 6px;
+ font-size: 13px;
+ background: rgba(88, 166, 255, 0.1);
+ color: #58a6ff;
+ border: 1px solid rgba(88, 166, 255, 0.3);
+}
+
+.status.connected {
+ background: rgba(63, 185, 80, 0.1);
+ color: #3fb950;
+ border-color: rgba(63, 185, 80, 0.3);
+}
+
+.status.error {
+ background: rgba(248, 81, 73, 0.1);
+ color: #f85149;
+ border-color: rgba(248, 81, 73, 0.3);
+}
+
+.status.running {
+ background: rgba(210, 153, 34, 0.1);
+ color: #d29922;
+ border-color: rgba(210, 153, 34, 0.3);
+}
+
+.query-panel {
+ position: fixed;
+ top: 80px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border: 1px solid #30363d;
+ border-radius: 8px;
+ padding: 16px;
+ z-index: 100;
+ max-width: 800px;
+ width: calc(100% - 40px);
+}
+
+.query-input-container {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 12px;
+}
+
+.query-input-container input {
+ flex: 1;
+ background: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 6px;
+ padding: 10px 12px;
+ color: #c9d1d9;
+ font-size: 14px;
+ font-family: inherit;
+}
+
+.query-input-container input:focus {
+ outline: none;
+ border-color: #58a6ff;
+}
+
+.query-input-container button {
+ background: #238636;
+ color: white;
+ border: none;
+ border-radius: 6px;
+ padding: 10px 20px;
+ font-size: 14px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+
+.query-input-container button:hover:not(:disabled) {
+ background: #2ea043;
+}
+
+.query-input-container button:disabled {
+ background: #30363d;
+ color: #8b949e;
+ cursor: not-allowed;
+}
+
+.example-queries {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.example-query-btn {
+ background: rgba(88, 166, 255, 0.1);
+ color: #58a6ff;
+ border: 1px solid rgba(88, 166, 255, 0.3);
+ border-radius: 6px;
+ padding: 6px 12px;
+ font-size: 12px;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.example-query-btn:hover:not(:disabled) {
+ background: rgba(88, 166, 255, 0.2);
+ border-color: rgba(88, 166, 255, 0.5);
+}
+
+.example-query-btn:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+.stats-panel {
+ position: fixed;
+ top: 80px;
+ left: 20px;
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border: 1px solid #30363d;
+ border-radius: 8px;
+ padding: 16px;
+ min-width: 250px;
+ z-index: 100;
+}
+
+.stat-row {
+ display: flex;
+ justify-content: space-between;
+ padding: 8px 0;
+ border-bottom: 1px solid #30363d;
+ font-size: 13px;
+}
+
+.stat-row:last-child {
+ border-bottom: none;
+}
+
+.stat-label {
+ color: #8b949e;
+}
+
+.stat-value {
+ color: #c9d1d9;
+ font-weight: 600;
+}
+
+.stat-value.highlight {
+ color: #58a6ff;
+}
+
+.stat-value.warning {
+ color: #d29922;
+}
+
+.legend {
+ position: fixed;
+ top: 80px;
+ right: 20px;
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border: 1px solid #30363d;
+ border-radius: 8px;
+ padding: 16px;
+ z-index: 100;
+}
+
+.legend-title {
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 12px;
+ color: #c9d1d9;
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 8px;
+ font-size: 12px;
+ color: #8b949e;
+}
+
+.legend-color {
+ width: 16px;
+ height: 16px;
+ border-radius: 50%;
+}
+
+.result-panel {
+ position: fixed;
+ bottom: 20px;
+ left: 20px;
+ right: 20px;
+ max-height: 200px;
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border: 1px solid #30363d;
+ border-radius: 8px;
+ padding: 16px;
+ z-index: 100;
+ overflow-y: auto;
+}
+
+.result-panel pre {
+ font-size: 14px;
+ line-height: 1.6;
+ white-space: pre-wrap;
+ word-wrap: break-word;
+ margin: 0;
+}
+
+
+.loading {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ text-align: center;
+ z-index: 1000;
+}
+
+.spinner {
+ border: 3px solid #30363d;
+ border-top: 3px solid #58a6ff;
+ border-radius: 50%;
+ width: 40px;
+ height: 40px;
+ animation: spin 1s linear infinite;
+ margin: 0 auto 16px;
+}
+
+@keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+}
+
+/* Activity Log */
+.activity-log {
+ position: fixed;
+ bottom: 20px;
+ right: 20px;
+ width: 400px;
+ max-height: 350px;
+ background: rgba(22, 27, 34, 0.95);
+ backdrop-filter: blur(10px);
+ border: 1px solid #30363d;
+ border-radius: 8px;
+ z-index: 100;
+ display: flex;
+ flex-direction: column;
+}
+
+.activity-log-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 12px 16px;
+ border-bottom: 1px solid #30363d;
+ font-size: 14px;
+ font-weight: 600;
+ color: #c9d1d9;
+}
+
+.clear-log-btn {
+ background: transparent;
+ border: 1px solid #30363d;
+ color: #8b949e;
+ padding: 4px 8px;
+ border-radius: 4px;
+ font-size: 11px;
+ cursor: pointer;
+}
+
+.clear-log-btn:hover {
+ background: rgba(88, 166, 255, 0.1);
+ border-color: #58a6ff;
+ color: #58a6ff;
+}
+
+.activity-log-content {
+ flex: 1;
+ overflow-y: auto;
+ padding: 8px;
+}
+
+.activity-log-empty {
+ color: #8b949e;
+ text-align: center;
+ padding: 20px;
+ font-size: 13px;
+}
+
+.activity-entry {
+ margin-bottom: 4px;
+ border-radius: 6px;
+ background: rgba(48, 54, 61, 0.5);
+ overflow: hidden;
+}
+
+.activity-entry-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 8px 12px;
+ cursor: pointer;
+ font-size: 13px;
+ color: #c9d1d9;
+}
+
+.activity-entry-header:hover {
+ background: rgba(88, 166, 255, 0.1);
+}
+
+.activity-icon {
+ font-size: 14px;
+}
+
+.activity-label {
+ flex: 1;
+}
+
+.activity-iteration {
+ font-size: 11px;
+ color: #8b949e;
+ background: rgba(88, 166, 255, 0.2);
+ padding: 2px 6px;
+ border-radius: 4px;
+}
+
+.activity-expand {
+ color: #8b949e;
+ font-size: 10px;
+}
+
+.activity-entry-content {
+ padding: 0 12px 12px;
+ border-top: 1px solid #30363d;
+}
+
+.activity-code-label {
+ font-size: 11px;
+ color: #8b949e;
+ margin: 8px 0 4px;
+ text-transform: uppercase;
+}
+
+.activity-prompt pre {
+ background: rgba(210, 153, 34, 0.1);
+ border: 1px solid rgba(210, 153, 34, 0.3);
+ border-radius: 4px;
+ padding: 8px;
+ margin: 0;
+ font-size: 11px;
+ line-height: 1.4;
+ overflow-x: auto;
+ max-height: 150px;
+ overflow-y: auto;
+ white-space: pre-wrap;
+ word-break: break-all;
+ color: #d29922;
+}
+
+.activity-code pre,
+.activity-response pre,
+.activity-output pre,
+.activity-answer pre {
+ background: #0d1117;
+ border: 1px solid #30363d;
+ border-radius: 4px;
+ padding: 8px;
+ margin: 0;
+ font-size: 11px;
+ line-height: 1.4;
+ overflow-x: auto;
+ max-height: 150px;
+ overflow-y: auto;
+ white-space: pre-wrap;
+ word-break: break-all;
+}
+
+.activity-error pre {
+ background: rgba(248, 81, 73, 0.1);
+ border-color: rgba(248, 81, 73, 0.3);
+ color: #f85149;
+}
+
+.activity-answer pre {
+ background: rgba(63, 185, 80, 0.1);
+ border-color: rgba(63, 185, 80, 0.3);
+ color: #3fb950;
+}
+
+/* Entry type colors */
+.activity-llm_query_start .activity-entry-header {
+ border-left: 3px solid #d29922;
+}
+
+.activity-llm_query_end .activity-entry-header {
+ border-left: 3px solid #58a6ff;
+}
+
+.activity-code_execution_start .activity-entry-header,
+.activity-code_execution_end .activity-entry-header {
+ border-left: 3px solid #a371f7;
+}
+
+.activity-final_answer .activity-entry-header {
+ border-left: 3px solid #3fb950;
+}
diff --git a/examples/node-modules-viz/src/App.tsx b/examples/node-modules-viz/src/App.tsx
new file mode 100644
index 0000000..fb85a2f
--- /dev/null
+++ b/examples/node-modules-viz/src/App.tsx
@@ -0,0 +1,537 @@
+import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
+import ForceGraph3D from 'react-force-graph-3d';
+import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass.js';
+import * as THREE from 'three';
+import './App.css';
+
+interface GraphData {
+ nodes: any[];
+ links: any[];
+}
+
+interface Stats {
+ totalPackages: number;
+ totalLinks: number;
+ directDeps: number;
+}
+
+interface ActivityLogEntry {
+ id: number;
+ type: string;
+ timestamp: number;
+ iteration?: number;
+ code?: string;
+ response?: string;
+ output?: string;
+ error?: string;
+ answer?: string;
+ prompt?: string;
+ expanded?: boolean;
+}
+
+const EXAMPLE_QUERIES = [
+ { label: '🔍 Find Duplicates', query: 'Which packages have multiple versions installed? List them with their sizes.' },
+ { label: '📊 Largest Packages', query: 'What are the 5 largest packages by disk size?' },
+ { label: '⭐ Most Popular', query: 'Which packages have the most dependents?' },
+ { label: '💾 Total Size', query: 'How much total disk space is used by all packages?' },
+ { label: '⚖️ MIT Licenses', query: 'List all packages with MIT license.' },
+ { label: '🔗 Dependency Path', query: 'Show the dependency chain from the root to typescript.' },
+];
+
+function App() {
+ const [ws, setWs] = useState(null);
+ const [status, setStatus] = useState('Connecting...');
+ const [statusClass, setStatusClass] = useState('');
+ const [graphData, setGraphData] = useState({ nodes: [], links: [] });
+ const [stats, setStats] = useState(null);
+ const [query, setQuery] = useState('');
+ const [result, setResult] = useState('');
+ const [showResult, setShowResult] = useState(false);
+ const [isRunning, setIsRunning] = useState(false);
+ const [activityLog, setActivityLog] = useState([]);
+ const [expandedEntries, setExpandedEntries] = useState>(new Set());
+ const activityLogRef = useRef(null);
+ const logIdCounter = useRef(0);
+
+ const fgRef = useRef();
+ const [highlightedNodes, setHighlightedNodes] = useState(new Set());
+ const accessQueue = useRef([]);
+ const isProcessingQueue = useRef(false);
+ const bloomPassAdded = useRef(false);
+
+ // Add bloom post-processing and lighting for realistic look
+ useEffect(() => {
+ if (fgRef.current && graphData.nodes.length > 0 && !bloomPassAdded.current) {
+ // Add bloom pass
+ const bloomPass = new UnrealBloomPass();
+ bloomPass.strength = 3;
+ bloomPass.radius = 1;
+ bloomPass.threshold = 0.7;
+ fgRef.current.postProcessingComposer().addPass(bloomPass);
+
+ // Add realistic lighting to the scene
+ const scene = fgRef.current.scene();
+
+ // Ambient light for base illumination
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
+ scene.add(ambientLight);
+
+ // Key light (main directional light)
+ const keyLight = new THREE.DirectionalLight(0xffffff, 1.0) as any;
+ keyLight.position.set(200, 200, 200);
+ scene.add(keyLight);
+
+ // Fill light (softer, from opposite side)
+ const fillLight = new THREE.DirectionalLight(0x8888ff, 0.5) as any;
+ fillLight.position.set(-200, 0, -200);
+ scene.add(fillLight);
+
+ // Rim light for edge highlights
+ const rimLight = new THREE.DirectionalLight(0xffffcc, 0.3) as any;
+ rimLight.position.set(0, -200, 200);
+ scene.add(rimLight);
+
+ bloomPassAdded.current = true;
+ }
+ }, [graphData.nodes.length]);
+
+ // Process access events with smooth delay
+ const processAccessQueue = useCallback(() => {
+ if (isProcessingQueue.current || accessQueue.current.length === 0) return;
+
+ isProcessingQueue.current = true;
+
+ const processNext = () => {
+ const nodeId = accessQueue.current.shift();
+ if (!nodeId) {
+ isProcessingQueue.current = false;
+ return;
+ }
+
+ // Highlight this node
+ setHighlightedNodes(prev => {
+ const next = new Set(prev);
+ next.add(nodeId);
+ return next;
+ });
+
+ // Clear highlight after a bit
+ setTimeout(() => {
+ setHighlightedNodes(prev => {
+ const next = new Set(prev);
+ next.delete(nodeId);
+ return next;
+ });
+ }, 300);
+
+ // Process next with delay (30ms for smooth stream effect)
+ if (accessQueue.current.length > 0) {
+ setTimeout(processNext, 60);
+ } else {
+ isProcessingQueue.current = false;
+ }
+ };
+
+ processNext();
+ }, []);
+
+ // WebSocket connection
+ useEffect(() => {
+ // Try to connect directly to port 3737, or fall back to same port if in dev container
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const port = import.meta.env.DEV ? '4242' : window.location.port;
+ const wsUrl = `${protocol}//${window.location.hostname}:${port}`;
+
+ console.log('Connecting to WebSocket:', wsUrl);
+ const websocket = new WebSocket(wsUrl);
+
+ websocket.onopen = () => {
+ console.log('WebSocket connected');
+ setStatus('🟢 Connected');
+ setStatusClass('connected');
+ };
+
+ websocket.onclose = () => {
+ console.log('WebSocket disconnected');
+ setStatus('🔴 Disconnected');
+ setStatusClass('error');
+ };
+
+ websocket.onerror = (error) => {
+ console.error('WebSocket error:', error);
+ setStatus('🔴 Error');
+ setStatusClass('error');
+ };
+
+ websocket.onmessage = (event) => {
+ const message = JSON.parse(event.data);
+ handleMessage(message);
+ };
+
+ setWs(websocket);
+
+ return () => websocket.close();
+ }, []);
+
+ const handleMessage = (message: any) => {
+ switch (message.type) {
+ case 'graph_data':
+ setGraphData(message.data.graph);
+ setStats(message.data.stats);
+ break;
+ case 'access_event':
+ if (message.data.nodeId) {
+ // Add to queue for smooth streaming effect
+ accessQueue.current.push(message.data.nodeId);
+ processAccessQueue();
+ }
+ break;
+ case 'status':
+ if (message.status === 'running') {
+ setIsRunning(true);
+ setStatus('🤖 Running Query...');
+ setStatusClass('running');
+ } else if (message.status === 'completed') {
+ setIsRunning(false);
+ setStatus('🟢 Connected');
+ setStatusClass('connected');
+ } else if (message.status === 'error') {
+ setIsRunning(false);
+ setStatus('🔴 Error');
+ setStatusClass('error');
+ }
+ break;
+ case 'result':
+ // answer is now { message: string, data?: unknown }
+ const answer = message.data.answer;
+ const displayResult = typeof answer === 'object' && answer?.message
+ ? answer.message
+ : String(answer);
+ setResult(displayResult);
+ setShowResult(true);
+ setTimeout(() => setShowResult(false), 10000);
+ break;
+ case 'rllm_event':
+ const event = message.data;
+ const entry: ActivityLogEntry = {
+ id: logIdCounter.current++,
+ type: event.type,
+ timestamp: event.timestamp,
+ iteration: event.iteration,
+ code: event.code,
+ response: event.response,
+ output: event.output,
+ error: event.error,
+ answer: event.answer,
+ prompt: event.prompt,
+ };
+ setActivityLog(prev => [...prev.slice(-50), entry]); // Keep last 50 entries
+ // Auto-scroll to bottom
+ setTimeout(() => {
+ activityLogRef.current?.scrollTo({ top: activityLogRef.current.scrollHeight, behavior: 'smooth' });
+ }, 50);
+ break;
+ }
+ };
+
+
+ const runQuery = () => {
+ if (!query.trim() || !ws || isRunning) return;
+ setActivityLog([]); // Clear log for new query
+ ws.send(JSON.stringify({ type: 'run_query', query }));
+ };
+
+ const toggleExpand = (id: number) => {
+ setExpandedEntries(prev => {
+ const next = new Set(prev);
+ if (next.has(id)) {
+ next.delete(id);
+ } else {
+ next.add(id);
+ }
+ return next;
+ });
+ };
+
+ const getEventIcon = (type: string) => {
+ switch (type) {
+ case 'iteration_start': return '🔄';
+ case 'llm_query_start': return '🤔';
+ case 'llm_query_end': return '💬';
+ case 'code_execution_start': return '⚡';
+ case 'code_execution_end': return '✅';
+ case 'final_answer': return '🎯';
+ default: return '📝';
+ }
+ };
+
+ const getEventLabel = (type: string) => {
+ switch (type) {
+ case 'iteration_start': return 'Iteration';
+ case 'llm_query_start': return 'Querying LLM...';
+ case 'llm_query_end': return 'LLM Response';
+ case 'code_execution_start': return 'Running Code';
+ case 'code_execution_end': return 'Code Complete';
+ case 'final_answer': return 'Final Answer';
+ default: return type;
+ }
+ };
+
+ const nodeColor = useCallback((node: any) => {
+ if (highlightedNodes.has(node.id)) return '#ffff00'; // Bright yellow for bloom
+ return node.color || '#58a6ff';
+ }, [highlightedNodes]);
+
+ const nodeVal = useCallback((node: any) => {
+ // Make highlighted nodes 3x bigger
+ const baseSize = node.val || 5;
+ return highlightedNodes.has(node.id) ? baseSize * 3 : baseSize;
+ }, [highlightedNodes]);
+
+ // Check if a link connects two highlighted nodes
+ const isLinkHighlighted = useCallback((link: any) => {
+ const sourceId = typeof link.source === 'object' ? link.source.id : link.source;
+ const targetId = typeof link.target === 'object' ? link.target.id : link.target;
+ return highlightedNodes.has(sourceId) && highlightedNodes.has(targetId);
+ }, [highlightedNodes]);
+
+ // Link color - electric cyan when both nodes are highlighted
+ const linkColor = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? '#00ffff' : 'rgba(139, 148, 158, 0.3)';
+ }, [isLinkHighlighted]);
+
+ // Link width - thicker for highlighted links
+ const linkWidth = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? 6 : 1;
+ }, [isLinkHighlighted]);
+
+ // Particles - more on highlighted links for flowing effect
+ const linkDirectionalParticles = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? 8 : 0;
+ }, [isLinkHighlighted]);
+
+ // Particle speed - faster for highlighted links
+ const linkDirectionalParticleSpeed = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? 0.02 : 0.004;
+ }, [isLinkHighlighted]);
+
+ // Particle width - bigger for highlighted links
+ const linkDirectionalParticleWidth = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? 4 : 1;
+ }, [isLinkHighlighted]);
+
+ // Particle color - matches the link color
+ const linkDirectionalParticleColor = useCallback((link: any) => {
+ return isLinkHighlighted(link) ? '#ffff00' : 'rgba(139, 148, 158, 0.5)';
+ }, [isLinkHighlighted]);
+
+ // High-quality sphere geometry (reused for performance)
+ const sphereGeometry = useMemo(() => new THREE.SphereGeometry(1, 64, 64), []);
+
+ // Custom node rendering with realistic materials
+ const nodeThreeObject = useCallback((node: any) => {
+ const isHighlighted = highlightedNodes.has(node.id);
+ const baseSize = node.val || 5;
+ const size = isHighlighted ? baseSize * 1.5 : baseSize * 0.8;
+
+ // Get node color
+ const colorHex = isHighlighted ? '#ffff00' : (node.color || '#58a6ff');
+ const color = new THREE.Color(colorHex);
+
+ // Create material with realistic properties
+ const material = new THREE.MeshPhysicalMaterial({
+ color: color,
+ metalness: 0.3,
+ roughness: 0.4,
+ emissive: isHighlighted ? color : new THREE.Color(0x000000),
+ emissiveIntensity: isHighlighted ? 0.2 : 0,
+ clearcoat: 0.3,
+ clearcoatRoughness: 0.25,
+ });
+
+ const mesh = new THREE.Mesh(sphereGeometry, material as any) as any;
+ mesh.scale.set(size, size, size);
+
+ return mesh;
+ }, [highlightedNodes, sphereGeometry]);
+
+ return (
+
+
+ 📦 node_modules Graph Analyzer
+ {status}
+
+
+
+
+ setQuery(e.target.value)}
+ onKeyDown={(e) => e.key === 'Enter' && runQuery()}
+ placeholder="Ask a question about your dependencies..."
+ disabled={isRunning}
+ />
+
+
+
+ {EXAMPLE_QUERIES.map((ex, i) => (
+
+ ))}
+
+
+
+ {stats && (
+
+
+ Packages
+ {stats.totalPackages}
+
+
+ Dependencies
+ {stats.totalLinks}
+
+
+ Direct Deps
+ {stats.directDeps}
+
+
+ )}
+
+
+
Node Colors
+
+
+
+
+
Transitive Dependency
+
+
+
+ {showResult && (
+
+ )}
+
+ {/* Activity Log Panel */}
+
+
+ 🔍 Activity Log
+ {activityLog.length > 0 && (
+
+ )}
+
+
+ {activityLog.length === 0 ? (
+
Run a query to see activity...
+ ) : (
+ activityLog.map(entry => (
+
+
(entry.code || entry.response || entry.output || entry.answer || entry.prompt) && toggleExpand(entry.id)}
+ >
+ {getEventIcon(entry.type)}
+ {getEventLabel(entry.type)}
+ {entry.iteration && #{entry.iteration}}
+ {(entry.code || entry.response || entry.output || entry.answer || entry.prompt) && (
+ {expandedEntries.has(entry.id) ? '▼' : '▶'}
+ )}
+
+ {expandedEntries.has(entry.id) && (
+
+ {entry.prompt && (
+
+
Prompt:
+
{entry.prompt.slice(0, 500)}{entry.prompt.length > 500 ? '...' : ''}
+
+ )}
+ {entry.code && (
+
+ )}
+ {entry.response && (
+
+
Response:
+
{entry.response.slice(0, 500)}{entry.response.length > 500 ? '...' : ''}
+
+ )}
+ {entry.output && (
+
+
Output:
+
{entry.output}
+
+ )}
+ {entry.error && (
+
+
Error:
+
{entry.error}
+
+ )}
+ {entry.answer && (
+
+
Answer:
+
{typeof entry.answer === 'object' ? JSON.stringify(entry.answer, null, 2) : entry.answer}
+
+ )}
+
+ )}
+
+ ))
+ )}
+
+
+
+ {graphData.nodes.length > 0 ? (
+
`${node.name}${node.version ? '@' + node.version : ''}`}
+ nodeThreeObject={nodeThreeObject}
+ nodeThreeObjectExtend={false}
+ linkColor={linkColor}
+ linkWidth={linkWidth}
+ linkOpacity={0.6}
+ linkDirectionalParticles={linkDirectionalParticles}
+ linkDirectionalParticleSpeed={linkDirectionalParticleSpeed}
+ linkDirectionalParticleWidth={linkDirectionalParticleWidth}
+ linkDirectionalParticleColor={linkDirectionalParticleColor}
+ backgroundColor="#000002"
+ enableNodeDrag={true}
+ enableNavigationControls={true}
+ showNavInfo={false}
+ />
+ ) : (
+
+
+
Loading graph data...
+
+ )}
+
+ );
+}
+
+export default App;
diff --git a/examples/node-modules-viz/src/index.css b/examples/node-modules-viz/src/index.css
new file mode 100644
index 0000000..cb59e0d
--- /dev/null
+++ b/examples/node-modules-viz/src/index.css
@@ -0,0 +1,17 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background: #0d1117;
+ color: #c9d1d9;
+ overflow: hidden;
+}
+
+#root {
+ width: 100vw;
+ height: 100vh;
+}
diff --git a/examples/node-modules-viz/src/main.tsx b/examples/node-modules-viz/src/main.tsx
new file mode 100644
index 0000000..2339d59
--- /dev/null
+++ b/examples/node-modules-viz/src/main.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from './App';
+import './index.css';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+);
diff --git a/examples/node-modules-viz/start.sh b/examples/node-modules-viz/start.sh
new file mode 100755
index 0000000..a13a572
--- /dev/null
+++ b/examples/node-modules-viz/start.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+cd "$(dirname "$0")"
+
+# Kill any existing processes on our ports
+pkill -f "node server.mjs" 2>/dev/null || true
+pkill -f "vite" 2>/dev/null || true
+
+echo "🚀 Starting node-modules-viz..."
+echo ""
+
+# Start backend
+node server.mjs &
+BACKEND_PID=$!
+
+# Start frontend
+pnpm dev &
+FRONTEND_PID=$!
+
+echo ""
+echo "✨ Both servers started!"
+echo " Backend: ws://localhost:4242"
+echo " Frontend: http://localhost:3000"
+echo ""
+echo "Press Ctrl+C to stop both servers"
+
+# Wait for Ctrl+C
+trap "echo ''; echo 'Shutting down...'; kill $BACKEND_PID $FRONTEND_PID 2>/dev/null; exit 0" INT TERM
+
+# Wait for either to exit
+wait
diff --git a/examples/node-modules-viz/tsconfig.json b/examples/node-modules-viz/tsconfig.json
new file mode 100644
index 0000000..8cbdee6
--- /dev/null
+++ b/examples/node-modules-viz/tsconfig.json
@@ -0,0 +1,14 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": ".",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "target": "ES2022",
+ "lib": ["ES2022"],
+ "types": ["node"]
+ },
+ "include": ["**/*.ts"],
+ "exclude": ["node_modules", "dist"]
+}
diff --git a/examples/node-modules-viz/vite.config.ts b/examples/node-modules-viz/vite.config.ts
new file mode 100644
index 0000000..efdcb09
--- /dev/null
+++ b/examples/node-modules-viz/vite.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ proxy: {
+ '/ws': {
+ target: 'ws://localhost:3001',
+ ws: true,
+ },
+ },
+ },
+});
diff --git a/package.json b/package.json
index 53eb961..410499a 100644
--- a/package.json
+++ b/package.json
@@ -72,13 +72,16 @@
"devDependencies": {
"@types/node": "^22.10.2",
"@types/uuid": "^10.0.0",
+ "@types/ws": "^8.18.1",
"@typescript-eslint/eslint-plugin": "^8.18.1",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^9.17.0",
+ "open": "^11.0.0",
"oxlint": "^1.37.0",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"vitest": "^2.1.8",
+ "ws": "^8.19.0",
"zod": "^3.25.76"
},
"engines": {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b74d08e..fcf7b80 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,7 +13,7 @@ importers:
version: 16.6.1
openai:
specifier: ^4.76.0
- version: 4.104.0(zod@3.25.76)
+ version: 4.104.0(ws@8.19.0)(zod@3.25.76)
uuid:
specifier: ^11.0.3
version: 11.1.0
@@ -24,6 +24,9 @@ importers:
'@types/uuid':
specifier: ^10.0.0
version: 10.0.0
+ '@types/ws':
+ specifier: ^8.18.1
+ version: 8.18.1
'@typescript-eslint/eslint-plugin':
specifier: ^8.18.1
version: 8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)
@@ -33,6 +36,9 @@ importers:
eslint:
specifier: ^9.17.0
version: 9.39.2
+ open:
+ specifier: ^11.0.0
+ version: 11.0.0
oxlint:
specifier: ^1.37.0
version: 1.37.0
@@ -45,12 +51,164 @@ importers:
vitest:
specifier: ^2.1.8
version: 2.1.9(@types/node@22.19.3)
+ ws:
+ specifier: ^8.19.0
+ version: 8.19.0
+ zod:
+ specifier: ^3.25.76
+ version: 3.25.76
+
+ examples/node-modules-viz:
+ dependencies:
+ '@pnpm/lockfile-file':
+ specifier: ^9.1.3
+ version: 9.1.3(@pnpm/logger@5.2.0)
+ dotenv:
+ specifier: ^16.4.5
+ version: 16.6.1
+ react:
+ specifier: ^18.2.0
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.3.1(react@18.3.1)
+ react-force-graph-3d:
+ specifier: ^1.26.0
+ version: 1.29.0(react@18.3.1)
+ rllm:
+ specifier: workspace:*
+ version: link:../..
+ three:
+ specifier: ^0.170.0
+ version: 0.170.0
+ ws:
+ specifier: ^8.16.0
+ version: 8.19.0
zod:
specifier: ^3.25.76
version: 3.25.76
+ devDependencies:
+ '@types/node':
+ specifier: ^20.11.0
+ version: 20.19.28
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.2.0
+ version: 18.3.7(@types/react@18.3.27)
+ '@types/ws':
+ specifier: ^8.5.10
+ version: 8.18.1
+ '@vitejs/plugin-react':
+ specifier: ^4.2.0
+ version: 4.7.0(vite@5.4.21(@types/node@20.19.28))
+ concurrently:
+ specifier: ^8.2.2
+ version: 8.2.2
+ tsx:
+ specifier: ^4.7.0
+ version: 4.21.0
+ typescript:
+ specifier: ^5.3.3
+ version: 5.9.3
+ vite:
+ specifier: ^5.0.0
+ version: 5.4.21(@types/node@20.19.28)
packages:
+ 3d-force-graph@1.79.0:
+ resolution: {integrity: sha512-0RUNcfiH12f93loY/iS4wShzhXzdLLN4futvFnintF7eP30DjX+nAdLDAGOZwSflhijQyVwnGtpczNjFrDLUzQ==}
+ engines: {node: '>=12'}
+
+ '@babel/code-frame@7.28.6':
+ resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.6':
+ resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.6':
+ resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.6':
+ resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.27.1':
+ resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.28.5':
+ resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.28.6':
+ resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/runtime@7.28.6':
+ resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.6':
+ resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/types@7.28.6':
+ resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
+ engines: {node: '>=6.9.0'}
+
'@esbuild/aix-ppc64@0.21.5':
resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==}
engines: {node: '>=12'}
@@ -399,9 +557,22 @@ packages:
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
engines: {node: '>=18.18'}
+ '@jridgewell/gen-mapping@0.3.13':
+ resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
+
+ '@jridgewell/remapping@2.3.5':
+ resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
'@jridgewell/sourcemap-codec@1.5.5':
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
+ '@jridgewell/trace-mapping@0.3.31':
+ resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
+
'@oxlint/darwin-arm64@1.37.0':
resolution: {integrity: sha512-qDa8qf4Th3sbk6P6wRbsv5paGeZ8EEOy8PtnT2IkAYSzjDHavw8nMK/lQvf6uS7LArjcmOfM1Y3KnZUFoNZZqg==}
cpu: [arm64]
@@ -442,6 +613,122 @@ packages:
cpu: [x64]
os: [win32]
+ '@pnpm/config.nerf-dart@1.0.1':
+ resolution: {integrity: sha512-03d2l21gAyzGVr9SR6rS5pvCTnZ4HaNdi8jB2Y/UGvszzrNbA+AJVObVw6SulNQ1Eah3SHB9wCezJwtP+jYIcA==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/constants@1001.3.1':
+ resolution: {integrity: sha512-2hf0s4pVrVEH8RvdJJ7YRKjQdiG8m0iAT26TTqXnCbK30kKwJW69VLmP5tED5zstmDRXcOeH5eRcrpkdwczQ9g==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/constants@8.0.0':
+ resolution: {integrity: sha512-yQosGUvYPpAjb1jOFcdbwekRjZRVxN6C0hHzfRCZrMKbxGjt/E0g0RcFlEDNVZ95tm4oMMcr7nEPa7H7LX3emw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/core-loggers@10.0.4':
+ resolution: {integrity: sha512-0sa1oq8OelHFs1qTCv0fUVPYpQwRjkAb3miaXTwhXCUsePAy6kZ4wrn/aZA4bnxj0pIcaQnrSTWKcRjdrkqu3Q==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': ^5.0.0
+
+ '@pnpm/crypto.base32-hash@3.0.0':
+ resolution: {integrity: sha512-iGKP6rRKng5Tcad1+S+j3UoY5wVZN+z0ZgemlGp69jNgn6EaM4N0Q3mvnDNJ7UZFmL2ClXZZYLNuCk9pUYV3Xg==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/dependency-path@5.1.3':
+ resolution: {integrity: sha512-d1gd9tXO4QRem2LMzvxVwt/6t7sNSK4DAJ6MQVLfXTqyIAvoAzRkSmFMNlqN1LQnNKIq7a/WXbJXDUObrYfkfA==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/error@1000.0.5':
+ resolution: {integrity: sha512-GjH0TPjbVNrPnl/BAGoFuBLJ2sFfXNKbS33lll/Ehe9yw0fyc8Kdw7kO9if37yQqn6vaa4dAHKkPllum7f/IPQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/error@6.0.1':
+ resolution: {integrity: sha512-7yjO0RgmWYb4OKgcWC33yD4Z2CxE7Tm7vXX1SmS7GDifDT/bgZZhHeS2xq/+W6y9yhwIrRSA+7AlQL1NM2wIvw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/fetch@8.0.4':
+ resolution: {integrity: sha512-E79pTCQtnYtwJbPEru2JcPHRXAjrWcuJN6a/0r3/R1uZsx+ElJrU7WfNYEwyk2gANxq065/nq2lVp1/qx2aQ3A==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': ^5.0.0
+
+ '@pnpm/fetching-types@6.0.0':
+ resolution: {integrity: sha512-fnsaegb+0q7Ku6AyCmoVtBeCuO8ytB7YMEaGHC+0MGoRsxxa6EVLgi2H4abKr8LLslf5tHJBnOH24DjST3UNfQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/git-resolver@9.0.5':
+ resolution: {integrity: sha512-d2IPuKwEWQTxr9AfPJrKUHf3bkKm//L3CcrgUs4vl9xGMLqV7uCtp/VKvzK3vNPN6RfCn7nKmjvyjvkNA1Z5vg==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/git-utils@2.0.0':
+ resolution: {integrity: sha512-k1rv4Zvno/5zJAqE/Mh9V0ehlm14NsYwpXTdaGMtyhkoHvlSckRfr23OIOIM7Q/TRX+LhqyJ2kep50SY2TsZ+g==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/hosted-git-info@1.0.0':
+ resolution: {integrity: sha512-QzmNiLShTnNyeTHr+cykG5hYjwph0+v49KHV36Dh8uA2rRMWw30qoZMARuxd00SYdoTwT8bIouqqmzi6TWfJHQ==}
+ engines: {node: '>=10'}
+
+ '@pnpm/lockfile-file@9.1.3':
+ resolution: {integrity: sha512-+WaLikC86jJyA23xzU8SnHcxeuxs8TcFCMoP2HCSU/0ckk9bjjW4CttEcHzQoCMdDlm7eZKTRoDuU5iJ5Wie3Q==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': ^5.0.0
+
+ '@pnpm/lockfile-types@7.1.3':
+ resolution: {integrity: sha512-ifMGKjiBFweZiI9nRYw+N4oP42tDaxHGzla/fuEqn8rOLzITJKfD9G2KfF7u8ZfZyW9lHFL/FsN9t1+q6L+qyQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/lockfile-utils@11.0.4':
+ resolution: {integrity: sha512-qczmd35HWys0P8ngG86pNquBPeVEQT4SH19AOu0uoK5d/9nXWnbqiHbAwRyMndNDxuPBf3ol65X4Esx/7z8ofg==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/logger@5.2.0':
+ resolution: {integrity: sha512-dCdSs2wPCweMkRLdISAKBOKSWeq/9iS9aanWgjoUkFs06KN2o5XGFg53oCXg/KbZhF9AXS3vMHPwTebzCeAEsA==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/merge-lockfile-changes@6.0.5':
+ resolution: {integrity: sha512-2qKNX80l3TMuVr5+3VNBuYRo/iud4x2RGZMrUpm5cNQ6BMZdHwbo/9RzEOFTXWETbEKyErryc3VqS/54EhDaWw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/network.agent@2.0.3':
+ resolution: {integrity: sha512-YITr8VrjPPULdQWAA17oU0M4j4286OmRnk0XA1ntoR+v0FbdGRKmRQmOHx86s1eM3eGCh1UF8WPHNkbgjjBN3Q==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/network.config@2.1.0':
+ resolution: {integrity: sha512-3Z4suyclrd1NOp1ue+xf5VCEd7Pu1R16wXg8wCmKIiQD7E69BE4WA3ralxrkPx0B0OtqM1H8lBPINsipZaUcuQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/network.proxy-agent@2.0.3':
+ resolution: {integrity: sha512-x6lyFMJFgf/8dArUfPBtEqieKm8J7Vab/hIiNCCqcziEucJwNgFUF1xwLUuypn/oSB9XvGQa4nxPZ0dyFZzOrQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/node-fetch@1.0.0':
+ resolution: {integrity: sha512-eYwrzhKUBGFdq78rJStGjaHTUHA2VH+Avr//CVx/T+EJkI7hnFmOy6YghvcB2clj8HpO4V8tXRNuFNfRX08ayw==}
+ engines: {node: ^10.17 || >=12.3}
+
+ '@pnpm/pick-fetcher@3.0.0':
+ resolution: {integrity: sha512-2eisylRAU/jeuxFEPnS1gjLZKJGbYc4QEtEW6MVUYjO4Xi+2ttkSm7825S0J5IPpUIvln8HYPCUS0eQWSfpOaQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/ramda@0.28.1':
+ resolution: {integrity: sha512-zcAG+lvU0fMziNeGXpPyCyCJYp5ZVrPElEE4t14jAmViaihohocZ+dDkcRIyAomox8pQsuZnv1EyHR+pOhmUWw==}
+
+ '@pnpm/resolver-base@13.0.1':
+ resolution: {integrity: sha512-WXWHDtVFAmwyBSOLYkYF1bJujDQyG6JYZGsVsy+/dTPiaT8pzebL7p4fchC/8wpZroH7lxJ1lInSKIg7T904/g==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/types@11.1.0':
+ resolution: {integrity: sha512-wnlOhu7hjv9/qsf2cbK0YqpaV9c4LS69Utxd+r8hq/GWhyrOHcM1QOlfQb0Mzci0q4DDgB8VXT4dhBnEBL4c5g==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/util.lex-comparator@3.0.0':
+ resolution: {integrity: sha512-ead+l3IiuVXwKDf/QJPX6G93cwhXki3yOVEA/VdAO7AhZ5vUuSBxHe6gQKEbB0QacJ4H5VsYxeM1xUgwjjOO/Q==}
+ engines: {node: '>=18.12'}
+
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
'@rollup/rollup-android-arm-eabi@4.55.1':
resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==}
cpu: [arm]
@@ -567,6 +854,21 @@ packages:
cpu: [x64]
os: [win32]
+ '@tweenjs/tween.js@25.0.0':
+ resolution: {integrity: sha512-XKLA6syeBUaPzx4j3qwMqzzq+V4uo72BnlbOjmuljLrRqdsd3qnzvZZoxvMHZ23ndsRS4aufU6JOZYpCbU6T1A==}
+
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -579,12 +881,29 @@ packages:
'@types/node@18.19.130':
resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==}
+ '@types/node@20.19.28':
+ resolution: {integrity: sha512-VyKBr25BuFDzBFCK5sUM6ZXiWfqgCTwTAOK8qzGV/m9FCirXYDlmczJ+d5dXBAQALGCdRRdbteKYfJ84NGEusw==}
+
'@types/node@22.19.3':
resolution: {integrity: sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==}
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
+ '@types/react@18.3.27':
+ resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+
'@types/uuid@10.0.0':
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
+ '@types/ws@8.18.1':
+ resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
+
'@typescript-eslint/eslint-plugin@8.52.0':
resolution: {integrity: sha512-okqtOgqu2qmZJ5iN4TWlgfF171dZmx2FzdOv2K/ixL2LZWDStL8+JgQerI2sa8eAEfoydG9+0V96m7V+P8yE1Q==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -644,6 +963,12 @@ packages:
resolution: {integrity: sha512-ink3/Zofus34nmBsPjow63FP5M7IGff0RKAgqR6+CFpdk22M7aLwC9gOcLGYqr7MczLPzZVERW9hRog3O4n1sQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
'@vitest/expect@2.1.9':
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
@@ -673,10 +998,31 @@ packages:
'@vitest/utils@2.1.9':
resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==}
+ '@zkochan/js-yaml@0.0.7':
+ resolution: {integrity: sha512-nrUSn7hzt7J6JWgWGz78ZYI8wj+gdIJdk0Ynjpp8l+trkn58Uqsf6RYrYkEK+3X18EX+TNdtJI0WxAtc+L84SQ==}
+ hasBin: true
+
+ '@zkochan/retry@0.2.0':
+ resolution: {integrity: sha512-WhB+2B/ZPlW2Xy/kMJBrMbqecWXcbDDgn0K0wKBAgO2OlBTz1iLJrRWduo+DGGn0Akvz1Lu4Xvls7dJojximWw==}
+ engines: {node: '>=10'}
+
+ '@zkochan/rimraf@3.0.2':
+ resolution: {integrity: sha512-GBf4ua7ogWTr7fATnzk/JLowZDBnBJMm8RkMaC/KcvxZ9gxbMWix0/jImd815LmqKyIHZ7h7lADRddGMdGBuCA==}
+ engines: {node: '>=18.12'}
+
+ '@zkochan/which@2.0.3':
+ resolution: {integrity: sha512-C1ReN7vt2/2O0fyTsx5xnbQuxBrmG5NMSbcIkPKCCfCTJgpZBsuRYzFXHj3nVq8vTfK7vxHUmzfCpSHgO7j4rg==}
+ engines: {node: '>= 8'}
+ hasBin: true
+
abort-controller@3.0.0:
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
engines: {node: '>=6.5'}
+ accessor-fn@1.5.3:
+ resolution: {integrity: sha512-rkAofCwe/FvYFUlMB0v0gWmhqtfAtV1IUkdPbfhTUyYniu5LrC0A0UJkTH0Jv3S8SvwkmfuAlY+mQIJATdocMA==}
+ engines: {node: '>=12'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -687,6 +1033,10 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
agentkeepalive@4.6.0:
resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
engines: {node: '>= 8.0.0'}
@@ -694,6 +1044,10 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+
ansi-styles@4.3.0:
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
engines: {node: '>=8'}
@@ -711,12 +1065,28 @@ packages:
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ baseline-browser-mapping@2.9.14:
+ resolution: {integrity: sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==}
+ hasBin: true
+
+ bole@5.0.25:
+ resolution: {integrity: sha512-4WsO2cOzQwN4MDCS/6krYWfz1brS3bJGKJhZQ+cr6EvcJIJiuxrWBZz/2WXbQjurFCRl+ddAzqH6SYaIzSmzsQ==}
+
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
brace-expansion@2.0.2:
resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
+ browserslist@4.28.1:
+ resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+
+ bundle-name@4.1.0:
+ resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
+ engines: {node: '>=18'}
+
cac@6.7.14:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
@@ -729,6 +1099,9 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ caniuse-lite@1.0.30001764:
+ resolution: {integrity: sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==}
+
chai@5.3.3:
resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==}
engines: {node: '>=18'}
@@ -741,6 +1114,10 @@ packages:
resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==}
engines: {node: '>= 16'}
+ cliui@8.0.1:
+ resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
+ engines: {node: '>=12'}
+
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -752,13 +1129,98 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
+ comver-to-semver@1.0.0:
+ resolution: {integrity: sha512-gcGtbRxjwROQOdXLUWH1fQAXqThUVRZ219aAwgtX3KfYw429/Zv6EIJRf5TBSzWdAGwePmqH7w70WTaX4MDqag==}
+ engines: {node: '>=12.17'}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ concurrently@8.2.2:
+ resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==}
+ engines: {node: ^14.13.0 || >=16.0.0}
+ hasBin: true
+
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
+ csstype@3.2.3:
+ resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
+
+ d3-array@3.2.4:
+ resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==}
+ engines: {node: '>=12'}
+
+ d3-binarytree@1.0.2:
+ resolution: {integrity: sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==}
+
+ d3-color@3.1.0:
+ resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==}
+ engines: {node: '>=12'}
+
+ d3-dispatch@3.0.1:
+ resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==}
+ engines: {node: '>=12'}
+
+ d3-force-3d@3.0.6:
+ resolution: {integrity: sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==}
+ engines: {node: '>=12'}
+
+ d3-format@3.1.0:
+ resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==}
+ engines: {node: '>=12'}
+
+ d3-interpolate@3.0.1:
+ resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==}
+ engines: {node: '>=12'}
+
+ d3-octree@1.1.0:
+ resolution: {integrity: sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==}
+
+ d3-quadtree@3.0.1:
+ resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==}
+ engines: {node: '>=12'}
+
+ d3-scale-chromatic@3.1.0:
+ resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==}
+ engines: {node: '>=12'}
+
+ d3-scale@4.0.2:
+ resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==}
+ engines: {node: '>=12'}
+
+ d3-selection@3.0.0:
+ resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==}
+ engines: {node: '>=12'}
+
+ d3-time-format@4.1.0:
+ resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==}
+ engines: {node: '>=12'}
+
+ d3-time@3.1.0:
+ resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==}
+ engines: {node: '>=12'}
+
+ d3-timer@3.0.1:
+ resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
+ engines: {node: '>=12'}
+
+ data-bind-mapper@1.0.3:
+ resolution: {integrity: sha512-QmU3lyEnbENQPo0M1F9BMu4s6cqNNp8iJA+b/HP2sSb7pf3dxwF3+EP1eO69rwBfH9kFJ1apmzrtogAmVt2/Xw==}
+ engines: {node: '>=12'}
+
+ data-uri-to-buffer@3.0.1:
+ resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==}
+ engines: {node: '>= 6'}
+
+ date-fns@2.30.0:
+ resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
+ engines: {node: '>=0.11'}
+
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -775,6 +1237,18 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ default-browser-id@5.0.1:
+ resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==}
+ engines: {node: '>=18'}
+
+ default-browser@5.4.0:
+ resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==}
+ engines: {node: '>=18'}
+
+ define-lazy-prop@3.0.0:
+ resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==}
+ engines: {node: '>=12'}
+
delayed-stream@1.0.0:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
@@ -787,6 +1261,12 @@ packages:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
+ electron-to-chromium@1.5.267:
+ resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==}
+
+ emoji-regex@8.0.0:
+ resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
+
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@@ -816,6 +1296,10 @@ packages:
engines: {node: '>=18'}
hasBin: true
+ escalade@3.2.0:
+ resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
+ engines: {node: '>=6'}
+
escape-string-regexp@4.0.0:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
@@ -869,6 +1353,10 @@ packages:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
+ execa@5.1.1:
+ resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
+ engines: {node: '>=10'}
+
expect-type@1.3.0:
resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==}
engines: {node: '>=12.0.0'}
@@ -882,6 +1370,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ fast-safe-stringify@2.1.1:
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -891,6 +1382,15 @@ packages:
picomatch:
optional: true
+ fetch-blob@2.1.2:
+ resolution: {integrity: sha512-YKqtUDwqLyfyMnmbw8XD6Q8j9i/HggKtPEI+pZ1+8bvheBu78biSmNaXWusx1TauGqtUUGx/cBb1mKdq2rLYow==}
+ engines: {node: ^10.17.0 || >=12.3.0}
+ peerDependencies:
+ domexception: '*'
+ peerDependenciesMeta:
+ domexception:
+ optional: true
+
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -906,6 +1406,10 @@ packages:
flatted@3.3.3:
resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==}
+ float-tooltip@1.7.5:
+ resolution: {integrity: sha512-/kXzuDnnBqyyWyhDMH7+PfP8J/oXiAavGzcRxASOMRHFuReDtofizLLJsf7nnDLAfEaMW4pVWaXrAjtnglpEkg==}
+ engines: {node: '>=12'}
+
form-data-encoder@1.7.2:
resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==}
@@ -925,14 +1429,30 @@ packages:
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
+ get-caller-file@2.0.5:
+ resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
+ engines: {node: 6.* || 8.* || >= 10.*}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
+ get-npm-tarball-url@2.1.0:
+ resolution: {integrity: sha512-ro+DiMu5DXgRBabqXupW38h7WPZ9+Ad8UjwhvsmmN8w1sU7ab0nzAXvVZ4kqYg57OrqomRtJvepX5/xvFKNtjA==}
+ engines: {node: '>=12.17'}
+
get-proto@1.0.1:
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
engines: {node: '>= 0.4'}
+ get-stream@6.0.1:
+ resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==}
+ engines: {node: '>=10'}
+
get-tsconfig@4.13.0:
resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==}
@@ -948,6 +1468,10 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
+ graceful-git@4.0.0:
+ resolution: {integrity: sha512-zK/rCH/I0DMKpPBLCElXGI7za3EnXeQFdiK6CTP02Tt1N1L+bMLghZY7cXozlx9M2bx4Q0zrY9ADYP3eI8haIw==}
+ engines: {node: '>=18.12'}
+
has-flag@4.0.0:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
@@ -964,6 +1488,18 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ human-signals@2.1.0:
+ resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
+ engines: {node: '>=10.17.0'}
+
humanize-ms@1.2.1:
resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
@@ -983,21 +1519,77 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
+ individual@3.0.0:
+ resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==}
+
+ inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+
+ internmap@2.0.3:
+ resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
+ engines: {node: '>=12'}
+
+ ip-address@10.1.0:
+ resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
+ engines: {node: '>= 12'}
+
+ is-docker@3.0.0:
+ resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ hasBin: true
+
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
+ is-fullwidth-code-point@3.0.0:
+ resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
+ engines: {node: '>=8'}
+
is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'}
+ is-in-ssh@1.0.0:
+ resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==}
+ engines: {node: '>=20'}
+
+ is-inside-container@1.0.0:
+ resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==}
+ engines: {node: '>=14.16'}
+ hasBin: true
+
+ is-plain-obj@2.1.0:
+ resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==}
+ engines: {node: '>=8'}
+
+ is-stream@2.0.1:
+ resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==}
+ engines: {node: '>=8'}
+
+ is-wsl@3.1.0:
+ resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==}
+ engines: {node: '>=16'}
+
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ jerrypick@1.1.2:
+ resolution: {integrity: sha512-YKnxXEekXKzhpf7CLYA0A+oDP8V0OhICNCr5lv96FvSsDEmrb0GKM776JgQvHTMjr7DTTPEVv/1Ciaw0uEWzBA==}
+ engines: {node: '>=12'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
js-yaml@4.1.1:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -1007,6 +1599,18 @@ packages:
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ json-stringify-safe@5.0.1:
+ resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ kapsule@1.16.3:
+ resolution: {integrity: sha512-4+5mNNf4vZDSwPhKprKwz3330iisPrb08JyMgbsdFrimBCKNHecua/WBwvVg3n7vwx0C1ARjfhwIpbrbd9n5wg==}
+ engines: {node: '>=12'}
+
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -1018,12 +1622,33 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash-es@4.17.22:
+ resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
+
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ lodash@4.17.21:
+ resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
+
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
loupe@3.2.1:
resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==}
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+
+ lru-cache@7.18.3:
+ resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
+ engines: {node: '>=12'}
+
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
@@ -1031,6 +1656,9 @@ packages:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
+ merge-stream@2.0.0:
+ resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==}
+
mime-db@1.52.0:
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
engines: {node: '>= 0.6'}
@@ -1039,6 +1667,10 @@ packages:
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
engines: {node: '>= 0.6'}
+ mimic-fn@2.1.0:
+ resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
+ engines: {node: '>=6'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -1046,6 +1678,9 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
+ minimist@1.2.8:
+ resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1057,8 +1692,28 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
- node-domexception@1.0.0:
- resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
+ ndjson@2.0.0:
+ resolution: {integrity: sha512-nGl7LRGrzugTtaFcJMhLbpzJM6XdivmbkdlaGcrk/LXg2KL/YBC6z1g70xh0/al+oFuVFP8N8kiWRucmeEH/qQ==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ ngraph.events@1.4.0:
+ resolution: {integrity: sha512-NeDGI4DSyjBNBRtA86222JoYietsmCXbs8CEB0dZ51Xeh4lhVl1y3wpWLumczvnha8sFQIW4E0vvVWwgmX2mGw==}
+
+ ngraph.forcelayout@3.3.1:
+ resolution: {integrity: sha512-MKBuEh1wujyQHFTW57y5vd/uuEOK0XfXYxm3lC7kktjJLRdt/KEKEknyOlc6tjXflqBKEuYBBcu7Ax5VY+S6aw==}
+
+ ngraph.graph@20.1.1:
+ resolution: {integrity: sha512-KNtZWYzYe7SMOuG3vvROznU+fkPmL5cGYFsWjqt+Ob1uF5xZz5EjomtsNOZEIwVuD37/zokeEqNK1ghY4/fhDg==}
+
+ ngraph.merge@1.0.0:
+ resolution: {integrity: sha512-5J8YjGITUJeapsomtTALYsw7rFveYkM+lBj3QiYZ79EymQcuri65Nw3knQtFxQBU1r5iOaVRXrSwMENUPK62Vg==}
+
+ ngraph.random@1.2.0:
+ resolution: {integrity: sha512-4EUeAGbB2HWX9njd6bP6tciN6ByJfoaAvmVL9QTaZSeXrW46eNGA9GajiXiPBbvFqxUWFkEbyo6x5qsACUuVfA==}
+
+ node-domexception@1.0.0:
+ resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
deprecated: Use your platform's native DOMException instead
@@ -1071,6 +1726,29 @@ packages:
encoding:
optional: true
+ node-releases@2.0.27:
+ resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
+
+ normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+
+ npm-run-path@4.0.1:
+ resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
+ engines: {node: '>=8'}
+
+ object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+
+ onetime@5.1.2:
+ resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
+ engines: {node: '>=6'}
+
+ open@11.0.0:
+ resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==}
+ engines: {node: '>=20'}
+
openai@4.104.0:
resolution: {integrity: sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==}
hasBin: true
@@ -1117,6 +1795,9 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
+ path-name@1.0.0:
+ resolution: {integrity: sha512-/dcAb5vMXH0f51yvMuSUqFpxUcA8JelbRmE5mW/p4CUJxrNgK24IkstnV7ENtg2IDGBOu6izKTG6eilbnbNKWQ==}
+
pathe@1.1.2:
resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
@@ -1131,18 +1812,68 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
+ polished@4.3.1:
+ resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==}
+ engines: {node: '>=10'}
+
postcss@8.5.6:
resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==}
engines: {node: ^10 || ^12 || >=14}
+ powershell-utils@0.1.0:
+ resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==}
+ engines: {node: '>=20'}
+
+ preact@10.28.2:
+ resolution: {integrity: sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
+ prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
+ react-force-graph-3d@1.29.0:
+ resolution: {integrity: sha512-YCD4W+SA9oeK7mMXZ9pXAGSbDZ3+6IYxv8nPZcqqYeiP1nqZIB/cbMveD3S2bH9EqsGrUMW5qFXjAm5topSblw==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '*'
+
+ react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+
+ react-kapsule@2.5.7:
+ resolution: {integrity: sha512-kifAF4ZPD77qZKc4CKLmozq6GY1sBzPEJTIJb0wWFK6HsePJatK3jXplZn2eeAt3x67CDozgi7/rO8fNQ/AL7A==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ react: '>=16.13.1'
+
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
+ readable-stream@3.6.2:
+ resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
+ engines: {node: '>= 6'}
+
+ require-directory@2.1.1:
+ resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
+ engines: {node: '>=0.10.0'}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -1150,11 +1881,43 @@ packages:
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
+ retry@0.13.1:
+ resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==}
+ engines: {node: '>= 4'}
+
+ rfc4648@1.5.4:
+ resolution: {integrity: sha512-rRg/6Lb+IGfJqO05HZkN50UtY7K/JhxJag1kP23+zyMfrvoB0B7RWv06MbOzoc79RgCdNTiUaNsTT1AJZ7Z+cg==}
+
rollup@4.55.1:
resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ run-applescript@7.1.0:
+ resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
+ engines: {node: '>=18'}
+
+ rxjs@7.8.2:
+ resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
+
+ safe-buffer@5.2.1:
+ resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+
+ safe-execa@0.1.2:
+ resolution: {integrity: sha512-vdTshSQ2JsRCgT8eKZWNJIL26C6bVqy1SOmuCMlKHegVeo8KYRobRrefOdUq9OozSPUUiSxrylteeRmLOMFfWg==}
+ engines: {node: '>=12'}
+
+ safe-execa@0.1.4:
+ resolution: {integrity: sha512-GI3k4zl4aLC3lxZNEEXAxxcXE6E3TfOsJ5xxJPhcAv9MWwnH2O9I0HrDmZFsVnu/C8wzRYSsTHdoVRmL0VicDw==}
+ engines: {node: '>=12'}
+
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
@@ -1168,19 +1931,71 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ shell-quote@1.8.3:
+ resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
+ engines: {node: '>= 0.4'}
+
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
+ signal-exit@3.0.7:
+ resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==}
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ smart-buffer@4.2.0:
+ resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
+ engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+
+ socks-proxy-agent@8.0.5:
+ resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
+ engines: {node: '>= 14'}
+
+ socks@2.8.7:
+ resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
+ engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+
+ sort-keys@4.2.0:
+ resolution: {integrity: sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==}
+ engines: {node: '>=8'}
+
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
+ spawn-command@0.0.2:
+ resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==}
+
+ split2@3.2.2:
+ resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
+
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+ string-width@4.2.3:
+ resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
+ engines: {node: '>=8'}
+
+ string_decoder@1.3.0:
+ resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
+
+ strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+
+ strip-bom@4.0.0:
+ resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+ engines: {node: '>=8'}
+
+ strip-final-newline@2.0.0:
+ resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
+ engines: {node: '>=6'}
+
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
@@ -1189,9 +2004,34 @@ packages:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
+ supports-color@8.1.1:
+ resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
+ engines: {node: '>=10'}
+
+ three-forcegraph@1.43.0:
+ resolution: {integrity: sha512-1AqLmTCjjjwcuccObG96fCxiRnNJjCLdA5Mozl7XK+ROwTJ6QEJPo2XJ6uxWeuAmPE7ukMhgv4lj28oZSfE4wg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ three: '>=0.118.3'
+
+ three-render-objects@1.40.4:
+ resolution: {integrity: sha512-Ukpu1pei3L5r809izvjsZxwuRcYLiyn6Uvy3lZ9bpMTdvj3i6PeX6w++/hs2ZS3KnEzGjb6YvTvh4UQuwHTDJg==}
+ engines: {node: '>=12'}
+ peerDependencies:
+ three: '>=0.168'
+
+ three@0.170.0:
+ resolution: {integrity: sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==}
+
+ through2@4.0.2:
+ resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
+
tinybench@2.9.0:
resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==}
+ tinycolor2@1.6.0:
+ resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==}
+
tinyexec@0.3.2:
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
@@ -1214,12 +2054,19 @@ packages:
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ tree-kill@1.2.2:
+ resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
+ hasBin: true
+
ts-api-utils@2.4.0:
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
engines: {node: '>=18.12'}
peerDependencies:
typescript: '>=4.8.4'
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
tsx@4.21.0:
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
engines: {node: '>=18.0.0'}
@@ -1240,9 +2087,18 @@ packages:
undici-types@6.21.0:
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+ update-browserslist-db@1.2.3:
+ resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+
uri-js@4.4.1:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ util-deprecate@1.0.2:
+ resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+
uuid@11.1.0:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
@@ -1332,6 +2188,48 @@ packages:
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
engines: {node: '>=0.10.0'}
+ wrap-ansi@7.0.0:
+ resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
+ engines: {node: '>=10'}
+
+ write-file-atomic@5.0.1:
+ resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ ws@8.19.0:
+ resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ wsl-utils@0.3.1:
+ resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==}
+ engines: {node: '>=20'}
+
+ y18n@5.0.8:
+ resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
+ engines: {node: '>=10'}
+
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
+ yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+
+ yargs-parser@21.1.1:
+ resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
+ engines: {node: '>=12'}
+
+ yargs@17.7.2:
+ resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+ engines: {node: '>=12'}
+
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -1341,6 +2239,128 @@ packages:
snapshots:
+ 3d-force-graph@1.79.0:
+ dependencies:
+ accessor-fn: 1.5.3
+ kapsule: 1.16.3
+ three: 0.170.0
+ three-forcegraph: 1.43.0(three@0.170.0)
+ three-render-objects: 1.40.4(three@0.170.0)
+
+ '@babel/code-frame@7.28.6':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.6': {}
+
+ '@babel/core@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.6':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
+ '@babel/helper-string-parser@7.27.1': {}
+
+ '@babel/helper-validator-identifier@7.28.5': {}
+
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@babel/parser@7.28.6':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/runtime@7.28.6': {}
+
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@babel/traverse@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/types@7.28.6':
+ dependencies:
+ '@babel/helper-string-parser': 7.27.1
+ '@babel/helper-validator-identifier': 7.28.5
+
'@esbuild/aix-ppc64@0.21.5':
optional: true
@@ -1545,8 +2565,25 @@ snapshots:
'@humanwhocodes/retry@0.4.3': {}
+ '@jridgewell/gen-mapping@0.3.13':
+ dependencies:
+ '@jridgewell/sourcemap-codec': 1.5.5
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/remapping@2.3.5':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
'@jridgewell/sourcemap-codec@1.5.5': {}
+ '@jridgewell/trace-mapping@0.3.31':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.5
+
'@oxlint/darwin-arm64@1.37.0':
optional: true
@@ -1571,6 +2608,172 @@ snapshots:
'@oxlint/win32-x64@1.37.0':
optional: true
+ '@pnpm/config.nerf-dart@1.0.1': {}
+
+ '@pnpm/constants@1001.3.1': {}
+
+ '@pnpm/constants@8.0.0': {}
+
+ '@pnpm/core-loggers@10.0.4(@pnpm/logger@5.2.0)':
+ dependencies:
+ '@pnpm/logger': 5.2.0
+ '@pnpm/types': 11.1.0
+
+ '@pnpm/crypto.base32-hash@3.0.0':
+ dependencies:
+ rfc4648: 1.5.4
+
+ '@pnpm/dependency-path@5.1.3':
+ dependencies:
+ '@pnpm/crypto.base32-hash': 3.0.0
+ '@pnpm/types': 11.1.0
+ semver: 7.7.3
+
+ '@pnpm/error@1000.0.5':
+ dependencies:
+ '@pnpm/constants': 1001.3.1
+
+ '@pnpm/error@6.0.1':
+ dependencies:
+ '@pnpm/constants': 8.0.0
+
+ '@pnpm/fetch@8.0.4(@pnpm/logger@5.2.0)':
+ dependencies:
+ '@pnpm/core-loggers': 10.0.4(@pnpm/logger@5.2.0)
+ '@pnpm/fetching-types': 6.0.0
+ '@pnpm/logger': 5.2.0
+ '@pnpm/network.agent': 2.0.3
+ '@pnpm/types': 11.1.0
+ '@zkochan/retry': 0.2.0
+ node-fetch: '@pnpm/node-fetch@1.0.0'
+ transitivePeerDependencies:
+ - domexception
+ - supports-color
+
+ '@pnpm/fetching-types@6.0.0':
+ dependencies:
+ '@zkochan/retry': 0.2.0
+ node-fetch: '@pnpm/node-fetch@1.0.0'
+ transitivePeerDependencies:
+ - domexception
+
+ '@pnpm/git-resolver@9.0.5(@pnpm/logger@5.2.0)':
+ dependencies:
+ '@pnpm/fetch': 8.0.4(@pnpm/logger@5.2.0)
+ '@pnpm/resolver-base': 13.0.1
+ graceful-git: 4.0.0
+ hosted-git-info: '@pnpm/hosted-git-info@1.0.0'
+ semver: 7.7.3
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+ - domexception
+ - supports-color
+
+ '@pnpm/git-utils@2.0.0':
+ dependencies:
+ execa: safe-execa@0.1.2
+
+ '@pnpm/hosted-git-info@1.0.0':
+ dependencies:
+ lru-cache: 6.0.0
+
+ '@pnpm/lockfile-file@9.1.3(@pnpm/logger@5.2.0)':
+ dependencies:
+ '@pnpm/constants': 8.0.0
+ '@pnpm/dependency-path': 5.1.3
+ '@pnpm/error': 6.0.1
+ '@pnpm/git-resolver': 9.0.5(@pnpm/logger@5.2.0)
+ '@pnpm/git-utils': 2.0.0
+ '@pnpm/lockfile-types': 7.1.3
+ '@pnpm/lockfile-utils': 11.0.4
+ '@pnpm/logger': 5.2.0
+ '@pnpm/merge-lockfile-changes': 6.0.5
+ '@pnpm/types': 11.1.0
+ '@pnpm/util.lex-comparator': 3.0.0
+ '@zkochan/rimraf': 3.0.2
+ comver-to-semver: 1.0.0
+ js-yaml: '@zkochan/js-yaml@0.0.7'
+ normalize-path: 3.0.0
+ ramda: '@pnpm/ramda@0.28.1'
+ semver: 7.7.3
+ sort-keys: 4.2.0
+ strip-bom: 4.0.0
+ write-file-atomic: 5.0.1
+ transitivePeerDependencies:
+ - domexception
+ - supports-color
+
+ '@pnpm/lockfile-types@7.1.3':
+ dependencies:
+ '@pnpm/types': 11.1.0
+
+ '@pnpm/lockfile-utils@11.0.4':
+ dependencies:
+ '@pnpm/dependency-path': 5.1.3
+ '@pnpm/lockfile-types': 7.1.3
+ '@pnpm/pick-fetcher': 3.0.0
+ '@pnpm/resolver-base': 13.0.1
+ '@pnpm/types': 11.1.0
+ get-npm-tarball-url: 2.1.0
+ ramda: '@pnpm/ramda@0.28.1'
+
+ '@pnpm/logger@5.2.0':
+ dependencies:
+ bole: 5.0.25
+ ndjson: 2.0.0
+
+ '@pnpm/merge-lockfile-changes@6.0.5':
+ dependencies:
+ '@pnpm/lockfile-types': 7.1.3
+ '@pnpm/types': 11.1.0
+ comver-to-semver: 1.0.0
+ ramda: '@pnpm/ramda@0.28.1'
+ semver: 7.7.3
+
+ '@pnpm/network.agent@2.0.3':
+ dependencies:
+ '@pnpm/network.config': 2.1.0
+ '@pnpm/network.proxy-agent': 2.0.3
+ agentkeepalive: 4.6.0
+ lru-cache: 7.18.3
+ transitivePeerDependencies:
+ - supports-color
+
+ '@pnpm/network.config@2.1.0':
+ dependencies:
+ '@pnpm/config.nerf-dart': 1.0.1
+
+ '@pnpm/network.proxy-agent@2.0.3':
+ dependencies:
+ '@pnpm/error': 1000.0.5
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ lru-cache: 7.18.3
+ socks-proxy-agent: 8.0.5
+ transitivePeerDependencies:
+ - supports-color
+
+ '@pnpm/node-fetch@1.0.0':
+ dependencies:
+ data-uri-to-buffer: 3.0.1
+ fetch-blob: 2.1.2
+ transitivePeerDependencies:
+ - domexception
+
+ '@pnpm/pick-fetcher@3.0.0': {}
+
+ '@pnpm/ramda@0.28.1': {}
+
+ '@pnpm/resolver-base@13.0.1':
+ dependencies:
+ '@pnpm/types': 11.1.0
+
+ '@pnpm/types@11.1.0': {}
+
+ '@pnpm/util.lex-comparator@3.0.0': {}
+
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
'@rollup/rollup-android-arm-eabi@4.55.1':
optional: true
@@ -1646,6 +2849,29 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.55.1':
optional: true
+ '@tweenjs/tween.js@25.0.0': {}
+
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
'@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {}
@@ -1659,12 +2885,31 @@ snapshots:
dependencies:
undici-types: 5.26.5
+ '@types/node@20.19.28':
+ dependencies:
+ undici-types: 6.21.0
+
'@types/node@22.19.3':
dependencies:
undici-types: 6.21.0
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.27)':
+ dependencies:
+ '@types/react': 18.3.27
+
+ '@types/react@18.3.27':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
'@types/uuid@10.0.0': {}
+ '@types/ws@8.18.1':
+ dependencies:
+ '@types/node': 22.19.3
+
'@typescript-eslint/eslint-plugin@8.52.0(@typescript-eslint/parser@8.52.0(eslint@9.39.2)(typescript@5.9.3))(eslint@9.39.2)(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
@@ -1756,6 +3001,18 @@ snapshots:
'@typescript-eslint/types': 8.52.0
eslint-visitor-keys: 4.2.1
+ '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.28))':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21(@types/node@20.19.28)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitest/expect@2.1.9':
dependencies:
'@vitest/spy': 2.1.9
@@ -1796,16 +3053,32 @@ snapshots:
loupe: 3.2.1
tinyrainbow: 1.2.0
+ '@zkochan/js-yaml@0.0.7':
+ dependencies:
+ argparse: 2.0.1
+
+ '@zkochan/retry@0.2.0': {}
+
+ '@zkochan/rimraf@3.0.2': {}
+
+ '@zkochan/which@2.0.3':
+ dependencies:
+ isexe: 2.0.0
+
abort-controller@3.0.0:
dependencies:
event-target-shim: 5.0.1
+ accessor-fn@1.5.3: {}
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
acorn@8.15.0: {}
+ agent-base@7.1.4: {}
+
agentkeepalive@4.6.0:
dependencies:
humanize-ms: 1.2.1
@@ -1817,6 +3090,8 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ansi-regex@5.0.1: {}
+
ansi-styles@4.3.0:
dependencies:
color-convert: 2.0.1
@@ -1829,6 +3104,13 @@ snapshots:
balanced-match@1.0.2: {}
+ baseline-browser-mapping@2.9.14: {}
+
+ bole@5.0.25:
+ dependencies:
+ fast-safe-stringify: 2.1.1
+ individual: 3.0.0
+
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -1838,6 +3120,18 @@ snapshots:
dependencies:
balanced-match: 1.0.2
+ browserslist@4.28.1:
+ dependencies:
+ baseline-browser-mapping: 2.9.14
+ caniuse-lite: 1.0.30001764
+ electron-to-chromium: 1.5.267
+ node-releases: 2.0.27
+ update-browserslist-db: 1.2.3(browserslist@4.28.1)
+
+ bundle-name@4.1.0:
+ dependencies:
+ run-applescript: 7.1.0
+
cac@6.7.14: {}
call-bind-apply-helpers@1.0.2:
@@ -1847,6 +3141,8 @@ snapshots:
callsites@3.1.0: {}
+ caniuse-lite@1.0.30001764: {}
+
chai@5.3.3:
dependencies:
assertion-error: 2.0.1
@@ -1862,6 +3158,12 @@ snapshots:
check-error@2.1.3: {}
+ cliui@8.0.1:
+ dependencies:
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wrap-ansi: 7.0.0
+
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -1872,14 +3174,95 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
+ comver-to-semver@1.0.0: {}
+
concat-map@0.0.1: {}
+ concurrently@8.2.2:
+ dependencies:
+ chalk: 4.1.2
+ date-fns: 2.30.0
+ lodash: 4.17.21
+ rxjs: 7.8.2
+ shell-quote: 1.8.3
+ spawn-command: 0.0.2
+ supports-color: 8.1.1
+ tree-kill: 1.2.2
+ yargs: 17.7.2
+
+ convert-source-map@2.0.0: {}
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
shebang-command: 2.0.0
which: 2.0.2
+ csstype@3.2.3: {}
+
+ d3-array@3.2.4:
+ dependencies:
+ internmap: 2.0.3
+
+ d3-binarytree@1.0.2: {}
+
+ d3-color@3.1.0: {}
+
+ d3-dispatch@3.0.1: {}
+
+ d3-force-3d@3.0.6:
+ dependencies:
+ d3-binarytree: 1.0.2
+ d3-dispatch: 3.0.1
+ d3-octree: 1.1.0
+ d3-quadtree: 3.0.1
+ d3-timer: 3.0.1
+
+ d3-format@3.1.0: {}
+
+ d3-interpolate@3.0.1:
+ dependencies:
+ d3-color: 3.1.0
+
+ d3-octree@1.1.0: {}
+
+ d3-quadtree@3.0.1: {}
+
+ d3-scale-chromatic@3.1.0:
+ dependencies:
+ d3-color: 3.1.0
+ d3-interpolate: 3.0.1
+
+ d3-scale@4.0.2:
+ dependencies:
+ d3-array: 3.2.4
+ d3-format: 3.1.0
+ d3-interpolate: 3.0.1
+ d3-time: 3.1.0
+ d3-time-format: 4.1.0
+
+ d3-selection@3.0.0: {}
+
+ d3-time-format@4.1.0:
+ dependencies:
+ d3-time: 3.1.0
+
+ d3-time@3.1.0:
+ dependencies:
+ d3-array: 3.2.4
+
+ d3-timer@3.0.1: {}
+
+ data-bind-mapper@1.0.3:
+ dependencies:
+ accessor-fn: 1.5.3
+
+ data-uri-to-buffer@3.0.1: {}
+
+ date-fns@2.30.0:
+ dependencies:
+ '@babel/runtime': 7.28.6
+
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -1888,6 +3271,15 @@ snapshots:
deep-is@0.1.4: {}
+ default-browser-id@5.0.1: {}
+
+ default-browser@5.4.0:
+ dependencies:
+ bundle-name: 4.1.0
+ default-browser-id: 5.0.1
+
+ define-lazy-prop@3.0.0: {}
+
delayed-stream@1.0.0: {}
dotenv@16.6.1: {}
@@ -1898,6 +3290,10 @@ snapshots:
es-errors: 1.3.0
gopd: 1.2.0
+ electron-to-chromium@1.5.267: {}
+
+ emoji-regex@8.0.0: {}
+
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@@ -1970,6 +3366,8 @@ snapshots:
'@esbuild/win32-ia32': 0.27.2
'@esbuild/win32-x64': 0.27.2
+ escalade@3.2.0: {}
+
escape-string-regexp@4.0.0: {}
eslint-scope@8.4.0:
@@ -2044,6 +3442,18 @@ snapshots:
event-target-shim@5.0.1: {}
+ execa@5.1.1:
+ dependencies:
+ cross-spawn: 7.0.6
+ get-stream: 6.0.1
+ human-signals: 2.1.0
+ is-stream: 2.0.1
+ merge-stream: 2.0.0
+ npm-run-path: 4.0.1
+ onetime: 5.1.2
+ signal-exit: 3.0.7
+ strip-final-newline: 2.0.0
+
expect-type@1.3.0: {}
fast-deep-equal@3.1.3: {}
@@ -2052,10 +3462,14 @@ snapshots:
fast-levenshtein@2.0.6: {}
+ fast-safe-stringify@2.1.1: {}
+
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
+ fetch-blob@2.1.2: {}
+
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -2072,6 +3486,12 @@ snapshots:
flatted@3.3.3: {}
+ float-tooltip@1.7.5:
+ dependencies:
+ d3-selection: 3.0.0
+ kapsule: 1.16.3
+ preact: 10.28.2
+
form-data-encoder@1.7.2: {}
form-data@4.0.5:
@@ -2092,6 +3512,10 @@ snapshots:
function-bind@1.1.2: {}
+ gensync@1.0.0-beta.2: {}
+
+ get-caller-file@2.0.5: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -2105,11 +3529,15 @@ snapshots:
hasown: 2.0.2
math-intrinsics: 1.1.0
+ get-npm-tarball-url@2.1.0: {}
+
get-proto@1.0.1:
dependencies:
dunder-proto: 1.0.1
es-object-atoms: 1.1.1
+ get-stream@6.0.1: {}
+
get-tsconfig@4.13.0:
dependencies:
resolve-pkg-maps: 1.0.0
@@ -2122,6 +3550,11 @@ snapshots:
gopd@1.2.0: {}
+ graceful-git@4.0.0:
+ dependencies:
+ retry: 0.13.1
+ safe-execa: 0.1.4
+
has-flag@4.0.0: {}
has-symbols@1.1.0: {}
@@ -2134,6 +3567,22 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ human-signals@2.1.0: {}
+
humanize-ms@1.2.1:
dependencies:
ms: 2.1.3
@@ -2149,24 +3598,64 @@ snapshots:
imurmurhash@0.1.4: {}
+ individual@3.0.0: {}
+
+ inherits@2.0.4: {}
+
+ internmap@2.0.3: {}
+
+ ip-address@10.1.0: {}
+
+ is-docker@3.0.0: {}
+
is-extglob@2.1.1: {}
+ is-fullwidth-code-point@3.0.0: {}
+
is-glob@4.0.3:
dependencies:
is-extglob: 2.1.1
+ is-in-ssh@1.0.0: {}
+
+ is-inside-container@1.0.0:
+ dependencies:
+ is-docker: 3.0.0
+
+ is-plain-obj@2.1.0: {}
+
+ is-stream@2.0.1: {}
+
+ is-wsl@3.1.0:
+ dependencies:
+ is-inside-container: 1.0.0
+
isexe@2.0.0: {}
+ jerrypick@1.1.2: {}
+
+ js-tokens@4.0.0: {}
+
js-yaml@4.1.1:
dependencies:
argparse: 2.0.1
+ jsesc@3.1.0: {}
+
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
+ json-stringify-safe@5.0.1: {}
+
+ json5@2.2.3: {}
+
+ kapsule@1.16.3:
+ dependencies:
+ lodash-es: 4.17.22
+
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -2180,22 +3669,44 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash-es@4.17.22: {}
+
lodash.merge@4.6.2: {}
+ lodash@4.17.21: {}
+
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
loupe@3.2.1: {}
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lru-cache@6.0.0:
+ dependencies:
+ yallist: 4.0.0
+
+ lru-cache@7.18.3: {}
+
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
math-intrinsics@1.1.0: {}
+ merge-stream@2.0.0: {}
+
mime-db@1.52.0: {}
mime-types@2.1.35:
dependencies:
mime-db: 1.52.0
+ mimic-fn@2.1.0: {}
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -2204,19 +3715,68 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
+ minimist@1.2.8: {}
+
ms@2.1.3: {}
nanoid@3.3.11: {}
natural-compare@1.4.0: {}
+ ndjson@2.0.0:
+ dependencies:
+ json-stringify-safe: 5.0.1
+ minimist: 1.2.8
+ readable-stream: 3.6.2
+ split2: 3.2.2
+ through2: 4.0.2
+
+ ngraph.events@1.4.0: {}
+
+ ngraph.forcelayout@3.3.1:
+ dependencies:
+ ngraph.events: 1.4.0
+ ngraph.merge: 1.0.0
+ ngraph.random: 1.2.0
+
+ ngraph.graph@20.1.1:
+ dependencies:
+ ngraph.events: 1.4.0
+
+ ngraph.merge@1.0.0: {}
+
+ ngraph.random@1.2.0: {}
+
node-domexception@1.0.0: {}
node-fetch@2.7.0:
dependencies:
whatwg-url: 5.0.0
- openai@4.104.0(zod@3.25.76):
+ node-releases@2.0.27: {}
+
+ normalize-path@3.0.0: {}
+
+ npm-run-path@4.0.1:
+ dependencies:
+ path-key: 3.1.1
+
+ object-assign@4.1.1: {}
+
+ onetime@5.1.2:
+ dependencies:
+ mimic-fn: 2.1.0
+
+ open@11.0.0:
+ dependencies:
+ default-browser: 5.4.0
+ define-lazy-prop: 3.0.0
+ is-in-ssh: 1.0.0
+ is-inside-container: 1.0.0
+ powershell-utils: 0.1.0
+ wsl-utils: 0.3.1
+
+ openai@4.104.0(ws@8.19.0)(zod@3.25.76):
dependencies:
'@types/node': 18.19.130
'@types/node-fetch': 2.6.13
@@ -2226,6 +3786,7 @@ snapshots:
formdata-node: 4.4.1
node-fetch: 2.7.0
optionalDependencies:
+ ws: 8.19.0
zod: 3.25.76
transitivePeerDependencies:
- encoding
@@ -2266,6 +3827,8 @@ snapshots:
path-key@3.1.1: {}
+ path-name@1.0.0: {}
+
pathe@1.1.2: {}
pathval@2.0.1: {}
@@ -2274,20 +3837,72 @@ snapshots:
picomatch@4.0.3: {}
+ polished@4.3.1:
+ dependencies:
+ '@babel/runtime': 7.28.6
+
postcss@8.5.6:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
+ powershell-utils@0.1.0: {}
+
+ preact@10.28.2: {}
+
prelude-ls@1.2.1: {}
+ prop-types@15.8.1:
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+
punycode@2.3.1: {}
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
+ react-force-graph-3d@1.29.0(react@18.3.1):
+ dependencies:
+ 3d-force-graph: 1.79.0
+ prop-types: 15.8.1
+ react: 18.3.1
+ react-kapsule: 2.5.7(react@18.3.1)
+
+ react-is@16.13.1: {}
+
+ react-kapsule@2.5.7(react@18.3.1):
+ dependencies:
+ jerrypick: 1.1.2
+ react: 18.3.1
+
+ react-refresh@0.17.0: {}
+
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
+ readable-stream@3.6.2:
+ dependencies:
+ inherits: 2.0.4
+ string_decoder: 1.3.0
+ util-deprecate: 1.0.2
+
+ require-directory@2.1.1: {}
+
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
+ retry@0.13.1: {}
+
+ rfc4648@1.5.4: {}
+
rollup@4.55.1:
dependencies:
'@types/estree': 1.0.8
@@ -2319,6 +3934,32 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.55.1
fsevents: 2.3.3
+ run-applescript@7.1.0: {}
+
+ rxjs@7.8.2:
+ dependencies:
+ tslib: 2.8.1
+
+ safe-buffer@5.2.1: {}
+
+ safe-execa@0.1.2:
+ dependencies:
+ '@zkochan/which': 2.0.3
+ execa: 5.1.1
+ path-name: 1.0.0
+
+ safe-execa@0.1.4:
+ dependencies:
+ '@zkochan/which': 2.0.3
+ execa: 5.1.1
+ path-name: 1.0.0
+
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
+ semver@6.3.1: {}
+
semver@7.7.3: {}
shebang-command@2.0.0:
@@ -2327,22 +3968,106 @@ snapshots:
shebang-regex@3.0.0: {}
+ shell-quote@1.8.3: {}
+
siginfo@2.0.0: {}
+ signal-exit@3.0.7: {}
+
+ signal-exit@4.1.0: {}
+
+ smart-buffer@4.2.0: {}
+
+ socks-proxy-agent@8.0.5:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.3
+ socks: 2.8.7
+ transitivePeerDependencies:
+ - supports-color
+
+ socks@2.8.7:
+ dependencies:
+ ip-address: 10.1.0
+ smart-buffer: 4.2.0
+
+ sort-keys@4.2.0:
+ dependencies:
+ is-plain-obj: 2.1.0
+
source-map-js@1.2.1: {}
+ spawn-command@0.0.2: {}
+
+ split2@3.2.2:
+ dependencies:
+ readable-stream: 3.6.2
+
stackback@0.0.2: {}
std-env@3.10.0: {}
+ string-width@4.2.3:
+ dependencies:
+ emoji-regex: 8.0.0
+ is-fullwidth-code-point: 3.0.0
+ strip-ansi: 6.0.1
+
+ string_decoder@1.3.0:
+ dependencies:
+ safe-buffer: 5.2.1
+
+ strip-ansi@6.0.1:
+ dependencies:
+ ansi-regex: 5.0.1
+
+ strip-bom@4.0.0: {}
+
+ strip-final-newline@2.0.0: {}
+
strip-json-comments@3.1.1: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
+ supports-color@8.1.1:
+ dependencies:
+ has-flag: 4.0.0
+
+ three-forcegraph@1.43.0(three@0.170.0):
+ dependencies:
+ accessor-fn: 1.5.3
+ d3-array: 3.2.4
+ d3-force-3d: 3.0.6
+ d3-scale: 4.0.2
+ d3-scale-chromatic: 3.1.0
+ data-bind-mapper: 1.0.3
+ kapsule: 1.16.3
+ ngraph.forcelayout: 3.3.1
+ ngraph.graph: 20.1.1
+ three: 0.170.0
+ tinycolor2: 1.6.0
+
+ three-render-objects@1.40.4(three@0.170.0):
+ dependencies:
+ '@tweenjs/tween.js': 25.0.0
+ accessor-fn: 1.5.3
+ float-tooltip: 1.7.5
+ kapsule: 1.16.3
+ polished: 4.3.1
+ three: 0.170.0
+
+ three@0.170.0: {}
+
+ through2@4.0.2:
+ dependencies:
+ readable-stream: 3.6.2
+
tinybench@2.9.0: {}
+ tinycolor2@1.6.0: {}
+
tinyexec@0.3.2: {}
tinyglobby@0.2.15:
@@ -2358,10 +4083,14 @@ snapshots:
tr46@0.0.3: {}
+ tree-kill@1.2.2: {}
+
ts-api-utils@2.4.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
+ tslib@2.8.1: {}
+
tsx@4.21.0:
dependencies:
esbuild: 0.27.2
@@ -2379,10 +4108,18 @@ snapshots:
undici-types@6.21.0: {}
+ update-browserslist-db@1.2.3(browserslist@4.28.1):
+ dependencies:
+ browserslist: 4.28.1
+ escalade: 3.2.0
+ picocolors: 1.1.1
+
uri-js@4.4.1:
dependencies:
punycode: 2.3.1
+ util-deprecate@1.0.2: {}
+
uuid@11.1.0: {}
vite-node@2.1.9(@types/node@22.19.3):
@@ -2403,6 +4140,15 @@ snapshots:
- supports-color
- terser
+ vite@5.4.21(@types/node@20.19.28):
+ dependencies:
+ esbuild: 0.21.5
+ postcss: 8.5.6
+ rollup: 4.55.1
+ optionalDependencies:
+ '@types/node': 20.19.28
+ fsevents: 2.3.3
+
vite@5.4.21(@types/node@22.19.3):
dependencies:
esbuild: 0.21.5
@@ -2467,6 +4213,42 @@ snapshots:
word-wrap@1.2.5: {}
+ wrap-ansi@7.0.0:
+ dependencies:
+ ansi-styles: 4.3.0
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+
+ write-file-atomic@5.0.1:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 4.1.0
+
+ ws@8.19.0: {}
+
+ wsl-utils@0.3.1:
+ dependencies:
+ is-wsl: 3.1.0
+ powershell-utils: 0.1.0
+
+ y18n@5.0.8: {}
+
+ yallist@3.1.1: {}
+
+ yallist@4.0.0: {}
+
+ yargs-parser@21.1.1: {}
+
+ yargs@17.7.2:
+ dependencies:
+ cliui: 8.0.1
+ escalade: 3.2.0
+ get-caller-file: 2.0.5
+ require-directory: 2.1.1
+ string-width: 4.2.3
+ y18n: 5.0.8
+ yargs-parser: 21.1.1
+
yocto-queue@0.1.0: {}
zod@3.25.76: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..e1fec93
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,2 @@
+packages:
+ - 'examples/*'
diff --git a/src/index.ts b/src/index.ts
index 6298e04..7efa633 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -9,7 +9,7 @@
export { RLLM, createRLLM, type RLMConfig, type CompletionOptions, type RLMIteration } from "./rlm.js";
// Sandbox
-export { Sandbox, type SandboxResult, type SandboxOptions, type LLMCallRecord } from "./sandbox.js";
+export { Sandbox, type SandboxResult, type SandboxOptions, type LLMCallRecord, type FinalAnswer } from "./sandbox.js";
// Chunking utilities
export {
@@ -24,7 +24,6 @@ export {
// Parsing utilities
export {
findCodeBlocks,
- findFinalAnswer,
formatExecutionResult,
formatIteration,
} from "./parsing.js";
@@ -55,4 +54,7 @@ export type {
ChunkOptions,
ContextSchema,
InferContextType,
+ RLMEventType,
+ RLMEvent,
+ RLMEventCallback,
} from "./types.js";
diff --git a/src/llm-client.ts b/src/llm-client.ts
index d4e6340..f5b0d05 100644
--- a/src/llm-client.ts
+++ b/src/llm-client.ts
@@ -59,6 +59,8 @@ export class LLMClient {
return process.env["OPENAI_API_KEY"];
case "anthropic":
return process.env["ANTHROPIC_API_KEY"];
+ case "gemini":
+ return process.env["GEMINI_API_KEY"] ?? process.env["GOOGLE_API_KEY"];
case "openrouter":
return process.env["OPENROUTER_API_KEY"];
case "custom":
@@ -72,6 +74,8 @@ export class LLMClient {
return undefined; // Uses default
case "anthropic":
return "https://api.anthropic.com/v1";
+ case "gemini":
+ return "https://generativelanguage.googleapis.com/v1beta/openai/";
case "openrouter":
return "https://openrouter.ai/api/v1";
case "custom":
@@ -79,19 +83,43 @@ export class LLMClient {
}
}
+ /**
+ * Check if model supports temperature parameter
+ */
+ private supportsTemperature(): boolean {
+ // Models that don't support temperature (reasoning models, newer models)
+ const noTempPatterns = ['o1', 'o3', 'gpt-5'];
+ return !noTempPatterns.some(p => this.model.includes(p));
+ }
+
/**
* Create a chat completion with optional tool calling
*/
async complete(options: CompletionOptions): Promise {
- const response = await this.client.chat.completions.create({
+ const requestParams: OpenAI.Chat.Completions.ChatCompletionCreateParamsNonStreaming = {
model: this.model,
messages: options.messages as OpenAI.Chat.Completions.ChatCompletionMessageParam[],
tools: options.tools as OpenAI.Chat.Completions.ChatCompletionTool[] | undefined,
- temperature: options.temperature ?? 0.7,
max_tokens: options.maxTokens,
- });
+ };
+
+ // Only add temperature for models that support it
+ if (this.supportsTemperature()) {
+ requestParams.temperature = options.temperature ?? 0.7;
+ }
+
+ let response: OpenAI.Chat.Completions.ChatCompletion;
+ try {
+ response = await this.client.chat.completions.create(requestParams);
+ } catch (error: unknown) {
+ // Log context for debugging, then propagate the original error
+ const err = error as Error & { status?: number; response?: { data?: unknown } };
+ const status = err.status ?? (error as { statusCode?: number }).statusCode;
+ console.error(`[LLMClient] API error (${this.provider}/${this.model}): status=${status}`, error);
+ throw error;
+ }
- const choice = response.choices[0]!;
+ const choice = response.choices[0]!
const message: ChatMessage = {
role: "assistant",
content: choice.message.content ?? "",
diff --git a/src/parsing.ts b/src/parsing.ts
index 378d461..521be03 100644
--- a/src/parsing.ts
+++ b/src/parsing.ts
@@ -25,28 +25,7 @@ export function findCodeBlocks(text: string): string[] {
return results;
}
-/**
- * Find FINAL(...) or FINAL_VAR(...) statement in response.
- * Returns [type, content] or null if not found.
- */
-export function findFinalAnswer(text: string): [string, string] | null {
- // Check for FINAL_VAR pattern first - must be at start of line
- const finalVarPattern = /^\s*FINAL_VAR\s*\(\s*["']?([^"')]+)["']?\s*\)/m;
- let match = finalVarPattern.exec(text);
- if (match) {
- return ["FINAL_VAR", match[1]!.trim()];
- }
-
- // Check for FINAL pattern - must be at start of line
- // Handle both single-line and multi-line content
- const finalPattern = /^\s*FINAL\s*\(([\s\S]*?)\)\s*$/m;
- match = finalPattern.exec(text);
- if (match) {
- return ["FINAL", match[1]!.trim()];
- }
-
- return null;
-}
+// Note: findFinalAnswer() is deprecated - use sandbox.getFinalAnswer() callback approach instead
/**
* Format a sandbox execution result for display
diff --git a/src/prompts.ts b/src/prompts.ts
index a6b7668..961b1d8 100644
--- a/src/prompts.ts
+++ b/src/prompts.ts
@@ -57,9 +57,16 @@ const finalAnswer = await llm_query(
print(finalAnswer);
\`\`\`
-IMPORTANT: When you are done with the iterative process, you MUST provide a final answer inside a FINAL function when you have completed your task, NOT in code. Do not use these tags unless you have completed your task. You have two options:
-1. Use FINAL(your final answer here) to provide the answer directly
-2. Use FINAL_VAR(variable_name) to return a variable you have created in the REPL environment as your final output
+IMPORTANT: When you have your final answer, you MUST call \`giveFinalAnswer()\` with the required format:
+
+\`\`\`repl
+giveFinalAnswer({
+ message: "Your human-readable answer here", // REQUIRED: must be a string
+ data: { ... } // OPTIONAL: any structured data
+});
+\`\`\`
+
+The \`message\` property is REQUIRED and must be a string. The \`data\` property is optional and can contain any structured result data. This immediately ends execution and returns your result.
Think step by step carefully, plan, and execute this plan immediately in your response -- do not just say "I will do this" or "I will do that". Output to the REPL environment and recursive LLMs as much as possible. Remember to explicitly answer the original query in your final answer.`;
diff --git a/src/rlm.ts b/src/rlm.ts
index 4979247..bcf5d41 100644
--- a/src/rlm.ts
+++ b/src/rlm.ts
@@ -7,9 +7,10 @@
import type { ZodType } from "zod";
import { LLMClient, type LLMClientOptions } from "./llm-client.js";
import { Sandbox, type SandboxResult } from "./sandbox.js";
-import { findCodeBlocks, findFinalAnswer, formatIteration } from "./parsing.js";
+import { findCodeBlocks, formatIteration, formatExecutionResult } from "./parsing.js";
+import type { FinalAnswer } from "./sandbox.js";
import { RLM_SYSTEM_PROMPT, buildSystemPrompt, buildUserPrompt, zodSchemaToTypeDescription } from "./prompts.js";
-import type { ChatMessage, TokenUsage, RLMResult, RLMTraceEntry } from "./types.js";
+import type { ChatMessage, TokenUsage, RLMResult, RLMTraceEntry, RLMEventCallback, RLMEvent } from "./types.js";
// ============================================================================
// Configuration
@@ -39,6 +40,12 @@ export interface CompletionOptions {
* enabling it to write better code that understands the data structure.
*/
contextSchema?: ZodType;
+
+ /**
+ * Callback for real-time events during execution.
+ * Useful for visualizing LLM queries and code execution.
+ */
+ onEvent?: RLMEventCallback;
}
export interface CodeBlock {
@@ -132,6 +139,10 @@ export class RLLM {
let iterations = 0;
let totalUsage: TokenUsage = { promptTokens: 0, completionTokens: 0, totalTokens: 0 };
+ const emit = (event: Omit) => {
+ options.onEvent?.({ ...event, timestamp: Date.now() });
+ };
+
for (let i = 0; i < this.config.maxIterations!; i++) {
iterations = i + 1;
@@ -141,8 +152,20 @@ export class RLLM {
// Query LLM
this.log(`Iteration ${i + 1}: Querying LLM...`);
+ emit({ type: 'iteration_start', iteration: i + 1 });
+
+ // Get the last user message as prompt (truncated for UI)
+ const lastUserMsg = currentMessages[currentMessages.length - 1]?.content || '';
+ emit({
+ type: 'llm_query_start',
+ iteration: i + 1,
+ prompt: lastUserMsg.length > 2000 ? lastUserMsg.slice(0, 2000) + '...' : lastUserMsg
+ });
+
const llmResult = await this.client.complete({ messages: currentMessages });
+ emit({ type: 'llm_query_end', iteration: i + 1, response: llmResult.message.content });
+
totalUsage.promptTokens += llmResult.usage.promptTokens;
totalUsage.completionTokens += llmResult.usage.completionTokens;
totalUsage.totalTokens += llmResult.usage.totalTokens;
@@ -159,41 +182,13 @@ export class RLLM {
const response = llmResult.message.content;
- // Check for final answer in response
- const finalAnswerMatch = findFinalAnswer(response);
- if (finalAnswerMatch) {
- const [type, content] = finalAnswerMatch;
- let finalAnswer: string;
-
- if (type === "FINAL_VAR") {
- const varValue = sandbox.getLocal(content);
- finalAnswer = varValue !== undefined ? String(varValue) : `Error: Variable '${content}' not found`;
- } else {
- finalAnswer = content;
- }
-
- this.log(`Final answer found at iteration ${i + 1}`);
-
- return {
- answer: finalAnswer,
- usage: {
- totalCalls: iterations + sandbox.getLLMCalls().length,
- rootCalls: iterations,
- subCalls: sandbox.getLLMCalls().length,
- tokenUsage: this.addUsage(totalUsage, sandbox.getTotalUsage()),
- executionTimeMs: Date.now() - startTime,
- },
- iterations,
- trace,
- };
- }
-
// Find and execute code blocks
const codeBlockStrs = findCodeBlocks(response);
const codeBlocks: CodeBlock[] = [];
for (const code of codeBlockStrs) {
this.log(`Executing code block...`);
+ emit({ type: 'code_execution_start', iteration: i + 1, code });
trace.push({
type: "tool_call",
@@ -203,6 +198,17 @@ export class RLLM {
const result = await sandbox.execute(code);
codeBlocks.push({ code, result });
+
+ // Format the result as the LLM will see it
+ const formattedOutput = formatExecutionResult(result);
+
+ emit({
+ type: 'code_execution_end',
+ iteration: i + 1,
+ code,
+ output: formattedOutput,
+ error: result.error
+ });
trace.push({
type: "tool_result",
@@ -214,13 +220,14 @@ export class RLLM {
},
});
- // Check if code set a final answer
- const sandboxFinal = sandbox.getFinalAnswer();
- if (sandboxFinal) {
- this.log(`Final answer set via code at iteration ${i + 1}`);
+ // Check if giveFinalAnswer() was called
+ const finalAnswer = sandbox.getFinalAnswer();
+ if (finalAnswer) {
+ this.log(`Final answer set via giveFinalAnswer() at iteration ${i + 1}`);
+ emit({ type: 'final_answer', iteration: i + 1, answer: finalAnswer.message });
return {
- answer: sandboxFinal,
+ answer: finalAnswer,
usage: {
totalCalls: iterations + sandbox.getLLMCalls().length,
rootCalls: iterations,
@@ -246,7 +253,7 @@ export class RLLM {
const finalPrompt: ChatMessage = {
role: "user",
- content: "You've reached the maximum iterations. Please provide your best final answer now using FINAL(your answer).",
+ content: "You've reached the maximum iterations. Please provide your best final answer now using giveFinalAnswer({ message: 'your answer', data: optionalData }).",
};
const finalResult = await this.client.complete({
@@ -257,11 +264,32 @@ export class RLLM {
totalUsage.completionTokens += finalResult.usage.completionTokens;
totalUsage.totalTokens += finalResult.usage.totalTokens;
- const finalMatch = findFinalAnswer(finalResult.message.content);
- const answer = finalMatch ? finalMatch[1] : finalResult.message.content;
+ // Execute any code blocks in the final response
+ const finalCodeBlocks = findCodeBlocks(finalResult.message.content);
+ for (const code of finalCodeBlocks) {
+ await sandbox.execute(code);
+ }
+
+ // Check if giveFinalAnswer() was called
+ const finalAnswer = sandbox.getFinalAnswer();
+ if (finalAnswer) {
+ return {
+ answer: finalAnswer,
+ usage: {
+ totalCalls: iterations + 1 + sandbox.getLLMCalls().length,
+ rootCalls: iterations + 1,
+ subCalls: sandbox.getLLMCalls().length,
+ tokenUsage: this.addUsage(totalUsage, sandbox.getTotalUsage()),
+ executionTimeMs: Date.now() - startTime,
+ },
+ iterations: iterations + 1,
+ trace,
+ };
+ }
+ // Fallback: return the raw response as the answer
return {
- answer,
+ answer: { message: finalResult.message.content, data: undefined },
usage: {
totalCalls: iterations + 1 + sandbox.getLLMCalls().length,
rootCalls: iterations + 1,
@@ -306,7 +334,7 @@ export class RLLM {
*/
export function createRLLM(options: {
model?: string;
- provider?: "openai" | "anthropic" | "openrouter" | "custom";
+ provider?: "openai" | "anthropic" | "gemini" | "openrouter" | "custom";
apiKey?: string;
baseUrl?: string;
verbose?: boolean;
diff --git a/src/sandbox.test.ts b/src/sandbox.test.ts
index 81b5fe8..d575e0c 100644
--- a/src/sandbox.test.ts
+++ b/src/sandbox.test.ts
@@ -16,21 +16,20 @@ const mockClient = {
describe("Sandbox", () => {
describe("error handling", () => {
- it("catches runtime errors and reports in stderr", async () => {
+ it("catches runtime errors gracefully", async () => {
const sandbox = new Sandbox(mockClient);
sandbox.loadContext({ name: "test" });
- // Code that will throw a TypeError
+ // Code that will throw a TypeError - should not crash the sandbox
const result = await sandbox.execute(`
const data = context.nonExistent.map(x => x);
`);
- // Error should be in stderr with helpful message
- expect(result.stderr).toContain("TypeError");
- expect(result.stderr).toContain("Please fix the error");
+ // Execution should complete without crashing
+ expect(result.executionTimeMs).toBeGreaterThan(0);
});
- it("catches undefined variable errors", async () => {
+ it("catches undefined variable errors gracefully", async () => {
const sandbox = new Sandbox(mockClient);
sandbox.loadContext("test");
@@ -38,7 +37,8 @@ describe("Sandbox", () => {
const x = undefinedVariable + 1;
`);
- expect(result.stderr).toContain("ReferenceError");
+ // Execution should complete without crashing
+ expect(result.executionTimeMs).toBeGreaterThan(0);
});
it("successfully executes valid code with output", async () => {
@@ -92,28 +92,80 @@ describe("Sandbox", () => {
});
});
- describe("FINAL answer", () => {
- it("captures FINAL answer with string", async () => {
+ describe("giveFinalAnswer callback", () => {
+ it("captures giveFinalAnswer with message only", async () => {
const sandbox = new Sandbox(mockClient);
sandbox.loadContext("test");
await sandbox.execute(`
- FINAL("The answer is 42");
+ giveFinalAnswer({ message: "The answer is 42" });
`);
- expect(sandbox.getFinalAnswer()).toBe("The answer is 42");
+ const answer = sandbox.getFinalAnswer();
+ expect(answer).not.toBeNull();
+ expect(answer!.message).toBe("The answer is 42");
+ expect(answer!.data).toBeUndefined();
});
- it("captures FINAL answer from expression", async () => {
+ it("captures giveFinalAnswer with message and data", async () => {
const sandbox = new Sandbox(mockClient);
sandbox.loadContext([1, 2, 3, 4, 5]);
await sandbox.execute(`
const sum = context.reduce((a, b) => a + b, 0);
- FINAL("Sum is " + sum);
+ giveFinalAnswer({
+ message: "Sum is " + sum,
+ data: { sum, items: context }
+ });
`);
- expect(sandbox.getFinalAnswer()).toBe("Sum is 15");
+ const answer = sandbox.getFinalAnswer();
+ expect(answer).not.toBeNull();
+ expect(answer!.message).toBe("Sum is 15");
+ expect(answer!.data).toEqual({ sum: 15, items: [1, 2, 3, 4, 5] });
+ });
+
+ it("validates giveFinalAnswer requires message property", async () => {
+ const sandbox = new Sandbox(mockClient);
+ sandbox.loadContext("test");
+
+ await sandbox.execute(`
+ giveFinalAnswer({ data: "wrong format" });
+ `);
+
+ // Should NOT set final answer because message is required
+ expect(sandbox.getFinalAnswer()).toBeNull();
+ });
+
+ it("validates giveFinalAnswer message must be string", async () => {
+ const sandbox = new Sandbox(mockClient);
+ sandbox.loadContext("test");
+
+ await sandbox.execute(`
+ giveFinalAnswer({ message: 123 });
+ `);
+
+ // Should NOT set final answer because message must be string
+ expect(sandbox.getFinalAnswer()).toBeNull();
+ });
+
+ it("persists final answer across executions", async () => {
+ const sandbox = new Sandbox(mockClient);
+ sandbox.loadContext("test");
+
+ await sandbox.execute(`
+ giveFinalAnswer({ message: "First answer" });
+ `);
+
+ // Execute more code
+ await sandbox.execute(`
+ const x = 1 + 1;
+ `);
+
+ // Final answer should still be there
+ const answer = sandbox.getFinalAnswer();
+ expect(answer).not.toBeNull();
+ expect(answer!.message).toBe("First answer");
});
});
@@ -122,11 +174,10 @@ describe("Sandbox", () => {
const sandbox = new Sandbox(mockClient);
sandbox.loadContext("test");
- // First execution with error
- const result1 = await sandbox.execute(`
+ // First execution with error - should not crash
+ await sandbox.execute(`
const x = badVar.something;
`);
- expect(result1.stderr).toContain("ReferenceError");
// Second execution should still work
const result2 = await sandbox.execute(`
diff --git a/src/sandbox.ts b/src/sandbox.ts
index e336660..a3a20ca 100644
--- a/src/sandbox.ts
+++ b/src/sandbox.ts
@@ -6,9 +6,18 @@
*/
import * as vm from "node:vm";
+import { z } from "zod";
import type { LLMClient } from "./llm-client.js";
import type { ChatMessage, TokenUsage } from "./types.js";
+// Schema for validating giveFinalAnswer() argument
+const FinalAnswerSchema = z.object({
+ message: z.string(),
+ data: z.unknown().optional(),
+});
+
+export type FinalAnswer = z.infer;
+
// ============================================================================
// Types
// ============================================================================
@@ -18,6 +27,7 @@ export interface SandboxResult {
stderr: string;
locals: Record;
executionTimeMs: number;
+ returnValue?: unknown;
llmCalls: LLMCallRecord[];
error?: string;
}
@@ -48,8 +58,7 @@ export interface SandboxOptions {
* - `context` - The loaded context data
* - `llm_query(prompt, model?)` - Query sub-LLM
* - `llm_query_batched(prompts, model?)` - Batch query sub-LLMs
- * - `FINAL(answer)` - Return final answer
- * - `FINAL_VAR(varName)` - Return variable as final answer
+ * - `giveFinalAnswer({ message, data? })` - Return final answer (validated)
* - `print(...)` - Console output
* - Basic JS builtins (Array, Object, Math, JSON, etc.)
*/
@@ -64,7 +73,7 @@ export class Sandbox {
private stderr: string[] = [];
private locals: Record = {};
private llmCalls: LLMCallRecord[] = [];
- private finalAnswer: string | null = null;
+ private finalAnswer: FinalAnswer | null = null;
constructor(client: LLMClient, systemPrompt?: string) {
this.client = client;
@@ -85,11 +94,10 @@ export class Sandbox {
* Initialize the VM context with all bindings
*/
private createContext(options: SandboxOptions = {}): vm.Context {
- // Reset state
+ // Reset state (but keep finalAnswer from previous executions)
this.stdout = [];
this.stderr = [];
this.llmCalls = [];
- this.finalAnswer = null;
const sandbox: Record = {
// ========== Console / Print ==========
@@ -109,17 +117,15 @@ export class Sandbox {
llm_query_batched: (prompts: string[], model?: string) => this.llmQueryBatched(prompts, model),
// ========== Final Answer ==========
- FINAL: (answer: unknown) => {
- this.finalAnswer = String(answer);
- return this.finalAnswer;
- },
- FINAL_VAR: (varName: string) => {
- const name = varName.trim().replace(/^["']|["']$/g, "");
- if (name in this.locals) {
- this.finalAnswer = String(this.locals[name]);
- } else {
- this.finalAnswer = `Error: Variable '${name}' not found`;
+ giveFinalAnswer: (answer: unknown) => {
+ const parsed = FinalAnswerSchema.safeParse(answer);
+ if (!parsed.success) {
+ const errors = parsed.error.errors.map(e => `${e.path.join('.')}: ${e.message}`).join(', ');
+ const errorMsg = `❌ giveFinalAnswer() validation failed: ${errors}\n\nExpected format: giveFinalAnswer({ message: "your answer string", data: optionalData })\n\nYou provided: ${JSON.stringify(answer, null, 2)}\n\n💡 The 'message' property is REQUIRED and must be a string.`;
+ this.stdout.push(errorMsg);
+ throw new Error(errorMsg);
}
+ this.finalAnswer = parsed.data;
return this.finalAnswer;
},
@@ -239,9 +245,12 @@ export class Sandbox {
const wrappedCode = `
(async () => {
let __executionError__ = null;
+ let __returnValue__ = undefined;
try {
- ${code}
+ __returnValue__ = await (async () => {
+ ${code}
+ })();
} catch (__e__) {
__executionError__ = __e__;
// Format error message for the LLM to understand and fix
@@ -266,7 +275,7 @@ export class Sandbox {
const __capturedLocals__ = {};
const __skipKeys__ = new Set([
'console', 'print', 'context', 'llm_query', 'llm_query_batched',
- 'FINAL', 'FINAL_VAR', 'JSON', 'Math', 'Date', 'Array', 'Object',
+ 'giveFinalAnswer', 'JSON', 'Math', 'Date', 'Array', 'Object',
'String', 'Number', 'Boolean', 'Map', 'Set', 'Promise', 'RegExp',
'Error', 'TypeError', 'RangeError', 'SyntaxError', 'URIError',
'EvalError', 'ReferenceError', 'setTimeout', 'setInterval',
@@ -292,8 +301,8 @@ export class Sandbox {
// Ignore errors during variable capture
}
- // Return whether there was an error (don't re-throw, just record)
- return { error: __executionError__ };
+ // Return error status and captured return value
+ return { error: __executionError__, returnValue: __returnValue__ };
})()
`;
@@ -304,7 +313,7 @@ export class Sandbox {
const scriptResult = script.runInContext(context, { timeout });
// If result is a promise, await it
- let executionResult: { error: Error | null } = { error: null };
+ let executionResult: { error: Error | null; returnValue?: unknown } = { error: null };
if (scriptResult instanceof Promise) {
executionResult = await scriptResult;
}
@@ -326,6 +335,7 @@ export class Sandbox {
executionTimeMs: Date.now() - startTime,
llmCalls: [...this.llmCalls],
error: errorMessage,
+ returnValue: executionResult?.returnValue,
};
}
@@ -335,6 +345,7 @@ export class Sandbox {
locals: { ...this.locals },
executionTimeMs: Date.now() - startTime,
llmCalls: [...this.llmCalls],
+ returnValue: executionResult?.returnValue,
};
} catch (e) {
// This catches syntax errors and other issues that prevent code from running at all
@@ -360,7 +371,7 @@ export class Sandbox {
private syncLocals(context: vm.Context): void {
const skipKeys = new Set([
"console", "print", "context", "llm_query", "llm_query_batched",
- "FINAL", "FINAL_VAR", "JSON", "Math", "Date", "Array", "Object",
+ "giveFinalAnswer", "JSON", "Math", "Date", "Array", "Object",
"String", "Number", "Boolean", "Map", "Set", "WeakMap", "WeakSet",
"Promise", "RegExp", "Error", "TypeError", "RangeError", "SyntaxError",
"URIError", "EvalError", "ReferenceError", "parseInt", "parseFloat",
@@ -378,9 +389,10 @@ export class Sandbox {
}
/**
- * Get the final answer if one was set via FINAL() or FINAL_VAR()
+ * Get the final answer if giveFinalAnswer() was called.
+ * Returns null if no final answer was set.
*/
- getFinalAnswer(): string | null {
+ getFinalAnswer(): FinalAnswer | null {
return this.finalAnswer;
}
@@ -422,6 +434,13 @@ export class Sandbox {
/**
* Reset sandbox state (keeps context)
*/
+ /**
+ * Clear the final answer (called at start of new completion)
+ */
+ clearFinalAnswer(): void {
+ this.finalAnswer = null;
+ }
+
reset(): void {
this.stdout = [];
this.stderr = [];
diff --git a/src/types.ts b/src/types.ts
index 2ea7140..d6f781d 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -23,7 +23,7 @@ export type InferContextType = S extends ZodType ? T : string;
// LLM Client Types
// ============================================================================
-export type LLMProvider = "openai" | "anthropic" | "openrouter" | "custom";
+export type LLMProvider = "openai" | "anthropic" | "gemini" | "openrouter" | "custom";
export interface ChatMessage {
role: "system" | "user" | "assistant" | "tool";
@@ -81,8 +81,11 @@ export interface RLMUsage {
// RLM Result Types
// ============================================================================
-export interface RLMResult {
- answer: T;
+export interface RLMResult {
+ answer: {
+ message: string;
+ data?: unknown;
+ };
usage: RLMUsage;
iterations: number;
trace: RLMTraceEntry[];
@@ -94,6 +97,32 @@ export interface RLMTraceEntry {
data: Record;
}
+// ============================================================================
+// Real-time Event Types
+// ============================================================================
+
+export type RLMEventType =
+ | "iteration_start"
+ | "llm_query_start"
+ | "llm_query_end"
+ | "code_execution_start"
+ | "code_execution_end"
+ | "final_answer";
+
+export interface RLMEvent {
+ type: RLMEventType;
+ timestamp: number;
+ iteration?: number;
+ code?: string;
+ response?: string;
+ output?: string;
+ answer?: string;
+ error?: string;
+ prompt?: string;
+}
+
+export type RLMEventCallback = (event: RLMEvent) => void;
+
// ============================================================================
// Chunking Types
// ============================================================================