@reyanshgupta/ditto-cli 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +205 -72
  2. package/package.json +6 -6
package/README.md CHANGED
@@ -5,20 +5,26 @@
5
5
  [![MIT license](https://img.shields.io/badge/license-MIT-6f42c1.svg)](LICENSE)
6
6
  [![Rust 1.85+](https://img.shields.io/badge/rust-1.85%2B-6f42c1.svg)](https://www.rust-lang.org/)
7
7
 
8
- Keep work, personal, and client Claude Code, Codex, opencode, OMP, Prime Agent, and Pi logins apart.
8
+ **Keep your work, personal, and client logins apart across Claude Code, Codex, fx, opencode, OMP, Prime Agent, Pi, and every other coding agent [Orca](https://github.com/stablyai/orca) runs — Gemini CLI, Copilot, Cursor, Grok, Kimi, Goose, and two dozen more.**
9
9
 
10
- Ditto CLI gives each profile its own authentication and session history while keeping the capabilities you configured once. Pick a profile in the terminal, then launch any of the six tools. Your existing setup stays available as the `default` profile.
10
+ Each profile gets its own credentials and conversation history. Everything you set up once skills, subagents, slash commands, hooks, plugins, memory files — stays shared, so a profile is the same working environment signed in as somebody else. Your existing setup keeps working as the `default` profile; nothing is moved, copied, or migrated.
11
11
 
12
- Ditto CLI takes its name from the shape-shifting Pokémon: one small tool, whichever coding identity you need.
12
+ ```bash
13
+ ditto-cli create work # a profile with logins of its own
14
+ ditto-cli claude work # launch Claude Code inside it
15
+ ditto-cli workspace use work # or bind the project once and stop typing the name
16
+ ```
17
+
18
+ Pick a profile in the terminal, then launch any of the six tools:
13
19
 
14
20
  ```text
15
21
  ┌──────────────────────────────────────────────────────────────────────────────┐
16
- Ditto CLI choose a profile, then a tool
22
+ Ditto CLI choose a profile, then a tool
17
23
  └──────────────────────────────────────────────────────────────────────────────┘
18
24
  ┌ Profiles ──────────────┐┌ Selected profile ──────────────────────────────────┐
19
25
  │ default existing ││work Isolated profile │
20
- │› work ││ │
21
- personal ││★ Used when no profile is named │
26
+ │ personal ││ │
27
+ │› work ││★ Used when no profile is named │
22
28
  │ ││ │
23
29
  │ ││Sign-in status │
24
30
  │ ││Claude Code ● Signed in │
@@ -33,16 +39,28 @@ Ditto CLI takes its name from the shape-shifting Pokémon: one small tool, which
33
39
  │ ││Codex ~/.ditto/profiles/work/codex │
34
40
  │ ││opencode …/work/opencode/data/opencode │
35
41
  │ ││OMP ~/.omp/profiles/work/agent │
36
- │ ││Prime Agent …/.ditto/profiles/work/prime-agent │
42
+ │ ││Prime Agent ~/.ditto/profiles/work/prime-agent │
37
43
  │ ││Pi ~/.ditto/profiles/work/pi │
38
44
  └────────────────────────┘└────────────────────────────────────────────────────┘
39
45
  ┌──────────────────────────────────────────────────────────────────────────────┐
40
- c Claude Code · x Codex · o opencode · p OMP · a Prime · i Pi
41
- ↑↓ select · n new · e rename · d default
42
- l sign in · L sign out · r refresh · q quit
46
+ c Claude Code · x Codex · o opencode · p OMP · a Prime · i Pi
47
+ ↑↓ select · n new · e rename · d default
48
+ l sign in · L sign out · r refresh · q quit
43
49
  └──────────────────────────────────────────────────────────────────────────────┘
44
50
  ```
45
51
 
52
+ Ditto CLI takes its name from the shape-shifting Pokémon: one small tool, whichever coding identity you need.
53
+
54
+ ## Contents
55
+
56
+ **Getting going** — [How it works](#how-it-works) · [Install](#install) · [Update](#update) · [Quick start](#quick-start)
57
+
58
+ **Everyday use** — [TUI controls](#tui-controls) · [Command-line usage](#command-line-usage) · [Which profile a command uses](#which-profile-a-command-uses) · [Workspaces](#workspaces) · [Shell integration](#shell-integration) · [Knowing which profile you are in](#knowing-which-profile-you-are-in)
59
+
60
+ **What a profile keeps and shares** — [Settings a new profile inherits](#settings-a-new-profile-inherits) · [Skills, subagents, and everything else you set up once](#skills-subagents-and-everything-else-you-set-up-once) · [Where credentials and files are stored](#where-credentials-and-files-are-stored)
61
+
62
+ **Reference** — [Scripting and agents](#scripting-and-agents) · [Environment variables](#environment-variables) · [Renaming a profile signs Claude Code out](#renaming-a-profile-signs-claude-code-out) · [Windows notes](#windows-notes) · [Remove Ditto CLI](#remove-ditto-cli) · [Development](#development)
63
+
46
64
  ## How it works
47
65
 
48
66
  Claude Code, Codex, opencode, OMP, Prime Agent, and Pi keep user-level configuration and login state on disk. That works until you need separate accounts for different jobs. Manually moving auth files around is easy to get wrong, and it is hard to tell which account a new session will use.
@@ -53,48 +71,53 @@ Instead, Ditto CLI launches each tool pointed at the selected profile:
53
71
  | --- | --- |
54
72
  | Claude Code | `CLAUDE_CONFIG_DIR=~/.ditto/profiles/<name>/claude` |
55
73
  | Codex | `CODEX_HOME=~/.ditto/profiles/<name>/codex` |
74
+ | fx | a private `HOME` at `~/.ditto/profiles/<name>/fx-home` that mirrors yours, with `FX_DISABLE_KEYCHAIN=1` so the login is a file inside it rather than a user-wide Keychain entry |
56
75
  | opencode | `XDG_DATA_HOME`, `XDG_CONFIG_HOME`, and `XDG_STATE_HOME` under `~/.ditto/profiles/<name>/opencode` |
57
76
  | OMP | `omp --profile <name>` and `OMP_PROFILE=<name>` |
58
77
  | Prime Agent | `PRIME_AGENT_CODING_AGENT_DIR=~/.ditto/profiles/<name>/prime-agent` and a matching `PRIME_AGENT_SESSION_DIR` |
59
78
  | Pi | `PI_CODING_AGENT_DIR=~/.ditto/profiles/<name>/pi` and a matching `PI_CODING_AGENT_SESSION_DIR` |
60
79
 
80
+ No config files are swapped. Profiles stay independent, and switching only affects the process Ditto CLI launched.
81
+
82
+ <details>
83
+ <summary>Why opencode, Prime Agent, and Pi take more than one variable</summary>
84
+
85
+ <br>
86
+
61
87
  opencode has no single home variable; it resolves its directories the XDG way, so Ditto CLI pins the three bases that hold credentials, configuration, and session state. `XDG_CACHE_HOME` is deliberately left alone so profiles keep sharing opencode's downloaded tooling. Those variables are set for the launched process and anything it starts, including commands opencode's own tools run.
62
88
 
63
89
  Prime Agent and Pi settings can name a session directory, so managed profiles set `PRIME_AGENT_SESSION_DIR` or `PI_CODING_AGENT_SESSION_DIR` explicitly. That keeps transcripts and local harness state inside the selected profile even while reusable settings and capabilities are linked to yours.
64
90
 
65
- No config files are swapped. Profiles remain independent, and switching only affects the process Ditto CLI launched.
91
+ Every other agent, listed under [other agents](#other-agents), is pointed at the profile the same way, through whichever of these levers it exposes: a variable of its own, the XDG bases, or a private `HOME`.
92
+
93
+ </details>
66
94
 
67
95
  ## Install
68
96
 
69
97
  Ditto CLI needs at least one of the supported CLIs to be useful: [Claude Code](https://code.claude.com/docs/en/setup), [OpenAI Codex CLI](https://github.com/openai/codex), [opencode](https://opencode.ai/docs/), [Oh My Pi](https://github.com/can1357/oh-my-pi), [Prime Agent](https://github.com/PrimeIntellect-ai/prime-agent), or [Pi](https://pi.dev).
70
98
 
71
- On macOS and Linux, Homebrew installs the prebuilt binary for your platform, so it needs no Rust toolchain:
99
+ Take whichever channel you already use. They all install the same binary, and the command is `ditto-cli` either way:
72
100
 
73
- ```bash
74
- brew install reyanshgupta/tap/ditto-cli
75
- ```
101
+ | Channel | Command | Notes |
102
+ | --- | --- | --- |
103
+ | **Homebrew** | `brew install reyanshgupta/tap/ditto-cli` | macOS and Linux. Prebuilt, so no Rust toolchain. |
104
+ | **npm** | `npm install -g @reyanshgupta/ditto-cli` | macOS, Linux, and Windows. The same prebuilt binaries. |
105
+ | **binstall** | `cargo binstall ditto-cli` | Takes the released binary rather than compiling one. |
106
+ | **Cargo** | `cargo install ditto-cli` | Builds from source; needs Rust 1.85 or newer. |
76
107
 
77
- npm carries the same prebuilt binaries, on macOS, Linux, and Windows alike. If you installed Claude Code or Codex with npm, it is already here:
108
+ If you installed Claude Code or Codex with npm, npm is already here. You can also try Ditto CLI without installing it:
78
109
 
79
110
  ```bash
80
- npm install -g @reyanshgupta/ditto-cli
81
- npx @reyanshgupta/ditto-cli list # or try it without installing
111
+ npx @reyanshgupta/ditto-cli list
82
112
  ```
83
113
 
84
114
  The npm package is scoped because plain `ditto-cli` on npm belongs to an unrelated project. The command it installs is still `ditto-cli`.
85
115
 
86
- Cargo builds from source instead, and needs Rust 1.85 or newer:
116
+ `cargo binstall` is a good deal quicker than `cargo install`, because a source build compiles a bundled SQLite before it links anything. To build from the latest source, or from a checkout:
87
117
 
88
118
  ```bash
89
- cargo install ditto-cli # from crates.io
90
- cargo install --git https://github.com/reyanshgupta/ditto-cli # from the latest source
91
- ```
92
-
93
- `cargo binstall ditto-cli` takes the released binary instead of compiling one, which is a good deal quicker: a source build compiles a bundled SQLite before it links anything.
119
+ cargo install --git https://github.com/reyanshgupta/ditto-cli
94
120
 
95
- Or from a local checkout:
96
-
97
- ```bash
98
121
  git clone https://github.com/reyanshgupta/ditto-cli.git
99
122
  cd ditto-cli
100
123
  cargo install --path .
@@ -110,7 +133,7 @@ On Windows, `cargo install` already writes into `%USERPROFILE%\.cargo\bin`, whic
110
133
 
111
134
  Ditto CLI installs as `ditto-cli`, not `ditto`: macOS already uses that name for its built-in file-copy utility at `/usr/bin/ditto`.
112
135
 
113
- macOS, Linux, and Windows are all supported, and every command below behaves the same on each. Two smaller things differ on Windows — see [Windows notes](#windows-notes).
136
+ macOS, Linux, and Windows are all supported, and every command below behaves the same on each. A few smaller things differ on Windows — see [Windows notes](#windows-notes).
114
137
 
115
138
  ### Update
116
139
 
@@ -142,21 +165,26 @@ Against an npm install it does not start at all. npm keeps its copy inside its o
142
165
 
143
166
  ## Quick start
144
167
 
145
- Open Ditto CLI:
168
+ Open the picker:
146
169
 
147
170
  ```bash
148
171
  ditto-cli
149
172
  ```
150
173
 
151
- Then:
152
-
153
- 1. Press `n` and name the profile, such as `work`.
154
- 2. Select the profile and press `l`, then choose Claude Code, Codex, opencode, or Prime Agent. Prime Agent opens directly on its `/login` dialog.
155
- 3. Press `c` for Claude Code, `x` for Codex, `o` for opencode, or `a` for Prime Agent.
156
- 4. Press `p` for OMP or `i` for Pi, then use `/login` inside it.
174
+ 1. **Make a profile.** Press `n` and name it, such as `work`.
175
+ 2. **Sign in.** With the profile selected, press `l` and choose Claude Code, Codex, opencode, or Prime Agent; Prime Agent opens straight onto its `/login` dialog. (OMP and Pi have no sign-in command of their own — launch them in step 3 and run `/login` inside.)
176
+ 3. **Launch a tool** with its key: `c` Claude Code, `x` Codex, `o` opencode, `p` OMP, `a` Prime Agent, `i` Pi.
157
177
 
158
178
  Each tool keeps its own credentials. Signing in to one does not copy credentials into another.
159
179
 
180
+ Or skip the picker entirely — every one of those steps has a command:
181
+
182
+ ```bash
183
+ ditto-cli create work
184
+ ditto-cli claude work -- auth login
185
+ ditto-cli claude work
186
+ ```
187
+
160
188
  ## TUI controls
161
189
 
162
190
  | Key | Action |
@@ -164,11 +192,13 @@ Each tool keeps its own credentials. Signing in to one does not copy credentials
164
192
  | `↑` / `↓` or `k` / `j` | Select a profile |
165
193
  | `c` | Launch Claude Code |
166
194
  | `x` | Launch Codex |
195
+ | `f` | Launch fx |
167
196
  | `o` | Launch opencode |
168
197
  | `p` | Launch OMP |
169
198
  | `a` | Launch Prime Agent |
170
199
  | `i` | Launch Pi |
171
- | `l` | Sign in with Claude Code, Codex, opencode, or Prime Agent |
200
+ | `Enter` or `t` | Launch any installed agent: a list of them all, filtered as you type |
201
+ | `l` | Sign in with Claude Code, Codex, fx, opencode, or Prime Agent |
172
202
  | `L` | Sign out, with confirmation |
173
203
  | `n` | Create a profile |
174
204
  | `e` | Rename the selected profile |
@@ -184,54 +214,64 @@ Renaming keeps the profile's settings, session history, and its Codex, opencode,
184
214
 
185
215
  ## Command-line usage
186
216
 
187
- The TUI is optional. Everything it does works directly from the shell:
217
+ The TUI is optional. Everything it does works directly from the shell.
218
+
219
+ **Manage profiles**
188
220
 
189
221
  ```bash
190
- # Profiles
191
222
  ditto-cli create work
192
223
  ditto-cli rename work client-a
193
- ditto-cli delete work --yes # removes credentials and sessions for good
224
+ ditto-cli delete work --yes # removes credentials and sessions for good
194
225
  ditto-cli list
195
- ditto-cli status client-a # sign-in state for all six tools
226
+ ditto-cli status client-a # sign-in state for every tool
196
227
  ditto-cli paths client-a
197
- ditto-cli sync client-a # re-copy your Claude Code settings into it
228
+ ditto-cli sync client-a # preserve configuration for one profile
229
+ ditto-cli sync --all # preserve it for every managed profile
230
+ ditto-cli sync --all --history # also backfill existing conversations
231
+ ```
198
232
 
199
- # Launch a tool, with short aliases where shown
200
- ditto-cli claude client-a # or: cc
201
- ditto-cli codex client-a # or: cx
202
- ditto-cli opencode client-a # or: oc
203
- ditto-cli omp client-a
204
- ditto-cli prime-agent client-a # or: pa
205
- ditto-cli pi client-a
233
+ **Launch a tool**
234
+
235
+ | Command | Alias | Pass arguments through with `--` |
236
+ | --- | --- | --- |
237
+ | `ditto-cli claude client-a` | `cc` | `ditto-cli cc client-a -- --model opus` |
238
+ | `ditto-cli codex client-a` | `cx` | `ditto-cli cx client-a -- --search` |
239
+ | `ditto-cli fx client-a` | — | `ditto-cli fx client-a -- login` |
240
+ | `ditto-cli opencode client-a` | `oc` | `ditto-cli oc client-a -- --model anthropic/claude-opus-5` |
241
+ | `ditto-cli omp client-a` | — | `ditto-cli omp client-a -- --model opus` |
242
+ | `ditto-cli prime-agent client-a` | `pa` | `ditto-cli pa client-a -- --model claude-opus-4-1` |
243
+ | `ditto-cli pi client-a` | — | `ditto-cli pi client-a -- --model anthropic/claude-opus-4-6` |
244
+ | `ditto-cli gemini client-a` | — | `ditto-cli gemini client-a -- --model gemini-2.5-pro`, and so on for every [other agent](#other-agents), by its command name |
206
245
 
207
- # Pass arguments to the underlying CLI after --
208
- ditto-cli claude client-a -- --model opus
209
- ditto-cli codex client-a -- --search
210
- ditto-cli opencode client-a -- --model anthropic/claude-opus-5
211
- ditto-cli omp client-a -- --model opus
212
- ditto-cli prime-agent client-a -- --model claude-opus-4-1
213
- ditto-cli pi client-a -- --model anthropic/claude-opus-4-6
246
+ Everything after `--` goes to the tool untouched, so any flag it accepts works. `ditto-cli --help` lists every agent.
214
247
 
215
- # Bind a directory to a profile
216
- ditto-cli workspace # what this directory launches with
248
+ **Bind a directory, so launches from it need no profile name**
249
+
250
+ ```bash
251
+ ditto-cli workspace # what this directory launches with
217
252
  ditto-cli workspace use client-a
218
253
  ditto-cli workspace clear
219
254
  ditto-cli workspace list
220
255
  ditto-cli workspace auto off
256
+ ```
257
+
258
+ **Choose the profile used when a command names none and no directory is bound**
221
259
 
222
- # Put Ditto in front of the tools' own names
223
- eval "$(ditto-cli shell-init zsh)" # bash, fish, zsh; reads SHELL when omitted
260
+ ```bash
261
+ ditto-cli default # report it
262
+ ditto-cli default client-a # pin it
263
+ ditto-cli default --clear # release it
264
+ ```
224
265
 
225
- # The profile used when a command names none and no directory is bound
226
- ditto-cli default # report it
227
- ditto-cli default client-a # pin it
228
- ditto-cli default --clear # release it
266
+ **Put Ditto in front of the tools' own names, and show the profile inside Claude Code**
229
267
 
230
- # Show the profile inside Claude Code
231
- ditto-cli indicator client-a # report the status line setting
268
+ ```bash
269
+ eval "$(ditto-cli shell-init zsh)" # bash, fish, zsh; reads SHELL when omitted
270
+
271
+ ditto-cli indicator client-a # report the status line setting
232
272
  ditto-cli indicator client-a --on
233
273
  ditto-cli indicator client-a --off
234
- ditto-cli indicator client-a --keep-mine # in front of the status line you have
274
+ ditto-cli indicator client-a --keep-mine # in front of the status line you have
235
275
  ```
236
276
 
237
277
  The native authentication commands can be called through a profile too:
@@ -373,6 +413,8 @@ $ cd ~/code/client-a
373
413
  $ omp --model opus # the same as: ditto-cli omp -- --model opus
374
414
  ```
375
415
 
416
+ A function is written for every agent Ditto knows, the [other agents](#other-agents) included, so `gemini`, `copilot`, or `goose` typed in a bound directory go through Ditto too.
417
+
376
418
  Two ways back out, both printed in the script itself: `command omp` runs OMP with Ditto out of the way, and `ditto-cli omp <profile>` launches another profile for that one run.
377
419
 
378
420
  A function named after a tool cannot loop back into itself. Ditto starts a tool through `execvp`, which searches `PATH` and never looks at shell functions. And if `ditto-cli` cannot be found at all, each function falls through to the tool, so a half-finished update cannot take `claude` with it.
@@ -419,7 +461,7 @@ Every tool also names the profile in the window and tab title:
419
461
  ditto:client-a — Codex — my-repo
420
462
  ```
421
463
 
422
- All six tools write their own titles and keep updating them as you work, so a title set once before handing over is overwritten within moments. Instead, Ditto CLI runs the tool in a pseudoterminal and rewrites the title sequences on their way to the terminal, adding `ditto:<profile>` in front of whatever the tool called itself. You keep the tool's own title and gain the profile.
464
+ The tools write their own titles and keep updating them as you work, so a title set once before handing over is overwritten within moments. Instead, Ditto CLI runs the tool in a pseudoterminal and rewrites the title sequences on their way to the terminal, adding `ditto:<profile>` in front of whatever the tool called itself. You keep the tool's own title and gain the profile.
423
465
 
424
466
  Everything else is forwarded byte for byte. Colours, hyperlinks, clipboard writes, mouse reporting, and anything the tool draws are untouched, and the tool still gets a real terminal, the right window size, and your keystrokes as it always did. Ditto CLI exits with the tool's own exit status.
425
467
 
@@ -437,6 +479,22 @@ herdr pane report-metadata <pane> --source ditto --token profile=<profile>
437
479
 
438
480
  herdr shows it as a pane token, and detection, agent state, and the Claude Code status line all keep working. Nothing needs configuring — it keys off `HERDR_PANE_ID`, which herdr sets in every pane it opens. If herdr is not running or its CLI is not on `PATH` the report is skipped and the launch carries on.
439
481
 
482
+ ### Under Orca
483
+
484
+ [Orca](https://github.com/stablyai/orca) runs agents side by side in worktrees of their own, and reads the same two things herdr does — the pane's foreground process and the title the agent writes — to know which agent a terminal is running and whether it is working or waiting. It also holds a prompt back until the foreground process is the agent it launched. A pseudoterminal in between costs it all three, so Ditto CLI steps aside inside an Orca terminal as well. It keys off `ORCA_PANE_KEY`, which Orca sets in every terminal it opens. Orca has no command that labels a pane already open, so there is nothing to report the profile to: Claude Code's status line still names it, and the other tools run without it in the title.
485
+
486
+ Orca starts an agent by typing its command into your shell, which leaves two ways to put a profile in front of it:
487
+
488
+ - With [shell integration](#shell-integration) loaded, nothing else is needed. The `claude` Orca types is already the function that routes through Ditto CLI.
489
+ - Otherwise open Orca's **Settings → Agents** and set the agent's **Command** to `ditto-cli claude --`, or `ditto-cli claude client-a --` to pin one profile. Orca appends its own arguments and the prompt after it, and everything after `--` reaches the tool. The same works for every agent Ditto knows, which is every agent Orca runs; `ditto-cli --help` lists them.
490
+
491
+ Which profile a worktree launches with follows the usual [resolution](#how-a-directory-is-resolved). A `.ditto.toml` committed at the repository root is checked out into every worktree Orca creates; `ditto-cli workspace` binds a directory that cannot carry one.
492
+
493
+ Two things to know:
494
+
495
+ - Orca's own account switchers and Ditto profiles compose. Orca picks a Claude account by swapping the credentials in `~/.claude` and a Codex account by pointing `CODEX_HOME` at a directory of its own; both are what the `default` profile resolves to, so Orca decides who `default` is, and Ditto profiles are everyone else.
496
+ - Orca's agent status hooks are written to `~/.claude/settings.json`. A profile created before they were turned on has none; `ditto-cli sync <profile>` copies them in, unless the profile already had `hooks` of its own, which `sync` reports under `kept`.
497
+
440
498
  ## Scripting and agents
441
499
 
442
500
  Add `--json` to any reporting command and it prints one JSON object on stdout. The flag is global, so it reads correctly on either side of the subcommand:
@@ -461,7 +519,9 @@ ditto-cli --json status client-a
461
519
  }
462
520
  ```
463
521
 
464
- `list`, `status`, `paths`, `create`, `rename`, `delete`, `default`, `workspace`, and `indicator` all answer in JSON. Errors become `{"error": "..."}` on stderr, and every failure exits 1. `shell-init` prints a script for a shell to read rather than a report, so `--json` has nothing to do there.
522
+ The [other agents](#other-agents) follow in the order `--help` lists them, each `unavailable` unless it is installed, so the shape is the same on every machine. The human report shows only the ones you have.
523
+
524
+ `list`, `status`, `paths`, `create`, `rename`, `delete`, `sync`, `default`, `workspace`, and `indicator` all answer in JSON. Errors become `{"error": "..."}` on stderr, and every failure exits 1. `shell-init` prints a script for a shell to read rather than a report, so `--json` has nothing to do there.
465
525
 
466
526
  Running `ditto-cli` with no subcommand opens the picker, which needs an interactive terminal. Without one it exits 1 and names the commands to use instead, rather than failing on a terminal that was never there.
467
527
 
@@ -481,11 +541,12 @@ So creating a profile copies `~/.claude/settings.json` into it. What Ditto CLI i
481
541
  From then on the profile's settings are its own. Change the model in one profile and the others keep theirs; a later `ditto-cli sync` fills in settings the profile has never answered and leaves the ones it has:
482
542
 
483
543
  ```bash
484
- ditto-cli sync client-a # bring it up to date, keeping its own answers
544
+ ditto-cli sync client-a # bring one profile up to date
545
+ ditto-cli sync --all # bring every managed profile up to date
485
546
  ditto-cli sync client-a --overwrite # your configuration wins outright
486
547
  ```
487
548
 
488
- `sync` is also how profiles created before this behaviour existed catch up.
549
+ Naming a profile preserves configuration for that profile only; `--all` makes the scope every managed profile. `sync` is also how profiles created before this behaviour existed catch up, including tool directories added by a newer Ditto release.
489
550
 
490
551
  ## Skills, subagents, and everything else you set up once
491
552
 
@@ -502,12 +563,22 @@ A profile exists to be signed in as somebody else, not to be a different working
502
563
 
503
564
  These are symbolic links, so a skill you write tomorrow is in every profile the moment you save it, with nothing to sync and no copies to drift apart. Everything else — `.claude.json`, `auth.json`, sessions, session artifacts, history, `agent.db` — stays inside the profile, which is the whole of what a profile keeps to itself. Prime Agent and Pi keep `models.json` private too, because custom provider definitions may contain literal API keys and secret headers.
504
565
 
566
+ Conversation history is not linked: chats can contain account- or client-specific work, and shared writable session stores would make every profile's future activity visible to every other profile. Existing Claude Code, Codex, opencode, OMP, Prime Agent, and Pi conversations can instead be copied once, without replacing anything already in the destination. Choose one profile explicitly or all managed profiles explicitly:
567
+
568
+ ```bash
569
+ ditto-cli sync client-a --history
570
+ ditto-cli sync --all --history
571
+ ```
572
+
573
+ After the copy, every tool keeps writing to that profile's own history store.
574
+
505
575
  What is shared is a named list rather than everything-but-the-credentials. Ditto CLI learning about a new extension directory late costs you a missing feature; sharing a new credential file by accident would cost you the isolation the tool exists for.
506
576
 
507
577
  Profiles created before this have real directories where the links go. `sync` reports those and leaves them alone; `--adopt` points them at yours, moving what was there aside as `<name>.before-ditto` rather than deleting it:
508
578
 
509
579
  ```bash
510
580
  ditto-cli sync client-a # link what can be linked, report what cannot
581
+ ditto-cli sync --all # do that for every managed profile
511
582
  ditto-cli sync client-a --adopt # replace the profile's own copies too
512
583
  ```
513
584
 
@@ -525,18 +596,75 @@ ditto-cli: repaired claude/skills/apple-design; it was installed pointing at not
525
596
 
526
597
  `ditto-cli sync <profile>` does the same for every tool at once and reports them under `repaired`. A link is only rewritten when reading it against the path the installer was given names something that exists, so a link that is relative and broken for reasons of its own is left exactly as it is.
527
598
 
599
+ ## Other agents
600
+
601
+ Besides the tools above, Ditto CLI launches every other coding agent [Orca](https://github.com/stablyai/orca) does, by the agent's own command name: `ditto-cli gemini client-a -- --model gemini-2.5-pro`. They share one mechanism, described by a table in `src/tools.rs` rather than by code, and `ditto-cli --help` lists them. Each is pointed at the profile through whichever lever it exposes:
602
+
603
+ - **Its own variable**, set to a directory inside the profile, the way `CODEX_HOME` is.
604
+ - **The XDG bases**, pinned inside the profile the way they are for opencode, for agents that follow that convention. `XDG_CACHE_HOME` is left shared.
605
+ - **A private `HOME`**, for agents that derive their paths from the home directory and honour nothing else. The profile's home mirrors your real one entry by entry — shell startup files, Git configuration, SSH, toolchains — except for the agent's own directory, so commands the agent runs still find your setup. fx works the same way.
606
+
607
+ | Agent | Command | Pointed at the profile by | Sign-in read from |
608
+ | --- | --- | --- | --- |
609
+ | Gemini CLI | `gemini` | `GEMINI_CLI_HOME` | `oauth_creds.json` |
610
+ | Qwen Code | `qwen` | `QWEN_HOME` | `oauth_creds.json` |
611
+ | OpenClaude | `openclaude` | `OPENCLAUDE_CONFIG_DIR` | `.credentials.json`; the macOS Keychain, which it prefers, is user-wide |
612
+ | Copilot | `copilot` | `COPILOT_HOME` | not readable: the token is in the OS keychain, keyed by GitHub account |
613
+ | Cursor Agent | `cursor-agent` | `CURSOR_CONFIG_DIR`, with `AGENT_CLI_CREDENTIAL_STORE=file` | not readable |
614
+ | Grok | `grok` | `GROK_HOME` | `auth.json` |
615
+ | Devin | `devin` | XDG bases | `credentials.toml` |
616
+ | Kimi Code | `kimi` | `KIMI_CODE_HOME` | `credentials/` |
617
+ | Cline | `cline` | `CLINE_DIR` | `data/settings/providers.json` |
618
+ | Codebuff | `codebuff` | private `HOME` (`~/.config/manicode`) | `credentials.json` |
619
+ | Continue | `cn` | `CONTINUE_GLOBAL_DIR` | none: current builds have no sign-in |
620
+ | Command Code | `command-code` | private `HOME` | `auth.json` |
621
+ | Hermes Agent | `hermes` | `HERMES_HOME` | `auth.json`, `.env` |
622
+ | OpenClaw | `openclaw` | `OPENCLAW_STATE_DIR` | `credentials/`, `secrets.json`, `.env` |
623
+ | Mistral Vibe | `vibe` | `VIBE_HOME`, with `VIBE_TEST_DISABLE_KEYRING=1` | `.env` |
624
+ | Rovo Dev | `acli` | private `HOME` (`~/.rovodev` and `~/.acli`) | `~/.acli` |
625
+ | Amp | `amp` | XDG bases | `secrets.json` |
626
+ | Droid | `droid` | private `HOME`, with `FACTORY_DISABLE_KEYRING=1` | not readable |
627
+ | Goose | `goose` | XDG bases, with `GOOSE_DISABLE_KEYRING=1` | `secrets.yaml` |
628
+ | Aider | `aider` | private `HOME` | `oauth-keys.env`; API keys come from the environment |
629
+ | Crush | `crush` | XDG bases | the data directory's `crush.json` |
630
+ | Kilo Code | `kilo` | XDG bases | `auth.json` |
631
+ | Kiro | `kiro-cli` | private `HOME` | `data.sqlite3` |
632
+ | Auggie | `auggie` | private `HOME` | `session.json` |
633
+ | Antigravity | `agy` | private `HOME` | not readable: the Google login is in the OS keychain with no way out, so only `GEMINI_API_KEY` is per profile |
634
+ | MiMo Code | `mimo` | XDG bases | `auth.json` |
635
+ | Ante | `ante` | `ANTE_HOME` | `auth/` |
636
+ | Trae | `traecli` | private `HOME` | `cli/auth.json`; the least certain entry, since Trae's documentation names no variable |
637
+ | Autohand | `autohand` | `AUTOHAND_HOME` | not readable: the login shares `config.json` with the settings |
638
+
639
+ "Not readable" means `ditto-cli status` reports the agent as `unavailable` even when it is installed: the login is somewhere Ditto has no file to check, and a keychain entry is one every profile shares besides. Sign in with the agent's own command through Ditto — `ditto-cli grok client-a -- login` — or from inside it; the `sign_in` field of `ditto-cli create --json` names the right form for each.
640
+
641
+ What a profile links back to yours, and what `sync --history` copies once, follows the same rule as for the built-in tools: settings, instructions, skills, commands, and plugins are linked; credentials, sessions, and caches are not. MCP configuration files are not linked either, because the ones that carry OAuth tokens are named the same as the ones that do not. Every entry's own caveats — an undocumented variable, a keychain with no switch, a fork's macOS data directory — are in the comment beside it in `src/tools.rs`, and `DITTO_<AGENT>_BIN` overrides any agent's executable (`DITTO_CURSOR_AGENT_BIN=agent`).
642
+
528
643
  ## Where credentials and files are stored
529
644
 
530
645
  Ditto CLI does not ask for passwords, parse OAuth tokens, or keep credentials in its state file. Claude Code, Codex, and opencode authentication still runs through their installed CLIs, and OMP, Prime Agent, and Pi authentication through `/login` inside their interfaces. Each tool stores the result wherever it normally would, under the directory Ditto CLI pointed it at:
531
646
 
532
647
  - **Claude Code** uses the selected `CLAUDE_CONFIG_DIR`. On macOS the credentials themselves stay in the system Keychain, keyed to that directory's path, which is what keeps two profiles from sharing one login — and why [renaming a profile signs Claude Code out](#renaming-a-profile-signs-claude-code-out).
533
648
  - **Codex** keeps its auth state under the selected `CODEX_HOME`.
649
+ - **fx** keeps its login and sessions under `.fx` in the profile's private home; the macOS Keychain, which it would otherwise use, is refused because it is user-wide.
534
650
  - **opencode** writes `auth.json` into the selected data directory.
535
651
  - **OMP** keeps auth, settings, sessions, and caches under `~/.omp/profiles/<name>/agent`.
536
652
  - **Prime Agent** keeps ordinary provider credentials in the selected agent directory's `auth.json`; its sessions and session artifacts stay below the same profile.
537
653
  - **Pi** keeps provider credentials in the selected agent directory's `auth.json`; `PI_CODING_AGENT_SESSION_DIR` keeps its transcripts below the same profile.
654
+ - **Every other agent** stores its login under the directory or home it was handed; the [table above](#other-agents) says which file, and which agents keep it in a keychain instead.
655
+
656
+ <details>
657
+ <summary><strong>Prime Agent has two upstream exceptions to that boundary</strong></summary>
658
+
659
+ <br>
660
+
661
+ Prime Inference login uses the Prime CLI credential at `~/.prime/config.json`, for which Prime Agent exposes no path override; that one provider therefore remains shared across Ditto profiles. `PRIME_API_KEY` and other API-key environment variables are shared for the same reason any inherited environment credential is.
662
+
663
+ Prime Agent also uses one user-wide background-service socket, so its `agents`, `list`, and `attach` views can see running agents from other profiles even though each agent receives its selected auth and session roots.
664
+
665
+ Ditto's status report counts provider credentials in the isolated `auth.json`, not these ambient sources.
538
666
 
539
- Prime Agent currently has two upstream exceptions to that boundary. Prime Inference login uses the Prime CLI credential at `~/.prime/config.json`, for which Prime Agent exposes no path override; that one provider therefore remains shared across Ditto profiles. `PRIME_API_KEY` and other API-key environment variables are shared for the same reason any inherited environment credential is. Prime Agent also uses one user-wide background-service socket, so its `agents`, `list`, and `attach` views can see running agents from other profiles even though each agent receives its selected auth and session roots. Ditto's status report counts provider credentials in the isolated `auth.json`, not these ambient sources.
667
+ </details>
540
668
 
541
669
  Ditto CLI's own files are laid out like this:
542
670
 
@@ -552,6 +680,8 @@ Ditto CLI's own files are laid out like this:
552
680
  │ │ ├── config/opencode/
553
681
  │ │ ├── data/opencode/ # auth.json lives here
554
682
  │ │ └── state/opencode/
683
+ │ ├── grok/ # one directory per other agent, named after it
684
+ │ ├── droid-home/ # or a private home, for the agents that need one
555
685
  │ ├── prime-agent/ # auth.json and sessions live here
556
686
  │ └── pi/ # auth.json and sessions live here
557
687
  └── personal/
@@ -566,7 +696,7 @@ The nested `opencode/` directory is opencode's own doing: it appends its name to
566
696
 
567
697
  Directories are created with user-only permissions on macOS and Linux; see [Windows notes](#windows-notes) for the difference there.
568
698
 
569
- The `default` profile points to `~/.claude`, `~/.codex`, opencode's own `~/.local/share/opencode` and `~/.config/opencode` (or wherever your `XDG_*` variables already send them), OMP's native `~/.omp/agent` profile, Prime Agent's `~/.prime/agent` directory (or `PRIME_AGENT_CODING_AGENT_DIR` when set), and Pi's `~/.pi/agent` directory (or `PI_CODING_AGENT_DIR` when set). It exposes your existing setup without copying or migrating anything. For opencode the `default` profile resolves the same XDG bases opencode would pick on its own, so pointing at it changes nothing.
699
+ The `default` profile points to `~/.claude`, `~/.codex` (or `CODEX_HOME` when set), opencode's own `~/.local/share/opencode` and `~/.config/opencode` (or wherever your `XDG_*` variables already send them), OMP's native `~/.omp/agent` profile, Prime Agent's `~/.prime/agent` directory (or `PRIME_AGENT_CODING_AGENT_DIR` when set), and Pi's `~/.pi/agent` directory (or `PI_CODING_AGENT_DIR` when set). It exposes your existing setup without copying or migrating anything. For opencode the `default` profile resolves the same XDG bases opencode would pick on its own, so pointing at it changes nothing.
570
700
 
571
701
  ## Environment variables
572
702
 
@@ -575,13 +705,16 @@ The `default` profile points to `~/.claude`, `~/.codex`, opencode's own `~/.loca
575
705
  | `DITTO_HOME` | Move Ditto CLI's state and profile directory from `~/.ditto` |
576
706
  | `DITTO_CLAUDE_BIN` | Override the `claude` executable |
577
707
  | `DITTO_CODEX_BIN` | Override the `codex` executable |
708
+ | `DITTO_FX_BIN` | Override the `fx` executable |
578
709
  | `DITTO_OPENCODE_BIN` | Override the `opencode` executable |
579
710
  | `DITTO_OMP_BIN` | Override the `omp` executable |
580
711
  | `DITTO_PRIME_AGENT_BIN` | Override the `prime-agent` executable |
581
712
  | `DITTO_PI_BIN` | Override the `pi` executable |
713
+ | `DITTO_<AGENT>_BIN` | Override any other agent's executable, named from its command: `DITTO_CURSOR_AGENT_BIN` |
582
714
  | `DITTO_PROFILE` | Selected profile name exported to every launched tool, and what Claude Code's status line reports |
583
715
  | `DITTO_NO_PROXY` | Hand the terminal straight to the tool, leaving the title to it (macOS and Linux) |
584
716
  | `HERDR_PANE_ID` | Read, not set: herdr names the pane it opened, and Ditto CLI steps aside and reports the profile to herdr. See [Under herdr](#under-herdr) |
717
+ | `ORCA_PANE_KEY` | Read, not set: Orca names the terminal it opened, and Ditto CLI steps aside. See [Under Orca](#under-orca) |
585
718
  | `NO_COLOR` | Draw the Claude Code status line without colour |
586
719
 
587
720
  Example:
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reyanshgupta/ditto-cli",
3
- "version": "0.3.6",
4
- "description": "Switch between Claude Code, Codex, opencode, OMP, and Prime Agent accounts",
3
+ "version": "0.4.0",
4
+ "description": "Switch between Claude Code, Codex, opencode, OMP, Prime Agent, Pi, and 29 more coding-agent accounts",
5
5
  "keywords": [
6
6
  "claude-code",
7
7
  "codex",
@@ -33,9 +33,9 @@
33
33
  "node": ">=18"
34
34
  },
35
35
  "optionalDependencies": {
36
- "@reyanshgupta/ditto-cli-darwin-arm64": "0.3.6",
37
- "@reyanshgupta/ditto-cli-darwin-x64": "0.3.6",
38
- "@reyanshgupta/ditto-cli-linux-x64": "0.3.6",
39
- "@reyanshgupta/ditto-cli-win32-x64": "0.3.6"
36
+ "@reyanshgupta/ditto-cli-darwin-arm64": "0.4.0",
37
+ "@reyanshgupta/ditto-cli-darwin-x64": "0.4.0",
38
+ "@reyanshgupta/ditto-cli-linux-x64": "0.4.0",
39
+ "@reyanshgupta/ditto-cli-win32-x64": "0.4.0"
40
40
  }
41
41
  }