Imported from jzhoulab/nebula-notebook (
AGENTS.md). Install upstream withnpx skills add jzhoulab/nebula-notebook. Copyright stays with the author.
AGENTS.md
This file provides guidance to Codex (Codex.ai/code) when working with code in this repository.
Project Overview
Nebula Notebook is a web-based Jupyter notebook alternative with AI integration. It uses real Jupyter kernels via ZeroMQ, supports multiple LLM providers (Gemini, OpenAI, Anthropic), and provides real filesystem access. The backend is Node.js/TypeScript with Express, communicating with Jupyter kernels via the ZeroMQ protocol.
Development Commands
# Install dependencies
npm install # Frontend + backend dependencies
cd node-server && npm install # Backend dependencies
# Run the application
npm run dev # Development mode with hot reload (Vite on :3000, Node on :8000)
npm run start # Alias for npm run dev
npm run prod # Production mode (Node.js on :3000 only)
# Build
npm run build # Production build (frontend + backend)
npm run preview # Preview production build
# Testing
npm test # Run all frontend tests (vitest)
npm test -- --watch # Watch mode for TDD
cd node-server && npm test # Run backend tests
Architecture
Frontend (React 19 + TypeScript + Vite)
Core Notebook Components:
components/Notebook.tsx- Main notebook container: multi-tab management, file I/O, kernel sessions, undo/redo, search, inline renamecomponents/Cell.tsx- Individual cell: execution controls, AI generation, queue position, output scrollingcomponents/CodeEditor.tsx- CodeMirror 6 editor: syntax highlighting, kernel-based completion, indentation detectioncomponents/CellOutput.tsx- Output rendering: stdout/stderr/error/HTML/image, truncation, resizable collapsed statecomponents/VirtualCellList.tsx- React Virtuoso for large notebook performance (1000px overscan)
Dashboard & File Browser:
components/Dashboard.tsx- Landing page: recent notebooks, file browser, active kernels, terminals, tipscomponents/FileBrowser.tsx- File/folder browser: sidebar and inline variants, create/delete/rename/upload/downloadcomponents/FileListItem.tsx- File list item: icons, metadata, inline rename, context actions
Terminal:
components/TerminalPage.tsx- Full-screen terminal with persistent named terminalscomponents/TerminalInstance.tsx- xterm.js integration with WebSocket PTY connectioncomponents/TerminalPanel.tsx- Embedded terminal panel in notebook view
AI & Chat:
components/AIChatSidebar.tsx- AI assistant: full notebook context, multi-message history, image support
Utilities & Dialogs:
components/SettingsModal.tsx- Settings: General, AI, Appearance, Notifications tabscomponents/KernelManager.tsx- Kernel management: list sessions, memory usage, interrupt/restartcomponents/HistoryPanel.tsx- Notebook history: timeline view, preview, restore to any pointcomponents/NotebookSearch.tsx- Find & replace: regex, case-sensitive, navigate matchescomponents/AuthGate.tsx/TOTPLogin.tsx- 2FA authentication
Services:
services/kernelService.ts- Multi-session kernel management, WebSocket streaming, code completionservices/fileService.ts- Filesystem operations, notebook I/O, history/session persistenceservices/llmService.ts- Multi-provider LLM client (Google, OpenAI, Anthropic)services/authService.ts- JWT token management for 2FAservices/terminalService.ts- Terminal session managementservices/clusterService.ts- Multi-server clustering support
Hooks:
hooks/useUndoRedo.ts- Operation-based notebook history (insert/delete/move/update cells)hooks/useAutosave.ts- Debounced autosave (1s) with conflict detectionhooks/useConflictResolution.ts- mtime-based conflict handling
Utilities:
utils/notebookAvatar.ts- Deterministic notebook avatarsutils/indentationDetector.ts- Auto-detect code indentationlib/notebookOperations.ts- Pure functions for notebook statelib/diffUtils.ts- Diff and patch utilities
Backend (Node.js + TypeScript + Express)
Entry & Config:
node-server/src/index.ts- Express server, WebSocket setup, auth initialization
Routes:
routes/auth.ts- 2FA authentication endpointsroutes/kernel.ts- Kernel session management and WebSocketroutes/notebook.ts- Notebook cells, history, session, agent permissionsroutes/fs.ts- Filesystem operations (list, read, write, rename, delete, upload, download)routes/llm.ts- LLM generation endpointsroutes/python.ts- Python environment discoveryroutes/cluster.ts- Multi-server clustering
Services:
kernel/kernel-service.ts- Jupyter kernel management via ZeroMQkernel/kernelspec.ts- Kernel discoveryfs/fs-service.ts- Filesystem operations, notebook I/O, metadata preservationllm/llm-service.ts- Multi-provider LLM abstractionauth/auth-service.ts- TOTP 2FA and JWT managementauth/auth-middleware.ts- Route and WebSocket authenticationterminal/pty-manager.ts- PTY session managementdiscovery/discovery-service.ts- Python environment discovery
Key API Endpoints
Authentication (unprotected)
GET /api/auth/status- Check 2FA config and auth statusPOST /api/auth/verify- Verify TOTP code, get JWT token
Kernels (protected)
GET /api/kernels- List available kernelspecsPOST /api/kernels/start- Start kernel sessionPOST /api/kernels/for-file- Get or create kernel for notebook (one notebook = one kernel)WS /api/kernels/{id}/ws- WebSocket for kernel I/O and completionPOST /api/kernels/{id}/interrupt- Interrupt executionPOST /api/kernels/{id}/restart- Restart kernelGET /api/kernels/sessions- List active sessions with memory usage
Notebook (protected)
GET /api/notebook/cells- Get cells + kernelspec + mtimePOST /api/notebook/save- Save notebook cellsGET/POST /api/notebook/history- Load/save operation historyGET/POST /api/notebook/session- Load/save editing stateGET /api/notebook/agent-status- Check agent permissionsPOST /api/notebook/permit-agent- Grant/revoke agent accessWS /api/notebook/{path}/ws- Real-time notebook sync
Filesystem (protected)
GET /api/fs/list- List directory contentsGET /api/fs/read- Read file contentsGET /api/fs/download- Download file (raw stream)POST /api/fs/write- Write filePOST /api/fs/create- Create file or folderDELETE /api/fs/delete- Delete file or folderPOST /api/fs/rename- Rename/move (handles notebook metadata files)POST /api/fs/duplicate- Duplicate file or folderPOST /api/fs/upload- Upload file (multipart)
Terminals (protected)
GET /api/terminals- List terminalsPOST /api/terminals- Create terminalPOST /api/terminals/named/{name}- Get or create named terminalWS /api/terminals/{id}/ws- WebSocket for terminal I/O
LLM (protected)
POST /api/llm/generate- Generate textPOST /api/llm/generate-structured- Generate JSON (code + explanation + action)POST /api/llm/chat- Chat with notebook context
Python Discovery (protected)
GET /api/python/environments- List Python envs and kernelspecsPOST /api/python/install-kernel- Register Python env as kernel
URL Parameters
?file=<path>- Open notebook file directly?terminal=<name>- Open persistent named terminal
Configuration
- Frontend dev server proxies
/api/*to backend (configured invite.config.ts) - 2FA config stored in
~/.nebula/auth.json(mode 0600) - Notebook history/session stored in
.nebula/directory alongside notebooks - TypeScript paths:
@/*maps to project root
Settings (localStorage)
interface NebulaSettings {
rootDirectory: string;
llmProvider: 'google' | 'openai' | 'anthropic';
llmModel: string;
lastKernel: string;
notifyOnLongRun?: boolean;
notifyThresholdSeconds?: number;
notifySoundEnabled?: boolean;
indentation?: 'auto' | '2' | '4' | '8' | 'tab';
showLineNumbers?: boolean;
showCellIds?: boolean;
apiKeys?: { google?, openai?, anthropic? };
}
Authentication (2FA)
Nebula uses TOTP-based two-factor authentication:
- First Start: Server prints QR code to terminal. Scan with authenticator app.
- Login: Enter 6-digit code in the UI
- Trust Browser: Check "Trust this browser" for 30-day sessions (vs 24 hours)
- Rate Limiting: 5 attempts per 30 seconds
Config file: ~/.nebula/auth.json contains the TOTP secret.
Keyboard Shortcuts
The notebook supports Jupyter-style keyboard shortcuts with two modes:
- Edit Mode (blue border): Cursor in editor
- Cell Mode (green border): Press Escape to enter
Edit Mode (blue border)
| Shortcut | Action |
|---|---|
Shift+Enter |
Run cell and advance to next |
Ctrl/Cmd+Enter |
Run current cell only |
Escape |
Exit to cell mode |
Ctrl/Cmd+Z / Y |
Undo / Redo (text only) |
Cell Mode (green border, press Escape to enter)
| Shortcut | Action |
|---|---|
Enter |
Enter edit mode |
A / B |
Insert cell above / below |
M / Y |
Convert to Markdown / Code |
X / C / V |
Cut / Copy / Paste cell |
Shift+V |
Paste cell above |
E / D |
Enqueue / Dequeue cell for batch execution |
Delete/Backspace |
Delete cell |
Arrow Up/Down |
Navigate between cells |
Ctrl/Cmd+Shift+↑/↓ |
Move cell up / down |
Global
| Shortcut | Action |
|---|---|
Ctrl/Cmd+S |
Save notebook |
Ctrl/Cmd+F |
Open search |
Ctrl/Cmd+C |
Interrupt kernel (when busy) |
| `Ctrl+`` | Toggle integrated terminal |
Key Features
Cell Execution Queue
- Press
Ein cell mode to enqueue cells for batch execution - Press
Dto dequeue - FIFO execution order with queue position display
History & Session
- Full operation history persisted to
.nebula/directory - Restore notebook to any point in history via History panel
- Session state preserved: active cell, unflushed edits
File Browser
- Sidebar variant (notebook view) and inline variant (dashboard)
- Create/rename/duplicate/delete files and folders
- Drag-and-drop upload
- Filter to notebooks only (preference persisted)
- Inline rename by clicking filename
Terminals
- Integrated terminal panel (`Ctrl+``)
- Persistent named terminals via
?terminal=nameURL - Multiple tabs can share same terminal session
AI Integration
- Chat sidebar with full notebook context
- Cell generation from prompts
- Error fixing suggestions
- Multi-provider support (Gemini, OpenAI, Anthropic)
MCP Server
Nebula includes an MCP server for AI agent integration, enabling agents to:
- Run code in notebooks
- Analyze data
- Execute notebook operations headlessly
Undo/Redo System
Nebula uses a dual undo/redo architecture:
1. Text-Level Undo (CodeMirror)
Ctrl/Cmd+Z/Shift+Zin edit mode- Character-level undo per cell
2. Notebook-Level Undo (useUndoRedo hook)
- Toolbar Undo/Redo buttons
- Operations: insert/delete/move cells, type changes, metadata changes
- Full operation log with timestamps for session replay
Operation Types
type UndoableOperation =
| { type: 'insertCell'; index: number; cell: Cell }
| { type: 'deleteCell'; index: number; cell: Cell }
| { type: 'moveCell'; fromIndex: number; toIndex: number }
| { type: 'updateContent'; cellId: string; oldContent: string; newContent: string }
| { type: 'updateMetadata'; cellId: string; changes: MetadataChanges }
| { type: 'batch'; operations: UndoableOperation[] };
Performance
- Progressive rendering: Cells render in batches of 10 via
requestIdleCallback(VirtualCellList) - Lazy CodeMirror: Cells start as
<pre>placeholders, swap to CodeMirror when near viewport - Autosave: 1-second debounce with conflict detection
- Output Truncation: Large outputs truncated for display (data preserved)
Scroll Jump Prevention (IMPORTANT — do not regress)
CodeMirror 6 defers line measurement for off-screen content. When a cell enters the viewport, CM re-measures its lines and can change height by 500px+. This causes visible scroll jumps, especially when jumping to the bottom of a notebook and scrolling back up.
The fix uses height pinning during the <pre> → CodeMirror transition (Cell.tsx):
- Each cell renders a
<pre>placeholder with matching CSS (break-all, same font/padding as CM) - When the cell enters the viewport (IntersectionObserver, 300px margin), the editor wrapper div
is locked to the
<pre>height (height: Xpx; overflow: hidden) before React swaps to CM - CM mounts inside the locked container and goes through async measurement passes (height oscillates) — but none of this affects page layout because the wrapper is locked
- A ResizeObserver watches for CM to stop resizing (150ms of stability), then releases the pin
- The final height delta (
<pre>vs settled CM) is typically <10px — imperceptible
Three things that must stay in sync for this to work:
- Cell.tsx
<pre>: must havebreak-alland matching font/padding to closely match CM's final height - CodeEditor.tsx
.cm-scroller: must haveheight: auto !importantto override@uiw/react-codemirror'sheight: 100% !importantdefault — without this, CM editors collapse to ~625px with internal scrollbars - Cell.tsx height pinning: the IntersectionObserver must lock height BEFORE
setEditorMounted(true), and the ResizeObserver must wait for CM to fully settle before releasing
Do NOT: remove lazy mounting (causes CM to estimate heights for all cells upfront — wrong estimates
cause jumps when cells enter viewport). Do NOT remove break-all from <pre> (causes large height
mismatch). Do NOT remove height: auto !important from .cm-scroller (causes tiny editors).
Metadata Preservation
Cell metadata is preserved across load/save:
nebula_id: Internal cell ID for history trackingscrolled: Jupyter-standard collapsed output state_metadata: Unknown metadata from external tools preserved
Test Locations
- Frontend:
components/__tests__/,hooks/__tests__/,services/__tests__/,lib/__tests__/ - Backend:
node-server/src/__tests__/ - Framework: Vitest
TDD Workflow (Required)
- Write failing tests first
- Run tests to confirm they fail
- Implement minimum code to pass
- Run tests to confirm they pass
- Refactor while keeping tests green
- Commit when tests pass