p.enthalabs

GitHub - tinybig-ai/jep: One interface for every coding agent. On whatever device you're on.

![Image 1: jep](https://github.com/tinybig-ai/jep/blob/main/docs/images/jep.png)

**One interface for every coding agent. On whatever device you're on.**

jep is a remote control for **opencode**, **codex** and **claude**, **pi** and more. Reachable from **Telegram**, the **Android app**, or any client behind the gateway.

![Image 2](https://github.com/tinybig-ai/jep/blob/main/package.json)![Image 3](https://github.com/tinybig-ai/jep/blob/main/package.json)![Image 4](https://github.com/tinybig-ai/jep/blob/main/src/app/harnesses.ts)![Image 5](https://github.com/tinybig-ai/jep/blob/main/docs/GATEWAY.md)![Image 6](https://github.com/tinybig-ai/jep/blob/main/LICENSE)

![Image 7: check](https://github.com/tinybig-ai/jep/actions/workflows/check.yml)![Image 8: android](https://github.com/tinybig-ai/jep/actions/workflows/android.yml)

[![Image 9: jep architecture: clients (Telegram, Android, gateway) on the left, a hexagonal core wrapping the HarnessAdapter port in the middle, and harnesses (opencode, Claude Code, Codex) on the right](https://github.com/tinybig-ai/jep/raw/main/docs/images/architecture.png)](https://github.com/tinybig-ai/jep/blob/main/docs/images/architecture.png)

Clients on one side, harnesses on the other, the hexagonal core in between.

- * *

Why jep

[](https://github.com/tinybig-ai/jep#why-jep)

Every agent ships its own CLI, its own models, and its own way of working away from a desk: Claude Code has a polished mobile app, opencode and codex meet you at the terminal. Reaching one from another device means bolting a _specific_ app onto a _specific_ harness, duplicated per harness, and you still switch harnesses to reach a given model.

jep is the shared layer: one harness contract, one set of clients, every agent. It's a framework too, so a new harness or client drops in without touching the rest.

- **🧠 One core, every harness.** A single `HarnessAdapter` port normalizes each agent: **opencode** as the flagship, full adapters for **Claude Code** and **Codex**, so any harness behind the layer is reachable from any device.

- **📱 Clients wherever you are.** A **Telegram bot** with native rich messages (tables, collapsible thinking and tool calls, stop buttons, permission prompts) and a **Jetpack Compose Android app**. The gateway makes a web dashboard, Slack bot, or desktop widget a client too.

- **⚡ Live from the first word.** Turns stream into an animated draft: a 💭 thinking line, expandable tool calls, the answer tail updating every ~700 ms. Tap **stop** to cut a turn, **steer** to replace it.

- **🎙 Speak your prompt.** Voice notes are transcribed with whisper and land as real prompts, with a receipt of what was _heard_.

- **🖥 The repo, from your client.** Read-only git screens (status, log, diff), one confirmed **push**, and config-as-data toggles for **MCP servers and skills**.

- **💸 Spend you can see.** A usage screen and pinned cost chip report what the harness priced each turn: tokens, model, USD. **Free by default** until you pick a paid model.

- **🔒 Private by default.** Sessions live in an isolated data home, your CLI's store untouched; the daemon serves Tailscale or your LAN. Self-hosted, account-free.

- * *

Quick start

[](https://github.com/tinybig-ai/jep#quick-start) Two minutes to your first agent reply from wherever you are.

Requirements

[](https://github.com/tinybig-ai/jep#requirements)

- **Node ≥ 26** (the daemon runs native TypeScript, no build step)

- at least one agent CLI installed: **opencode**, **codex**, or **claude**

Download

[](https://github.com/tinybig-ai/jep#download) Every release carries:

- **`jep-<version>.apk`**: the signed Android app. Install it on the phone.

- **`JepBar-<version>.zip`**: the macOS menu-bar app (status and a pairing QR). Unzip it into Applications. It isn't notarized yet, so open it the first time with right-click → Open.

- **iOS**: not on the App Store or TestFlight yet. Build it from Xcode; see clients/ios.

The daemon is on npm: `npm install -g @tinybig-ai/jep`, then run `jep`.

0. One command, no checkout

[](https://github.com/tinybig-ai/jep#0-one-command-no-checkout)

JEP_TG_TOKEN=<from @BotFather> npx --yes @tinybig-ai/jep

This runs the Telegram bot. The same daemon serves the Android app and other clients through the gateway (set `JEP_GW_PORT`); the client guides are in docs/clients. For the launchd daemon, the Android build, or hacking on jep, clone below.

1. Install

[](https://github.com/tinybig-ai/jep#1-install)

git clone https://github.com/tinybig-ai/jep.git && cd jep npm install

2. Run the daemon

[](https://github.com/tinybig-ai/jep#2-run-the-daemon)

point at a workspace (defaults to the current directory)

export JEP_WORKSPACES="$HOME/your-project"

npm start

> On macOS, `sh scripts/install.sh` installs it as a **launchd daemon** that survives reboots, crashes and full-disk-access quirks. See docs/PROCESSES.md. For a quick dev loop, `npm start` is all you need.

> Both installers also register a small Claude Code hook (`scripts/claude-handoff-hook.mjs`): when you continue a Claude conversation from your phone, a desktop window that still has it open catches up on your next message: Claude is handed what was said on the phone first. If the transcript cannot be read, it stops the message and says to run `claude -c`. `JEP_NO_CLAUDE_HOOK=1` skips it.

> On Linux, `sh scripts/install-linux.sh` installs it as a **systemd user service** that restarts on a crash and, with `loginctl enable-linger`, comes back after a reboot. Logs: `journalctl --user -u jep-tg -f`. CI boots the daemon on Debian in mock mode, and has systemd verify the unit, on every push.

3. Pick a client

[](https://github.com/tinybig-ai/jep#3-pick-a-client) jep runs as one daemon and reaches you through whatever client you prefer. Each client has its own setup and command surface:

- **Telegram bot** → clients/telegram: pair it from your chat, then text it. Rich messages, voice notes, git and usage screens, and a streaming stop/steer loop.

- **macOS menu bar** → clients/macos: daemon status and a QR to pair a phone.

- **iOS app** → clients/ios: the same app for iPhone and iPad, in SwiftUI.

- **Android app** → clients/android: Point it at the gateway, pair, and you have conversations, streaming turns, and notifications in a native Compose client.

- **Anything else** → the gateway: a token-authenticated HTTP + SSE door that any client (web dashboard, Slack bot, desktop widget) can speak to.

Telegram reaches you from anywhere, since the daemon polls out and nothing is exposed. The **Android app** and every **gateway** client instead talk _to_ your machine, so they need a network path to it: **Tailscale**, another VPN, or the LAN. The gateway has no TLS of its own, so it rides the network's encryption (WireGuard), like SSH.

Client setup lives in those READMEs so the top of this file stays the same whichever client you choose.

- * *

The Android app

[](https://github.com/tinybig-ai/jep#the-android-app) An example of one client. It talks to the same daemon over the gateway. The Telegram face and the gateway itself are in docs/clients.

- * *

How it works

[](https://github.com/tinybig-ai/jep#how-it-works) jep is a **hexagonal / ports-and-adapters** system. The core defines one contract (`HarnessAdapter`, domain events, and message types) and two kinds of adapter plug into it.

Loading

flowchart LR subgraph Core["Hexagonal core"] PORTS["Ports: HarnessAdapter · DomainEvent · Message · Ask<br/>(src/core/ports.ts, types.ts)"] PURE["Pure satellites<br/>git · usage · skills · mcpconfig · transcribe"] COMP["Compliance suite<br/>feature-parity gate"] end

subgraph Harnesses["Agent harnesses (driven adapters)"] OP["🔵 opencode serve<br/>spawn · SSE · approval"] CX["🔺 Codex"] CL["🔶 Claude Code<br/>+ MCP ask bridge"] YO["🟢 yours → plugins/adapters ⤵"] end

subgraph Frontends["Presentation (driving adapters)"] TG["📱 Telegram bot<br/>rich blocks · HTML · streaming"] GW["🌐 Gateway (HTTP + SSE)<br/>JEP_GW_PORT"] AND["🤖 Android app<br/>Compose, via Gateway"] end

TG --> PORTS GW --> PORTS AND --> GW PORTS --> OP PORTS --> CX PORTS --> CL PORTS -.-> YO PURE -.-> PORTS COMP -.-> PORTS

The hexagon in practice

[](https://github.com/tinybig-ai/jep#the-hexagon-in-practice)

- **The core doesn't know Telegram or opencode.** Frontends see only the port type, a `provider/model` string, a `Message`, an `AskRequest`. Harnesses emit `DomainEvent`s; the client decides how to present them.

- **Every reply degrades, never fails.** The render pipeline is _Rich Message blocks → Telegram HTML (box-drawn tables) → plain text_, and a rejected parse retries without markup. A fancy feature breaking looks a little worse; it doesn't crash the bot.

- **State is isolated, credentials are shared.** The daemon runs each harness in its own `XDG_DATA_HOME` so your CLI's sessions are untouchable, but mirrors your `auth.json` in so every model in the picker actually runs.

- **Turn ownership follows the caller.** The session started it is the session that owns it. One process, shared ports, no cross-talk.

Rules and rationales live in docs/PHILOSOPHY.md.

- * *

Write your own integration

[](https://github.com/tinybig-ai/jep#write-your-own-integration) This is the whole point of the architecture: **the seam is small, provable, and the same on both sides.**

Add an agent harness (driven side)

[](https://github.com/tinybig-ai/jep#add-an-agent-harness-driven-side) Implement `HarnessAdapter` (`src/core/ports.ts`) for your favorite agent, it doesn't need to run over HTTP, or even be a server.

// src/harnesses/my-harness.ts import type { HarnessAdapter } from "../core/ports.ts" import type { DomainEvent, Message, SessionSummary } from "../core/types.ts"

export async function startMyHarness(workspace: string): Promise<HarnessAdapter> { const proc = spawn(`my-harness serve ${workspace}`) return { id: "my-harness", workspace, endpoint: proc.url, health: async () => ({ healthy: true, version: "0.1.0" }), createSession: async (title): Promise<SessionSummary> => /* … */, prompt: async (sessionID, text, { signal, model, filePaths }): Promise<Message> => { /* run the actual turn */ }, messages: async (sessionID) => /* history */, abort: async (sessionID) => /* cancel the in-flight turn */, respondAsk: async (sessionID, askID, optionID) => /* answer permission prompts */, events: async function* () { yield { type: "server.connected" } /* stream deltas */ }, close: async () => proc.kill(), } }

Register it in the harness registry, **one row, and nothing else**:

// src/app/harnesses.ts { id: "my-harness", icon: "🟢", start: (dir) => startMyHarness(dir), available: async () => probeCli("my-harness") }

Then prove it. jep ships a **compliance suite** (`src/core/compliance.ts`) that boots your adapter, creates a session, prompts it, reads history back, and reports a per-method feature-parity matrix, no boilerplate test to write:

npm run probe:harness -- my-harness

Every client is written against the same list, so **full parity for your harness means every client works, unchanged.**

Build a new client (driving side)

[](https://github.com/tinybig-ai/jep#build-a-new-client-driving-side)

Telegram and the Android app are just two consumers of the core. The gateway is a thin token-authenticated HTTP + SSE surface over the _same_ ports: sessions, history, prompts, streaming events, asks, usage, diffs, even a tmux terminal. A web dashboard, a Slack bot, or a desktop widget is a third consumer behind the same door (`JEP_GW_PORT`), with the full endpoint reference in docs/GATEWAY.md.

When a client grows its own surface (commands, screens, flows), document it in its own README under docs/clients/. Adapters can live in this repo today and in their own repos tomorrow, without touching the core.

- * *

Development

[](https://github.com/tinybig-ai/jep#development)

npm install # dev deps: typescript, @types/node · runtime: undici npm run check # tsc typecheck + node --test suite (~1s, no network) npm run test:e2e # opt-in: replays fixtures against a REAL harness (~45s) npm run tg:mock # replay a Telegram fixture against the echo harness, dump every wire call

The rendering pipeline is pure and deterministic (markdown → HTML/rich blocks, string in, blocks out) so the unit tests pin down escape-ordering, box-table geometry and streaming safety at every prefix. The full verification ritual and deploy rules: docs/PROCESSES.md.

Repository layout

[](https://github.com/tinybig-ai/jep#repository-layout)

``` src/ app/ entry points (tg.ts composition root, probe, pair) core/ hexagonal core: ports, types, compliance, pure satellites harnesses/ harness adapters: opencode · codex · claude clients/ telegram/ the Telegram face: bot · api · renderers · store · pair gateway/ HTTP/SSE transport for native clients push/ FCM (Android) and APNs (iOS) push importers/ · terminals/ cross-store session import · tmux shell android/ the native Android client (Kotlin · Jetpack Compose) ios/ the native iOS client (SwiftUI · JepKit) docs/ PHILOSOPHY · GATEWAY · PROCESSES · clients/ ```

Contributing

[](https://github.com/tinybig-ai/jep#contributing) jep is new, small, and built on a deliberate seam, so the surest way to help is to **implement the port for a harness you love**, or to **build a client** on top of the gateway. Issues and PRs welcome.

- **CONTRIBUTING.md**: dev setup, the change loop, house rules, review and governance.

- **docs/ADD_A_HARNESS.md**: the harness-adapter on-ramp, start to proven PR.

- **docs/GATEWAY.md**: build a client on the same ports.

Before opening a PR, skim docs/PHILOSOPHY.md, because "never move on from a broken state" applies to the bot that runs on people's devices.

Security

[](https://github.com/tinybig-ai/jep#security) Please don't open a public issue for a vulnerability. See SECURITY.md.

Contributors

[](https://github.com/tinybig-ai/jep#contributors) Thanks to everyone who helps build jep. This list updates itself.

![Image 10: jep contributors](https://github.com/tinybig-ai/jep/graphs/contributors)

License

[](https://github.com/tinybig-ai/jep#license) MIT

- * *

One layer. Every harness. Runs on your machine.