Wrap the official Claude CLI's interactive TUI in a real pseudo-terminal
(node-pty), parse the rendered screen with a headless VT emulator, and
re-expose the running session as an HTTP app-server (REST + Server-Sent
Events). One process per conversation, full feature parity with the
human-facing TUI — OAuth flows, permission prompts, steering, and reasoning
streams included.
npm i -g june1815
june1815 gogogo[ok] claude /usr/local/bin/claude (v1.4.2)
[ok] auth source env_oauth
[ok] http bind 127.0.0.1:7150
june1815 ready
URL http://127.0.0.1:7150
bearer ad3f...e91c
june1815 is a thin server that:
- Spawns
claude(the interactive TUI, notclaude -p) inside a PTY, one process per conversation. - Reads the raw byte stream into
@xterm/headlessto maintain a virtual screen buffer. - Parses typed events (
text_delta,reasoning_delta,tool_use,usage,interrupted,done, …) out of the rendered TUI. - Streams those events to HTTP clients via Server-Sent Events, with a REST control plane for conversations, interrupts, queueing, steering, and auth.
The result is a local app-server you can drive from any HTTP client — IDE plugins, agent frameworks, automation scripts — without coupling to Anthropic's private SDK protocol.
See docs/design/architecture.md for the full picture.
# global CLI (from npm registry)
npm i -g june1815
# or install the latest release directly from GitHub (bypasses npm workspaces Git issues)
npm install -g https://github.com/fibegg/june1815/releases/latest/download/june1815.tgz
# or one-shot run
npx june1815 doctor
# or Docker
docker run --rm -p 7150:7150 \
-e CLAUDE_CODE_OAUTH_TOKEN=$CLAUDE_CODE_OAUTH_TOKEN \
ghcr.io/fibegg/june1815:latestRequirements: Node ≥ 22, an authenticated claude (claude auth login
or CLAUDE_CODE_OAUTH_TOKEN). If claude is not on PATH, june1815 gogogo
will offer to install it interactively or do so non-interactively under
--auto-install / JUNE1815_AUTO_INSTALL=1.
june1815 gogogo [--host H] [--port N] [--auto-install] [--model M] [--effort low|...|max]
[--headless | --interactive] [--config PATH] [--data-dir PATH]
[--log-level LEVEL]
june1815 doctor # diagnose PATH, auth, geometry, bind target
june1815 config show # resolved config tree (secrets redacted)
june1815 config example # print the annotated example yml
june1815 --version
The bearer token accepted by the server may be carried as
Authorization: Bearer <token> (preferred for programmatic clients),
?token=<token> (for typing into a browser address bar), or as the
june1815_token cookie planted by the server after a successful header
or query auth (so static assets can fetch without an explicit header).
See ADR-0010.
| Method | Path | Description |
|---|---|---|
GET |
/healthz |
liveness + version (no auth) |
GET |
/v1/auth/status |
{authenticated, source, envKey?, path?} |
POST |
/v1/auth/token |
{token} — store in june1815's token file |
DELETE |
/v1/auth |
clear token file |
GET |
/v1/conversations |
list |
POST |
/v1/conversations |
{cwd, id?, model?, effort?, systemPromptAppend?} → 201 |
GET |
/v1/conversations/:id |
summary |
DELETE |
/v1/conversations/:id |
204 |
POST |
/v1/conversations/:id/messages |
{text, attachments?} → SSE stream until done |
POST |
/v1/conversations/:id/interrupt |
abort in-flight turn |
POST |
/v1/conversations/:id/queue |
{text, attachments?} — enqueue without streaming |
POST |
/v1/conversations/:id/steer |
{text} — steer the in-flight turn |
SSE event types: text_delta, reasoning_delta, tool_use, usage,
interrupted, done, error, auth_required, permission_prompt,
ping. Schemas live in src/server/events.ts and re-export as the
june1815/events subpath.
Both /messages and /queue accept an optional attachments array.
Each entry is { kind: 'image' | 'file', dataUrl, name?, contentType? }
where dataUrl is the standard data:<mime>;base64,<bytes> form. Files
are sanitized, written to
<dataDir>/uploads/<conversationId>/<messageId>/<name>, and referenced
as @<absolute-path> lines prepended to the user text — the convention
claude uses to attach a local file to a turn.
curl -N -H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-X POST $URL/v1/conversations/$CID/messages \
-d "$(jq -n --arg img "$(base64 < photo.png)" '{
text: "what is in this image?",
attachments: [{ kind: "image", dataUrl: ("data:image/png;base64," + $img), name: "photo.png" }]
}')"A React + Tailwind + shadcn-style chat lives at / when the server is
started with JUNE1815_UI_ENABLED=1 (or ui.enabled: true in
june1815.yml). The UI is bundled with the npm package; nothing extra to
install or run.
JUNE1815_UI_ENABLED=1 june1815 gogogo
# open the URL with `?token=<bearer>` once; the SPA captures the token,
# strips it from the address bar, and the bearer-set cookie carries
# auth across asset fetches.The UI supports: creating and switching between conversations, streaming SSE events (text + reasoning + tool calls + usage), Enter-to-send, Shift+Enter for newline, Enter-while-busy to steer, drag-drop / paste / file-picker image attachments, and a one-click interrupt.
UI dev mode (proxies API calls to a running server on 7150):
JUNE1815_UI_ENABLED=1 june1815 gogogo & # in one terminal
npm run dev:ui # in another → http://localhost:5173Use this when you're hacking on the package or want to try the UI before the first npm publish.
git clone https://github.com/fibegg/june1815.git
cd june1815
nvm use # respects .nvmrc (Node 22)
npm install # installs both root and the `ui` workspaceThat single npm install resolves the workspace at ui/, so you don't
need to run cd ui && npm install separately.
This is the fastest loop for UI changes — Vite serves the React app on
:5173 with HMR, and proxies /v1/* and /healthz to a real june1815
server you start separately. You don't need JUNE1815_UI_ENABLED here
because the API server isn't the one serving the UI.
# terminal 1 — build the server once, then run it
npm run build:server
node dist/cli/bin.js gogogo --headless --port 7150
# stdout prints a single line: {"url":"http://127.0.0.1:7150","token":"<bearer>"}# terminal 2 — Vite dev server, proxying API calls
npm run dev:ui
# ➜ Local: http://localhost:5173/Open http://localhost:5173/?token=<bearer> (paste the token from
terminal 1). The TokenGate captures it once; reloads keep you logged in
via sessionStorage. Edit any ui/src/** file and the browser updates in
under a second.
This runs the built UI from the same Node process serving the API — the same code path a freshly-installed npm copy would execute.
npm run build # builds server + UI into dist/
JUNE1815_UI_ENABLED=1 \
node dist/cli/bin.js gogogo # interactive: opens with a clack TUI
# or
JUNE1815_UI_ENABLED=1 \
node dist/cli/bin.js gogogo --headless --port 7150In interactive mode the boot output prints the URL and bearer; in
headless mode it prints one JSON line. Open the URL with ?token=...
appended.
For server-only changes, run the TypeScript entry directly through tsx
— no build, no dist/:
npm install --no-save tsx # if not already there transitively
npx tsx src/cli/bin.ts gogogo --headless --port 7150This still requires JUNE1815_UI_ENABLED=1 plus an existing dist/ui/ if
you want the UI; pure API/server hacking doesn't need either.
Two paths.
npm link — global symlink, no rebuild on every change:
# in june1815
npm run build
npm link
# in your consumer project
npm link june1815
node -e "import('june1815').then((m) => console.log(Object.keys(m)))"Re-run npm run build in june1815 whenever you change source files;
the symlink picks up the new dist/ immediately.
npm pack — a real local tarball for the closest-to-published
behavior:
# in june1815
npm run build
npm pack # writes june1815-0.0.0.tgz
# in your consumer
npm install /absolute/path/to/june1815-0.0.0.tgz# requires: `claude` on PATH and an authenticated source
# (CLAUDE_CODE_OAUTH_TOKEN, ANTHROPIC_API_KEY, or `claude auth login`)
npm run build:server
npm run test:e2eWithout claude or a token the suite skips with a clear stderr line
([e2e] skipping suite: ...) and exits 0 — no need to fake anything.
npm run clean # deletes dist/, coverage/, .vitest-cache/
rm -rf ~/.local/share/june1815 # purges saved bearer + session markers
rm -rf /tmp/june1815-e2e-* # any orphaned e2e temp dirsAny tool that spawns the claude CLI in its stream-JSON IPC mode —
the official @anthropic-ai/claude-agent-sdk, custom SDK clients, the
-p print mode — can spawn june1815 instead and get back the same
wire shape. Internally, june1815 drives the real claude through its
TUI, parses the screen, and re-emits each event as the matching
stream-JSON message on stdout.
Point the consumer at june1815 and set one env var:
export JUNE1815_CLAUDE_PATH=$(which claude)
# Then point whatever currently spawns `claude` at `june1815` instead.Smoke test it directly with newline-delimited JSON on stdin/stdout:
echo '{"type":"user","message":{"role":"user","content":[{"type":"text","text":"reply with exactly: HELLO"}]}}' \
| june1815 --output-format stream-json --verbose --input-format stream-json --model claude-opus-4-7The shim activates automatically whenever it sees
--output-format stream-json, --input-format stream-json, -p, or
--print. With any other invocation, june1815 behaves as the normal
gogogo / doctor / config CLI.
See docs/design/shim-mode.md for the
full wire-protocol spec and arg-filtering rules.
When june1815 reports a tool call (tool_use) on the wire, it
synthesizes a structured input object from the TUI's view of the
tool. Built-in mappings cover well-known claude tools out of the box
(Read, Bash, Edit, Grep, WebFetch, …). Custom or MCP tools
get a {summary: "<raw>"} fallback unless you ship a tool-defs file:
Discovery order (later wins): built-ins → JUNE1815_TOOL_DEFS env var
(:-separated paths) → ~/.config/june1815/tool-defs.json →
--tool-defs CLI flag (repeatable).
See docs/design/tool-plugins.md for
the full spec and more examples.
CLI flags > process.env (JUNE1815_*) > ./june1815.yml >
~/.config/june1815/june1815.yml > defaults.
- See
.env.examplefor every env key. - See
june1815.example.ymlfor the annotated YAML. - Run
june1815 config showfor the live resolved tree.
docs/design/— architecture and replication guide.docs/adr/— every non-trivial decision recorded.docs/alloy/— runnable Alloy 6 spec suites covering session lifecycle, message queue, auth/config priority, and HTTP API contract..agents/skills/— internal skill notes on the best practices applied across the codebase.examples/— sample clients.
npm ci && npm run ci to run the full check loop. Tests use Vitest;
lint with ESLint flat config; formatting via Prettier. Alloy specs run
with scripts/run-alloy.sh (Alloy 6.2.0 + OpenJDK 21).
End-to-end tests (npm run test:e2e) spawn the built CLI as a child
process and drive the full API surface — including streaming,
queueing, steering, interrupt, and image attachments. They skip
cleanly when claude is not on PATH or no authentication source is
present, so first-time contributors aren't blocked.
See docs/CONTRIBUTING.md.
MIT. See LICENSE.