Develop kndl
Clone kndl, run it from the checkout, and make a change that passes the checks. How to open a pull request is in CONTRIBUTING.md.
What you need
Section titled “What you need”- macOS or Linux, with git.
- Bun 1.4 or newer.
- Claude Code (
claude) onPATH.bun run typecheckneeds it to check the mod; it doesn’t need to be signed in. - tmux, for the Runner’s tmux tests. Without it,
bun testskips them. - To run Sessions: a Mac with Apple Silicon, or Linux with KVM your user can open (add it to the
kvmgroup), and a Claude setup token fromclaude setup-token, saved on your Account page.
Get the code
Section titled “Get the code”git clone https://github.com/kddige/kndl.gitcd kndlbun installbun install also brings microsandbox’s runtime for your platform. Every command on this page runs from the checkout’s root.
Run the checks
Section titled “Run the checks”CI runs these on every pull request:
bun run typecheckbun run lintbun run format:check # `bun run format` fixes itbun test| Command | What it does |
|---|---|
bun run typecheck | tsc --noEmit in each code package and in scripts/, and astro check in packages/docs. |
bun run lint | oxlint, configured in .oxlintrc.json. |
bun run format:check | oxfmt, configured in .oxfmtrc.json: formatting and import order. |
bun test | Every package’s tests, including the mod’s pure modules (packages/mod/src/*.spec.ts). KNDL_SANDBOX_IT=1 adds the tests that boot real Sandboxes. |
bun run mod:check | Builds the mod and runs claude plugin validate and claude plugin test on each plugin. |
Find your way around
Section titled “Find your way around”kndl is one Bun workspace in TypeScript, on Effect.
| Path | What it is |
|---|---|
packages/protocol | The messages the Hub, Runners, the mod and the web UI exchange, as Effect Schemas, and which home a process keeps its state in. See Protocol. |
packages/hub | The Hub: an HTTP and WebSocket server with its data in SQLite. |
packages/runner | A Runner: runs each Session’s Claude Code in tmux in a microsandbox Sandbox, on its Creator’s setup token, and relays its mod’s calls to the Hub. |
packages/mod | The Claude Code mod each Session loads, as two plugins: hub-link reports Turns and subagents to the Hub, and hub-link-commands delivers prompts and stops (ADR 0014). |
packages/cli | The kndl command. Its decisions are pure modules with tests; Host.ts and Shell.ts do the I/O. KNDL_DRY_RUN=1 prints the commands that change the system instead of running them. |
packages/web | The web UI: React on Vite, with Base UI and StyleX. |
packages/docs | This site: Astro Starlight. |
install.sh | The installer curl … | sh runs. |
scripts/ | The PR title check and the release tooling. |
GLOSSARY.md, docs/adr/ | The glossary and the decisions. |
Run the Hub
Section titled “Run the Hub”A checkout keeps its state apart from an install: the Hub’s database is packages/hub/hub.db, and everything else is in ~/.kndl-dev, so a checkout and an install can share a machine.
Put the Hub’s settings in packages/hub/.env.local, which bun run hub reads. Settings in your shell’s environment win over the file’s. git ignores .env* and *.pem files.
KNDL_DEV_LOGIN=1HUB_SECRETS_KEY=… # from `bun run secrets-key`Make a secrets key once, and back it up with hub.db:
bun run secrets-keyThen run the Hub one of two ways:
| To | Run | Open |
|---|---|---|
| Work on the web UI, with hot reload | bun run hub and, in another terminal, bun run web | http://localhost:5173 |
| Serve the built web UI, as an install does | bun run web:build, then HUB_PUBLIC_URL=http://127.0.0.1:7777 bun run hub | http://127.0.0.1:7777 |
HUB_PUBLIC_URL must be the address you open: it defaults to Vite’s http://localhost:5173. Vite passes the Hub’s routes through to the Hub at HUB_URL, by default http://127.0.0.1:7777.
To sign in with GitHub instead of dev login, add the App’s settings to .env.local, as Set up the GitHub App describes, and make a Setup link to become the Owner:
bun run setup-linkEvery setting is in the configuration reference.
Run a Runner
Section titled “Run a Runner”On the same machine as the Hub, issue a Runner token, then start the Runner with it:
bun run runner-token build-boxRUNNER_TOKEN=chr_… bun run runnerbun run runner builds the mod first. The token is shown once; running runner-token again for the same name replaces it. For a Hub on another machine, also set HUB_URL.
To keep the token out of your shell history, put the settings in an env file:
# ~/.kndl-dev/runner.env (chmod 600)HUB_URL=https://hub.example.comRUNNER_TOKEN=chr_…bun --env-file="$HOME/.kndl-dev/runner.env" run runnerA checkout’s Runner uses tmux socket kndl-dev and keeps Sessions in ~/.kndl-dev/sessions, so it can run beside an installed one. It never updates itself: pull the checkout to the Hub’s commit and restart it. Every setting is in the configuration reference.
To look at Sandboxes by hand, run microsandbox’s CLI from the Runner’s package:
cd packages/runner && bun x msb listWork on the web UI without a Hub
Section titled “Work on the web UI without a Hub”The web UI runs against an in-process fake Hub that plays scripted Sessions:
VITE_FAKE_HUB=1 bun run web # signed inVITE_FAKE_HUB=signed-out bun run web # starting signed outEvery component in its states is at /kit. The docs’ screenshots are taken against the fake Hub.
Work on the mod
Section titled “Work on the mod”bun run mod:buildclaude --plugin-dir packages/mod/plugins/hub-link --plugin-dir packages/mod/plugins/hub-link-commandsmod:build bundles the mod into each plugin’s hooks/hub-link-lib.js. A running Session picks up a new build at its next start, wake or change of model.
Work on this site
Section titled “Work on this site”bun run docs:dev # http://localhost:4321/kndl/bun run docs:build # checks every link, tooPages are Markdown in packages/docs/src/content/docs, written to docs/design/docs-style.md. The glossary and the decisions are rendered from GLOSSARY.md and docs/adr/: edit those. Pushes to main that touch the site deploy it to GitHub Pages.
Run the CLI from a checkout
Section titled “Run the CLI from a checkout”bun run cli statusIt manages the installed home (KNDL_HOME, or ~/.kndl), never ~/.kndl-dev.
Releases
Section titled “Releases”A Release is a dated tag on main, like v2026.10.1, cut by hand. Pushing the tag runs CI and creates the GitHub Release, with notes built from the merged pull request titles. The steps are in docs/agents/releasing.md, and the reasons in ADR 0025.
Agents in the repo
Section titled “Agents in the repo”Coding agents write much of kndl, often in kndl Sessions. AGENTS.md (also CLAUDE.md) is their instructions, docs/agents/ holds the workflows they follow, and .claude/skills/ the skills they use. Pull requests written with an agent are welcome; you’re responsible for what you submit.
Checkout problems
Section titled “Checkout problems”| What you see | Fix |
|---|---|
The Runner exits with a ConfigError naming RUNNER_TOKEN | Set RUNNER_TOKEN: Run a Runner. The Runner doesn’t read the Hub’s .env.local. |
The hub-link mod isn't built: … | Start the Runner with bun run runner, or run bun run mod:build first. |
The Hub's web UI isn't built. | Run bun run web:build and reload, or use bun run web and open http://localhost:5173. |
Settings in .env.local are ignored | The file goes in packages/hub/, not the checkout’s root. Check your shell doesn’t export the same names. |
Only the web UI at http://localhost:5173 may make this request | Open the address HUB_PUBLIC_URL names, or change it. |
Everything else is in Troubleshooting.
kndl is an independent project. It is not affiliated with or endorsed by Anthropic. Claude and Claude Code are trademarks of Anthropic, PBC. About kndl