Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

Use it whenever you don't want to explain the whole job again — you hit an agent's usage limit, switch tools mid-task, pick up older work, or want a clean record of what happened.

Works with **Claude Code**, **Codex**, **Cursor**, **Cline**, **Kimi**, **Antigravity**, **OpenCode**, **Pi Agent**, **ZCode**, and **DeepSeek Harness**.
Works with **Claude Code**, **Codex**, **Copilot CLI**, **Cursor**, **Cline**, **Kimi**, **Antigravity**, **OpenCode**, **Pi Agent**, **ZCode**, and **DeepSeek Harness**.

<div align="center">

Expand Down Expand Up @@ -54,7 +54,7 @@ herdr plugin install wilbeibi/herdr-catchup

## Usage

Agents: `claude` · `codex` · `cursor` · `cline` · `kimi` · `agy` (Antigravity) · `opencode` · `pi-agent`
Agents: `claude` · `codex` · `copilot` · `cursor` · `cline` · `kimi` · `agy` (Antigravity) · `opencode` · `pi-agent` · `zcode` · `deepseek` (dsh)

Omit `<agent>` and catchup uses whichever agent has the newest session in this directory. Inside a live session, that's usually the session you're in.

Expand Down
4 changes: 2 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: catchup
description: Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, Cline, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.
description: Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, Cline, Copilot CLI, Cursor, DeepSeek Harness, Kimi, OpenCode, Pi Agent, or ZCode session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.
---

# catchup
Expand All @@ -24,7 +24,7 @@ catchup fork <agent> # native resume, full state
catchup fork <agent> --into <other> # seed a different agent with the transcript
```

Agents: `codex`, `claude`, `agy` (Antigravity), `cline`, `cursor`, `deepseek` (dsh), `kimi`, `opencode`, `pi-agent`, `zcode`.
Agents: `codex`, `claude`, `agy` (Antigravity), `cline`, `copilot`, `cursor`, `deepseek` (dsh), `kimi`, `opencode`, `pi-agent`, `zcode`.

## Operation

Expand Down
20 changes: 18 additions & 2 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/wilbeibi/catchup/internal/claude"
"github.com/wilbeibi/catchup/internal/cline"
"github.com/wilbeibi/catchup/internal/codex"
"github.com/wilbeibi/catchup/internal/copilot"
"github.com/wilbeibi/catchup/internal/cursor"
"github.com/wilbeibi/catchup/internal/deepseek"
"github.com/wilbeibi/catchup/internal/kimi"
Expand All @@ -40,7 +41,8 @@ const helpText = `Usage: catchup [agent[/<rank>]] [flags] read a past ses
catchup fork --into <agent> --from <file | - | url>
catchup install-skill [agent]

Agents: codex, claude, agy (Antigravity), cline, cursor, deepseek (dsh), kimi, opencode, pi-agent, zcode
Agents: codex, claude, agy (Antigravity), cline, copilot, cursor, deepseek (dsh),
kimi, opencode, pi-agent, zcode
Omit the agent to use whichever has the newest session here. Bare ` + "`catchup`" + `
prints that session in full, as Markdown. The flags refine three things:
which session, how much of it, and as what.
Expand Down Expand Up @@ -317,6 +319,7 @@ func providerNames() []string {
session.ProviderClaude,
session.ProviderAgy,
session.ProviderCline,
session.ProviderCopilot,
session.ProviderCursor,
session.ProviderDeepSeek,
session.ProviderKimi,
Expand All @@ -338,6 +341,8 @@ func selectProvider(name string) (session.Provider, error) {
return agy.New(), nil
case session.ProviderCline:
return cline.New(), nil
case session.ProviderCopilot:
return copilot.New(), nil
case session.ProviderCursor:
return cursor.New(), nil
case session.ProviderDeepSeek:
Expand All @@ -363,7 +368,7 @@ func selectProvider(name string) (session.Provider, error) {
if name == "dsh" {
return nil, fmt.Errorf(`unknown agent "dsh"; DeepSeek Harness's agent name is deepseek`)
}
return nil, fmt.Errorf("unknown agent %q (want codex, claude, agy, cline, cursor, deepseek, kimi, opencode, pi-agent, or zcode); run catchup --help", name)
return nil, fmt.Errorf("unknown agent %q (want codex, claude, agy, cline, copilot, cursor, deepseek, kimi, opencode, pi-agent, or zcode); run catchup --help", name)
}
}

Expand Down Expand Up @@ -805,6 +810,10 @@ func intoCommand(target, prompt, model string) (string, []string, error) {
return "cline", append(modelArgs("--model", model), "-i", prompt), nil
case session.ProviderCursor:
return "cursor-agent", append(modelArgs("--model", model), prompt), nil
case session.ProviderCopilot:
// -i starts interactive and auto-executes the prompt; a bare -p is
// non-interactive and exits when the answer lands.
return "copilot", append(modelArgs("--model", model), "-i", prompt), nil
case session.ProviderKimi:
// Kimi rejects positional arguments and its -p flag is
// non-interactive print mode, so there is no way to start an
Expand Down Expand Up @@ -896,6 +905,13 @@ func forkCommand(src session.Source, model string) (string, []string, error) {
// Cline has no fork, and a bare --id only prints a session summary
// and exits; -i opens the TUI resumed on the session.
return "cline", append([]string{"-i", "--id", src.Ref.SessionID}, modelArgs("--model", model)...), nil
case session.ProviderCopilot:
if src.Ref.SessionID == "" {
return "", nil, fmt.Errorf("fork copilot: missing session id")
}
// Copilot has no fork; --resume is its native resume, and it takes
// the id inline (a bare --resume opens the session picker).
return "copilot", append([]string{"--resume=" + src.Ref.SessionID}, modelArgs("--model", model)...), nil
case session.ProviderCursor:
if src.Ref.SessionID == "" {
return "", nil, fmt.Errorf("fork cursor: missing session id")
Expand Down
5 changes: 5 additions & 0 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,10 @@ func TestForkCommand(t *testing.T) {
{"opencode", session.Source{Ref: session.Ref{Provider: session.ProviderOpenCode, SessionID: "o1"}}, "", "opencode --session o1 --fork"},
{"pi path", session.Source{Ref: session.Ref{Provider: session.ProviderPiAgent, SessionID: "p1"}, Path: "/tmp/pi.jsonl"}, "", "pi --fork /tmp/pi.jsonl"},
{"codex with model", session.Source{Ref: session.Ref{Provider: session.ProviderCodex, SessionID: "c1"}}, "gpt-5.6", "codex fork c1 -m gpt-5.6"},
// The id is inline: a bare --resume opens Copilot's session picker,
// which would swallow a separated id as an unrelated argument.
{"copilot", session.Source{Ref: session.Ref{Provider: session.ProviderCopilot, SessionID: "gh1"}}, "", "copilot --resume=gh1"},
{"copilot with model", session.Source{Ref: session.Ref{Provider: session.ProviderCopilot, SessionID: "gh1"}}, "gpt-5.4", "copilot --resume=gh1 --model gpt-5.4"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -715,6 +719,7 @@ func TestIntoCommandModelPlacement(t *testing.T) {
{session.ProviderClaude, "--model M PROMPT"},
{session.ProviderAgy, "--model M -i PROMPT"},
{session.ProviderOpenCode, "--model M --prompt PROMPT"},
{session.ProviderCopilot, "--model M -i PROMPT"},
}
for _, tt := range tests {
t.Run(tt.target, func(t *testing.T) {
Expand Down
Loading
Loading