This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
VSCode extension for Julia Pluto notebooks. Integrates with @plutojl/rainbow package for Pluto backend communication. Extension activates on .jl files and provides notebook interface for Pluto notebooks.
npm run compile # Type check, lint, and build once
npm run watch # Watch mode (runs esbuild + tsc in parallel)
npm run package # Production build (minified, no sourcemaps)npm run check-types # TypeScript type checking only
npm run lint # ESLintnpm run test # Run all tests
npm run compile-tests # Compile tests to out/ directory
npm run watch-tests # Watch mode for testsPress F5 in VSCode to launch Extension Development Host window for testing.
Extension Entry Point (src/extension.ts)
- Activates on
onNotebook:pluto-notebookevent - Registers
PlutoNotebookSerializerfor.jlfile handling - Registers
PlutoNotebookControllerfor cell execution - All components added to context subscriptions for proper disposal
Notebook Serializer (src/serializer.ts)
- Implements
vscode.NotebookSerializerinterface - Parses Pluto .jl format with cell markers (
# ╔═╡), metadata, and reactive dependencies - Converts between Pluto format and VSCode
NotebookData/NotebookCellData - Handles markdown cells wrapped in
md"""...""" - Supports both
.pluto.jland.dyad.jlfile patterns
Notebook Controller (src/controller.ts)
- Implements notebook controller for cell execution
- Controller ID:
pluto-notebook-controller - Notebook type:
pluto-notebook - Supported language:
julia - Integrates with
@plutojl/rainbowfor real Pluto backend execution - Handles reactive cell updates and dependencies
- Supports rich output rendering via custom notebook renderer
PlutoManager (src/plutoManager.ts)
- Manages Pluto server lifecycle (start, stop, restart)
- Creates and caches notebook workers
- Handles notebook opening, closing, and execution
- Supports both spawned server and VSCode task-based server
- Shared between extension and MCP server for consistent state
MCP HTTP Server (src/mcpHttpServer.ts)
- HTTP-based MCP server for AI assistant integration
- 12 tools for notebook management and code execution
- Server-Sent Events (SSE) for streaming responses
- Health check endpoint for monitoring
- Shared PlutoManager instance with extension
Interactive Terminal (src/plutoTerminal.ts)
- Pseudoterminal implementation for Julia code execution
- Command history with arrow key navigation
- Rich output rendering in webviews
- Special commands (.help, .connect, .status, etc.)
- Ephemeral execution without creating notebook cells
Uses esbuild (esbuild.js) for bundling:
- Entry point:
src/extension.ts - Output:
dist/extension.js(CommonJS format) - External:
vscodemodule (provided by VSCode runtime) - Production: minified with no sourcemaps
- Development: sourcemaps enabled, watch mode available
TypeScript compilation targets Node16 module system and ES2022 with strict mode enabled.
- Pluto File Format: Pluto notebooks are Julia files with special cell markers and metadata, not simple JSON
- Reactive Evaluation: Pluto uses reactive cell execution model - cells re-run when dependencies change
- @plutojl/rainbow: Package for communicating with Pluto server; needs integration in controller
- Notebook Type: Registered as
pluto-notebookwith.jlfile pattern selector
IMPORTANT: Always import the node polyfill first:
import "@plutojl/rainbow/node-polyfill";Key API Patterns:
-
Creating Worker: Always trim notebook content before passing to
createWorker:const worker = await host.createWorker(notebookContent.trim()); await worker.connect();
-
Executing Cells:
- For NEW cells:
worker.waitSnippet(index, code)- takes index (number), returns CellResultData - For EXISTING cells:
worker.updateSnippetCode(cellId, code, run)thenworker.wait(true)thenworker.getSnippet(cellId)
- For NEW cells:
-
Getting Cell Data:
worker.getSnippet(cellId)returns{ input: CellInputData, results: CellResultData }or null- Use
cellData?.resultsto access the output
-
Minimal Valid Pluto Notebook Format:
### A Pluto.jl notebook ### # v0.19.40 using Markdown using InteractiveUtils # ╔═╡ <cell-uuid> md""" # Notebook Title """ # ╔═╡ Cell order: # ╟─<cell-uuid>
-
Markdown Cells:
- Pluto markdown cells are wrapped in
md"""...""" - When deserializing, extract content from wrapper
- When serializing, wrap markdown content back
- Pluto markdown cells are wrapped in
The extension is functional and includes:
- ✅ Pluto notebook serializer and controller (registered and functional)
- ✅ PlutoManager for server lifecycle management
- ✅ Integration with @plutojl/rainbow for Pluto backend communication
- ✅ Real cell execution with reactive updates
- ✅ Rich output rendering (HTML, plots, images) via notebook renderer
- ✅ Interactive terminal for ephemeral code execution
- ✅ MCP HTTP server for AI assistant integration
- ✅ VSCode task integration for Pluto server
- ✅ Shared state between extension and MCP clients
- ✅ Extension can be debugged with F5
- ✅ Build system works correctly
Completed:
- Pluto server management with automatic lifecycle handling
- Real-time cell execution using @plutojl/rainbow
- Rich output rendering in notebooks and terminal webviews
- MCP server with 12 tools for AI assistants
- Interactive terminal with command history
- Configuration commands for Claude Desktop and GitHub Copilot
In Progress:
- Full Pluto .jl file format parsing in serializer (currently basic implementation)
- Complete reactive evaluation model support
- Enhance Pluto .jl file format parsing in serializer
- Improve reactive dependency tracking
- Add more PlutoUI component support
- Enhance terminal capabilities (syntax highlighting, tab completion)
- Add notebook diff/merge tools
import "@plutojl/rainbow/node-polyfill";
import { spawn, ChildProcess } from "child_process";
import {
Host,
Worker,
from_julia,
getResult,
resolveIncludes,
} from "@plutojl/rainbow";
import fs from "fs";
function runServer(port: number = 1234): Promise<ChildProcess> {
return new Promise((resolve, reject) => {
const julia = spawn("julia", [
"-e",
`using Pluto;Pluto.run(port=${port};require_secret_for_open_links=false, require_secret_for_access=false, launch_browser=false)`,
]);
julia.stdout?.on("data", (data) => {
console.log(`Julia stdout: ${data}`);
if (data.toString().includes("Go to")) {
resolve(julia);
}
});
julia.stderr?.on("data", (data) => {
const chunk = `${data}`;
if (chunk.includes(port.toFixed(0))) {
resolve(julia);
}
});
julia.on("error", reject);
});
}
async function runAnalysis(
entryPoint: string = "../src/ElectricalComponents.jl",
analysisName: string
): Promise<any> {
const host = new Host("http://localhost:1234");
try {
const notebook = from_julia(resolveIncludes(fs, entryPoint), {
DyadEcosystemDependencies: {
uuid: "7bc808db-8006-421e-b546-062440d520b7",
compat: "=0.10.3",
},
});
console.log(`Notebook\n${notebook}`);
fs.writeFileSync("notebook.jl", notebook);
const worker = await host.createWorker(notebook.trim());
await worker.connect();
const result = await worker.waitSnippet(
0,
`begin
solution = ${analysisName}()
Dict(string(key) => DyadInterface.artifacts(solution, key) for key in DyadInterface.artifacts(solution))
end`
);
return result;
} catch (error) {
console.error(`Failed to run analysis ${analysisName}:`, error);
throw error;
}
}
export { runServer, runAnalysis };