sideby 0.1.0 → 0.2.1

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 (38) hide show
  1. package/README.md +71 -413
  2. package/README.zh-CN.md +71 -414
  3. package/dist/build.json +1 -1
  4. package/dist/src/cli/commands.d.ts +13 -0
  5. package/dist/src/cli/commands.js +86 -7
  6. package/dist/src/cli/json-schemas.js +35 -0
  7. package/dist/src/cli/main.js +99 -9
  8. package/dist/src/core/accounts.d.ts +2 -0
  9. package/dist/src/core/accounts.js +15 -0
  10. package/dist/src/core/config.d.ts +1 -0
  11. package/dist/src/core/config.js +3 -0
  12. package/dist/src/core/launch.d.ts +3 -1
  13. package/dist/src/core/resume.d.ts +9 -0
  14. package/dist/src/core/resume.js +15 -0
  15. package/dist/src/core/share-meaning.d.ts +6 -0
  16. package/dist/src/core/share-meaning.js +32 -0
  17. package/dist/src/core/shell-init.d.ts +7 -1
  18. package/dist/src/core/shell-init.js +25 -2
  19. package/dist/src/families/claude/index.js +3 -0
  20. package/dist/src/families/codex/index.js +3 -0
  21. package/dist/src/families/grok/index.js +4 -0
  22. package/dist/src/families/shared/sessions.d.ts +9 -0
  23. package/dist/src/families/shared/sessions.js +70 -0
  24. package/dist/src/index.d.ts +1 -1
  25. package/dist/src/panel/handler.d.ts +0 -2
  26. package/dist/src/panel/handler.js +1 -16
  27. package/dist/src/runtime.d.ts +48 -2
  28. package/dist/src/runtime.js +54 -1
  29. package/dist/src/types.d.ts +7 -0
  30. package/dist/testing/index.js +2 -1
  31. package/package.json +1 -1
  32. package/schemas/config.json +4 -0
  33. package/schemas/families.json +205 -0
  34. package/schemas/new.json +18 -0
  35. package/skills/sideby/SKILL.md +15 -6
  36. package/skills/sideby/references/accounts.md +1 -1
  37. package/skills/sideby/references/setup.md +31 -0
  38. package/skills/sideby/references/sharing.md +17 -0
package/README.md CHANGED
@@ -1,452 +1,110 @@
1
- # sideby
1
+ <p align="center">
2
+ <img alt="sideby: one shared config, every account in its own terminal" src="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/banner.jpg" width="880">
3
+ </p>
2
4
 
3
- **Run every AI coding account side by side.**
5
+ <p align="center">
6
+ <b>Run every AI coding account side by side.</b><br>
7
+ Claude Code, Codex, Grok Build and pi: one directory per account, one shared set of skills, hooks and rules, one panel for every quota.
8
+ </p>
4
9
 
5
- Claude Code, Codex, Grok Build and pi, each account in its own directory, all sharing one set of skills, hooks and rules, with a quota panel that shows every account at once.
10
+ <p align="center">
11
+ <a href="https://www.npmjs.com/package/sideby"><img alt="npm" src="https://img.shields.io/npm/v/sideby"></a>
12
+ <a href="https://github.com/Adonis0123/sideby/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/Adonis0123/sideby/actions/workflows/ci.yml/badge.svg"></a>
13
+ <img alt="Node 22.18+" src="https://img.shields.io/badge/node-%E2%89%A522.18-339933">
14
+ <a href="LICENSE"><img alt="MIT" src="https://img.shields.io/badge/license-MIT-blue"></a>
15
+ </p>
6
16
 
7
- ```sh
8
- npx sideby # try it
9
- npm i -g sideby # install the `sideby` command
10
- ```
11
-
12
- English | [中文](README.zh-CN.md)
13
-
14
- <picture>
15
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel-dark.png">
16
- <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png">
17
- <img alt="sideby panel: every account's 5h and 7d quota, 7-day token usage and health in one page" src="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png" width="1280">
18
- </picture>
17
+ <p align="center">English | <a href="README.zh-CN.md">中文</a></p>
19
18
 
20
19
  ## Why
21
20
 
