sideby 0.4.3 → 0.6.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 +2 -1
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +5 -0
- package/dist/src/cli/commands.js +67 -0
- package/dist/src/cli/json-schemas.js +30 -0
- package/dist/src/cli/main.js +5 -0
- package/dist/src/core/config.d.ts +14 -0
- package/dist/src/core/config.js +9 -0
- package/dist/src/core/handoff-enable.d.ts +52 -0
- package/dist/src/core/handoff-enable.js +169 -0
- package/dist/src/core/handoff-overview.d.ts +26 -0
- package/dist/src/core/handoff-overview.js +63 -0
- package/dist/src/core/handoff-run.js +2 -1
- package/dist/src/core/handoff-select.d.ts +2 -0
- package/dist/src/core/handoff-select.js +11 -5
- package/dist/src/core/model-choice.d.ts +41 -0
- package/dist/src/core/model-choice.js +128 -0
- package/dist/src/families/claude/index.js +2 -0
- package/dist/src/families/claude/model.d.ts +2 -0
- package/dist/src/families/claude/model.js +42 -0
- package/dist/src/families/claude/quota-cache.d.ts +7 -0
- package/dist/src/families/claude/quota-cache.js +40 -0
- package/dist/src/families/claude/usage.d.ts +2 -0
- package/dist/src/families/claude/usage.js +13 -1
- package/dist/src/families/codex/index.js +2 -0
- package/dist/src/families/codex/model.d.ts +2 -0
- package/dist/src/families/codex/model.js +59 -0
- package/dist/src/families/codex/rollout.d.ts +5 -0
- package/dist/src/families/codex/rollout.js +45 -3
- package/dist/src/families/grok/handoff.js +1 -1
- package/dist/src/families/shared/model-args.d.ts +7 -0
- package/dist/src/families/shared/model-args.js +28 -0
- package/dist/src/handoff/messages.js +1 -1
- package/dist/src/panel/handler.d.ts +3 -0
- package/dist/src/panel/handler.js +69 -0
- package/dist/src/panel/i18n.d.ts +67 -0
- package/dist/src/panel/i18n.js +134 -0
- package/dist/src/panel/page-script.js +328 -13
- package/dist/src/panel/page-styles.js +14 -1
- package/dist/src/panel/page.js +1 -0
- package/dist/src/quota/statusline-tap.js +13 -2
- package/dist/src/runtime.d.ts +5 -2
- package/dist/src/runtime.js +2 -1
- package/dist/src/types.d.ts +29 -0
- package/package.json +3 -2
- package/schemas/model.json +169 -0
- package/schemas/quota.json +6 -0
- package/skills/sideby/SKILL.md +2 -1
- package/skills/sideby/references/handoff.md +2 -2
|
@@ -167,7 +167,11 @@ code, .mono { font-family: var(--mono); font-size: 0.92em; }
|
|
|
167
167
|
.ident-name { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; row-gap: 3px; }
|
|
168
168
|
.ident-name .name { font-weight: var(--strong-weight); font-size: var(--font-size); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
169
169
|
.ident-email { color: var(--muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 1px; }
|
|
170
|
-
.email-btn { all: unset; cursor: pointer; border-radius: 4px; }
|
|
170
|
+
.email-btn, .model-btn { all: unset; cursor: pointer; border-radius: 4px; }
|
|
171
|
+
.model-btn:hover { color: var(--text); text-decoration: underline dotted; }
|
|
172
|
+
.model-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
|
|
173
|
+
.model-set { color: var(--accent); }
|
|
174
|
+
.md-now { display: flex; flex-direction: column; gap: 4px; }
|
|
171
175
|
.email-btn:hover { color: var(--text); text-decoration: underline dotted; }
|
|
172
176
|
.email-btn:focus-visible { outline: var(--focus-outline); outline-offset: 2px; }
|
|
173
177
|
.ident-email + .ident-sub { color: var(--faint); margin-top: 0; }
|
|
@@ -338,6 +342,15 @@ a.btn:hover { text-decoration: none; }
|
|
|
338
342
|
.banner-warn { background: var(--warn-soft); border-color: transparent; color: var(--warn); }
|
|
339
343
|
.banner ul { margin: 6px 0 0; padding-left: 18px; }
|
|
340
344
|
.banner-actions { display: flex; gap: 8px; flex: none; }
|
|
345
|
+
.hp-how { margin: 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; }
|
|
346
|
+
.hp-steps { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 8px; }
|
|
347
|
+
.hp-steps li { display: flex; gap: 8px; }
|
|
348
|
+
.hp-steps li::before { content: '•'; color: var(--muted); }
|
|
349
|
+
.hp-steps li > div { flex: 1; min-width: 0; }
|
|
350
|
+
.hp-rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
|
|
351
|
+
.hp-rows li { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 14px; padding: 8px 0; border-bottom: 1px solid var(--border); }
|
|
352
|
+
.hp-rows li:last-child { border-bottom: 0; }
|
|
353
|
+
#handoff-page > * + * { margin-top: 12px; }
|
|
341
354
|
.ah-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; align-items: end; }
|
|
342
355
|
.tune-model { border: 1px solid var(--border); border-radius: calc(var(--radius) - 4px); padding: 10px 12px; display: flex; flex-direction: column; gap: 8px; }
|
|
343
356
|
.tune-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
|
package/dist/src/panel/page.js
CHANGED
|
@@ -76,6 +76,7 @@ export function renderPage(opts) {
|
|
|
76
76
|
<div id="banners"></div>
|
|
77
77
|
<nav id="pages" class="pages" role="tablist" hidden></nav>
|
|
78
78
|
<section id="summary" class="summary" aria-busy="true">${SKELETON_STAT.repeat(4)}</section>
|
|
79
|
+
<section id="handoff-page" class="block" aria-labelledby="h-handoff" hidden></section>
|
|
79
80
|
<section id="tuning-page" class="block" aria-labelledby="h-tuning" hidden></section>
|
|
80
81
|
<section id="accounts-page" class="block" aria-labelledby="h-accounts">
|
|
81
82
|
<div class="section-head acct-head"><h2 id="h-accounts"><span data-i18n="acct.title">Accounts</span><span id="acct-count" class="muted small"></span></h2></div>
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// Nothing about the cache may change what the original command receives, prints or returns.
|
|
5
5
|
import { spawn } from 'node:child_process';
|
|
6
6
|
import { constants } from 'node:os';
|
|
7
|
-
import { accountNameFromEnv, cacheStateDir, parseRateLimits, writeQuotaCache, } from '../families/claude/quota-cache.js';
|
|
7
|
+
import { accountNameFromEnv, cacheStateDir, mergeQuotaCache, parseRateLimits, readQuotaCache, writeQuotaCache, } from '../families/claude/quota-cache.js';
|
|
8
8
|
/** Command the wrapper starts with; `quota setup claude` detects an existing wrapper by this prefix. */
|
|
9
9
|
export const TAP_COMMAND = 'sideby statusline-tap';
|
|
10
10
|
export const ORIG_FLAG = '--orig-b64';
|
|
@@ -88,7 +88,18 @@ export async function runStatuslineTap(argv, env, stdin, stdout, stderr = proces
|
|
|
88
88
|
const name = accountNameFromEnv(env);
|
|
89
89
|
if (!cache || !name)
|
|
90
90
|
return;
|
|
91
|
-
|
|
91
|
+
// Several sessions of one Account share this cache; an idle one must not put back older numbers.
|
|
92
|
+
const stateDir = cacheStateDir(env);
|
|
93
|
+
const prev = await readQuotaCache(stateDir, name).catch(() => null);
|
|
94
|
+
const merged = prev?.status === 'ok' ? mergeQuotaCache(prev.windows, cache) : cache;
|
|
95
|
+
if (!merged) {
|
|
96
|
+
// Older numbers: write nothing, and show what the cache already knows.
|
|
97
|
+
if (prev?.status === 'ok')
|
|
98
|
+
cache = { ...cache, observedAt: prev.observedAt, windows: prev.windows };
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
cache = merged;
|
|
102
|
+
await writeQuotaCache(stateDir, name, merged);
|
|
92
103
|
}
|
|
93
104
|
catch {
|
|
94
105
|
// Caching is best effort.
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -218,9 +218,12 @@ export interface Runtime {
|
|
|
218
218
|
handoffSettings(): HandoffSettings;
|
|
219
219
|
/**
|
|
220
220
|
* Where an Auto Handoff from `origin` would go now (spec §3.17 "选号"): every Account of every Family is a
|
|
221
|
-
* candidate; `used` lists the chain's Accounts so far.
|
|
221
|
+
* candidate; `used` lists the chain's Accounts so far. `ahead` decides early what happens once the origin's fullest
|
|
222
|
+
* window reaches the threshold, as plan.json and the Panel need.
|
|
222
223
|
*/
|
|
223
|
-
handoffDecision(origin: Account, used: readonly string[]
|
|
224
|
+
handoffDecision(origin: Account, used: readonly string[], opts?: {
|
|
225
|
+
ahead?: boolean;
|
|
226
|
+
}): Promise<HandoffDecision>;
|
|
224
227
|
readContext(): ReadContext;
|
|
225
228
|
}
|
|
226
229
|
export declare class UnknownFamilyError extends UserError {
|
package/dist/src/runtime.js
CHANGED
|
@@ -561,7 +561,7 @@ export async function createRuntime(opts = {}) {
|
|
|
561
561
|
return planHandoff(f.id, candidates, readContext().now, o);
|
|
562
562
|
},
|
|
563
563
|
handoffSettings: () => handoffSettings(config),
|
|
564
|
-
async handoffDecision(origin, used) {
|
|
564
|
+
async handoffDecision(origin, used, opts = {}) {
|
|
565
565
|
const candidates = await Promise.all((await accounts()).map(async (a) => {
|
|
566
566
|
const f = familyOf(a.family);
|
|
567
567
|
const [installed, login, quota, identity] = await Promise.all([
|
|
@@ -601,6 +601,7 @@ export async function createRuntime(opts = {}) {
|
|
|
601
601
|
aliases: aliasRefs(config.aliases),
|
|
602
602
|
used,
|
|
603
603
|
now: now(),
|
|
604
|
+
...(opts.ahead ? { ahead: true } : {}),
|
|
604
605
|
});
|
|
605
606
|
},
|
|
606
607
|
readContext,
|
package/dist/src/types.d.ts
CHANGED
|
@@ -77,6 +77,8 @@ export interface FamilyDef {
|
|
|
77
77
|
};
|
|
78
78
|
loginState?(account: Account): Promise<LoginState>;
|
|
79
79
|
model?(account: Account): Promise<string | undefined>;
|
|
80
|
+
/** Choosing the model and effort an Account starts with, as Host arguments (spec §3.19, ADR-0014). */
|
|
81
|
+
modelChoice?: FamilyModelChoice;
|
|
80
82
|
/**
|
|
81
83
|
* Who the Account is signed in as, from the Host's own login file: identity fields such as the email and the
|
|
82
84
|
* organization only, never a token or any other credential value (ADR-0003). Return undefined when unknown; an
|
|
@@ -224,6 +226,30 @@ export interface TokenTotals {
|
|
|
224
226
|
cacheWriteTokens: number;
|
|
225
227
|
totalTokens: number;
|
|
226
228
|
}
|
|
229
|
+
/** A model and an effort level; a field left out means the Host's own default. */
|
|
230
|
+
export interface ModelChoice {
|
|
231
|
+
model?: string;
|
|
232
|
+
effort?: string;
|
|
233
|
+
}
|
|
234
|
+
export interface FamilyModelChoice {
|
|
235
|
+
/** Effort levels the Host accepts at launch, lowest first. */
|
|
236
|
+
efforts: readonly string[];
|
|
237
|
+
/** Model names to offer; any other name may be typed. */
|
|
238
|
+
models: readonly string[];
|
|
239
|
+
/**
|
|
240
|
+
* What the Host's own config sets for new sessions of the Account. Reads only the model and effort keys. A
|
|
241
|
+
* per-model effort is looked up for `model` (the model sideby starts it with), else the config's own model, else
|
|
242
|
+
* `lastModel` (the model of the newest session).
|
|
243
|
+
*/
|
|
244
|
+
configured(account: Account, model?: string, lastModel?: string): Promise<ModelChoice>;
|
|
245
|
+
/** Host arguments that start a session with `choice`. */
|
|
246
|
+
toArgs(choice: ModelChoice): string[];
|
|
247
|
+
/** The choice in `args`, and the other arguments in their order. */
|
|
248
|
+
fromArgs(args: readonly string[]): {
|
|
249
|
+
choice: ModelChoice;
|
|
250
|
+
rest: string[];
|
|
251
|
+
};
|
|
252
|
+
}
|
|
227
253
|
export type UsageResult = ({
|
|
228
254
|
status: 'ok';
|
|
229
255
|
days: number;
|
|
@@ -239,6 +265,9 @@ export type UsageResult = ({
|
|
|
239
265
|
}[];
|
|
240
266
|
/** ISO time of the newest record the reader saw, not later than now. */
|
|
241
267
|
lastActivityAt?: string;
|
|
268
|
+
/** Model and effort of the newest turn the reader saw, when the logs record them (spec §3.19). */
|
|
269
|
+
lastModel?: string;
|
|
270
|
+
lastEffort?: string;
|
|
242
271
|
} & TokenTotals) | {
|
|
243
272
|
status: 'unavailable';
|
|
244
273
|
reason: 'no-source' | 'no-session' | 'unrecognized';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sideby",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Run work, personal and API-key accounts at the same time, without their configs drifting apart.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Adonis0123 (https://github.com/Adonis0123)",
|
|
@@ -72,9 +72,10 @@
|
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"@biomejs/biome": "2.5.15",
|
|
74
74
|
"@types/node": "^22.18.0",
|
|
75
|
+
"motion": "13.4.3",
|
|
75
76
|
"typescript": "^7.0.2"
|
|
76
77
|
},
|
|
77
78
|
"packageManager": "pnpm@10.15.0",
|
|
78
79
|
"readmeFilename": "README.md",
|
|
79
|
-
"readme": "<p align=\"center\">\n <img alt=\"sideby: work, personal and API-key accounts, one terminal each, one shared config\" src=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/banner.jpg\" width=\"880\">\n</p>\n\n<p align=\"center\">\n <b>Run work, personal and API-key accounts at the same time, without their configs drifting apart.</b><br>\n Each account is its own directory and its own terminal. Skills, hooks and settings stay linked to your main account. Claude Code, Codex, Grok Build and pi.\n</p>\n\n<p align=\"center\">\n <a href=\"https://www.npmjs.com/package/sideby\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/sideby\"></a>\n <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>\n <img alt=\"Node 22.18+\" src=\"https://img.shields.io/badge/node-%E2%89%A522.18-339933\">\n <a href=\"LICENSE\"><img alt=\"MIT\" src=\"https://img.shields.io/badge/license-MIT-blue\"></a>\n</p>\n\n<p align=\"center\">English | <a href=\"README.zh-CN.md\">中文</a></p>\n\n## Why not just an alias?\n\n`CLAUDE_CONFIG_DIR` plus a shell alias already starts a second account. It does not keep skills and settings in one place, and it does not show quota for every account. [Does this break a provider's terms?](docs/guide/faq.md#terms)\n\n| | At the same time | Skills, hooks, settings | Quota and usage | Setup |\n|---|---|---|---|---|\n| `CLAUDE_CONFIG_DIR` and an alias | Yes. Each terminal exports the variable. | Each directory is its own copy. They drift unless you symlink by hand. | That session's status line | One alias per account |\n| [cc-switch](https://github.com/farion1231/cc-switch) | Enabling a provider writes the host's live config, so open sessions share that switch. | Plugin settings are copied across providers as a separate step. | The active provider, from its quota endpoint | Install the desktop app |\n| sideby | Yes. `sideby run` selects the directory for that process and leaves your shell alone. | Linked to the main account. `sideby doctor --fix` repairs copies. Sign-in and sessions stay separate. | Every account, from local files. Claude needs `quota setup`. | `npm i -g sideby`, then `sideby new` and sign in |\n\ncc-switch's docs describe that switch: it writes the live settings file ([README](https://github.com/farion1231/cc-switch#readme)). [#1105](https://github.com/farion1231/cc-switch/issues/1105) and [#2908](https://github.com/farion1231/cc-switch/issues/2908) ask for several sessions at once. [#1106](https://github.com/farion1231/cc-switch/issues/1106) is closed; the maintainer says Claude Code's \"open terminal\" can start one provider without changing the global config.\n\n## Quick start\n\nRequires 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.\n\n```sh\nnpm i -g sideby\nsideby new claude work # create ~/.claude-work, sharing ~/.claude's skills and hooks\nsideby login claude:work # sign in once\nsideby run work # Claude Code on the work account\nsideby ui # every account's quota at http://127.0.0.1:17420\n```\n\n`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`.\n\n## Set up with your AI agent\n\nPaste this into Claude Code, Codex or any coding agent:\n\n```text\nInstall the sideby CLI (npm package `sideby`) and its agent skill, then set up a second Claude Code account for me.\nFollow \"Set up sideby for a user\" in https://raw.githubusercontent.com/Adonis0123/sideby/main/llms.txt\n```\n\nThe 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?\".\n\n## What it looks like\n\n<img alt=\"Two terminals: the personal account and the work account running at the same time, then the local quota panel\" src=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/demo.gif\" width=\"1280\">\n\n<picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel-dark.png\">\n <source media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png\">\n <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\">\n</picture>\n\n## What else it does\n\n| You want to | Run | Guide |\n|---|---|---|\n| Add an API key account (DeepSeek, a gateway, …) | `sideby new claude deepseek --api` | [API accounts](docs/guide/usage.md#api-accounts) |\n| Find and fix config drift between accounts | `sideby doctor --fix` | [Doctor](docs/guide/usage.md#doctor) |\n| See Claude quota (Codex needs no setup) | `sideby quota setup claude --yes` | [Claude quota](docs/guide/usage.md#claude-quota) |\n| Find the cheapest auto-compact size from your own sessions, and use it in one click | `sideby tune` (or the panel's Context tuning tab) | [Context tuning](docs/guide/usage.md#context-tuning) |\n| Move on when an account hits its limit | `sideby next claude` | [Handoff](docs/guide/usage.md#when-an-account-hits-its-limit) |\n| Let a running task move to the next account by itself when quota runs low (off by default; see the risk) | `sideby handoff enable` (or the panel) | [Auto Handoff](docs/guide/usage.md#auto-handoff) |\n| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |\n| 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) |\n| Open the panel from the Dock or app menu | `sideby app install` | [Desktop app](docs/guide/panel.md#desktop-app) |\n| Show the panel inside your own local page | `createPanelHandler()` | [Embed the panel](docs/guide/panel.md#embed-the-panel) |\n| Add a Host or a pre-launch check | a local plugin | [Plugins](docs/guide/plugins.md) |\n\n## Supported hosts\n\n| Host | Account directories | Sign-in | Quota | Usage (7 days) |\n|---|---|---|---|---|\n| Claude Code | `~/.claude`, `~/.claude-<name>` | `claude auth login` | ✓ after `quota setup claude` | ✓ |\n| Codex CLI | `~/.codex`, `~/.codex-<name>` | `codex login` | ✓ | ✓ |\n| Grok Build | `~/.grok`, `~/.grok-<name>` | `grok login` | no public source | no public source |\n| pi | `~/.pi/agent`, `~/.pi-<name>/agent` | `/login` inside pi | no public source | no public source |\n\nsideby 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).\n\n## Platforms\n\nmacOS and Linux. CI runs the suite on `ubuntu-latest` and `macos-latest`, Node 22 and 24. **Windows is untested.** `sideby app` only installs a launcher on macOS and Linux. Linked items are symlinks, `sideby shell-init` prints bash and zsh functions, and the Claude quota wrapper runs `/bin/sh`.\n\n## Roadmap\n\n**Gemini CLI.** Gemini can point at another config directory with `GEMINI_CONFIG_DIR`. Older docs use `GEMINI_CLI_HOME`; current builds treat that name as deprecated, and exit on startup if it is set together with an exact directory override. A sideby family still needs a tested Gemini version, a login check that does not read tokens, and a list of which files to link or copy. That is more than a small patch, so it is not in this version.\n\n## Safe by design\n\n- It never edits a Host's global config to switch accounts. It moves a task to another account only when you turn on Auto Handoff, and never proxies requests or pools logins.\n- It never reads, prints or sends a credential value. Nothing leaves your machine: sideby calls no remote service.\n- `doctor --fix` never replaces a real file and never changes where a link points.\n\nDetails and the terms-of-service question are in the [FAQ](docs/guide/faq.md).\n\nUsage questions and ideas go in [Discussions](https://github.com/Adonis0123/sideby/discussions).\n\n## Docs\n\n- [Using sideby](docs/guide/usage.md): accounts, API accounts, Doctor, quota, Handoff, Host notes\n- [Commands](docs/guide/commands.md): every command, exit codes and `--json`\n- [Configuration](docs/guide/configuration.md): `config.json`, aliases, shell functions\n- [Panel](docs/guide/panel.md): the panel, desktop app and embedding\n- [Plugins](docs/guide/plugins.md): write your own Family or launch hook\n- [FAQ](docs/guide/faq.md): terms of service, data locations, alternatives, uninstall\n- [Changelog](https://github.com/Adonis0123/sideby/releases)\n\n## For AI agents\n\n- [`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.\n- [`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.\n- [`AGENTS.md`](AGENTS.md): rules for agents working on this repository.\n\n## License\n\n[MIT](LICENSE)\n"
|
|
80
|
+
"readme": "<p align=\"center\">\n <img alt=\"sideby: work, personal and API-key accounts, one terminal each, one shared config\" src=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/banner.jpg\" width=\"880\">\n</p>\n\n<p align=\"center\">\n <b>Run work, personal and API-key accounts at the same time, without their configs drifting apart.</b><br>\n Each account is its own directory and its own terminal. Skills, hooks and settings stay linked to your main account. Claude Code, Codex, Grok Build and pi.\n</p>\n\n<p align=\"center\">\n <a href=\"https://www.npmjs.com/package/sideby\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/sideby\"></a>\n <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>\n <img alt=\"Node 22.18+\" src=\"https://img.shields.io/badge/node-%E2%89%A522.18-339933\">\n <a href=\"LICENSE\"><img alt=\"MIT\" src=\"https://img.shields.io/badge/license-MIT-blue\"></a>\n</p>\n\n<p align=\"center\">English | <a href=\"README.zh-CN.md\">中文</a></p>\n\n## Why not just an alias?\n\n`CLAUDE_CONFIG_DIR` plus a shell alias already starts a second account. It does not keep skills and settings in one place, and it does not show quota for every account. [Does this break a provider's terms?](docs/guide/faq.md#terms)\n\n| | At the same time | Skills, hooks, settings | Quota and usage | Setup |\n|---|---|---|---|---|\n| `CLAUDE_CONFIG_DIR` and an alias | Yes. Each terminal exports the variable. | Each directory is its own copy. They drift unless you symlink by hand. | That session's status line | One alias per account |\n| [cc-switch](https://github.com/farion1231/cc-switch) | Enabling a provider writes the host's live config, so open sessions share that switch. | Plugin settings are copied across providers as a separate step. | The active provider, from its quota endpoint | Install the desktop app |\n| sideby | Yes. `sideby run` selects the directory for that process and leaves your shell alone. | Linked to the main account. `sideby doctor --fix` repairs copies. Sign-in and sessions stay separate. | Every account, from local files. Claude needs `quota setup`. | `npm i -g sideby`, then `sideby new` and sign in |\n\ncc-switch's docs describe that switch: it writes the live settings file ([README](https://github.com/farion1231/cc-switch#readme)). [#1105](https://github.com/farion1231/cc-switch/issues/1105) and [#2908](https://github.com/farion1231/cc-switch/issues/2908) ask for several sessions at once. [#1106](https://github.com/farion1231/cc-switch/issues/1106) is closed; the maintainer says Claude Code's \"open terminal\" can start one provider without changing the global config.\n\n## Quick start\n\nRequires 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.\n\n```sh\nnpm i -g sideby\nsideby new claude work # create ~/.claude-work, sharing ~/.claude's skills and hooks\nsideby login claude:work # sign in once\nsideby run work # Claude Code on the work account\nsideby ui # every account's quota at http://127.0.0.1:17420\n```\n\n`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`.\n\n## Set up with your AI agent\n\nPaste this into Claude Code, Codex or any coding agent:\n\n```text\nInstall the sideby CLI (npm package `sideby`) and its agent skill, then set up a second Claude Code account for me.\nFollow \"Set up sideby for a user\" in https://raw.githubusercontent.com/Adonis0123/sideby/main/llms.txt\n```\n\nThe 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?\".\n\n## What it looks like\n\n<img alt=\"Two terminals: the personal account and the work account running at the same time, both linked to the main account's skills and settings, then the local quota panel, and at 95% Auto Handoff moving work's session to the next account\" src=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/demo.gif\" width=\"1280\">\n\n<picture>\n <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel-dark.png\">\n <source media=\"(prefers-color-scheme: light)\" srcset=\"https://raw.githubusercontent.com/Adonis0123/sideby/main/docs/assets/panel.png\">\n <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\">\n</picture>\n\n## What else it does\n\n| You want to | Run | Guide |\n|---|---|---|\n| Add an API key account (DeepSeek, a gateway, …) | `sideby new claude deepseek --api` | [API accounts](docs/guide/usage.md#api-accounts) |\n| Find and fix config drift between accounts | `sideby doctor --fix` | [Doctor](docs/guide/usage.md#doctor) |\n| See Claude quota (Codex needs no setup) | `sideby quota setup claude --yes` | [Claude quota](docs/guide/usage.md#claude-quota) |\n| Find the cheapest auto-compact size from your own sessions, and use it in one click | `sideby tune` (or the panel's Context tuning tab) | [Context tuning](docs/guide/usage.md#context-tuning) |\n| See each account's model and effort, and set them per account | `sideby model <account>` (or click the model in the panel) | [Model and effort](docs/guide/usage.md#model-and-effort) |\n| Move on when an account hits its limit | `sideby next claude` | [Handoff](docs/guide/usage.md#when-an-account-hits-its-limit) |\n| Let a running task move to the next account by itself when quota runs low (off by default; see the risk) | `sideby handoff enable` (or the panel) | [Auto Handoff](docs/guide/usage.md#auto-handoff) |\n| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |\n| 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) |\n| Open the panel from the Dock or app menu | `sideby app install` | [Desktop app](docs/guide/panel.md#desktop-app) |\n| Show the panel inside your own local page | `createPanelHandler()` | [Embed the panel](docs/guide/panel.md#embed-the-panel) |\n| Add a Host or a pre-launch check | a local plugin | [Plugins](docs/guide/plugins.md) |\n\n## Supported hosts\n\n| Host | Account directories | Sign-in | Quota | Usage (7 days) |\n|---|---|---|---|---|\n| Claude Code | `~/.claude`, `~/.claude-<name>` | `claude auth login` | ✓ after `quota setup claude` | ✓ |\n| Codex CLI | `~/.codex`, `~/.codex-<name>` | `codex login` | ✓ | ✓ |\n| Grok Build | `~/.grok`, `~/.grok-<name>` | `grok login` | no public source | no public source |\n| pi | `~/.pi/agent`, `~/.pi-<name>/agent` | `/login` inside pi | no public source | no public source |\n\nsideby 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).\n\n## Platforms\n\nmacOS and Linux. CI runs the suite on `ubuntu-latest` and `macos-latest`, Node 22 and 24. **Windows is untested.** `sideby app` only installs a launcher on macOS and Linux. Linked items are symlinks, `sideby shell-init` prints bash and zsh functions, and the Claude quota wrapper runs `/bin/sh`.\n\n## Roadmap\n\n**Gemini CLI.** Gemini can point at another config directory with `GEMINI_CONFIG_DIR`. Older docs use `GEMINI_CLI_HOME`; current builds treat that name as deprecated, and exit on startup if it is set together with an exact directory override. A sideby family still needs a tested Gemini version, a login check that does not read tokens, and a list of which files to link or copy. That is more than a small patch, so it is not in this version.\n\n## Safe by design\n\n- It never edits a Host's global config to switch accounts. It moves a task to another account only when you turn on Auto Handoff, and never proxies requests or pools logins.\n- It never reads, prints or sends a credential value. Nothing leaves your machine: sideby calls no remote service.\n- `doctor --fix` never replaces a real file and never changes where a link points.\n\nDetails and the terms-of-service question are in the [FAQ](docs/guide/faq.md).\n\nUsage questions and ideas go in [Discussions](https://github.com/Adonis0123/sideby/discussions).\n\n## Docs\n\n- [Using sideby](docs/guide/usage.md): accounts, API accounts, Doctor, quota, Handoff, Host notes\n- [Commands](docs/guide/commands.md): every command, exit codes and `--json`\n- [Configuration](docs/guide/configuration.md): `config.json`, aliases, shell functions\n- [Panel](docs/guide/panel.md): the panel, desktop app and embedding\n- [Plugins](docs/guide/plugins.md): write your own Family or launch hook\n- [FAQ](docs/guide/faq.md): terms of service, data locations, alternatives, uninstall\n- [Changelog](https://github.com/Adonis0123/sideby/releases)\n\n## For AI agents\n\n- [`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.\n- [`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.\n- [`AGENTS.md`](AGENTS.md): rules for agents working on this repository.\n\n## License\n\n[MIT](LICENSE)\n"
|
|
80
81
|
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "sideby/model.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"schemaVersion",
|
|
7
|
+
"account",
|
|
8
|
+
"family",
|
|
9
|
+
"settable",
|
|
10
|
+
"efforts",
|
|
11
|
+
"models",
|
|
12
|
+
"sideby",
|
|
13
|
+
"host",
|
|
14
|
+
"last"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schemaVersion": {
|
|
18
|
+
"type": "number",
|
|
19
|
+
"const": 1
|
|
20
|
+
},
|
|
21
|
+
"account": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"family": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"settable": {
|
|
28
|
+
"type": "boolean",
|
|
29
|
+
"description": "Whether sideby can set the model and effort for this account."
|
|
30
|
+
},
|
|
31
|
+
"reason": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"efforts": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": {
|
|
37
|
+
"type": "string"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"models": {
|
|
41
|
+
"type": "array",
|
|
42
|
+
"items": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
"description": "Model names to offer; any other name may be given."
|
|
46
|
+
},
|
|
47
|
+
"sideby": {
|
|
48
|
+
"type": "object",
|
|
49
|
+
"properties": {
|
|
50
|
+
"model": {
|
|
51
|
+
"type": "string"
|
|
52
|
+
},
|
|
53
|
+
"effort": {
|
|
54
|
+
"type": "string"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"host": {
|
|
59
|
+
"type": "object",
|
|
60
|
+
"properties": {
|
|
61
|
+
"model": {
|
|
62
|
+
"type": "string"
|
|
63
|
+
},
|
|
64
|
+
"effort": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"last": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"model": {
|
|
73
|
+
"type": "string"
|
|
74
|
+
},
|
|
75
|
+
"effort": {
|
|
76
|
+
"type": "string"
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
"model": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"required": [
|
|
83
|
+
"value",
|
|
84
|
+
"from"
|
|
85
|
+
],
|
|
86
|
+
"properties": {
|
|
87
|
+
"value": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"from": {
|
|
91
|
+
"anyOf": [
|
|
92
|
+
{
|
|
93
|
+
"type": "string",
|
|
94
|
+
"const": "sideby"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"type": "string",
|
|
98
|
+
"const": "host"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"type": "string",
|
|
102
|
+
"const": "last"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"effort": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"required": [
|
|
111
|
+
"value",
|
|
112
|
+
"from"
|
|
113
|
+
],
|
|
114
|
+
"properties": {
|
|
115
|
+
"value": {
|
|
116
|
+
"type": "string"
|
|
117
|
+
},
|
|
118
|
+
"from": {
|
|
119
|
+
"anyOf": [
|
|
120
|
+
{
|
|
121
|
+
"type": "string",
|
|
122
|
+
"const": "sideby"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"type": "string",
|
|
126
|
+
"const": "host"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "string",
|
|
130
|
+
"const": "last"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"change": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"required": [
|
|
139
|
+
"changed",
|
|
140
|
+
"applied",
|
|
141
|
+
"file",
|
|
142
|
+
"diff",
|
|
143
|
+
"args"
|
|
144
|
+
],
|
|
145
|
+
"properties": {
|
|
146
|
+
"changed": {
|
|
147
|
+
"type": "boolean"
|
|
148
|
+
},
|
|
149
|
+
"applied": {
|
|
150
|
+
"type": "boolean"
|
|
151
|
+
},
|
|
152
|
+
"file": {
|
|
153
|
+
"type": "string"
|
|
154
|
+
},
|
|
155
|
+
"diff": {
|
|
156
|
+
"type": "string"
|
|
157
|
+
},
|
|
158
|
+
"args": {
|
|
159
|
+
"type": "array",
|
|
160
|
+
"items": {
|
|
161
|
+
"type": "string"
|
|
162
|
+
},
|
|
163
|
+
"description": "The account arguments in the config after the change."
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
"title": "sideby model --json"
|
|
169
|
+
}
|
package/schemas/quota.json
CHANGED
package/skills/sideby/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: sideby
|
|
3
3
|
description: Use when the user runs, or wants to run, several AI coding accounts (Claude Code, Codex, Grok Build, pi) side by side. Covers installing sideby and setting up a new account ("install sideby", "set up another Claude account", "add cc008", "配置一个 cc00x", "再加一个 Codex 账号"), explaining what accounts share and what stays separate ("what do my accounts share?", "这些账号共享了什么"), showing which account a session is ("show [cc001] in the status line", "加上账号标识"), seeing accounts, quota and usage, moving on when one hits its 5-hour or weekly limit, adding a short command (alias), and repairing drifted account directories (skills, hooks, rules or settings not shared, wrong symlinks, credential file permissions). Drives the `sideby` CLI with `--json`; the agent does every step except browser sign-in, filling in secrets and starting interactive sessions, which it hands to the user as one command.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "0.
|
|
5
|
+
version: "0.6.0"
|
|
6
6
|
requires:
|
|
7
7
|
bins: [sideby, node]
|
|
8
8
|
cliHelp: sideby --help
|
|
@@ -45,6 +45,7 @@ Account refs are `<family>:<name>`; a bare `<name>` works only when it is unique
|
|
|
45
45
|
| Turn on Auto Handoff ("自动交接", "hand over when quota runs low"), see what it still needs, hand this session over now, or pick an account with room for a subtask | `sideby handoff status --json`, `sideby handoff enable --json` (exit 10 first), `sideby handoff ready`, `sideby next <family> --json` | `references/handoff.md` |
|
|
46
46
|
| Create an account with a given name, an API account, or a short command | `sideby new`, `sideby alias add` | `references/accounts.md` |
|
|
47
47
|
| Tune the auto-compact threshold ("which compaction setting costs the fewest tokens"), use the suggestion or undo it | `sideby tune --json`, `sideby tune apply` (exit 10 first), `sideby tune undo` | `references/diagnose.md` |
|
|
48
|
+
| See or set the model and effort an account starts with ("用哪个模型", "set effort to high for cc002") | `sideby model <acct> --json`; `sideby model <acct> <model\|default> --effort <level\|default>` (exit 10 first, then `--yes` after the user agrees) | — |
|
|
48
49
|
| See Claude quota (it shows `not-enabled`) | `sideby quota setup claude` (exits 10) | `references/quota.md` |
|
|
49
50
|
| See everything at once | `sideby ui` (local panel on `127.0.0.1:17420`; `--no-open` prints the address) | — |
|
|
50
51
|
| Start an account | give them `sideby run <ref> [-- host args]` | — |
|
|
@@ -39,7 +39,7 @@ Use this when the user says an account hit its 5-hour or weekly limit ("usage li
|
|
|
39
39
|
|
|
40
40
|
With config `"handoff": { "auto": true }`, a session sideby started hands its task to the next account when quota runs low; see the guide's Auto Handoff section for the settings. If you are in such a session (the environment has `SIDEBY_HANDOFF_RUN`):
|
|
41
41
|
|
|
42
|
-
- When sideby tells you the quota is at 80%, write the handoff brief at the path it names and keep it current: the goal, what is done, the remaining steps, key decisions and why, the files that matter. Reference specs, ADRs, commits and diffs by path; list the skills the next agent should use; never include keys, passwords or personal data.
|
|
42
|
+
- When sideby tells you the quota is at 80%, write the handoff brief at the path it names and keep it current (do not tell the user that another account will take over; sideby decides that and says so itself): the goal, what is done, the remaining steps, key decisions and why, the files that matter. Reference specs, ADRs, commits and diffs by path; list the skills the next agent should use; never include keys, passwords or personal data.
|
|
43
43
|
- When it says the quota is at 95%, finish the current step, update the brief, and end your turn. sideby starts the next account from it.
|
|
44
44
|
- When the user asks to hand over now, update the brief, run `sideby handoff ready` (add `--brief <file>` when the user's own handoff tool wrote one), and end your turn.
|
|
45
45
|
|
|
@@ -51,7 +51,7 @@ With config `"handoff": { "auto": true }`, a session sideby started hands its ta
|
|
|
51
51
|
4. Work through `nextSteps` in order. `sideby quota setup claude` and `sideby handoff setup grok` also exit 10 first: ask before adding `--yes`. `npm i -g sideby` installs or upgrades sideby on PATH; ask first. Codex needs the user to trust sideby's hooks once per account (`/hooks` in Codex); you cannot do that for them.
|
|
52
52
|
5. Run `sideby handoff status --json` again and report which families are `ready`.
|
|
53
53
|
|
|
54
|
-
To turn it off: `sideby handoff disable --json`, then `--yes` after the user agrees; the other settings stay. The panel
|
|
54
|
+
To turn it off: `sideby handoff disable --json`, then `--yes` after the user agrees; the other settings stay. The panel's **Auto Handoff** page does all of this in one click (turn on, with one confirmation; turn off) and shows where each account's work goes next; for a user who is not in a terminal, point them there instead of walking through the commands.
|
|
55
55
|
|
|
56
56
|
## Picking an account for a subtask
|
|
57
57
|
|