175 lines
4.8 KiB
Markdown
175 lines
4.8 KiB
Markdown
# clarc
|
|
|
|
Self-hosted remote control for Claude Code. Wrap CLI in PTY, stream output to server, approve prompts from phone.
|
|
|
|
## Project
|
|
|
|
Pure Bun stack: no frameworks, minimal dependencies. Target ~1000-1500 lines total.
|
|
|
|
Core tech:
|
|
|
|
- bun-pty for PTY wrapper (FFI to Rust, pre-built binaries)
|
|
- bun:sqlite for persistence
|
|
- Bun.serve() for HTTP + WebSocket + SSE
|
|
- plain text output (no xterm.js) for mobile-friendly display
|
|
|
|
## Terminal Emulation
|
|
|
|
Output processing uses @xterm/headless for proper VT100 emulation:
|
|
- src/terminal.ts - headless terminal wrapper
|
|
- One terminal instance per session (created on auth, disposed on close)
|
|
- serializeAsHTML() produces HTML for SSE broadcast
|
|
- ansi.ts and ansi-carryover.ts are deprecated (kept for rollback)
|
|
|
|
See docs/terminal-emulation.md for design rationale.
|
|
|
|
## References
|
|
|
|
Inspiration and patterns to steal from:
|
|
|
|
| Source | Path | What to look at |
|
|
| ---------- | ---------------- | ------------------------------------------------------------------ |
|
|
| crabigator | `../crabigator/` | Architecture, WebSocket protocol, cloud client, session management |
|
|
| bun-pty | `../bun-pty/` | PTY API, spawn patterns, event handling |
|
|
|
|
Key files in crabigator:
|
|
|
|
- `src/cloud/client.rs` - WebSocket connection, reconnection logic
|
|
- `src/cloud/events.rs` - event types for streaming
|
|
- `src/capture.rs` - output capture patterns
|
|
- `workers/crabigator-api/src/session-do.ts` - session state management
|
|
|
|
Key files in bun-pty:
|
|
|
|
- `src/index.ts` - spawn API, IPty interface
|
|
- `src/terminal.ts` - FFI bindings (for understanding internals)
|
|
|
|
## Contribution policy
|
|
|
|
If bun-pty needs changes, fork and contribute upstream. Don't vendor or patch locally.
|
|
|
|
## Workflow
|
|
|
|
After making changes:
|
|
|
|
```bash
|
|
just check # lint + typecheck + test
|
|
```
|
|
|
|
Commit after each logical change. One change = one commit. Keep commits atomic.
|
|
|
|
### Code review
|
|
|
|
Run code review after completing a phase or significant chunk of work:
|
|
- Use `superpowers:code-reviewer` agent
|
|
- Provide: what was implemented, git range (base SHA to head SHA), requirements
|
|
- Fix critical/important issues before moving on
|
|
- Minor issues can be noted and deferred
|
|
|
|
Review loop: implement → just check → code review → fix issues → re-review if needed → pause for user.
|
|
|
|
## Bun defaults
|
|
|
|
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
- Use `bun test` instead of `jest` or `vitest`
|
|
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
- Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
|
|
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
- Bun automatically loads .env, so don't use dotenv.
|
|
|
|
## APIs
|
|
|
|
- `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
|
|
- `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
|
|
- `Bun.redis` for Redis. Don't use `ioredis`.
|
|
- `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
|
|
- `WebSocket` is built-in. Don't use `ws`.
|
|
- Prefer `Bun.file` over `node:fs`'s readFile/writeFile
|
|
- Bun.$`ls` instead of execa.
|
|
|
|
## Testing
|
|
|
|
Use `bun test` to run tests.
|
|
|
|
```ts#index.test.ts
|
|
import { test, expect } from "bun:test";
|
|
|
|
test("hello world", () => {
|
|
expect(1).toBe(1);
|
|
});
|
|
```
|
|
|
|
## Frontend
|
|
|
|
Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
|
|
|
|
Server:
|
|
|
|
```ts#index.ts
|
|
import index from "./index.html"
|
|
|
|
Bun.serve({
|
|
routes: {
|
|
"/": index,
|
|
"/api/users/:id": {
|
|
GET: (req) => {
|
|
return new Response(JSON.stringify({ id: req.params.id }));
|
|
},
|
|
},
|
|
},
|
|
// optional websocket support
|
|
websocket: {
|
|
open: (ws) => {
|
|
ws.send("Hello, world!");
|
|
},
|
|
message: (ws, message) => {
|
|
ws.send(message);
|
|
},
|
|
close: (ws) => {
|
|
// handle close
|
|
}
|
|
},
|
|
development: {
|
|
hmr: true,
|
|
console: true,
|
|
}
|
|
})
|
|
```
|
|
|
|
HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
|
|
|
|
```html#index.html
|
|
<html>
|
|
<body>
|
|
<h1>Hello, world!</h1>
|
|
<script type="module" src="./frontend.tsx"></script>
|
|
</body>
|
|
</html>
|
|
```
|
|
|
|
With the following `frontend.tsx`:
|
|
|
|
```tsx#frontend.tsx
|
|
import React from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
|
|
// import .css files directly and it works
|
|
import './index.css';
|
|
|
|
const root = createRoot(document.body);
|
|
|
|
export default function Frontend() {
|
|
return <h1>Hello, world!</h1>;
|
|
}
|
|
|
|
root.render(<Frontend />);
|
|
```
|
|
|
|
Then, run index.ts
|
|
|
|
```sh
|
|
bun --hot ./index.ts
|
|
```
|
|
|
|
For more information, read the Bun API docs in `node_modules/bun-types/docs/**.mdx`.
|