A JupyterLab extension that bridges the cell UI to a local OpenCode Serve process. The extension is a dual package: a Python server extension exposed under /opencode-bridge/*, plus a TypeScript frontend that registers per-cell toolbars. Backend (Python, tornado) - Slice 1: config + auth + OpenCode HTTP client (tornado.httpclient, no aiohttp). 4 settings in schema/plugin.json (url, user, password, request timeout). - Slice 2: handlers for /hello, /health, /providers, /edit. - Slice 2.1 (correction): SessionManager with 1 notebook = 1 session mapping, async-safe via per-path locks, 404 recovery via invalidate(). Two new endpoints: GET /sessions, DELETE /session?notebook=<path>. - 32 pytest tests pass. Frontend (TypeScript, JupyterLab 4.6) - src/types.ts: CellContext, OpenCodeRequest/Response, OpenCodeSettings. - src/context/cell_context.ts: extract CellContext from a CodeCell + its parent NotebookPanel, structured error collection. - src/api/opencode_client.ts: callOpenCodeEdit, callOpenCodeProviders. - src/components/opencode_cell_footer.ts: OpenCodeCellFooter Widget implementing ICellFooter with 3 buttons (optimize / fix / edit), resolved via this.parent instanceof CodeCell. NOT cellToolbar (does not exist in JL 4.6) and NOT Widget.findParent (removed in @lumino/widgets 2.x). - src/components/opencode_cell_factory.ts: Cell.ContentFactory subclass returning the OpenCodeCellFooter. - src/components/opencode_installer.ts: installOpenCodeEverywhere patches every notebook (existing + new) to use the custom factory. - src/index.ts: registers the factory, loads settings, fetches /providers on activation and logs the list to the console. - 23 jest tests pass (mocked JupyterLab boundary, pnpm path safe). Settings - 6 fields: 3 auth (url/user/password) + 1 timeout + 2 model selection (provider/model). Provider list is fetched at startup from /opencode-bridge/providers and printed to the browser console so users can copy values into Settings Editor. Docs - design.md: 6 sections covering architecture, UI flow, API contract, TS skeletons, session management (v0.2.1 correction), and provider/model selection (v0.2.2 addition). - CLAUDE.md: agent guidance for working in this repo. - TODO.md: remaining work for Slices 4-7 + v0.4+ backlog. CI - Gitea release workflow at .github/workflows/build.yml. - Bark notification helper (non-fatal on failure). Generated artefacts ignored: opencode_bridge/labextension/, _version.py, *.tsbuildinfo, junit.xml, test.ipynb scratch notebook.
6.8 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
opencode_bridge is a JupyterLab extension that acts as a thin bridge between the JupyterLab UI and a locally-running OpenCode Serve process (an AI coding assistant). The architecture, UI flow, and reference TypeScript skeletons are documented in design.md — read it before changing the interaction model. The high-level shape is: floating toolbar injected into each CodeCell → context provider (cell source, error traceback, neighbours) → REST/SSE call to the Jupyter server extension → server extension forwards to OpenCode Serve over local socket/HTTP.
This is a dual-package extension (one Python package + one NPM package) scaffolded from the JupyterLab extension-template v4.6.2 via Copier (see .copier-answers.yml). Do not hand-edit files marked "NEVER EDIT MANUALLY" in that file.
Repository layout
| Path | Purpose |
|---|---|
src/ |
TypeScript frontend (JupyterLab plugin lives in src/index.ts, REST helper in src/request.ts) |
opencode_bridge/ |
Python server extension package; routes registered in opencode_bridge/routes.py |
schema/plugin.json |
Frontend settings schema (currently empty properties: {}) |
style/ |
CSS entry points; base.css is empty, only index.css imports it |
lib/ |
Build output of src/ (gitignored) |
opencode_bridge/labextension/ |
Build output of the full labextension bundle (gitignored) |
ui-tests/ |
Playwright + Galata integration tests (separate package.json with its own jlpm install) |
jupyter-config/server-config/opencode_bridge.json |
Auto-enables the server extension on install |
design.md |
Architecture, UI flow, and reference TS skeletons — source of truth for the design |
.github/workflows/build.yml |
CI: uv build with Tsinghua PyPI mirror, then Gitea release + Bark notify |
The Python and JS package share the name opencode_bridge but live in different namespaces: the server extension is mounted at the URL prefix opencode-bridge/... (note the hyphen), while the Python/JS module name uses the underscore. Frontend → backend calls must hit <baseUrl>/opencode-bridge/<endpoint>.
Common commands
All JS commands go through jlpm (JupyterLab's pinned yarn). The yarn setup uses pnpm as the node linker (see .yarnrc.yml) and disables enableScripts; do not run plain npm install on the root.
Install / dev setup
python -m venv .venv && source .venv/bin/activate
pip install --editable ".[dev,test]"
jupyter-builder develop . --overwrite # link frontend
jupyter server extension enable opencode_bridge # server ext must be enabled manually in dev
jlpm install
Build
jlpm build # tsc + jupyter-builder (dev), fast iteration
jlpm build:prod # full clean prod build for distribution
jlpm watch # tsc -w + jupyter-builder watch in parallel
Tests
jlpm test # jest unit tests (src/__tests__/*) with coverage
pytest # python tests (opencode_bridge/tests/*) — uses pytest-jupyter
# Integration (Playwright + Galata):
(cd ui-tests && jlpm install && jlpm playwright install) # once
jlpm build:prod # required before UI tests
(cd ui-tests && jlpm playwright test) # run
(cd ui-tests && jlpm playwright test -u) # update snapshots
Jest config (jest.config.js) extends @jupyterlab/testutils/lib/jest-config; tests match src/.*/.*.spec.ts[x]?$. ESLint ignores **/*.js, **/*.d.ts, tests, __tests__, and ui-tests — TS files outside ui-tests are the only ones linted.
Lint / format
jlpm lint # stylelint + prettier + eslint (--fix variants)
jlpm lint:check # CI variant, no --fix
Prettier is configured for single quotes, no trailing commas, no arrow parens (see package.json overrides). Stylelint enforces kebab-case class selectors.
Release
RELEASE.md documents the manual flow (hatch version, jlpm clean:all, python -m build). The CI workflow auto-builds and creates a Gitea release on v* tags, then pushes a Bark notification (.github/scripts/bark-notify.sh) — Bark failures are non-fatal by design.
Conventions specific to this repo
- Server extension is enabled in two places:
jupyter-config/server-config/opencode_bridge.json(auto, on pip install) and must be enabled manually in dev mode. The pytest config (conftest.py) re-enables it via thejp_server_configfixture. - URL prefix vs. package name: REST endpoints are mounted under
opencode-bridge/(hyphen), even though the Python and JS packages are namedopencode_bridge(underscore).src/request.tshardcodes the hyphenated prefix; keep them in sync if you add endpoints. - Endpoint handlers must decorate every verb method with
@tornado.web.authenticated(seeopencode_bridge/routes.py); the existingHelloRouteHandleronly implementsget— if you add POST/PATCH/PUT/DELETE, decorate each one. - Settings:
schema/plugin.jsoncurrently has no properties; the frontend loads it viaISettingRegistryinsrc/index.ts. When you add a setting, updateschema/plugin.json, thesettingRegistry.load(...)consumer, and document defaults. - Dev install of UI tests is a separate subproject:
ui-tests/package.jsonis not part of the root workspace. Runjlpm installinsideui-tests/once; do not move those deps to the root. - Generated artefacts:
opencode_bridge/_version.pyis generated by hatch (gitignored) andopencode_bridge/labextension/is generated by the labextension build (gitignored). If you see them missing locally, you haven't runjlpm buildyet. - PyPI mirror:
pyproject.tomlpins the Tsinghua mirror foruv/pip, and CI setsUV_DEFAULT_INDEXto the same. Other users on slow/non-China networks may need to override this.
Where to start when changing…
- Adding a new REST endpoint: add the handler in
opencode_bridge/routes.py(with@tornado.web.authenticatedon every verb), register the route underopencode-bridge/<name>insetup_route_handlers, then call it from the frontend via therequestAPIhelper insrc/request.ts. - Changing the cell UI / toolbar / inline prompt box:
design.mdhas working reference skeletons forsrc/context_provider.tsandsrc/components/cell_toolbar.ts— implement them; do not invent a new interaction model without updating the design doc. - Adding a JupyterLab setting:
schema/plugin.json+ consume viaISettingRegistryinsrc/index.ts. - Packaging / release:
RELEASE.md+.github/workflows/build.yml; remember CI uses Gitea, not GitHub Releases.