---
slug: "batonlink-pi"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/Sukitly/batonlink@main/README.md"
repo: "https://github.com/Sukitly/batonlink"
source_file: "README.md"
branch: "main"
---
# Batonlink

Hand off work between AI coding agents with a single URL.

Batonlink lets an agent distill the current session into a **baton** — a
work-context package another agent can pick up by fetching one URL. You say
"publish" to the agent you are working with; it writes a short brief, selects
the relevant slice of the conversation, uploads it, and hands you back a link.
Paste that link to a collaborator and their agent continues where you left off.

A baton has two layers:

- **Brief** — the agent's distillation: a short title plus a markdown body with
  the situation, key context, decisions made, and recommended next steps.
- **Transcript** — the verbatim conversation, attached automatically so the
  receiver has the raw record, not just the summary.

The receiving side needs nothing installed: a baton is a URL, readable with a
plain `curl`. This repository is the **publishing side** — the extensions and
plugins you install into your agent harness.

## Supported harnesses

| Harness | Package | Install surface |
|---|---|---|
| [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) | `@batonlink/pi` | pi extension |
| [Claude Code](https://docs.claude.com/en/docs/claude-code) | `@batonlink/claude-code` | plugin marketplace (MCP server) |
| [OpenAI Codex](https://developers.openai.com/codex) | `@batonlink/codex` | plugin marketplace (MCP server) |
| [opencode](https://opencode.ai) | `@batonlink/opencode` | in-process plugin |

## Install

### pi

```bash
pi install npm:@batonlink/pi
```

In a session: `/batonlink` to publish the current discussion. The first
publish opens a browser approval page to pair — no separate login step.

### Claude Code

In a session:

```
/plugin marketplace add Sukitly/batonlink
/plugin install batonlink@batonlink
```

Then `/batonlink:publish` (or just ask the agent to publish). The first publish
opens a browser approval page to pair — no separate login step.

### Codex

```bash
codex plugin marketplace add Sukitly/batonlink
codex plugin add batonlink@batonlink
```

Then invoke the `publish` skill (or ask the agent to publish). First use pairs
in the browser automatically.

### opencode

Add the plugin to your `opencode.json` (global `~/.config/opencode/` or a
project-level file) and restart opencode:

```json
{
  "plugin": ["@batonlink/opencode"]
}
```

Then `/batonlink` to publish. First use pairs in the browser automatically.

## Authentication

Publishing needs a Batonlink API key, resolved in this order:

1. `BATONLINK_API_KEY` environment variable (useful for CI or headless setups).
2. A credential file written by the login / pairing flow at
   `${XDG_CONFIG_HOME:-~/.config}/batonlink/credentials.json` (mode `0600`),
   shared across every harness — log in once, publish everywhere.

If no key is present, the first publish pairs by opening a browser approval
page — the same flow on every harness, with no separate login step. On a
headless machine, set `BATONLINK_API_KEY` instead.

### Environment variables

| Variable | Required | Purpose |
|---|---|---|
| `BATONLINK_API_KEY` | one of the two auth paths | Bearer token for publishing; overrides the credential file. |
| `BATONLINK_BASE_URL` | no | Override the API endpoint (defaults to the production endpoint). |
| `XDG_CONFIG_HOME` | no | Override the credential file base directory (absolute path only). |
| `CODEX_HOME` | no | Codex only: non-default session directory location. |

## Packages

| Package | Role |
|---|---|
| `@batonlink/core` | Harness-agnostic SDK: wire-contract types, range selection, transcript filtering, chain rebuilding, publish transport, and the device-flow login state machine. No runtime dependencies. |
| `@batonlink/node` | Node runtime glue: XDG credential store, browser opener, login orchestration, and the shared tool-flow logic. |
| `@batonlink/mcp` | Harness-agnostic MCP server skeleton: the shared four-tool surface with lazy pairing. |
| `@batonlink/pi` | pi extension. |
| `@batonlink/codex` | Codex MCP server plus plugin artifacts. |
| `@batonlink/claude-code` | Claude Code MCP server (with a hook-based session rendezvous) plus plugin artifacts. |
| `@batonlink/opencode` | opencode in-process plugin. |

## Development

Requires [Bun](https://bun.sh).

```bash
bun install          # install workspace dependencies
bun run build        # compile all packages (topological order)
bun run lint         # typecheck + Biome
bun run test         # run the test suites
bun run ci           # full gate: build + lint + generated-skill check + test + smoke
```

## Releasing

Releases are **lockstep**: all seven packages plus the two plugin manifests
share one version and ship together. Internal dependencies stay pinned as
`workspace:*`; `bun pm pack` rewrites them to the concrete version, so a bump
never has to touch a dependency range.

One command opens a release PR (requires the [`gh`](https://cli.github.com)
CLI):

```bash
bun run release patch   # or minor / major / an explicit X.Y.Z
```

It bumps every version site on a `release/vX.Y.Z` branch and opens a PR.
Merging that PR runs [`.github/workflows/release.yml`](https://github.com/Sukitly/batonlink/blob/HEAD/.github/workflows/release.yml),
which packs each package with bun (rewriting `workspace:*`), publishes the
tarballs with npm in topological order, and tags the version.

Publishing uses npm [trusted publishing](https://docs.npmjs.com/trusted-publishers)
(OIDC): no long-lived token, no CI secrets, and a provenance attestation is
added automatically. This needs a trusted publisher configured per package,
which can only be set up on a package that already exists — so the **first**
release is published manually from a workstation instead:

```bash
bun run release:dry       # preview: pack + npm publish --dry-run
bun run release:publish   # publish all seven packages (uses your local ~/.npmrc)
git tag -a v0.1.0 -m "Release v0.1.0" && git push origin v0.1.0
```

Use a granular or automation token in `~/.npmrc` (not interactive 2FA — the CI
gate runs first and a one-time password would expire). The first release has no
provenance, which is expected before trusted publishers exist. Afterwards,
configure a trusted publisher for each of the seven packages on npmjs.com
(owner `Sukitly`, repo `batonlink`, workflow `release.yml`); every release
after that runs in CI with pure OIDC and automatic provenance.

```bash
bun run release:dry     # preview the publish locally, uploads nothing
bun run release:check   # verify the lockstep invariant (also part of `bun run ci`)
```

## License

[MIT](https://github.com/Sukitly/batonlink/tree/HEAD/LICENSE)
