@reyanshgupta/ditto-cli 0.3.7 → 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.
- package/README.md +192 -72
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -5,20 +5,26 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.rust-lang.org/)
|
|
7
7
|
|
|
8
|
-
Keep work, personal, and client Claude Code, Codex, opencode, OMP, Prime Agent, and
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
│
|
|
22
|
+
│ Ditto CLI choose a profile, then a tool │
|
|
17
23
|
└──────────────────────────────────────────────────────────────────────────────┘
|
|
18
24
|
┌ Profiles ──────────────┐┌ Selected profile ──────────────────────────────────┐
|
|
19
25
|
│ default existing ││work Isolated profile │
|
|
20
|
-
|
|
21
|
-
|
|
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
|
|
42
|
+
│ ││Prime Agent ~/.ditto/profiles/work/prime-agent │
|
|
37
43
|
│ ││Pi ~/.ditto/profiles/work/pi │
|
|
38
44
|
└────────────────────────┘└────────────────────────────────────────────────────┘
|
|
39
45
|
┌──────────────────────────────────────────────────────────────────────────────┐
|
|
40
|
-
│
|
|
41
|
-
│
|
|
42
|
-
│
|
|
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
|
-
|
|
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
|
-
|
|
99
|
+
Take whichever channel you already use. They all install the same binary, and the command is `ditto-cli` either way:
|
|
72
100
|
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
90
|
-
cargo install --git https://github.com/reyanshgupta/ditto-cli # from the latest source
|
|
91
|
-
```
|
|
119
|
+
cargo install --git https://github.com/reyanshgupta/ditto-cli
|
|
92
120
|
|
|
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.
|
|
94
|
-
|
|
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.
|
|
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
|
|
168
|
+
Open the picker:
|
|
146
169
|
|
|
147
170
|
```bash
|
|
148
171
|
ditto-cli
|
|
149
172
|
```
|
|
150
173
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
| `
|
|
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,56 +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
|
|
224
|
+
ditto-cli delete work --yes # removes credentials and sessions for good
|
|
194
225
|
ditto-cli list
|
|
195
|
-
ditto-cli status client-a
|
|
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
|
|
198
|
-
ditto-cli sync --all
|
|
199
|
-
ditto-cli sync --all --history
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
ditto-cli codex client-a # or: cx
|
|
204
|
-
ditto-cli opencode client-a # or: oc
|
|
205
|
-
ditto-cli omp client-a
|
|
206
|
-
ditto-cli prime-agent client-a # or: pa
|
|
207
|
-
ditto-cli pi client-a
|
|
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
|
+
```
|
|
232
|
+
|
|
233
|
+
**Launch a tool**
|
|
208
234
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
ditto-cli
|
|
212
|
-
ditto-cli
|
|
213
|
-
ditto-cli
|
|
214
|
-
ditto-cli
|
|
215
|
-
ditto-cli
|
|
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 |
|
|
216
245
|
|
|
217
|
-
|
|
218
|
-
|
|
246
|
+
Everything after `--` goes to the tool untouched, so any flag it accepts works. `ditto-cli --help` lists every agent.
|
|
247
|
+
|
|
248
|
+
**Bind a directory, so launches from it need no profile name**
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
ditto-cli workspace # what this directory launches with
|
|
219
252
|
ditto-cli workspace use client-a
|
|
220
253
|
ditto-cli workspace clear
|
|
221
254
|
ditto-cli workspace list
|
|
222
255
|
ditto-cli workspace auto off
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**Choose the profile used when a command names none and no directory is bound**
|
|
223
259
|
|
|
224
|
-
|
|
225
|
-
|
|
260
|
+
```bash
|
|
261
|
+
ditto-cli default # report it
|
|
262
|
+
ditto-cli default client-a # pin it
|
|
263
|
+
ditto-cli default --clear # release it
|
|
264
|
+
```
|
|
226
265
|
|
|
227
|
-
|
|
228
|
-
ditto-cli default # report it
|
|
229
|
-
ditto-cli default client-a # pin it
|
|
230
|
-
ditto-cli default --clear # release it
|
|
266
|
+
**Put Ditto in front of the tools' own names, and show the profile inside Claude Code**
|
|
231
267
|
|
|
232
|
-
|
|
233
|
-
ditto-cli
|
|
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
|
|
234
272
|
ditto-cli indicator client-a --on
|
|
235
273
|
ditto-cli indicator client-a --off
|
|
236
|
-
ditto-cli indicator client-a --keep-mine
|
|
274
|
+
ditto-cli indicator client-a --keep-mine # in front of the status line you have
|
|
237
275
|
```
|
|
238
276
|
|
|
239
277
|
The native authentication commands can be called through a profile too:
|
|
@@ -375,6 +413,8 @@ $ cd ~/code/client-a
|
|
|
375
413
|
$ omp --model opus # the same as: ditto-cli omp -- --model opus
|
|
376
414
|
```
|
|
377
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
|
+
|
|
378
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.
|
|
379
419
|
|
|
380
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.
|
|
@@ -421,7 +461,7 @@ Every tool also names the profile in the window and tab title:
|
|
|
421
461
|
ditto:client-a — Codex — my-repo
|
|
422
462
|
```
|
|
423
463
|
|
|
424
|
-
|
|
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.
|
|
425
465
|
|
|
426
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.
|
|
427
467
|
|
|
@@ -439,6 +479,22 @@ herdr pane report-metadata <pane> --source ditto --token profile=<profile>
|
|
|
439
479
|
|
|
440
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.
|
|
441
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
|
+
|
|
442
498
|
## Scripting and agents
|
|
443
499
|
|
|
444
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:
|
|
@@ -463,6 +519,8 @@ ditto-cli --json status client-a
|
|
|
463
519
|
}
|
|
464
520
|
```
|
|
465
521
|
|
|
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
|
+
|
|
466
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.
|
|
467
525
|
|
|
468
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.
|
|
@@ -538,18 +596,75 @@ ditto-cli: repaired claude/skills/apple-design; it was installed pointing at not
|
|
|
538
596
|
|
|
539
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.
|
|
540
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
|
+
|
|
541
643
|
## Where credentials and files are stored
|
|
542
644
|
|
|
543
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:
|
|
544
646
|
|
|
545
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).
|
|
546
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.
|
|
547
650
|
- **opencode** writes `auth.json` into the selected data directory.
|
|
548
651
|
- **OMP** keeps auth, settings, sessions, and caches under `~/.omp/profiles/<name>/agent`.
|
|
549
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.
|
|
550
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.
|
|
551
666
|
|
|
552
|
-
|
|
667
|
+
</details>
|
|
553
668
|
|
|
554
669
|
Ditto CLI's own files are laid out like this:
|
|
555
670
|
|
|
@@ -565,6 +680,8 @@ Ditto CLI's own files are laid out like this:
|
|
|
565
680
|
│ │ ├── config/opencode/
|
|
566
681
|
│ │ ├── data/opencode/ # auth.json lives here
|
|
567
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
|
|
568
685
|
│ ├── prime-agent/ # auth.json and sessions live here
|
|
569
686
|
│ └── pi/ # auth.json and sessions live here
|
|
570
687
|
└── personal/
|
|
@@ -579,7 +696,7 @@ The nested `opencode/` directory is opencode's own doing: it appends its name to
|
|
|
579
696
|
|
|
580
697
|
Directories are created with user-only permissions on macOS and Linux; see [Windows notes](#windows-notes) for the difference there.
|
|
581
698
|
|
|
582
|
-
The `default` profile points to `~/.claude`, `~/.codex
|
|
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.
|
|
583
700
|
|
|
584
701
|
## Environment variables
|
|
585
702
|
|
|
@@ -588,13 +705,16 @@ The `default` profile points to `~/.claude`, `~/.codex`, opencode's own `~/.loca
|
|
|
588
705
|
| `DITTO_HOME` | Move Ditto CLI's state and profile directory from `~/.ditto` |
|
|
589
706
|
| `DITTO_CLAUDE_BIN` | Override the `claude` executable |
|
|
590
707
|
| `DITTO_CODEX_BIN` | Override the `codex` executable |
|
|
708
|
+
| `DITTO_FX_BIN` | Override the `fx` executable |
|
|
591
709
|
| `DITTO_OPENCODE_BIN` | Override the `opencode` executable |
|
|
592
710
|
| `DITTO_OMP_BIN` | Override the `omp` executable |
|
|
593
711
|
| `DITTO_PRIME_AGENT_BIN` | Override the `prime-agent` executable |
|
|
594
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` |
|
|
595
714
|
| `DITTO_PROFILE` | Selected profile name exported to every launched tool, and what Claude Code's status line reports |
|
|
596
715
|
| `DITTO_NO_PROXY` | Hand the terminal straight to the tool, leaving the title to it (macOS and Linux) |
|
|
597
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) |
|
|
598
718
|
| `NO_COLOR` | Draw the Claude Code status line without colour |
|
|
599
719
|
|
|
600
720
|
Example:
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reyanshgupta/ditto-cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Switch between Claude Code, Codex, opencode, OMP,
|
|
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.
|
|
37
|
-
"@reyanshgupta/ditto-cli-darwin-x64": "0.
|
|
38
|
-
"@reyanshgupta/ditto-cli-linux-x64": "0.
|
|
39
|
-
"@reyanshgupta/ditto-cli-win32-x64": "0.
|
|
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
|
}
|