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
52 changes: 52 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,55 @@ jobs:
throw "PSScriptAnalyzer reported $($findings.Count) error-severity finding(s)."
}
Write-Host "No error-severity findings."

# The bash session-continuation suite (bats). Runs the REAL launcher against
# real fixture trees on both Linux and macOS. bats-core and jq are fetched
# fresh by the runner (never committed): jq via the system package manager,
# bats-core via the system package manager when available, otherwise
# tests/run-bats.sh clones the pinned v1.11.0 into tests/vendor on demand.
bats:
name: bats (${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4

- name: Install bats + jq
run: |
if command -v apt-get >/dev/null 2>&1; then
sudo apt-get update -y
sudo apt-get install -y bats jq
elif command -v brew >/dev/null 2>&1; then
brew install bats-core jq
else
echo "no supported package manager (apt-get/brew) on this runner" >&2
exit 1
fi
bats --version
jq --version

- name: Run the bats suite
run: bash tests/run-bats.sh

# The PowerShell session-continuation suite (Pester). Uses the Pester 3.4
# shipped with Windows PowerShell 5.1 (the suite is 3.4-compatible). The
# PowerShell launcher parses JSON natively, so this suite needs no jq.
pester:
name: pester (windows-latest)
runs-on: windows-latest
steps:
- uses: actions/checkout@v4

- name: Install Pester 3.4.0 (matches the 3.x-compatible suite)
shell: powershell
run: |
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
Install-PackageProvider -Name NuGet -Force -Scope CurrentUser
Install-Module -Name Pester -RequiredVersion 3.4.0 -Force -SkipPublisherCheck -Scope CurrentUser

- name: Run the Pester suite
shell: powershell
run: powershell -NoProfile -ExecutionPolicy Bypass -File tests/run-pester.ps1
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,8 @@ node_modules/
*.swp
*~
outreach_plan.md

# Test dependencies are fetched on demand (vendored bats-core + jq binary).
# Never commit them; tests/run-bats.sh and tests/helpers/common.bash bootstrap
# them into tests/vendor/ when missing.
tests/vendor/
94 changes: 94 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ Each profile gets an automatic shell alias:
| `multi-cli export <tool>/<name> [path]` | Archive a profile to `.tar.gz` (`.zip` on Windows) |
| `multi-cli import <archive> <tool>/<name>` | Restore a profile from an archive |

#### Sessions

| Command | Description |
|---------|-------------|
| `multi-cli continue <tool> <src> <dest>` | Copy conversation state (sessions/transcripts/history) from one profile to another — never credentials |
| `multi-cli continue <tool> <src> <dest> --no-merge` | Overwrite destination files instead of keeping newer ones |
| `multi-cli continue <tool> <src> <dest> --dry-run` | Preview what would be copied, change nothing |

`base` works as a profile name on either end and means the tool's real home dir (`~/.codex`, `~/.claude`, …). Supported for `codex`, `claude-cli`, `gemini-cli`, and `commandcode`. See [Continue a Chat Across Accounts](#continue-a-chat-across-accounts).

#### Utilities

| Command | Description |
Expand Down Expand Up @@ -153,6 +163,43 @@ Each tool's `<id>/adapter.json` declares which strategy to use.

---

<a id="continue-a-chat-across-accounts"></a>

### Continue a Chat Across Accounts

Hit a rate limit on account A mid-conversation? Switch to a profile logged into account B and pick the chat up where it stopped. `multi-cli continue` copies the portable conversation state — sessions, transcripts, history — between profiles. **Credentials are never copied.**

```bash
# You were working in codex/work (account A) and got rate-limited.
# codex/personal is logged into account B.
multi-cli continue codex work personal # copy the conversation state
multi-cli continue codex work personal --dry-run # preview first, if you like

codex-personal # launch account B's profile
codex resume <session-id> # resume the same chat (codex ≥ 0.30)
```

Run `codex resume` with no argument to open an interactive picker of past sessions, so you never have to look up an id. If you do need it, the session id is the UUID in the rollout filename under `sessions/YYYY/MM/DD/`.

`base` is a valid profile name on either end and refers to the tool's real home dir (`~/.codex`, `~/.claude`, …), so you can continue to or from your default install.

By default, files are **merged** — newer files in the destination are kept. Pass `--no-merge` to overwrite the destination instead, or `--dry-run` to preview without changing anything.

After copying, resume inside the destination profile with the tool's own command:

| Tool | Resume command |
|------|----------------|
| codex | `codex resume <session-id>` (≥ 0.30) |
| claude-cli | `claude --resume <session-id>` (run from the same project directory) |
| gemini-cli | `gemini --resume` (auto-saved last session) or `/chat resume <tag>` for saved checkpoints |
| commandcode | launch from the same working directory |

**Not supported:** `opencode` (sessions and credentials live in one shared SQLite database) and `cursor` (chats are stored in SQLite keyed to the workspace path).

> New profiles are seeded from `base` by default — conversation state, plus skills/config assets for full profiles. Pass `--no-seed` to `multi-cli new` to start empty.

---

### Profile Types

| Flag | Meaning |
Expand Down Expand Up @@ -335,6 +382,16 @@ multi-cli claude-cli/work
| `multi-cli export <工具>/<名称> [路径]` | 归档为 `.tar.gz`(Windows 为 `.zip`) |
| `multi-cli import <归档文件> <工具>/<名称>` | 从归档恢复配置文件 |

#### 会话

| 命令 | 说明 |
|------|------|
| `multi-cli continue <工具> <源> <目标>` | 将会话状态(会话/记录/历史)从一个配置文件复制到另一个 — 绝不复制凭证 |
| `multi-cli continue <工具> <源> <目标> --no-merge` | 覆盖目标文件,而非保留较新的文件 |
| `multi-cli continue <工具> <源> <目标> --dry-run` | 预览将复制的内容,不做任何更改 |

`base` 在源端或目标端均可作为配置文件名,表示工具的真实主目录(`~/.codex`、`~/.claude` 等)。支持 `codex`、`claude-cli`、`gemini-cli` 和 `commandcode`。详见 [跨账户继续聊天](#跨账户继续聊天)。

#### 实用工具

| 命令 | 说明 |
Expand Down Expand Up @@ -364,6 +421,43 @@ multi-cli 根据工具支持情况使用五种隔离策略:

---

<a id="跨账户继续聊天"></a>

### 跨账户继续聊天

对话进行到一半,账户 A 触发了速率限制?切换到登录账户 B 的配置文件,从中断处接着聊。`multi-cli continue` 会在配置文件之间复制可移植的会话状态 — 会话、记录、历史。**凭证绝不会被复制。**

```bash
# 你原本在 codex/work(账户 A)工作并触发了速率限制。
# codex/personal 登录的是账户 B。
multi-cli continue codex work personal # 复制会话状态
multi-cli continue codex work personal --dry-run # 如需,可先预览

codex-personal # 启动账户 B 的配置文件
codex resume <session-id> # 继续同一聊天(codex ≥ 0.30)
```

直接运行 `codex resume`(不带参数)会打开历史会话的交互式选择器,无需查找 id。若确实需要,session id 即 `sessions/YYYY/MM/DD/` 下 rollout 文件名中的 UUID。

`base` 在源端或目标端均可作为配置文件名,表示工具的真实主目录(`~/.codex`、`~/.claude` 等),因此可以继续到默认安装或从默认安装继续。

默认情况下文件会**合并** — 保留目标中较新的文件。传入 `--no-merge` 改为覆盖目标,或 `--dry-run` 仅预览而不做更改。

复制后,在目标配置文件内用工具自身的命令继续:

| 工具 | 继续命令 |
|------|----------|
| codex | `codex resume <session-id>`(≥ 0.30) |
| claude-cli | `claude --resume <session-id>`(在同一项目目录下运行) |
| gemini-cli | `gemini --resume`(自动保存的上次会话)或 `/chat resume <tag>`(已保存的检查点) |
| commandcode | 从同一工作目录启动 |

**不支持:** `opencode`(会话与凭证共用一个 SQLite 数据库)和 `cursor`(聊天存储在按工作区路径键控的 SQLite 中)。

> 新配置文件默认从 `base` 播种 — 会话状态,以及完全配置文件的技能/配置资源。向 `multi-cli new` 传入 `--no-seed` 可从空白开始。

---

### 配置文件类型

| 参数 | 含义 |
Expand Down
12 changes: 12 additions & 0 deletions claude-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ claude-cli-personal # different account; runs concurrently
- **full** *(default)* — separate `settings.json`, `.credentials.json`, `skills/`, `agents/`, `plugins/`, `commands/`, `todos/`, `projects/`, `history.jsonl`.
- **shared** — symlinks `settings.json`, `skills/`, `agents/`, `plugins/`, `commands/` from `~/.claude/`. Only credentials and history stay isolated.

## Continue a chat across accounts

Rate-limited on one account? Copy the conversation state to a profile logged into another, then resume the same chat.

```bash
multi-cli continue claude-cli work personal # copy sessions/history (never credentials)
claude-cli-personal
claude --resume <session-id> # run from the same project directory
```

`base` works as either profile name and means `~/.claude`. Default merge keeps newer destination files; `--no-merge` overwrites, `--dry-run` previews.

## Verified

Smoke-tested live against `Claude Code 2.1.143` on Windows.
6 changes: 6 additions & 0 deletions claude-cli/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"linkable": ["settings.json", "skills", "agents", "plugins", "commands"],
"neverLink": [".credentials.json", "todos", "projects", "history.jsonl"]
},
"session": {
"portable": true,
"paths": ["projects", "history.jsonl", "todos"],
"credentials": [".credentials.json"],
"resumeHint": "Resume a copied conversation with `claude --resume <session-id>`, run from the same project directory."
},
"install": "npm i -g @anthropic-ai/claude-code",
"versionCommand": ["--version"],
"status": "stable"
Expand Down
14 changes: 14 additions & 0 deletions codex/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ codex-personal # logs into account B; both can run simultaneously
- **full** *(default)* — fresh `CODEX_HOME`, separate auth/config/sessions/skills.
- **shared** — symlinks `config.toml`, `skills/`, `agents/`, `prompts/`, `mcp-configs/`, `plugins/` from `~/.codex/`. Only `auth.json` and `sessions/` stay isolated.

## Continue a chat across accounts

Rate-limited on one account? Copy the conversation state to a profile logged into another, then resume the same chat.

```bash
multi-cli continue codex work personal # copy sessions/history (never auth)
codex-personal
codex resume <session-id> # codex ≥ 0.30
```

Run `codex resume` with no argument to pick from past sessions interactively — no id lookup needed. The id is otherwise the UUID in the rollout filename under `sessions/YYYY/MM/DD/`.

`base` works as either profile name and means `~/.codex`. Default merge keeps newer destination files; `--no-merge` overwrites, `--dry-run` previews.

## Verified

Smoke-tested live against `codex-cli 0.130.0` on Windows.
6 changes: 6 additions & 0 deletions codex/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"linkable": ["config.toml", "skills", "agents", "prompts", "mcp-configs", "plugins"],
"neverLink": ["auth.json", "sessions", "history.jsonl"]
},
"session": {
"portable": true,
"paths": ["sessions", "history.jsonl", "archived_sessions", "session_index.jsonl"],
"credentials": ["auth.json"],
"resumeHint": "Resume a copied conversation with `codex resume <session-id>`."
},
"install": "npm i -g @openai/codex",
"versionCommand": ["--version"],
"status": "stable"
Expand Down
11 changes: 11 additions & 0 deletions commandcode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ commandcode-personal
- **full** *(default)* — separate `auth.json`, `history.jsonl`, `projects/`, `file-history/`, `skills/`, `taste/`, `plans/`.
- **shared** — symlinks `skills/`, `taste/`, `plans/` from `~/.commandcode/`. Auth and history stay isolated.

## Continue a chat across accounts

Rate-limited on one account? Copy the conversation state to a profile logged into another, then resume the same chat.

```bash
multi-cli continue commandcode work personal # copy history/projects (never auth)
commandcode-personal # resume from the same working directory
```

`base` works as either profile name and means `~/.commandcode`. Default merge keeps newer destination files; `--no-merge` overwrites, `--dry-run` previews.

## Caveats

- Tools that read `~/.gitconfig`, `~/.ssh/`, etc. will work — they're symlinked through.
Expand Down
6 changes: 6 additions & 0 deletions commandcode/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@
"linkable": ["skills", "taste", "plans"],
"neverLink": ["auth.json", "history.jsonl", "projects", "file-history"]
},
"session": {
"portable": true,
"paths": ["projects", "history.jsonl", "plans"],
"credentials": ["auth.json"],
"resumeHint": "Resume a copied conversation from the same working directory it was started in."
},
"install": "npm i -g commandcode",
"versionCommand": ["--version"],
"status": "stable",
Expand Down
4 changes: 4 additions & 0 deletions cursor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ cursor-personal # second window, account B — both run side-by-side
- **full** *(default)* — separate user data dir, separate extensions dir.
- **shared** — symlinks `sandbox.json` and `cli-config.json` from `~/.cursor/`. Auth and workspace state stay isolated.