22
- - **Side by side, not switching.** Tools such as cc-switch and magpie change a Host's global config, so one provider is active at a time; that suits people who move between providers. sideby keeps every account in its own directory and starts the official CLI with that directory selected. Your work subscription, personal subscription and a DeepSeek key can run in three terminals at the same time, and the parent shell is never changed.
23
- - **One copy of skills, hooks and rules.** The Main Account (for example `~/.claude`) is the source. Other accounts link or copy its Shared Items, using the rule each Host needs. `sideby doctor` finds drift and `--fix` repairs what is safe. It never replaces a real file, never changes where a link points, and never reads credential values.
24
- - **Every quota on one screen.** `sideby quota` and `sideby ui` show each account's 5-hour and 7-day quota, when it resets, how old the data is, and the last 7 days of tokens. sideby reads Claude Code and Codex local records itself (no ccusage needed) and takes Claude quota from the data Claude Code passes to your status line. No network calls, no token reads.
25
-
26
- ## Quick start
27
-
28
- Requires Node 22.18 or later and at least one Host CLI installed and signed in.
29
-
30
- ```sh
31
- sideby # list accounts; with only ~/.claude you see claude:main
32
- sideby new claude work # create ~/.claude-work, linked to your shared config
33
- sideby login claude:work # sign in once, in your own terminal
34
- sideby run work # start Claude Code with the work account
35
- ```
36
-
37
- `sideby run claude:main` keeps using `~/.claude` at the same time. Arguments after `--` go to the Host: `sideby run work -- --resume`.
38
-
39
- With a few accounts, `sideby list` looks like this:
40
-
41
- ```
42
- ACCOUNT TYPE LOGIN EMAIL MODEL DIR
43
- claude:main main ✓ me@example.com opus ~/.claude
44
- claude:deepseek api key — deepseek-v4 ~/.claude-deepseek
45
- claude:work sub ✓ work@example.com opus ~/.claude-work
46
- codex:team sub ✓ team@example.com gpt-5.5 ~/.codex-team
47
- grok:lab sub login needed — grok-code ~/.grok-lab
48
- ```
49
-
50
- EMAIL is who the account is signed in as, read from the host's login file (Claude Code, Codex, Grok Build). sideby reads only that identity field there, never a token (ADR-0003).
51
-
52
- If a family plugin fails while reading an account (for example its settings file is not valid JSON), `list` still shows the account and prints the problem on stderr; `list --json` puts it in that account's `problems` array.
53
-
54
- ### API accounts
55
-
56
- ```sh
57
- sideby new claude deepseek --api
58
- $EDITOR ~/.claude-deepseek/proxy.env # fill in the variables; the file stays mode 600
59
- sideby run deepseek
60
- ```
61
-
62
- `proxy.env` uses a small dotenv subset (`KEY=VALUE`, optional `export `, quotes, `#` comments, and `$NAME` / `${NAME}` referring to a key set earlier in the same file or to `HOME`, `USER`, `LOGNAME`, `TMPDIR` or an `XDG_*_HOME` location; no other shell variable is read and there is no command substitution). Its variables reach only the launched Host process. sideby refuses to start unless the file's mode is exactly 600 and tells you to run `chmod 600`.
63
-
64
- An API account needs no sign-in: its login state is `not-needed`, shown as `key` in the LOGIN column of `sideby list`.
65
-
66
- pi is the exception. pi can keep a `proxy.env` in any account to load provider keys, so sideby loads the file at launch but still treats the account as a subscription account that signs in with `/login`.
67
-
68
- ### Doctor
69
-
70
- ```sh
71
- sideby doctor # every account
72
- sideby doctor work # one account, or a family: sideby doctor grok
73
- sideby doctor --fix # apply the safe repairs
74
- ```
75
-
76
- ```
77
- ✓ claude:work subscription shared 4/4 ~/.claude-work
78
- ✗ grok:lab subscription shared 2/3 ~/.grok-lab
79
- fail hooks must be a real copy, not a link (host says: "Grok hooks directory has wrong type (expected real directory)")
80
- run `sideby doctor --fix` to replace the link with a copy
81
-
82
- 1 issue(s) can be fixed with `sideby doctor --fix`.
83
- ```
84
-
85
- - `shared n/m` counts the Shared Items the Main Account actually has. An item the Main Account does not have (many people have no `commands` or `themes`) is skipped: no warning, not counted.
86
- - A link whose target exists but is not the Main Account's item is a warning (`link.other-target`), since you may have set it up on purpose; where the Host needs a real file or copy, any link is a failure (`symlink-forbidden`). A dangling link is a failure (`link.dangling`). sideby never changes where a link points; it only reports.
87
- - A real file or directory where a link belongs is reported with a command to keep it (`mv <x> <x>.local && ln -s …`). sideby never replaces it.
88
- - Credential files (`proxy.env`, `auth.json`, `.claude.json`) are checked for type and mode 600; `--fix` sets the mode back to 600. The only content sideby reads from them is the `mcpServers` key of `.claude.json`, which it keeps in sync with the Main Account, and the identity fields shown as the account's email (`oauthAccount.emailAddress` and `organizationName` of `.claude.json`, the `email` of Grok's `auth.json` entry, the `email` claim of Codex's `id_token`); tokens are never read out, stored or shown. `--force` allows that sync to remove servers the account has and the Main Account does not.
89
- - Backup leftovers (`*.bak*`, `*backup*`, `*.tmp*`) are counted, never touched.
90
-
91
- Warnings alone exit 0; any failure exits 1.
92
-
93
- ### Panel
94
-
95
- ```sh
96
- sideby ui # http://127.0.0.1:17420, opens your browser
97
- sideby ui --port 8080
98
- sideby ui --no-open # print the address only
99
- ```
100
-
101
- The panel listens on `127.0.0.1` only. If another sideby panel already runs on the port, sideby opens that one; if another program holds the port, it tries the next ones and prints the address it used. The page shows each account's quota windows in aligned columns, usage, last use, sign-in email and health, and refreshes itself every 20 seconds while it is open (every 5 seconds for a few minutes after you turn on quota, so the first numbers appear soon after a status line reports them). A "Hide emails" switch masks addresses as `a•••@example.com` for screen sharing. It runs Doctor with one-click fixes, creates accounts (with an optional short command), and can turn on Claude quota after showing the diff. One quota setup covers every Claude Code account, so the "Turn on quota" button sits in the Claude Code group header, not on each account. For a new subscription account it gives you the `sideby login <account>` command to copy; signing in always happens in your terminal.
102
-
103
- #### Desktop app
104
-
105
- ```sh
106
- sideby app install # macOS: ~/Applications/sideby.app; Linux: a "sideby" entry in the app menu
107
- sideby app uninstall
108
- sideby ui --background # what the app runs: start the panel without a terminal, then open it
109
- sideby ui --stop # stop the background panel
110
- ```
111
-
112
- Double-click the app to open the panel. It starts the panel in the background when none is running (`sideby ui --background`), reuses it when one is, and opens your browser. The background panel keeps running after you close the tab and logs to `${XDG_STATE_HOME:-~/.local/state}/sideby/panel.log`. Apps started from Finder or a desktop menu get a minimal PATH, so the background panel asks your login shell (`$SHELL -ilc`) for its PATH to find hosts in places like `~/.local/bin`.
113
-
114
- The app runs the Node and sideby it was installed with: after upgrading either, run `sideby app install` again to update it in place. If your own page embeds the panel (see below), `sideby app install --url http://accounts.localhost:17333/` makes the app open that address instead. Install and uninstall only touch files that `sideby app install` created. The app is a local, unsigned launcher script; it is not meant to be copied to other machines.
115
-
116
- #### Embed the panel
21
+ You have a work subscription, a personal one and maybe an API key. Switching tools rewrite the Host's global config, so only one account is live at a time, and your skills and hooks slowly drift apart between accounts.
117
22
 
