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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ Each name links to its format documentation. Use the ID with `--from` and `--wit
| [Grok Bot](docs/formats/grok-bot.md) | `grok_bot` | Yes | Via local gateway |
| [fx](docs/formats/fx.md) | `fx` | Yes | Yes |
| [Antigravity](docs/formats/antigravity.md) | `antigravity` | Yes | Yes |
| [Qoder](docs/formats/qoder.md) | `qoder` | Yes | Yes |
| [Hermes Agent](docs/formats/hermes.md) | `hermes` | Yes | No |
| [Amp](docs/formats/amp.md) | `amp` | Yes | No |
| [Cloud Cowork](docs/formats/cowork-remote.md) | `cowork_remote` | Live account | No |
Expand Down
3 changes: 2 additions & 1 deletion cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ mod pager;
mod view;

pub const HARNESSES: &str = "harnesses: claude_code, claude_chat, chatgpt, codex, opencode, pi, campfire, cursor, cursor_desktop, grok, fx, hermes, \
amp, antigravity, simple, cowork, cowork_remote";
amp, antigravity, qoder, simple, cowork, cowork_remote";

/// The `txcript` binary's command line.
#[derive(Parser)]
Expand Down Expand Up @@ -1322,6 +1322,7 @@ mod style {
HarnessId::Hermes => "\x1b[93m", // bright yellow
HarnessId::Amp => "\x1b[95m", // bright magenta
HarnessId::Antigravity => "\x1b[94m", // bright blue
HarnessId::Qoder => "\x1b[38;5;99m", // violet
HarnessId::Simple => "\x1b[92m", // bright green
HarnessId::Cowork => "\x1b[38;5;208m", // orange
}
Expand Down
1 change: 1 addition & 0 deletions docs/formats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ than none.
| [fx.md](fx.md) | fx (Vercel) | `src/harness/fx.rs` |
| [hermes.md](hermes.md) | Hermes Agent | `src/harness/hermes.rs` |
| [antigravity.md](antigravity.md) | Antigravity (Google) | `src/harness/antigravity.rs` |
| [qoder.md](qoder.md) | Qoder CLI | `src/harness/qoder.rs` |
| [pi.md](pi.md) | pi | `src/harness/pi.rs` |
| [campfire.md](campfire.md) | Campfire (embeds pi) | `src/harness/campfire.rs` |
| [simple.md](simple.md) | Simple (txcript's own interchange format) | `src/harness/simple.rs` |
89 changes: 89 additions & 0 deletions docs/formats/qoder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Qoder

Qoder CLI uses [Claude Code's wire format](claude-code.md): JSONL conversation
records wrapping Anthropic messages, linked by `parentUuid`. Its surrounding
bookkeeping, titles, and storage layout differ. This document is
**reverse-engineered** from real local qodercli 1.1.65 sessions;
`src/harness/qoder.rs` is the authoritative mapping.

```
~/.qoder/projects/ ($QODER_CONFIG_DIR/projects overrides)
└── <encoded-cwd>/
├── <session>.jsonl conversation + bookkeeping records
└── <session>/ Qoder-internal sidecars
├── state.json
└── compression-v2/
```

## On disk

Sessions live at `~/.qoder/projects/<encoded-cwd>/<session>.jsonl`.
`QODER_CONFIG_DIR` overrides the config root when set and non-empty.
The China build, `qoderclicn`, uses `~/.qoder-cn` and ignores that variable;
set `QODER_CONFIG_DIR="$HOME/.qoder-cn"` to point txcript at its sessions.

Project names replace every UTF-16 code unit outside `[A-Za-z0-9]` with
`-`. If the encoded name exceeds 200 characters, it becomes the first 200
characters plus `-` plus `base36(abs(djb2-xor(original path)))`. The hash
uses the original path's UTF-16 units, seed 5381, and signed 32-bit
`h = (h * 33) ^ unit` arithmetic.

`QoderStore` discovers JSONL files exactly two levels below the projects
root, without descending into sidecar directories, and lists a file only
when it holds at least one line with a `type` and a `sessionId`. Neither `state.json`
nor `compression-v2/` is read or written; resume with full history was
verified without them. Native delete mirrors `qodercli --delete-session`:
only the `.jsonl` is removed, leaving the sidecar directory.

## Dissection of a transcript

`Qoder` shares Claude Code's native `Record` body and conversation codec.
Tool names and arguments are the same (`Read`, `Edit`, `Write`, `Bash`,
`Grep`, `Glob`, …); tool results ride on `user` lines. See
[claude-code.md](claude-code.md#dissection-of-a-transcript) for block mappings.

| Their name | What it is | Maps to |
|---|---|---|
| `user` / `assistant` | Envelope wrapping an Anthropic message | User / assistant `Message` |
| `attachment` | Hook output, skill and agent listings | `Record::Other`, no Common turn |
| `active-leaf` | Last-turn pointer | `Record::Other`, bookkeeping |
| `runtime-config`, `workspace-directories`, `worktree-state` | Runtime and workspace state | `Record::Other`, bookkeeping |
| `last-prompt` | Prompt bookkeeping | `Record::Other`, no Common turn |
| `custom-title` | User-set title (`qodercli -n <name>`) | `Meta.title`, highest precedence |
| `ai-title` | Generated title | `Meta.title` when no custom title exists |
| `relocated`, `file-history-snapshot` | Relocation and file-history bookkeeping | `Record::Other`, no Common turn |
| informational `system` | Notices and other bookkeeping | `Record::Other`, no Common turn |

Title precedence is `custom-title` > `ai-title`; Qoder has no `summary`
line. `from_common` writes conversation lines as Claude Code does, with an
`ai-title` line first when a title exists and a trailing `active-leaf`
naming the last turn. Model names, message ids, and usage from other
harnesses are accepted as-is.

## Lineage

`--fork-session` stamps copied lines with
`forkedFrom: {sessionId, messageUuid}`. The first such `sessionId` supplies
a Fork `Meta.lineage` parent, both at discovery and on load.

## Caveats

Common conversion loses bookkeeping, envelope extras, and unmodeled blocks
as in [Claude Code](claude-code.md#caveats), and `Meta.lineage` is read but
not written back. A foreign thinking signature is written through unchanged;
Qoder resumes with it. Native load/save retains bookkeeping records.

Resume with `qodercli --resume <id>`. Override txcript's launch command with
`TRANSCRIPT_QODER_RESUME_CMD`, for example
`TRANSCRIPT_QODER_RESUME_CMD='qoderclicn --resume {id}'` for the China build.

## References

- Observed local qodercli 1.1.65 sessions, including forks, long project
paths, deletion, and resume without sidecars.
- Conversation mapping: [claude-code.md](claude-code.md).

The authoritative txcript mapping is `src/harness/qoder.rs`, sharing the
conversation codec in `src/harness/claude_code.rs`.

Last verified: 2026-10-05, against src/harness/qoder.rs and real local qodercli 1.1.65 sessions.
4 changes: 2 additions & 2 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ writeFileSync("session.jsonl", convert(input, "codex", "claude_code"));
const common = JSON.parse(toCommon(input, "codex")); // { meta, messages }
const pi = fromCommon(JSON.stringify(common), "pi");

harnesses(); // ["claude_code","claude_chat","cowork_remote","chatgpt","codex","opencode","pi","campfire","cursor","cursor_desktop","grok","grok_bot","fx","hermes","amp","antigravity","simple","cowork"]
harnesses(); // ["claude_code","claude_chat","cowork_remote","chatgpt","codex","opencode","pi","campfire","cursor","cursor_desktop","grok","grok_bot","fx","hermes","amp","antigravity","qoder","simple","cowork"]
```

Text-in / text-out: `input` is the source harness's native session text and the result is the target's. Invalid harness names or unparseable input throw a JS `Error`.
Expand All @@ -243,7 +243,7 @@ const matches = JSON.parse(index.query(JSON.stringify({ pattern: "relay bug" }))

| Harness | Session text |
|---|---|
| `claude_code`, `codex`, `pi`, `campfire` | session JSONL |
| `claude_code`, `codex`, `pi`, `campfire`, `qoder` | session JSONL |
| `cowork_remote` | a native document with `session` detail response and ordered `events` (source-only) |
| `claude_chat` | one live conversation detail response (source-only; no account export arrays) |
| `chatgpt` | one live conversation detail response (source-only; no account export arrays) |
Expand Down
1 change: 1 addition & 0 deletions src/harness/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ pub mod grok_bot;
pub mod hermes;
pub mod opencode;
pub mod pi;
pub mod qoder;
pub mod simple;

pub(crate) mod jsonl;
Expand Down
Loading