cd /news/ai-tools/caged-code-vanilla-claude-code-binar… · home topics ai-tools article
[ARTICLE · art-95961] src=tomlarkworthy.github.io ↗ pub= topic=ai-tools verified=true sentiment=· neutral

Caged Code – Vanilla Claude Code Binary in a Webpage

A developer has released 'Caged Code', a webpage that embeds the vanilla Claude Code binary and connects it to a notebook environment via WebSocket, allowing users to run Claude Code through their Anthropic account or OpenRouter API endpoints, with defaults restricted to MiMo models. The tool, which is not endorsed by Anthropic, bridges a lopecode notebook to Claude Code and includes features such as variable watching, cell editing, and voice input/output.

read5 min views1 publishedAug 13, 2026

The vanilla Claude Code binary hosted in a webpage, hardwired to a notebook environment. This is not endorsed by Anthropic in any way. Use your existing Anthropic account through /login with token copying, or use OpenRouter API endpoints. Defaults to our OpenRouter demo endpoint, restricted to MiMo models only.

Live values of variables being observed by Claude Code.

Bridges a lopecode notebook to Claude Code via WebSocket.

Claude Code CLI  ←MCP→  Channel Server  ←WS→  This Module (browser)

Cell groups (in dependency order):

cc_chat

(main interface), cc_watch_table

(variable inspector)cc_config

, cc_notebook_id

, cc_activity

, cc_status

, cc_messages

, cc_watches

, cc_module

, voiceEnabled

cc_find_module

resolves module names via runtime.mains

cc_serialize_value

(safe value→string for transport)cc_watchers

observes variables, debounces & sends updatescc_handle_*

(one per MCP tool)cc_command_handlers

routes action→handlercc_ws

manages connect/pair/auto-reconnect and message dispatchcc_change_forwarder

streams cell edits, cc_voice

optional speech I/OcurrentModules

, exportToHTML

, compile

, history

, runtime-sdk helpersReactive Inputs.input()

cells holding connection state. These are the shared mutable atoms that UI and WebSocket cells read/write.

cc_find_module

resolves a module name (e.g. "@tomlarkworthy/exporter-3"

) to a runtime Module object by polling viewof currentModules.value

at call time (not reactively). cc_serialize_value

safely converts any runtime value to a truncated string for transport.

Subscribes to Observable variables via observe()

, debounces updates (1s), and forwards changes to Claude Code over the WebSocket. Also maintains the cc_watches

table UI.

One handler per MCP tool. Each receives a cmd

object and the Observable runtime

, returns {ok, result}

or {ok:false, error}

. Handlers: get-variable, define-variable, define-cell, delete-variable, list-variables, list-cells, run-tests, create-module, delete-module, eval, fork, watch, unwatch.

cc_command_handlers

maps action strings to handler functions. cc_ws

manages the WebSocket lifecycle: connect, pair with token, dispatch incoming messages (commands, replies, tool-activity), and auto-reconnect from the URL hash cc=

param or sessionStorage.

cc_change_forwarder

polls the local-change-history and streams new cell edits to Claude Code. cc_voice

provides optional speech recognition input and TTS output for hands-free pairing.

Serialize literate computational notebooks with their dependancies into single downloadable files. Double click to open locally. No server required, works in a file://

context for simplicity.

Inputs

, htl

, highlight

, _

(lodash) and markdown

are bundled for offline operation.Exporter improves upon exporter 2 by refactoring out Observable Javascript concepts, it works on the low level reprentation directly.

To put the exporter in one of your notebooks, first import the UI builder.

import {exporter, forkAnchor, exportAnchor } from '@tomlarkworthy/exporter'

Then call the builder to make the UI. You don't need to pass any options, but the options is where you can customise the output format.

exporter({
  handler: (action, state) => {}, // Optional UI click handler
  style: undefined,// customer reference to a style DOM node or a string to insert as a style block
  output: (out) => {}, // hook to get result of exporting
  notebook_url: undefined,// hardcode the default notebook_url
})

If you want to export without a UI, use the function exportToHTML

, see the example

import {exportToHTML } from '@tomlarkworthy/exporter'
php
async function exportToHTML({
  mains = new Map(), // (name -> module) Map of main modules
  runtime = _runtime,
  options = {} // Object, export options, e.g. head, title
} = {})

You can also just use inline anchor tags: forkAnchor() or downloadAnchor()

The HTML file contains <script>

blocks that hold content to serve internal network requests locally.

<script id="d/c2dae147641e012a@46" 
        type="text/plain"
        data-encoding="base64+gzip"
        data-mime="application/javascript"
>
...inline text or base64 string
</script>

Requests to the URL matching the id

are served locally, this includes import

, fetch

, XMLHttpRequest

and <script>

src attribute.

The main script loads the Observable runtime with no standard library and loads a boot module. The boot is responsible for setting up the standard library and the first real modules, which it discovers by reading the bootconf.json

. @tomlarkworthy/boot

is the default which comes with d3, Plot, md, htl and lodash local.

To help carry state across an export, the URL hash parameter is remembered in the bootconf.json

and set automatically when opening the file if one is not present. URLs are limited in size, ff you need to move large amount of data across an export, use a local FileAttachment instead.

Themes are sourced from NotebookKit. A theme is fetched once from Github once when switching themes, but integrated into the export, and reused locally on subsequent exports. This keeps the bundle small, and you only need a network connection if switching to obtain the new CSS source files.

Serialize a single module from the live runtime to a .js

module source string. Unlike module_specs

(which serializes all modules as part of the full HTML export pipeline), exportModuleJS

works on-demand against the live runtime with no task

dependency.

import {exportModuleJS} from "@tomlarkworthy/exporter-3"
const {source, fileAttachments} = await exportModuleJS(moduleId, {runtime, moduleNamesFn})

Parameters

moduleId

— module name string, e.g. "@tomlarkworthy/flow-queue"

options.runtime

— Observable runtime instance (default: _runtime

)options.moduleNamesFn

— function to build module name map (default: buildModuleNames

)Returns {source, fileAttachments}

source

— full .js

module source with export default function define(runtime, observer)

fileAttachments

Map<name, {url, mimeType}>

of the module's file attachments()

Figures out the import structure of a runtime, just pass a runtime to the function moduleMap

to get a summary of the modules. Returns a map indexed by a Module object to a record.

module -> {
  type: "notebook import" | "module variable",
  name: <module name>,
  module: <module object ref>
  dependsOn: [<module name>*],
  dependedBy: [<module name>*],
}

currentModules

()

We resolve what we can using variables named with prefix module

that hold module values. We forcePeek

the variables to make them resolve, which forces of the modules.

modules imported via notebook imports do not have module variables, so they are trickier to figure out. We can sniff the page DOM to find the import expressions, and try to map them to the modules we could to resolve earlier

── more in #ai-tools 4 stories · sorted by recency
── more on @anthropic 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/caged-code-vanilla-c…] indexed:0 read:5min 2026-08-13 ·