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.
- package/README.md +71 -413
- package/README.zh-CN.md +71 -414
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +13 -0
- package/dist/src/cli/commands.js +86 -7
- package/dist/src/cli/json-schemas.js +35 -0
- package/dist/src/cli/main.js +99 -9
- package/dist/src/core/accounts.d.ts +2 -0
- package/dist/src/core/accounts.js +15 -0
- package/dist/src/core/config.d.ts +1 -0
- package/dist/src/core/config.js +3 -0
- package/dist/src/core/launch.d.ts +3 -1
- package/dist/src/core/resume.d.ts +9 -0
- package/dist/src/core/resume.js +15 -0
- package/dist/src/core/share-meaning.d.ts +6 -0
- package/dist/src/core/share-meaning.js +32 -0
- package/dist/src/core/shell-init.d.ts +7 -1
- package/dist/src/core/shell-init.js +25 -2
- package/dist/src/families/claude/index.js +3 -0
- package/dist/src/families/codex/index.js +3 -0
- package/dist/src/families/grok/index.js +4 -0
- package/dist/src/families/shared/sessions.d.ts +9 -0
- package/dist/src/families/shared/sessions.js +70 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/panel/handler.d.ts +0 -2
- package/dist/src/panel/handler.js +1 -16
- package/dist/src/runtime.d.ts +48 -2
- package/dist/src/runtime.js +54 -1
- package/dist/src/types.d.ts +7 -0
- package/dist/testing/index.js +2 -1
- package/package.json +1 -1
- package/schemas/config.json +4 -0
- package/schemas/families.json +205 -0
- package/schemas/new.json +18 -0
- package/skills/sideby/SKILL.md +15 -6
- package/skills/sideby/references/accounts.md +1 -1
- package/skills/sideby/references/setup.md +31 -0
- package/skills/sideby/references/sharing.md +17 -0
package/README.md
CHANGED
|
@@ -1,452 +1,110 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
155
|
-
sideby
|
|
156
|
-
sideby
|
|
157
|
-
sideby
|
|
158
|
-
sideby
|
|
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
|
-
`
|
|
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
|
-
|
|
41
|
+
## Set up with your AI agent
|
|
168
42
|
|
|
169
|
-
|
|
43
|
+
Paste this into Claude Code, Codex or any coding agent:
|
|
170
44
|
|
|
171
|
-
```
|
|
172
|
-
sideby
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
##
|
|
62
|
+
## What else it does
|
|
196
63
|
|
|
197
|
-
|
|
|
64
|
+
| You want to | Run | Guide |
|
|
198
65
|
|---|---|---|
|
|
199
|
-
|
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
|
|
|
203
|
-
|
|
|
204
|
-
|
|
|
205
|
-
|
|
|
206
|
-
|
|
|
207
|
-
|
|
|
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
|
-
|
|
308
|
-
|
|
309
|
-
|
|
|
310
|
-
|
|
311
|
-
|
|
|
312
|
-
|
|
|
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
|
|
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
|
-
|
|
87
|
+
## Safe by design
|
|
319
88
|
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|