Ghost Panel

Ghost Panel.

A self-aware control panel for the web. Drops into any Three.js scene, 2D canvas, or DOM page — auto-detects what's there, mounts the right controls, and stays out of the way until you press Shift+D.

v0.1 MIT GitHub →

Introduction

Ghost Panel is a single function — createGhostPanel(options) — that walks your project on load, registers every named mesh / light / camera / DOM adapter it finds, and surfaces the right controls on selection.

It's framework-agnostic at the core, ships first-party adapters for React, Solid, Svelte, and Vue, and has a UMD bundle for plain <script> tags. Three.js is an optional peer dependency.

Install

Not published to npm yet — install from GitHub:

npm install github:epun/ghost-panel
npm install three     # peer dep, only if you're using Three.js

Or load the UMD bundle directly:

<script src="https://cdn.jsdelivr.net/gh/epun/ghost-panel@main/dist/ghost-panel.umd.js"></script>

Vite: add three to resolve.dedupe so your app and Ghost Panel share one Three.js instance — without it you get a duplicate-instance warning and instanceof checks that fail across the boundary.

// vite.config.js
export default { resolve: { dedupe: ['three'] } };

Quick start

Pass your scene + camera + renderer. Ghost Panel auto-detects 3D, registers everything in the scene, and mounts the right workflows.

import { createGhostPanel } from 'ghost-panel';

const ui = createGhostPanel({
  scene,
  camera,
  renderer,
  title: 'Inspector',
});

Open localhost. Press Shift+D to toggle panels. Click any object to inspect it. Shift+A opens the add menu.

Frameworks

The core ESM is framework-agnostic. First-party adapters wire lifecycle for every popular front-end. Pick yours:


    

API reference

Every method on the ui handle returned by createGhostPanel(options). Options are documented inline.

createGhostPanel(options) → UI
Mount the panel. Returns a handle with every method below.
OptionTypeDescription
sceneTHREE.SceneAuto-detects 3D workflow + scans for objects.
cameraTHREE.CameraUsed by the click-to-select raycaster and gizmo.
rendererTHREE.WebGLRendererSource of the canvas for pointer events.
controlsOrbitControlsOptional — focus / POV camera switches sync target here.
workflowstring[]Force-enable specific workflows (skips auto-detection).
workflowOptsobjectPer-workflow config — background, backgroundTargets, tracks, uniforms, etc.
titlestringInspector panel title (default 'Inspector').
scenePanelbooleanShow the left scene/outliner panel (default true).
visiblebooleanInitial visibility (default true).
liquidGlassbooleanApple-style backdrop blur skin.
ui.addFolder(name, opts?) → Folder
Add a collapsible folder. Returns a fluent Folder with addSlider, addColor, addCheckbox, addSelect, addCurveEditor, addVec3, addDial, addButton, addTrigger, addSequence, and more. Every value control auto-records undo.
ui.getFolder(name) → Folder
Look up an existing folder (auto-added by a workflow, or by an earlier addFolder call).
ui.objectManager.select(name, { additive? }) → void
Select an object. Pass { additive: true } to shift-click style add it to the multi-selection.
ui.objectManager.getSelectedNames() → string[]
Every currently-selected name (primary first/last in insertion order).
ui._group(names?) → string
Group the named (or currently multi-selected) objects under a new THREE.Group. Children keep their world transform. Pushes undo. Bound to ⌘G.
ui._ungroup(name) → string[]
Lift every direct child of the named group back to the scene root. Inverse of _group. Bound to ⇧⌘G.
ui._copy(name?) · ui._paste() · ui._duplicate(name?)
Programmatic clipboard. Bound to ⌘C / ⌘V / ⌘D. Three.js Object3Ds round-trip via .clone(); plain 2D shapes deep-clone the bag-of-numbers.
ui._undo.push(command) · ui._undo.undo() · ui._undo.redo()
Workflow-agnostic undo stack. A command is { undo(): void, redo(): void, label?: string, coalesceKey?: string }. Cmd+Z / Cmd+Shift+Z are wired by default.
ui.show() · ui.hide() · ui.toggle() · ui.bindToggleKey(key, opts)
Visibility control. The default toggle key is Shift+D.
ui.dispose() → void
Tear down every panel, listener, and observer. Safe to call multiple times.

Agent control (MCP)

Point Claude Code — or any MCP client — at a running panel and let it read the scene and drive it. The bridge is opt-in, loopback-only, and dev-only: nothing is exposed unless you ask for it.

1. Start the bridge. It prints a token.

npm install @modelcontextprotocol/sdk   # optional dep, server-side only
npx ghost-panel-mcp

2. Attach it in your dev build.

import { attachMCPBridge } from 'ghost-panel/mcp-bridge';

const ui = createGhostPanel({ scene, camera, renderer });

if (import.meta.env.DEV) {
  attachMCPBridge(ui, { token: 'paste-from-the-server' });
}

3. Register it with your client.

claude mcp add ghost-panel -- npx ghost-panel-mcp

Your agent now has seventeen tools against the live page.

Reads
describe_skills · suggest_skills · get_scene_tree · get_object · list_materials · get_panel_state · get_diagnostics · screenshot
Writes
select_object · set_transform · set_control · apply_skill · assign_material · set_camera · focus_object · undo · redo

Every write goes through the same public API a click would, so an agent's edit lands on your undo stack and Cmd+Z takes it back. Nothing ships executable code into the page — register_skill is deliberately absent from the MCP surface, because a skill carries apply() and teardown() function bodies and registering one remotely would be arbitrary code execution in your browser.

attachMCPBridge(ui, options) → Bridge
Connect a live panel to the bridge server. Returns a handle, also stored on ui.mcp: connected, readOnly, url, dispose().
OptionTypeDescription
urlstringBridge origin (default http://127.0.0.1:7391). Loopback only — anything else throws.
tokenstringShared token printed by the server. Omit only when it runs with --no-token.
readOnlybooleanServe reads and refuse every write. Inspection without control.
confirmfunction(tool, args) => boolean. Called before each mutating tool; return false to veto it. Reads are never gated.

The transport is Server-Sent Events down and fetch back up — both native to the browser — so the library still ships with no runtime dependencies. Only the Node server needs the MCP SDK, and it's an optional peer, so an ordinary install never pulls it in.

Live demos

Five runnable examples. Each one boots Ghost Panel against a different host: