Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Share, read, and continue agent sessions.

Spool turns work done with coding agents into durable web pages that other people can understand and resume. An author shares a real Session—not a screenshot or reconstructed recap—and readers can move from Summary to conversation, tool activity, files, and diff before continuing the work in their own agent.

> **Early stage.** Claude Code and Codex CLI shares are Public by default and can appear in Explore and search. Gemini CLI, OpenCode, and Pi shares remain Link-only until Discovery supports them. Signed-in authors can move a Session into a Team, making it Team-only for current members, or let Team Owners and Admins publish the Team-owned asset more broadly. Native Resume currently works for Claude Code and Codex CLI. Feedback is welcome through [Issues](https://github.com/spool-lab/spool/issues) or [Discord](https://discord.gg/aqeDxQUs5E).
> **Early stage.** Claude Code and Codex CLI shares are Public by default and can appear in Explore and search. Gemini CLI, OpenCode, Pi, and ZCode shares remain Link-only until Discovery supports them. Signed-in authors can move a Session into a Team, making it Team-only for current members, or let Team Owners and Admins publish the Team-owned asset more broadly. Native Resume currently works for Claude Code and Codex CLI. Feedback is welcome through [Issues](https://github.com/spool-lab/spool/issues) or [Discord](https://discord.gg/aqeDxQUs5E).

## Install the CLI

Expand Down Expand Up @@ -51,11 +51,11 @@ Nothing leaves the machine until the author confirms the Share flow started by `

## What Spool Includes

- **Session sharing** — publish Claude Code and Codex CLI Sessions publicly by default, with Link-only fallback for Gemini CLI, OpenCode, and Pi
- **Session sharing** — publish Claude Code and Codex CLI Sessions publicly by default, with Link-only fallback for Gemini CLI, OpenCode, Pi, and ZCode
- **Readable Session pages** — Summary, conversation, tool activity, touched files, diff, and record deep links
- **Native continuation** — materialize Claude Code and Codex CLI shares locally and continue them in their original agent format
- **Sensitive-data checks** — detect likely credentials, tokens, personal data, and local paths before sharing
- **Local preparation** — collect and organize Claude Code, Codex CLI, Gemini CLI, OpenCode, and Pi Sessions before deciding what to share
- **Local preparation** — collect and organize Claude Code, Codex CLI, Gemini CLI, OpenCode, Pi, and ZCode Sessions before deciding what to share
- **Agent access** — use the bundled Spool skill or JSON CLI output from any shell-capable agent
- **Public identity and Discovery** — author Profiles, Explore/search, Public Sessions, and continuation lineage
- **Team workspaces** — Owner, Admin, and Member roles; invitations; member-only Sessions; and explicit disclosure controls
Expand Down
2 changes: 1 addition & 1 deletion apps/backend/src/hub/wire.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ApiError } from '../errors'
// legitimate sessions.

export const OID_RE = /^[0-9a-f]{64}$/
export const SID_RE = /^(claude|codex|gemini|opencode|pi)_[0-9A-Za-z_-]{8,128}$/
export const SID_RE = /^(claude|codex|gemini|opencode|pi|zcode)_[0-9A-Za-z_-]{8,128}$/
export const TEAM_ID_RE = /^[0-9A-Za-z_-]{8,128}$/
export const PROJECT_ID_RE = /^project_[0-9A-Za-z_-]{8,192}$/

Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const ISOLATED_ENV = {
SPOOL_GEMINI_DIR: '/nonexistent/gemini',
SPOOL_OPENCODE_DIR: '/nonexistent/opencode',
SPOOL_PI_DIR: '/nonexistent/pi',
SPOOL_ZCODE_DIR: '/nonexistent/zcode',
}

function run(args: string[], env?: Record<string, string>, cwd?: string): string {
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/commands/share-resume.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1142,7 +1142,7 @@ describe('spool share local Agent Summary flow', () => {
})

describe('spool share → spool resume round trip', () => {
it.each(['gemini', 'opencode', 'pi'] as const)(
it.each(['gemini', 'opencode', 'pi', 'zcode'] as const)(
'shares indexed %s sessions through portable records',
async (provider) => {
const hub = makeHub()
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/content/docs/guides/agent-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Ask the agent to “share this session to Spool.” The skill:
4. runs `spool share --visibility-confirmed` without bypassing sensitive-data findings;
5. returns the durable URL.

Claude Code and Codex CLI Sessions are Public by default. Gemini CLI, OpenCode, and Pi Sessions use Link-only URLs until Explore supports them.
Claude Code and Codex CLI Sessions are Public by default. Gemini CLI, OpenCode, Pi, and ZCode Sessions use Link-only URLs until Explore supports them.

## Continue a Spool Session

Expand Down
1 change: 1 addition & 0 deletions apps/web/src/content/docs/guides/data-sources.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Spool prepares Sessions from supported coding agents so an author can choose wha
| Gemini CLI | Yes | Yes | Not yet | `~/.gemini/tmp/*/chats/` |
| OpenCode | Yes | Yes | Not yet | `~/.local/share/opencode/opencode.db` |
| Pi | Yes | Yes | Not yet | `~/.pi/agent/sessions/` |
| ZCode | Yes | Yes | Not yet | `~/.zcode/cli/db/db.sqlite` |

Claude Code and Codex profile directories are also detected:

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/content/docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ spool share <session-uuid>
`spool sessions list` uses the current project by default. Run `spool sessions list --all` to see recent Sessions
from every indexed project.

Claude Code, Codex CLI, Gemini CLI, OpenCode, and Pi Sessions can be shared. Native Resume currently supports Claude Code and Codex CLI shares.
Claude Code, Codex CLI, Gemini CLI, OpenCode, Pi, and ZCode Sessions can be shared. Native Resume currently supports Claude Code and Codex CLI shares.

## 3. Review before sharing

Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/content/docs/reference/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ spool share --visibility-confirmed # acknowledge visibility without a TTY
spool share --yes # skip all confirmations, including secret findings
```

Claude Code, Codex CLI, Gemini CLI, OpenCode, and Pi Sessions can be shared through the Hub. Claude and Codex preserve their native records; the other sources share the provider-neutral conversation prepared in the local index.
Claude Code, Codex CLI, Gemini CLI, OpenCode, Pi, and ZCode Sessions can be shared through the Hub. Claude and Codex preserve their native records; the other sources share the provider-neutral conversation prepared in the local index.

In an interactive terminal, omit Summary options. After the Session URL is live, Spool can detect a local Claude Code or Codex CLI installation and ask it to draft the Summary using the author’s own local Agent configuration.

Expand Down Expand Up @@ -170,7 +170,7 @@ spool resume <url> --workspace <dir> # choose the workspace root
spool resume <url> --no-exec # prepare without launching the agent
```

Native Resume currently supports Claude Code and Codex CLI shares. Gemini CLI, OpenCode, and Pi shares remain readable but are not offered as resumable.
Native Resume currently supports Claude Code and Codex CLI shares. Gemini CLI, OpenCode, Pi, and ZCode shares remain readable but are not offered as resumable.

Resume never modifies the source Shared Session.

Expand Down
1 change: 1 addition & 0 deletions apps/web/src/content/docs/reference/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ SPOOL_DATA_DIR=/some/path spool
| Gemini CLI | `~/.gemini/tmp/*/chats/` |
| OpenCode | `~/.local/share/opencode/opencode.db` |
| Pi | `~/.pi/agent/sessions/` |
| ZCode | `~/.zcode/cli/db/db.sqlite` |

These source locations are built in.

Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const SLUG_RE = /^[A-Za-z0-9_-]{21}$/
// v2 hub session ids: '<provider>_<provider-session-uuid>'. Mirrors the
// backend's SID_RE in apps/backend/src/hub/wire.ts. Exported for the
// /session/$sid route loader.
export const SID_RE = /^(claude|codex|gemini|opencode|pi)_[0-9A-Za-z_-]{8,128}$/
export const SID_RE = /^(claude|codex|gemini|opencode|pi|zcode)_[0-9A-Za-z_-]{8,128}$/

// Mirror of the server-side validator in apps/backend/src/handles.ts.
// We check on the client too so `/@bogus!!` falls through to tombstone
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/lib/session-reader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ describe('hub records → desktop-identical conversation', () => {
expect(conversation.recordToMessageId.get(1)).toBe(1)
})

it.each(['gemini', 'opencode', 'pi'] as const)(
it.each(['gemini', 'opencode', 'pi', 'zcode'] as const)(
'parses portable %s records under their real provider identity',
(provider: SessionProvider) => {
const lines = serializePortableSession({
Expand Down
4 changes: 4 additions & 0 deletions apps/web/src/styles/session-feed.css
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,10 @@
color: var(--color-source-pi);
}

.session-source.is-zcode svg {
color: var(--color-source-zcode);
}

.session-feed-cost {
color: var(--color-muted);
font-family: var(--font-mono);
Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/db/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ export function runMigrations(db: Database.Database): void {
('codex', '~/.codex/sessions'),
('gemini', '~/.gemini/tmp'),
('opencode', '~/.local/share/opencode/opencode.db'),
('pi', '~/.pi/agent/sessions');
('pi', '~/.pi/agent/sessions'),
('zcode', '~/.zcode/cli/db/db.sqlite');

CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/db/migration-v1-v3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ describe('migration v1-v3 (historical connector path)', () => {
const sources = (db.prepare('SELECT name FROM sources').all() as { name: string }[]).map(
(s) => s.name,
)
expect(sources.sort()).toEqual(['claude', 'codex', 'gemini', 'opencode', 'pi'])
expect(sources.sort()).toEqual(['claude', 'codex', 'gemini', 'opencode', 'pi', 'zcode'])

// Session preserved
const sess = db
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/db/migration-v5.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ describe('migration v5 (connector subsystem removal)', () => {
'gemini',
'opencode',
'pi',
'zcode',
])

// After v7: stars dropped, session star preserved as pin, capture star gone
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/db/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ export function getSessionWithMessages(
timestamp, is_sidechain AS isSidechain, tool_names AS toolNames, seq
FROM messages
WHERE session_id = ?
AND (is_sidechain = 0 OR parent_uuid LIKE 'opencode-subagent:%')
AND (is_sidechain = 0 OR parent_uuid LIKE 'opencode-subagent:%' OR parent_uuid LIKE 'zcode-subagent:%')
ORDER BY seq
`)
.all(session.id) as Array<Record<string, unknown>>
Expand Down Expand Up @@ -1296,6 +1296,7 @@ export function getStatus(db: Database.Database): StatusInfo {
const geminiRow = counts.find((r) => r.name === 'gemini')
const opencodeRow = counts.find((r) => r.name === 'opencode')
const piRow = counts.find((r) => r.name === 'pi')
const zcodeRow = counts.find((r) => r.name === 'zcode')

return {
dbPath: DB_PATH,
Expand All @@ -1305,6 +1306,7 @@ export function getStatus(db: Database.Database): StatusInfo {
geminiSessions: geminiRow?.cnt ?? 0,
opencodeSessions: opencodeRow?.cnt ?? 0,
piSessions: piRow?.cnt ?? 0,
zcodeSessions: zcodeRow?.cnt ?? 0,
lastSyncedAt: lastSync?.last ?? null,
dbSizeBytes: getDBSize(),
}
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/doctor/checks/daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export const daemonChecks: Check[] = [
return {
...base,
severity: 'ok',
message: `${status.totalSessions} sessions indexed (claude ${status.claudeSessions}, codex ${status.codexSessions}, gemini ${status.geminiSessions}, opencode ${status.opencodeSessions}, pi ${status.piSessions}); last synced ${synced}`,
message: `${status.totalSessions} sessions indexed (claude ${status.claudeSessions}, codex ${status.codexSessions}, gemini ${status.geminiSessions}, opencode ${status.opencodeSessions}, pi ${status.piSessions}, zcode ${status.zcodeSessions}); last synced ${synced}`,
details: {
totalSessions: status.totalSessions,
lastSyncedAt: status.lastSyncedAt,
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './parsers/codex.js'
export * from './parsers/gemini.js'
export * from './parsers/opencode.js'
export * from './parsers/pi.js'
export * from './parsers/zcode.js'
export * from './parsers/spool-prelude.js'
export * from './sync/syncer.js'
export * from './sync/watcher.js'
Expand Down
Loading