118
- Another local Node page can mount the panel under its own path. The handler keeps its own Host, Origin and token checks; you list the hosts your page answers on.
23
+ - **Side by side, not switching.** Each account lives in its own directory, and sideby starts the official CLI with that directory selected. Run three accounts in three terminals; your shell is never changed.
24
+ - **One copy of skills, hooks and rules.** Your Main Account (for example `~/.claude`) is the source. Other accounts link or copy its Shared Items ([what is shared](docs/guide/usage.md#what-each-account-shares)), and `sideby doctor --fix` repairs drift without touching real files or credentials.
25
+ - **Every quota on one screen.** 5-hour and 7-day quota, reset times and 7-day token usage for every account. Out of room? `sideby next` starts the account with the most left. All from local files: no network calls, no token reads.
119
26
 
120
- ```ts
121
- import { createServer } from 'node:http'
122
- import { createPanelHandler, createRuntime } from 'sideby'
123
-
124
- const panel = createPanelHandler({
125
- runtime: () => createRuntime(), // a factory: every request sees the disk as it is now
126
- basePath: '/accounts',
127
- allowedHosts: ['tools.localhost:8080'],
128
- theme: {
129
- colorScheme: 'light', // 'auto' (default) follows the system
130
- header: 'bar', // a full-width header strip instead of a title on the page background
131
- light: { font: '-apple-system, "PingFang SC", sans-serif', bg: '#f2f8fc', primary: '#e1f0fb', onPrimary: '#1f6396', radius: '10px', shadow: 'none' },
132
- },
133
- lang: 'en', // 'auto' (default) uses the language picked in the panel, then the browser's
134
- })
135
- createServer(async (req, res) => {
136
- if (!(await panel(req, res))) res.writeHead(404).end()
137
- }).listen(8080, '127.0.0.1')
138
- ```
139
-
140
- `theme` makes the page match yours. Its tokens become CSS custom properties inside the page's own nonce'd `<style>`, so the panel's strict CSP stays as it is. An iframe cannot read its parent's CSS variables, so pass concrete values.
141
-
142
- - **Tokens** (all optional): type `font`, `monoFont`, `fontSize`, `smallSize`, `controlSize`, `headingSize`, `largeSize`, `titleSize`, `titleWeight`, `headingWeight`, `strongWeight`, `buttonWeight`, `primaryWeight`; colors `bg`, `surface`, `surfaceMuted`, `border`, `borderStrong`, `text`, `muted`, `faint`, `hover`, `active`, `accent`, `accentSoft`, `focus`, `primary`, `primaryHover`, `primaryActive`, `onPrimary`, `primaryBorder`, `primaryHoverBorder`, `buttonText`, `buttonHoverBorder`, `buttonHoverText`, `checkColor`, `ok`, `okSoft`, `warn`, `warnSoft`, `fail`, `failSoft`, `logoBg`, `logoColor`, `logoBorder`, `shadow`, `focusOutline`, `focusRing`; shape and layout `radius`, `controlRadius`, `chipRadius`, `controlHeight`, `controlHeightSmall`, `fieldHeight`, `contentWidth`, `gutter`. [`src/panel/theme.ts`](src/panel/theme.ts) documents each one.
143
- - **Light and dark**: colors in `light` apply in light mode only; type, shape and layout tokens apply in both. Give `dark` for dark-mode colors, or set `colorScheme: 'light'` when your page has no dark mode.
144
- - **Checked when the handler is created**: an unknown token, or a value with anything but letters, digits, spaces and `# % ( ) , . + - / ' " _` (so no `;`, `{`, `}`, `<`, `\`, `*` or `:`), with `url(`, unbalanced parentheses or quotes, or over 200 characters, throws a `TypeError` that names the token.
145
- - Without `theme`, the page looks the same as `sideby ui`.
146
-
147
- `lang` (`'auto'`, `'en'` or `'zh'`) keeps the panel's language in step with your page. `'en'` or `'zh'` wins over the language picked in the panel and hides the panel's language switch; the choice picked there stays saved and applies again under `'auto'`. `'auto'` (the default) uses the language picked in the panel, then the browser language. Any other value throws a `TypeError` when the handler is created. The handler reads `lang` once: to switch language, create the handler again.
148
-
149
- ### Claude quota
27
+ ## Quick start
150
28
 
151
- Claude Code passes quota data only to its status line command, so sideby needs a one-time, reversible change:
29
+ Requires Node 22.18+ and at least one Host CLI (Claude Code, Codex, Grok Build or pi) installed. AI agents: follow the setup steps in [`llms.txt`](llms.txt) ("Set up sideby for a user") instead.
152
30
 
153
31
  ```sh
154
- npm i -g sideby # the status line calls `sideby` on every refresh
155
- sideby quota setup claude # shows the diff, changes nothing
156
- sideby quota setup claude --yes # writes the change
157
- sideby quota # 5h / 7d per account, with data age
158
- sideby quota teardown claude # restores the original file byte for byte
32
+ npm i -g sideby
33
+ sideby new claude work # create ~/.claude-work, sharing ~/.claude's skills and hooks
34
+ sideby login claude:work # sign in once
35
+ sideby run work # Claude Code on the work account
36
+ sideby ui # every account's quota at http://127.0.0.1:17420
159
37
  ```
160
38
 
161
- `setup` edits `~/.claude/settings.json`. It wraps your `statusLine.command` as `sideby statusline-tap --orig-b64 <base64 of your command>`; base64 keeps your command's quotes and spaces intact. If you have no status line, it sets `sideby statusline-tap`, which prints a short line such as `5h 72% · 7d 44%`. The original bytes go to sideby's state directory. Your status line output does not change; in our tests the extra cold start had a median of about 82 ms.
162
-
163
- Because Claude Code runs the wrapper on every refresh, `setup` refuses when `sideby` is not on `PATH` or comes from the npx cache, and tells you to run `npm i -g sideby` first.
164
-
165
- `teardown` writes the original back only if the file is still exactly what `setup` wrote. If you changed it since, `teardown` refuses and shows the diff; set `statusLine.command` back to your original command by hand (it is the base64 after `--orig-b64`), or delete `statusLine` if you had none.
39
+ `sideby run claude:main` keeps using `~/.claude` in another terminal at the same time. Arguments after `--` go to the Host: `sideby run work -- --resume`. To try it without installing, run `npx sideby`.
166
40
 
167
- Accounts that link `settings.json` to the Main Account (the default) get quota from the same setup. An account with its own `settings.json` shows quota as not enabled until that file's status line is wrapped the same way. Codex quota needs no setup: sideby reads it from local session files.
41
+ ## Set up with your AI agent
168
42
 
169
- ### When an account hits its limit
43
+ Paste this into Claude Code, Codex or any coding agent:
170
44
 
171
- ```sh
172
- sideby next claude # pick the account with the most quota left and start it
173
- sideby next codex --dry-run # only show the pick
174
- sideby next claude -- --model opus # Host arguments go after --
45
+ ```text
46
+ Install the sideby CLI (npm package `sideby`) and its agent skill, then set up a second Claude Code account for me.
47
+ Follow "Set up sideby for a user" in https://raw.githubusercontent.com/Adonis0123/sideby/main/llms.txt
175
48
  ```
176
49
 
177
- `next` ranks the family's accounts by their fullest quota window that has not reset yet, starts the lowest one, and lists the rest with why they were left out:
50
+ The agent checks Node and your Host CLIs, installs sideby and the skill, creates the account, tells you what it shares with your main one and makes its short command work. You sign in once in your browser; the agent gives you that one command. Once the skill is installed, short requests work too: "add another Claude account, cc008", "what do my Codex accounts share?", "which account still has quota?".
178
51
 
179
- ```
180
- ACCOUNT STATE NOTE
181
- → claude:work ready fullest window 23%
182
- claude:new unknown no quota data yet
183
- claude:key api left out; add --include-api to use it
184
- claude:main full back at 14:30
185
- ```
52
+ ## What it looks like
186
53
 
187
- - Accounts with a window at 85% or more are full; a window whose reset time has passed counts as empty again.
188
- - Accounts without quota data yet come after the ones with known room. API accounts cost money per request, so they join only with `--include-api`.
189
- - When every account is full, nothing starts and sideby tells you which one comes back first. When no account has quota data yet, nothing starts either: the pick would only be a guess. Turn quota on (`sideby quota setup claude`) or choose with `sideby run`.
190
- - The new account starts a new session: each account keeps its own sessions. Ask the old session for a short handoff note and paste it in.
191
- - Claude and Codex only; Grok and pi publish no quota, so `next grok` lists the accounts for you to choose.
54
+ <img alt="sideby in a terminal: list accounts, check quota, pick the next account, find and fix drift" src="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/demo.gif" width="1320">
192
55
 
193
- sideby recommends; you start the account. It never switches or rotates by itself (see the FAQ). The panel marks the same pick as **Next** in each family.
56
+ <picture>
57
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel-dark.png">
58
+ <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png">
59
+ <img alt="sideby panel: every account's 5h and 7d quota, 7-day token usage and health in one page" src="https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png" width="1280">
60
+ </picture>
194
61
 
195
- ## Commands
62
+ ## What else it does
196
63
 
197
- | Command | What it does | Exit code |
64
+ | You want to | Run | Guide |
198
65
  |---|---|---|
199
- | `sideby` | Same as `sideby list` | 0 |
200
- | `sideby list` | Accounts, type (main, sub or api), login state, model, directory; reading problems go to stderr | 0 |
201
- | `sideby run <acct> [-- args]` | Start the Host for one account | the Host's code; 128+n if killed by signal n |
202
- | `sideby new <family> <name> [--api] [--alias <short>]` | Create an account and lay out its Shared Items; `--alias` also adds a short command such as `cc008` to `aliases` | 0; 1 if partly failed or the alias was not added |
203
- | `sideby alias add <short> <acct> [-- args]` | Add a short command for an existing account, optionally with Host arguments that `sideby run` adds | 0, also when it already exists; 1 if the name is taken, invalid or the account does not exist |
204
- | `sideby alias rm <short>` | Remove a short command | 0, also when it is not there |
205
- | `sideby login <acct>` | Run the family's sign-in command for that account; for pi, start pi and tell you to type `/login` | the Host's code |
206
- | `sideby next <family> [--dry-run] [--include-api] [-- args]` | Recommend the account with the most quota left (Claude, Codex) and start it; `--dry-run` only recommends | the Host's code; 0 for `--dry-run` or `--json` with a pick; 1 if no account can be used, none has quota data yet, the family has no quota source or no accounts |
207
- | `sideby doctor [acct\|family] [--fix] [--force]` | Check Shared Items, credential file modes, leftovers; `--fix` repairs what is safe | 0 no failures (warnings allowed); 1 at least one failure |
208
- | `sideby quota [acct]` | Quota and 7-day usage | 0 |
209
- | `sideby quota setup claude [--yes]` | Show the change; with `--yes`, turn the Claude quota source on | 10 when it only showed the change; 0 once on; 1 if blocked or failed |
210
- | `sideby quota teardown claude` | Turn it off and restore the original file | 0; 1 if refused or failed |
211
- | `sideby ui [--port n] [--no-open]` | Local panel; runs until Ctrl+C | — |
212
- | `sideby ui --background` / `--stop` | Start the panel detached from the terminal (reusing a running one, or replacing it after sideby was upgraded) and open it / stop it | 0; 1 if it could not start or stop |
213
- | `sideby app install [--url <url>]` | Add a desktop app (macOS, Linux) that opens the panel, or `<url>` | 0; 1 on an unsupported platform or a file it did not create in the way |
214
- | `sideby app uninstall` | Remove what `app install` created | 0; 1 if it left a file it did not create |
215
- | `sideby shell-init zsh\|bash` | Print shell functions for every account and your aliases | 0 |
216
- | `sideby shell-init [zsh\|bash] --write` | Rewrite the configured `shellInitFile` with that output | 0; 1 if a file could not be written or none is configured |
217
- | `sideby plugins` | Loaded plugins and load errors | 0; 1 if any plugin failed to load |
218
-
219
- - Every command exits 2 on a usage error (unknown option, missing argument) and 1 when sideby itself fails (unknown account, invalid name, unreadable config). Exit code 10 means a change was shown and waits for your yes: run the same command again with `--yes`.
220
- - `<acct>` is `<family>:<name>`, or just `<name>` when it is unique across families. An ambiguous name is an error that lists the candidates.
221
- - `list`, `new`, `next`, `alias`, `doctor`, `quota` (including `setup` and `teardown`), `plugins` and `app` accept `--json`. JSON output carries `schemaVersion: 1`, and the JSON Schemas ship in [`schemas/`](schemas/). With `--json`, an error prints `{ "schemaVersion": 1, "error": "…", "code": "…" }`, where `code` is `usage` for a malformed command line or a reason such as `no-quota-source`. Adding a field keeps the version; removing, renaming or changing the meaning of a field bumps it.
222
- - No output, log or error message ever contains a credential value.
223
-
224
- ## Accounts
225
-
226
- | Family | Main Account | Other accounts | Selected with |
227
- |---|---|---|---|
228
- | claude | `~/.claude` | `~/.claude-<name>` | `CLAUDE_CONFIG_DIR` |
229
- | codex | `~/.codex` | `~/.codex-<name>` | `CODEX_HOME` |
230
- | grok | `~/.grok` | `~/.grok-<name>` | `GROK_HOME` |
231
- | pi | `~/.pi/agent` | `~/.pi-<name>/agent` | `PI_CODING_AGENT_DIR` |
232
-
233
- Accounts are found on disk; you never list them by hand. A name matches `^[a-z0-9][a-z0-9-]{0,31}$`, so `004` and `work` are both fine. A directory counts as an account only if it holds a `proxy.env` or at least one of the family's Shared Items, so another tool's directory that happens to match the pattern, such as claude-code-router's `~/.claude-code-router`, is not picked up. The Main Account is always called `main` and runs without the selection variable. Before every launch sideby clears Hijack Variables (for example a global `ANTHROPIC_API_KEY`) so an inherited value cannot override the account's identity.
234
-
235
- ## Configuration
236
-
237
- Optional. `${XDG_CONFIG_HOME:-~/.config}/sideby/config.json`, schema in [`schemas/config.json`](schemas/config.json):
238
-
239
- ```json
240
- {
241
- "accounts": {
242
- "claude:work": { "args": ["--model", "opus"] }
243
- },
244
- "aliases": {
245
- "cc": "claude:main",
246
- "ccw": "claude:work",
247
- "cx": "codex:main",
248
- "pi-kimi": { "account": "pi:main", "args": ["--model", "kimi-coding/k3"] }
249
- },
250
- "ignore": ["claude:backup"],
251
- "pluginDirs": ["~/code/sideby-plugins/litellm-gateway"],
252
- "plugins": {
253
- "account-script": { "enabled": true },
254
- "litellm-gateway": { "port": 4000 }
255
- },
256
- "extraSharedItems": {
257
- "claude": [
258
- { "path": "statusline-command.sh", "mode": "link" },
259
- { "path": "scripts", "mode": "link" }
260
- ]
261
- },
262
- "shellInitFile": { "zsh": "~/.config/sideby/shell-init.zsh" }
263
- }
264
- ```
265
-
266
- | Field | Meaning |
267
- |---|---|
268
- | `accounts.<ref>.args` | Arguments added for one account. Order: family defaults, then these, then an alias's `args`, then what you pass after `--`. sideby adds no dangerous flags by default; put them here per account if you want them. |
269
- | `aliases` | Short names mapped to an account ref, or to `{ "account": "<ref>", "args": [...] }` for one account started with fixed Host arguments (another model, say). `shell-init` turns each into a shell function, and commands such as `sideby run` and `sideby doctor` accept them too; only `sideby run` adds the `args`. Two aliases of one account share its login and sessions. An alias cannot set environment variables. |
270
- | `ignore` | Account refs (`<family>:<name>`) that look like accounts but are not. Doctor warns about names containing `bak`, `backup`, `old` or `tmp` and suggests adding them here. |
271
- | `pluginDirs` | Extra plugin directories. Absolute paths or paths starting with `~/`. |
272
- | `plugins.<name>` | Settings for one plugin; `enabled` turns it on or off, built-in families included. |
273
- | `shellInitFile` | Optional `{ "zsh": …, "bash": … }`, absolute or `~/` paths. Whenever sideby adds an account or adds or removes an alias (`sideby new`, `sideby alias`, the panel), it rewrites each file with what `sideby shell-init <shell>` prints, so a shell that sources the file picks up new commands. It never overwrites a file that `shell-init` did not write. |
274
- | `extraSharedItems.<family>` | Your own Shared Items on top of the built-in list, each with a Share Mode (`link`, `copy`, `link-or-copy`, `link-or-local`, `local`, `local-if-api`, `info`, `json-key`). `path` is relative to the account directory; absolute paths and `..` are rejected. |
275
-
276
- ### Shell functions
277
-
278
- ```sh
279
- # ~/.zshrc (or ~/.bashrc with `bash`)
280
- eval "$(sideby shell-init zsh)"
281
- ```
282
-
283
- This defines one function per account, such as `sideby-claude-work`, plus one per alias. With the config above, `ccw --resume` is the same as `sideby run claude:work -- --resume`. Accounts created later need a new shell (or another `eval`) before their function exists.
284
-
285
- To add a short command when you create an account, pass `--alias` (`sideby new claude 008 --alias cc008`) or fill in "Short command" in the panel's New account dialog. The panel suggests one from your other aliases of that family: `cc001` … `cc007` for `claude:001` … `claude:007` suggest `cc008`; aliases that do not end in their account's name, such as `codex001` for `codex:main`, are ignored. The alias goes into `aliases` in the config file, which sideby rewrites with every other key kept in order; it refuses a config that is not valid JSON and leaves it alone. If the account was created but the alias could not be written, it says so.
286
-
287
- For an account that already exists, or to start one account with other Host arguments, use `sideby alias add`:
288
-
289
- ```sh
290
- sideby alias add pi-kimi pi:main -- --model kimi-coding/k3 # pi-kimi = pi001 with another model
291
- sideby alias rm pi-kimi
292
- ```
293
-
294
- The panel shows every alias next to its account (hover one to see its arguments) and finds an account by them in search. Entry points that also change environment variables are not aliases; keep them in your shell rc.
295
-
296
- If you prefer sourcing a file over `eval` (a new shell then does not start Node), set `shellInitFile` and source it:
297
-
298
- ```sh
299
- sideby shell-init zsh --write # writes ~/.config/sideby/shell-init.zsh once
300
- echo 'source ~/.config/sideby/shell-init.zsh' >> ~/.zshrc
301
- ```
302
-
303
- sideby keeps that file up to date from then on. With `eval "$(sideby shell-init zsh)"` there is nothing to set up.
66
+ | Add an API key account (DeepSeek, a gateway, …) | `sideby new claude deepseek --api` | [API accounts](docs/guide/usage.md#api-accounts) |
67
+ | Find and fix config drift between accounts | `sideby doctor --fix` | [Doctor](docs/guide/usage.md#doctor) |
68
+ | See Claude quota (Codex needs no setup) | `sideby quota setup claude --yes` | [Claude quota](docs/guide/usage.md#claude-quota) |
69
+ | Move on when an account hits its limit | `sideby next claude` | [Handoff](docs/guide/usage.md#when-an-account-hits-its-limit) |
70
+ | Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |
71
+ | Reopen a session from Orca and the like in the account that holds it | `"resumeRouting": true` in config | [Resuming from other tools](docs/guide/configuration.md#resuming-from-other-tools) |
72
+ | Open the panel from the Dock or app menu | `sideby app install` | [Desktop app](docs/guide/panel.md#desktop-app) |
73
+ | Show the panel inside your own local page | `createPanelHandler()` | [Embed the panel](docs/guide/panel.md#embed-the-panel) |
74
+ | Add a Host or a pre-launch check | a local plugin | [Plugins](docs/guide/plugins.md) |
304
75
 
305
76
  ## Supported hosts
306
77
 
307
- Tested on 2026-10-05 with the versions below. sideby starts the official binary found on `PATH`; it never patches or wraps it.
308
-
309
- | Host | Family | Tested version | Sign-in | Quota | Usage (7 days) |
310
- |---|---|---|---|---|---|
311
- | Claude Code | `claude` | 2.1.289 | `claude auth login` | status line cache (after `quota setup claude`) | local session records |
312
- | Codex CLI | `codex` | 0.160.0 | `codex login` | local session files | local session files |
313
- | Grok Build | `grok` | 1.0.46 | `grok login` | no public source | no public source |
314
- | pi | `pi` | 1.0.2 | `/login` inside pi | no public source | no public source |
78
+ | Host | Account directories | Sign-in | Quota | Usage (7 days) |
79
+ |---|---|---|---|---|
80
+ | Claude Code | `~/.claude`, `~/.claude-<name>` | `claude auth login` | ✓ after `quota setup claude` | ✓ |
81
+ | Codex CLI | `~/.codex`, `~/.codex-<name>` | `codex login` | ✓ | ✓ |
82
+ | Grok Build | `~/.grok`, `~/.grok-<name>` | `grok login` | no public source | no public source |
83
+ | pi | `~/.pi/agent`, `~/.pi-<name>/agent` | `/login` inside pi | no public source | no public source |
315
84
 
316
- sideby counts usage itself from Claude Code's `projects/**/*.jsonl` and Codex's `sessions/**/rollout-*.jsonl`; you do not need ccusage. Grok Build and pi have no quota or usage source. API accounts show usage but no quota. If a Host is not installed, `list` still shows its accounts and marks the Host as missing; `run` tells you where to install it.
85
+ sideby starts the official binary on your `PATH` and never patches it. Tested versions and per-Host notes are in the [guide](docs/guide/usage.md#host-notes).
317
86
 
318
- Host-specific notes:
87
+ ## Safe by design
319
88
 
320
- - **codex**: non-main accounts get `-c cli_auth_credentials_store="file"` unless your arguments already set that key, so each account keeps its login in its own `auth.json`.
321
- - **grok**: Grok's sandbox rejects symlinks for some paths, so `hooks` and `hooks-paths` are copies, not links, and `trusted_folders.toml` and `config.toml` are the account's own files. Doctor quotes the Host's own message when it finds one.
322
- - **pi**: accounts live one level deeper (`~/.pi-<name>/agent`). `proxy.env` only loads provider keys; it does not make a pi account an API account.
323
-
324
- ## Plugins
325
-
326
- > [!WARNING]
327
- > **A plugin is local code with your full permissions.** sideby imports it into its own process; it can read and write anything you can. Only load plugins you wrote or have read. sideby refuses a plugin whose directory, `plugin.json` or entry file is owned by another user or writable by group or others.
328
-
329
- sideby loads built-in plugins first, then every directory in `${XDG_CONFIG_HOME:-~/.config}/sideby/plugins/`, then `pluginDirs`. Built-in families are plugins too.
330
-
331
- ```
332
- my-plugin/
333
- ├── plugin.json # name, version, description, optional main and userConfig
334
- ├── index.ts # export default { name, register(api) } satisfies Plugin
335
- └── index.test.ts # optional; test with sideby/testing
336
- ```
337
-
338
- ```json
339
- { "name": "litellm-gateway", "version": "0.1.0", "description": "Check the local gateway before launch" }
340
- ```
89
+ - It never edits a Host's global config to switch accounts, and never rotates accounts by itself.
90
+ - It never reads, prints or sends a credential value. Nothing leaves your machine: sideby calls no remote service.
91
+ - `doctor --fix` never replaces a real file and never changes where a link points.
341
92
 
342
- ```ts
343
- import type { Plugin } from 'sideby' // type imports only
93
+ Details and the terms-of-service question are in the [FAQ](docs/guide/faq.md).
344
94
 
345
- export default {
346
- name: 'litellm-gateway',
347
- register(api) {
348
- api.on('launch.before', { family: 'claude' }, async (ctx) => {
349
- if (ctx.account.kind !== 'api') return
350
- const port = Number(ctx.config.port ?? 4000)
351
- const up = await fetch(`http://127.0.0.1:${port}/health`).then((r) => r.ok, () => false)
352
- if (!up) throw api.abort(`gateway on port ${port} is not running; start it first`)
353
- })
354
- },
355
- } satisfies Plugin
356
- ```
357
-
358
- - Write plugins in `.ts` or `.js`. Node 22.18+ loads `.ts` from disk without a build step. The entry file is `index.ts` unless `plugin.json` sets `main`.
359
- - Use `import type` only. sideby passes everything a plugin needs through `api`, which keeps plugins working if sideby ships as a single binary later. Install sideby as a dev dependency (`npm i -D sideby`) to get the types; [`src/types.ts`](src/types.ts) is the full contract.
360
- - The `name` in `plugin.json` must match the exported `name`. A second plugin with the same name is reported as a conflict and not loaded.
361
- - `ctx.config` holds this plugin's settings from `config.plugins.<name>`, with `userConfig` defaults filled in.
362
-
363
- | Extension point | When | What it can do | Timeout |
364
- |---|---|---|---|
365
- | `api.family(def)` | when the plugin loads | Add a Family: directory layout, selection variable, Hijack Variables, default arguments, Shared Items, sign-in command, login check, model, optional `logo`, `readQuota`, `readUsage` and `quotaSetup` | — |
366
- | `launch.before` | before every `run` and `login` | Change `ctx.env` and `ctx.args`, or stop the launch with `throw api.abort(msg)` | 30 s |
367
- | `account.create.before` | before `new` (CLI or panel) writes anything | Refuse the new account with `throw api.abort(msg)`; `ctx` has `family`, `name`, `api`, the family's existing `accounts` and `config`. The CLI exits 1 and the panel shows `msg`, so say what to do instead | 10 s |
368
- | `account.created` | after `new` succeeds | Add files, print next steps with `ctx.log()` | 30 s |
369
- | `doctor.check` | for each account during Doctor | Return Findings, each optionally with a `fix()` | 10 s |
370
-
371
- A Family's `logo` is the mark the panel shows on its cards and in the Tool select: one SVG path in a 24×24 view box, for example from [Simple Icons](https://simpleicons.org).
372
-
373
- ```ts
374
- logo: { path: 'M11.503.131 1.891 5.678…', title: 'Cursor', color: '#000000' } // color is optional
375
- ```
376
-
377
- `path` may hold only SVG path data (commands, numbers, spaces, commas, dots, `+`, `-`; up to 20000 characters), `color` must be a hex color and `fillRule` is `nonzero` or `evenodd`. An invalid logo is dropped with a plugin error in `sideby plugins`; the Family still loads and the panel shows its initials. Leave out `color` for a black mark so it follows the text color in dark mode.
378
-
379
- Errors always name the plugin. A plugin that fails to load affects only itself; a failing `doctor.check` becomes a fail Finding for that account; a failing `launch.before` stops the launch; a family reader that throws (for example `model`) shows up as one of that account's `problems` in `list`. A `fix()` may write only inside the current account directory, through `api.fs.writeFileAtomic`.
380
-
381
- ### `account-script`
382
-
383
- A built-in plugin, off by default, for "run this before the account starts" (for example, start a local gateway that an API account needs):
384
-
385
- ```json
386
- { "plugins": { "account-script": { "enabled": true } } }
387
- ```
388
-
389
- ```sh
390
- $EDITOR ~/.claude-deepseek/sideby-before-launch
391
- chmod 700 ~/.claude-deepseek/sideby-before-launch
392
- ```
393
-
394
- Before each launch of that account, sideby runs `sideby-before-launch` with the account directory as working directory and the prepared launch environment. The file must be yours, executable, and not writable by group or others. If it fails or does not finish within 25 seconds, the Host is not started and sideby shows the last 20 lines of its stderr.
395
-
396
- ## FAQ
397
-
398
- ### Does this break any terms of service?
399
-
400
- sideby is built to stay inside what each Host already supports (see [ADR-0001](docs/adr/0001-side-by-side-not-switching.md) and [ADR-0003](docs/adr/0003-quota-from-local-and-official-sources.md)):
401
-
402
- - It does not switch: it never edits a Host's global config to change the active account. Each account is a separate config directory, the mechanism every Host documents (`CLAUDE_CONFIG_DIR`, `CODEX_HOME`, `GROK_HOME`, `PI_CODING_AGENT_DIR`).
403
- - It does not rotate: it never moves to another subscription when a quota runs out. `sideby next` recommends the account with the most room from data already on your machine; starting it is your call.
404
- - It does not proxy credentials: it never reads a subscription token to call a model, and never pools logins.
405
- - It does not call private endpoints: quota comes from local files and from data Claude Code hands to your status line command.
406
-
407
- This is the maintainers' own reading of the rules, not legal advice. Check your provider's terms for your situation.
408
-
409
- ### Why Node 22.18?
410
-
411
- From 22.18, Node strips TypeScript types by default, so sideby can `import()` your `.ts` plugins straight from disk. On older Node, sideby prints an upgrade hint and exits with 1.
412
-
413
- ### Where does sideby keep its data?
414
-
415
- | What | Where |
416
- |---|---|
417
- | Config (sideby only writes `aliases` in it, from `new --alias`, `sideby alias` or the panel) | `${XDG_CONFIG_HOME:-~/.config}/sideby/config.json` |
418
- | Your plugins | `${XDG_CONFIG_HOME:-~/.config}/sideby/plugins/` |
419
- | State: quota cache, last Doctor result, original `settings.json` bytes, background panel pid and logs | `${XDG_STATE_HOME:-~/.local/state}/sideby/` |
420
- | Desktop app (`sideby app install`) | macOS `~/Applications/sideby.app`; Linux `~/.local/share/applications/sideby.desktop`, `~/.local/share/sideby/`, `~/.local/share/icons/hicolor/*/apps/sideby.*` |
421
-
422
- sideby writes nothing of its own into Host account directories. The one exception is `quota setup claude --yes`, which changes `~/.claude/settings.json`. Account directories you create with `sideby new` belong to the Host.
423
-
424
- ### How is it different from aimux, agenv, cc-switch or magpie?
425
-
426
- They solve related problems with different trade-offs; pick the one that fits how you work.
427
-
428
- | | sideby | [aimux](https://github.com/Digital-Threads/aimux) | [agenv](https://github.com/combinatrix-ai/agenv) | [cc-switch](https://github.com/farion1231/cc-switch) | [magpie](https://github.com/yetone/magpie) |
429
- |---|---|---|---|---|---|
430
- | Model | one directory per account, run side by side | one directory per profile, run side by side | isolated profile with its own Host binary | switch the active provider in the Host's config | set each agent's model and provider from the menu bar |
431
- | Hosts | Claude Code, Codex, Grok Build, pi | Claude Code, Codex, Gemini CLI | Claude Code, Codex, Gemini CLI | many, including Grok Build and pi | several agents |
432
- | Quota | local records and status line data only | live limit probe; `run --auto` picks the subscription with the most headroom | — | — | — |
433
- | Shape | CLI, local web panel, local plugins | CLI with TUI | CLI with TUI | desktop app | menu bar app |
434
-
435
- ### How do I uninstall?
436
-
437
- ```sh
438
- sideby quota teardown claude # first, if you ran quota setup; the status line calls sideby
439
- sideby ui --stop && sideby app uninstall # if you use the desktop app
440
- npm rm -g sideby
441
- rm -rf ~/.config/sideby ~/.local/state/sideby # or your XDG_CONFIG_HOME / XDG_STATE_HOME paths
442
- ```
95
+ ## Docs
443
96
 
444
- Remove the `eval "$(sideby shell-init …)"` line from your shell rc file. Account directories such as `~/.claude-work` stay; delete them yourself if you no longer need them.
97
+ - [Using sideby](docs/guide/usage.md): accounts, API accounts, Doctor, quota, Handoff, Host notes
98
+ - [Commands](docs/guide/commands.md): every command, exit codes and `--json`
99
+ - [Configuration](docs/guide/configuration.md): `config.json`, aliases, shell functions
100
+ - [Panel](docs/guide/panel.md): the panel, desktop app and embedding
101
+ - [Plugins](docs/guide/plugins.md): write your own Family or launch hook
102
+ - [FAQ](docs/guide/faq.md): terms of service, data locations, alternatives, uninstall
445
103
 
446
104
  ## For AI agents
447
105
 
448
- - [`llms.txt`](llms.txt): commands, JSON contract and plugin API in one index.
449
- - [`skills/sideby/SKILL.md`](skills/sideby/SKILL.md): one Agent Skill for seeing, repairing and creating accounts and for moving on when one hits its limit. It routes to short references, so an agent loads only what the task needs. Install with `npx skills add Adonis0123/sideby -g`; `sideby doctor` warns when the installed copy is for another sideby version.
106
+ - [`llms.txt`](llms.txt): setup steps, commands, JSON contract and plugin API in one index. `sideby families --json` states what each Host's accounts share.
107
+ - [`skills/sideby/SKILL.md`](skills/sideby/SKILL.md): one Agent Skill for seeing, repairing and creating accounts and for moving on when one hits its limit. Install with `npx skills add Adonis0123/sideby -g`; `sideby doctor` warns when the installed copy is for another sideby version.
450
108
  - [`AGENTS.md`](AGENTS.md): rules for agents working on this repository.
451
109
 
452
110
  ## License