## Continue a chat across accounts

Not supported. Cursor keeps chats in SQLite keyed to the workspace path, so they can't be portably copied between profiles. `multi-cli continue cursor …` is therefore unavailable.

## Caveats

- Cursor's cloud sync, if enabled, can re-merge state between profiles. Disable sync per-profile after first login if you want hard isolation.
Expand Down
4 changes: 4 additions & 0 deletions cursor/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
"linkable": ["sandbox.json", "cli-config.json"],
"neverLink": []
},
"session": {
"portable": false,
"reason": "Chats live in sqlite state databases keyed to the workspace path and cannot be safely merged."
},
"install": "https://cursor.com/download",
"versionCommand": ["--version"],
"status": "stable"
Expand Down
12 changes: 12 additions & 0 deletions gemini-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ gemini-cli-personal
- **full** *(default)* — separate `oauth_creds.json`, `google_accounts.json`, `settings.json`, `history/`, `skills/`.
- **shared** — symlinks `settings.json`, `skills/`, `GEMINI.md` from `~/.gemini/`. Only OAuth state stays isolated.

## Continue a chat across accounts

Rate-limited on one account? Copy the conversation state to a profile logged into another, then resume the same chat.

```bash
multi-cli continue gemini-cli work personal # copy sessions/history (never OAuth)
gemini-cli-personal
gemini --resume # or /chat resume inside the session
```

`base` works as either profile name and means `~/.gemini`. Default merge keeps newer destination files; `--no-merge` overwrites, `--dry-run` previews.

## Verified

Smoke-tested live on Windows after `npm i -g @google/gemini-cli`. Specific binary version recorded in `tests/results.md` after the test run.
6 changes: 6 additions & 0 deletions gemini-cli/adapter.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
"linkable": ["settings.json", "skills", "GEMINI.md"],
"neverLink": ["oauth_creds.json", "google_accounts.json", "history"]
},
"session": {
"portable": true,
"paths": ["tmp"],
"credentials": ["oauth_creds.json", "google_accounts.json"],
"resumeHint": "Resume a copied conversation with `gemini --resume` or `/chat resume` inside the session."
},
"install": "npm i -g @google/gemini-cli",
"versionCommand": ["--version"],
"status": "stable"
Expand Down
Loading
Loading