sideby 0.6.0 → 0.7.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 +3 -2
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +2 -0
- package/dist/src/cli/commands.js +29 -2
- package/dist/src/cli/json-schemas.js +22 -0
- package/dist/src/cli/main.js +26 -2
- package/dist/src/core/desktop-app.d.ts +96 -0
- package/dist/src/core/desktop-app.js +371 -0
- package/dist/src/core/doctor.d.ts +6 -1
- package/dist/src/core/doctor.js +19 -0
- package/dist/src/core/tuning-apply.js +1 -1
- package/dist/src/families/claude/desktop.d.ts +6 -0
- package/dist/src/families/claude/desktop.js +28 -0
- package/dist/src/families/codex/desktop.d.ts +7 -0
- package/dist/src/families/codex/desktop.js +25 -0
- package/dist/src/families/desktop.d.ts +27 -0
- package/dist/src/families/desktop.js +9 -0
- package/dist/src/panel/handler.js +24 -5
- package/dist/src/panel/i18n.d.ts +8 -0
- package/dist/src/panel/i18n.js +16 -0
- package/dist/src/panel/page-logic.js +2 -2
- package/dist/src/panel/page-script.js +29 -2
- package/dist/src/runtime.d.ts +16 -0
- package/dist/src/runtime.js +5 -0
- package/package.json +2 -2
- package/schemas/desktop.json +80 -0
- package/schemas/list.json +20 -0
- package/skills/sideby/SKILL.md +4 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { HookBus } from '../plugins/bus.ts';
|
|
2
|
-
import type { Account, FamilyDef, Finding } from '../types.ts';
|
|
2
|
+
import type { Account, CheckResult, FamilyDef, Finding } from '../types.ts';
|
|
3
3
|
export interface AccountReport {
|
|
4
4
|
ref: string;
|
|
5
5
|
family: string;
|
|
@@ -35,6 +35,11 @@ export interface DoctorInput {
|
|
|
35
35
|
force?: boolean;
|
|
36
36
|
/** This sideby's version: when set, installed sideby skills of the checked Families are compared with it. */
|
|
37
37
|
skillVersion?: string;
|
|
38
|
+
/**
|
|
39
|
+
* More core checks for an Account that is not the Main Account, merged after its Shared Items. They are not
|
|
40
|
+
* counted in `shared`; a result's `fix` runs with the others under `--fix` and must stay within what it checked.
|
|
41
|
+
*/
|
|
42
|
+
extraChecks?: (account: Account) => Promise<CheckResult[]>;
|
|
38
43
|
}
|
|
39
44
|
export declare function runDoctor(input: DoctorInput): Promise<DoctorReport>;
|
|
40
45
|
/** Applies every fixable Finding once, in order; one failing fix never stops the others. */
|
package/dist/src/core/doctor.js
CHANGED
|
@@ -43,6 +43,25 @@ async function checkAccount(input, family, account) {
|
|
|
43
43
|
if (r.level !== 'ok')
|
|
44
44
|
findings.push(toFinding(r, account.ref, 'core'));
|
|
45
45
|
}
|
|
46
|
+
// Extra checks (for example a Desktop App's data folder) add Findings but never count toward `shared`.
|
|
47
|
+
// A failing extra check costs only its own Findings, never the whole run.
|
|
48
|
+
try {
|
|
49
|
+
for (const r of (await input.extraChecks?.(account)) ?? [])
|
|
50
|
+
if (r.level !== 'ok')
|
|
51
|
+
findings.push(toFinding(r, account.ref, 'core'));
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
findings.push({
|
|
55
|
+
level: 'warn',
|
|
56
|
+
account: account.ref,
|
|
57
|
+
item: 'extra',
|
|
58
|
+
code: 'extra-check.error',
|
|
59
|
+
message: `could not finish an extra check of ${account.ref}: ${e instanceof Error ? e.message : String(e)}`,
|
|
60
|
+
hint: 'run sideby doctor again; if it keeps failing, check that the folder it names is readable',
|
|
61
|
+
fixable: false,
|
|
62
|
+
source: 'core',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
46
65
|
if (looksLikeBackup(account.name))
|
|
47
66
|
findings.push({
|
|
48
67
|
level: 'warn',
|
|
@@ -32,7 +32,7 @@ function render(obj, original) {
|
|
|
32
32
|
}
|
|
33
33
|
async function readObject(file) {
|
|
34
34
|
const st = await stat(file).catch(() => null);
|
|
35
|
-
if (!st
|
|
35
|
+
if (!st?.isFile())
|
|
36
36
|
return { error: 'it is not a regular file' };
|
|
37
37
|
if (st.size > MAX_BYTES)
|
|
38
38
|
return { error: 'it is too large to be a settings file' };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DesktopAppDef } from '../desktop.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Claude Desktop (spec §3.20). Its Code tab honours CLAUDE_CONFIG_DIR; the data folder moves only with
|
|
4
|
+
* `--user-data-dir` (signed builds ignore CLAUDE_USER_DATA_DIR).
|
|
5
|
+
*/
|
|
6
|
+
export declare const claudeDesktop: DesktopAppDef;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Desktop (spec §3.20). Its Code tab honours CLAUDE_CONFIG_DIR; the data folder moves only with
|
|
3
|
+
* `--user-data-dir` (signed builds ignore CLAUDE_USER_DATA_DIR).
|
|
4
|
+
*/
|
|
5
|
+
export const claudeDesktop = {
|
|
6
|
+
family: 'claude',
|
|
7
|
+
title: 'Claude Desktop',
|
|
8
|
+
bundleId: 'com.anthropic.claudefordesktop',
|
|
9
|
+
bundleNames: ['Claude.app'],
|
|
10
|
+
installUrl: 'https://claude.ai/download',
|
|
11
|
+
openArgs: (app, dir, appData) => [
|
|
12
|
+
'-n',
|
|
13
|
+
'-a',
|
|
14
|
+
app,
|
|
15
|
+
'--env',
|
|
16
|
+
`CLAUDE_CONFIG_DIR=${dir}`,
|
|
17
|
+
'--args',
|
|
18
|
+
`--user-data-dir=${appData}`,
|
|
19
|
+
],
|
|
20
|
+
clearVars: ['CLAUDE_USER_DATA_DIR'],
|
|
21
|
+
signInInApp: true,
|
|
22
|
+
sharedFiles: [
|
|
23
|
+
{
|
|
24
|
+
path: 'claude_desktop_config.json',
|
|
25
|
+
mainPath: 'Library/Application Support/Claude/claude_desktop_config.json',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DesktopAppDef } from '../desktop.ts';
|
|
2
|
+
/**
|
|
3
|
+
* The Codex app (`ChatGPT.app`, spec §3.20). It shares the Account's terminal sign-in through CODEX_HOME. Its own
|
|
4
|
+
* code replaces the path `--user-data-dir` sets, so CODEX_ELECTRON_USER_DATA_PATH goes along (the app's "Codex Demo"
|
|
5
|
+
* recipe); both are internal to the app and may change.
|
|
6
|
+
*/
|
|
7
|
+
export declare const codexDesktop: DesktopAppDef;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Codex app (`ChatGPT.app`, spec §3.20). It shares the Account's terminal sign-in through CODEX_HOME. Its own
|
|
3
|
+
* code replaces the path `--user-data-dir` sets, so CODEX_ELECTRON_USER_DATA_PATH goes along (the app's "Codex Demo"
|
|
4
|
+
* recipe); both are internal to the app and may change.
|
|
5
|
+
*/
|
|
6
|
+
export const codexDesktop = {
|
|
7
|
+
family: 'codex',
|
|
8
|
+
title: 'Codex app',
|
|
9
|
+
bundleId: 'com.openai.codex',
|
|
10
|
+
bundleNames: ['ChatGPT.app', 'Codex.app'],
|
|
11
|
+
installUrl: 'https://openai.com/codex/',
|
|
12
|
+
openArgs: (app, dir, appData) => [
|
|
13
|
+
'-n',
|
|
14
|
+
'--env',
|
|
15
|
+
`CODEX_HOME=${dir}`,
|
|
16
|
+
'--env',
|
|
17
|
+
`CODEX_ELECTRON_USER_DATA_PATH=${appData}`,
|
|
18
|
+
app,
|
|
19
|
+
'--args',
|
|
20
|
+
`--user-data-dir=${appData}`,
|
|
21
|
+
],
|
|
22
|
+
clearVars: ['CODEX_ELECTRON_USER_DATA_PATH'],
|
|
23
|
+
signInInApp: false,
|
|
24
|
+
sharedFiles: [],
|
|
25
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface DesktopAppDef {
|
|
2
|
+
/** The Family id. */
|
|
3
|
+
family: string;
|
|
4
|
+
/** How messages name the app, for example `Claude Desktop`. */
|
|
5
|
+
title: string;
|
|
6
|
+
/** `CFBundleIdentifier` in the app's Info.plist. */
|
|
7
|
+
bundleId: string;
|
|
8
|
+
/** Bundle names to look for first in /Applications and ~/Applications. */
|
|
9
|
+
bundleNames: readonly string[];
|
|
10
|
+
installUrl: string;
|
|
11
|
+
/**
|
|
12
|
+
* `open` arguments for a non-main Account: a new instance (`-n`) with the Account's selecting variable and
|
|
13
|
+
* App Data. `app` is the bundle path, `dir` the Account directory.
|
|
14
|
+
*/
|
|
15
|
+
openArgs(app: string, dir: string, appData: string): string[];
|
|
16
|
+
/** Inherited variables cleared before `open`, besides the Family's Hijack Variables and selecting variable. */
|
|
17
|
+
clearVars: readonly string[];
|
|
18
|
+
/** The first sign-in happens inside the app, through a URL callback another running instance may catch. */
|
|
19
|
+
signInInApp: boolean;
|
|
20
|
+
/** Files of the App Data shared from the Main Account's default App Data as links (`mainPath` relative to HOME). */
|
|
21
|
+
sharedFiles: readonly {
|
|
22
|
+
path: string;
|
|
23
|
+
mainPath: string;
|
|
24
|
+
}[];
|
|
25
|
+
}
|
|
26
|
+
/** The Desktop App of a built-in Family id, if it has one. */
|
|
27
|
+
export declare function desktopDef(familyId: string): DesktopAppDef | undefined;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Desktop Apps of the built-in Families (spec §3.20, ADR-0015). Internal on purpose: not part of the public
|
|
2
|
+
// FamilyDef, so plugin Families cannot declare one in v1.
|
|
3
|
+
import { claudeDesktop } from './claude/desktop.js';
|
|
4
|
+
import { codexDesktop } from './codex/desktop.js';
|
|
5
|
+
const DESKTOP_APPS = new Map([claudeDesktop, codexDesktop].map((d) => [d.family, d]));
|
|
6
|
+
/** The Desktop App of a built-in Family id, if it has one. */
|
|
7
|
+
export function desktopDef(familyId) {
|
|
8
|
+
return DESKTOP_APPS.get(familyId);
|
|
9
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
4
4
|
import { aliasArgs, aliasRefs } from '../core/config.js';
|
|
5
|
+
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
5
6
|
import { UserError } from '../core/errors.js';
|
|
6
7
|
import { planHandoff } from '../core/handoff.js';
|
|
7
8
|
import { applyHandoffEnable, planHandoffEnable } from '../core/handoff-enable.js';
|
|
@@ -203,10 +204,11 @@ async function buildState(rt, opts) {
|
|
|
203
204
|
]);
|
|
204
205
|
const health = healthByAccount(history);
|
|
205
206
|
const aliases = aliasesByAccount(aliasRefs(rt.config.aliases), accounts);
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const
|
|
207
|
+
// Desktop Apps (spec §3.20): one process listing for every Account, run alongside the per-Account reads.
|
|
208
|
+
const states = await withDesktopApps(rt, accounts, Promise.all(accounts.map((a) => accountState(rt, a, families))));
|
|
209
|
+
const list = states.map((st) => {
|
|
210
|
+
const h = health.get(st.ref);
|
|
211
|
+
const names = aliases.get(st.ref);
|
|
210
212
|
const withArgs = (names ?? [])
|
|
211
213
|
.map((n) => [n, aliasArgs(rt.config.aliases?.[n])])
|
|
212
214
|
.filter(([, args]) => args.length);
|
|
@@ -216,7 +218,7 @@ async function buildState(rt, opts) {
|
|
|
216
218
|
...(names ? { aliases: names } : {}),
|
|
217
219
|
...(withArgs.length ? { aliasArgs: Object.fromEntries(withArgs) } : {}),
|
|
218
220
|
};
|
|
219
|
-
})
|
|
221
|
+
});
|
|
220
222
|
const handoff = handoffByFamily(rt, list, ctx.now);
|
|
221
223
|
return {
|
|
222
224
|
schemaVersion: 1,
|
|
@@ -434,6 +436,23 @@ export function createPanelHandler(opts) {
|
|
|
434
436
|
return { ref: found.ref, ...r, file: tildify(r.file, rt.paths.home) };
|
|
435
437
|
},
|
|
436
438
|
},
|
|
439
|
+
// Opens the Account's Desktop App, or brings its window to the front (spec §3.20). It creates the Account's
|
|
440
|
+
// app data folder and starts a program, so a read-only Panel refuses it (writes defaults to true).
|
|
441
|
+
'/api/account/desktop': {
|
|
442
|
+
async run(body) {
|
|
443
|
+
const account = optString(body, 'account');
|
|
444
|
+
if (!account)
|
|
445
|
+
throw new HttpError(400, '"account" is required');
|
|
446
|
+
const rt = await getRuntime();
|
|
447
|
+
const r = await openDesktopApp(rt, account);
|
|
448
|
+
const home = rt.paths.home;
|
|
449
|
+
return {
|
|
450
|
+
...r,
|
|
451
|
+
appPath: tildify(r.appPath, home),
|
|
452
|
+
...(r.appData ? { appData: tildify(r.appData, home) } : {}),
|
|
453
|
+
};
|
|
454
|
+
},
|
|
455
|
+
},
|
|
437
456
|
// The Auto Handoff page (spec §3.17): where work would go next and the last hand-overs. Reads only.
|
|
438
457
|
'/api/handoff/overview': {
|
|
439
458
|
writes: () => false,
|
package/dist/src/panel/i18n.d.ts
CHANGED
|
@@ -234,6 +234,14 @@ declare const en: {
|
|
|
234
234
|
'menu.like': string;
|
|
235
235
|
'menu.tune': string;
|
|
236
236
|
'menu.model': string;
|
|
237
|
+
'desk.open': string;
|
|
238
|
+
'desk.show': string;
|
|
239
|
+
'desk.opening': string;
|
|
240
|
+
'desk.openHint': string;
|
|
241
|
+
'desk.showHint': string;
|
|
242
|
+
'desk.started': string;
|
|
243
|
+
'desk.focused': string;
|
|
244
|
+
'desk.failed': string;
|
|
237
245
|
'md.title': string;
|
|
238
246
|
'md.intro': string;
|
|
239
247
|
'md.model': string;
|
package/dist/src/panel/i18n.js
CHANGED
|
@@ -236,6 +236,14 @@ const en = {
|
|
|
236
236
|
'menu.like': 'New account like this',
|
|
237
237
|
'menu.tune': 'Context tuning',
|
|
238
238
|
'menu.model': 'Model and effort…',
|
|
239
|
+
'desk.open': 'Open desktop app',
|
|
240
|
+
'desk.show': 'Desktop app open',
|
|
241
|
+
'desk.opening': 'Opening {app}…',
|
|
242
|
+
'desk.openHint': 'Open {app} as {ref}, in its own window next to your other accounts',
|
|
243
|
+
'desk.showHint': '{app} is open as {ref}; click to bring it to the front',
|
|
244
|
+
'desk.started': 'Opened {app} as {ref}.',
|
|
245
|
+
'desk.focused': '{app} for {ref} is in front.',
|
|
246
|
+
'desk.failed': 'Could not open the desktop app for {ref}: {msg}',
|
|
239
247
|
'md.title': 'Model and effort',
|
|
240
248
|
'md.intro': 'Applies to new sessions of {ref} that you start with sideby (a short command or sideby run). It is saved in sideby’s config; {host}’s own settings stay as they are.',
|
|
241
249
|
'md.model': 'Model',
|
|
@@ -685,6 +693,14 @@ const zh = {
|
|
|
685
693
|
'menu.like': '照此新建账号',
|
|
686
694
|
'menu.tune': '上下文调优',
|
|
687
695
|
'menu.model': '模型和 effort…',
|
|
696
|
+
'desk.open': '打开桌面应用',
|
|
697
|
+
'desk.show': '桌面应用已打开',
|
|
698
|
+
'desk.opening': '正在打开 {app}…',
|
|
699
|
+
'desk.openHint': '以 {ref} 打开 {app},和其他账号的窗口同时开着',
|
|
700
|
+
'desk.showHint': '{app} 已经以 {ref} 打开,点一下切到前台',
|
|
701
|
+
'desk.started': '已以 {ref} 打开 {app}。',
|
|
702
|
+
'desk.focused': '{ref} 的 {app} 已切到前台。',
|
|
703
|
+
'desk.failed': '没能打开 {ref} 的桌面应用:{msg}',
|
|
688
704
|
'md.title': '模型和 effort',
|
|
689
705
|
'md.intro': '只影响之后用 sideby 启动的 {ref} 新会话(短命令或 sideby run)。保存在 sideby 的配置里,不改 {host} 自己的设置。',
|
|
690
706
|
'md.model': '模型',
|
|
@@ -257,8 +257,8 @@ export function suggestName(names) {
|
|
|
257
257
|
if (!taken.work)
|
|
258
258
|
return 'work';
|
|
259
259
|
for (let i = 2;; i++)
|
|
260
|
-
if (!taken[
|
|
261
|
-
return
|
|
260
|
+
if (!taken[`work${i}`])
|
|
261
|
+
return `work${i}`;
|
|
262
262
|
}
|
|
263
263
|
/**
|
|
264
264
|
* A Finding's hint as the Panel shows it: for a fixable Finding, "run `sideby doctor --fix`" becomes the
|
|
@@ -53,7 +53,7 @@ const MAIN = String.raw `
|
|
|
53
53
|
silent: false, lastTry: 0, pending: false, fastUntil: 0, fastFamily: null,
|
|
54
54
|
}
|
|
55
55
|
// View state that is not saved: the search text, the attention filter, the Health check filter.
|
|
56
|
-
const ui = { query: '', attention: false, allFindings: false, healthRef: null, checkingRef: null, highlight: null, fade: true }
|
|
56
|
+
const ui = { query: '', attention: false, allFindings: false, healthRef: null, checkingRef: null, desktopRef: null, highlight: null, fade: true }
|
|
57
57
|
const $ = (id) => document.getElementById(id)
|
|
58
58
|
|
|
59
59
|
// ---------- preferences ----------
|
|
@@ -573,6 +573,7 @@ const MAIN = String.raw `
|
|
|
573
573
|
const items = [
|
|
574
574
|
{ icon: 'terminal', label: t('menu.copyRun'), hint: run, run: () => copyText(run) },
|
|
575
575
|
...(a.aliases || []).slice(0, 2).map((x) => ({ icon: 'terminal', label: t('menu.copyAlias'), hint: x, run: () => copyText(x) })),
|
|
576
|
+
a.desktopApp && a.desktopApp.installed && a.kind !== 'api' && !BOOT.readOnly ? { icon: 'terminal', label: t(a.desktopApp.running ? 'desk.show' : 'desk.open', { app: a.desktopApp.app }), disabled: Boolean(ui.desktopRef), run: () => openDesktop(a) } : null,
|
|
576
577
|
a.kind !== 'api' ? { icon: 'key', label: t('menu.copyLogin'), hint: 'sideby login ' + a.ref, run: () => copyText('sideby login ' + a.ref) } : null,
|
|
577
578
|
a.dir ? { icon: 'folder', label: t('menu.copyDir'), hint: a.dir, run: () => copyText(a.dir) } : null,
|
|
578
579
|
a.identity && a.identity.email ? { icon: 'mail', label: t('menu.copyEmail'), hint: emailText(a.identity.email), run: () => copyText(a.identity.email) } : null,
|
|
@@ -906,8 +907,34 @@ const MAIN = String.raw `
|
|
|
906
907
|
a.kind === 'api' && a.model ? h('span', { class: 'mono', title: t('acct.model') }, a.model) : null,
|
|
907
908
|
a.kind !== 'api' ? modelChip(a) : null,
|
|
908
909
|
a.kind !== 'api' && plan ? h('span', { class: 'plan' }, plan) : null,
|
|
910
|
+
desktopButton(a),
|
|
909
911
|
]))
|
|
910
912
|
}
|
|
913
|
+
// The family's desktop app as this account (spec §3.20): only where it is installed, never for API accounts or a
|
|
914
|
+
// read-only panel. A running window shows as "open"; the click brings it to the front instead of a second one.
|
|
915
|
+
function desktopButton(a) {
|
|
916
|
+
const d = a.desktopApp
|
|
917
|
+
if (!d || !d.installed || a.kind === 'api' || BOOT.readOnly) return null
|
|
918
|
+
const busy = ui.desktopRef === a.ref
|
|
919
|
+
const label = busy ? t('desk.opening', { app: d.app }) : t(d.running ? 'desk.show' : 'desk.open', { app: d.app })
|
|
920
|
+
return h('button', { class: 'tag tag-btn' + (d.running ? ' tag-ok' : ''), type: 'button', disabled: busy, title: t(d.running ? 'desk.showHint' : 'desk.openHint', { app: d.app, ref: a.ref }), 'aria-label': label + ' (' + a.ref + ')', onclick: (e) => { e.stopPropagation(); openDesktop(a) } },
|
|
921
|
+
d.running ? h('span', { class: 'dot dot-ok', 'aria-hidden': 'true' }) : null, label)
|
|
922
|
+
}
|
|
923
|
+
async function openDesktop(a) {
|
|
924
|
+
if (ui.desktopRef) return
|
|
925
|
+
ui.desktopRef = a.ref
|
|
926
|
+
renderAccounts()
|
|
927
|
+
try {
|
|
928
|
+
const r = await api('/api/account/desktop', { account: a.ref })
|
|
929
|
+
const text = r.action === 'started' ? t('desk.started', { app: r.app, ref: r.ref }) : r.action === 'focused' ? t('desk.focused', { app: r.app, ref: r.ref }) : ''
|
|
930
|
+
if (text) toast(text)
|
|
931
|
+
// Notices say what to do next (sign in, close other windows while signing in); they stay a little longer.
|
|
932
|
+
for (const n of r.notices || []) toast(n, { kind: 'info', duration: 12000 })
|
|
933
|
+
} catch (e) { toast(t('desk.failed', { ref: a.ref, msg: e.message }), { kind: 'fail' }) }
|
|
934
|
+
ui.desktopRef = null
|
|
935
|
+
renderAccounts()
|
|
936
|
+
load({ silent: true })
|
|
937
|
+
}
|
|
911
938
|
function notes(a) {
|
|
912
939
|
const fam = family(a.family)
|
|
913
940
|
const out = []
|
|
@@ -920,7 +947,7 @@ const MAIN = String.raw `
|
|
|
920
947
|
}
|
|
921
948
|
// What a row or card shows depends on more than the account (health, view preferences, language); the
|
|
922
949
|
// fingerprint covers what its click handlers use.
|
|
923
|
-
function rowSig(a) { return sig([a, healthOf(a.ref), isNext(a)]) }
|
|
950
|
+
function rowSig(a) { return sig([a, healthOf(a.ref), isNext(a), ui.desktopRef === a.ref]) }
|
|
924
951
|
// The Account 'sideby next <family>' would pick; the server ranks it with the CLI's rule (spec §3.13).
|
|
925
952
|
function isNext(a) { return Boolean(S.state && S.state.handoff && S.state.handoff[a.family] === a.ref) }
|
|
926
953
|
function row(a, now, cols) {
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type AccountRefError } from './core/accounts.ts';
|
|
2
2
|
import { type Config, type HandoffSettings } from './core/config.ts';
|
|
3
3
|
import { type CreateResult } from './core/create.ts';
|
|
4
|
+
import { type DesktopDeps } from './core/desktop-app.ts';
|
|
4
5
|
import { type DoctorReport } from './core/doctor.ts';
|
|
5
6
|
import { UserError } from './core/errors.ts';
|
|
6
7
|
import { type HandoffPlan } from './core/handoff.ts';
|
|
@@ -104,6 +105,15 @@ export interface AccountStatus extends Account {
|
|
|
104
105
|
identity?: AccountIdentity;
|
|
105
106
|
/** Plugin errors met while reading this Account (for example an unreadable settings file). */
|
|
106
107
|
problems?: string[];
|
|
108
|
+
/**
|
|
109
|
+
* The Family's Desktop App for this Account (spec §3.20): macOS only, Subscription Accounts of claude and codex.
|
|
110
|
+
* Filled by `sideby list` and the Panel, which list processes once for all Accounts; `status()` leaves it out.
|
|
111
|
+
*/
|
|
112
|
+
desktopApp?: {
|
|
113
|
+
app: string;
|
|
114
|
+
installed: boolean;
|
|
115
|
+
running: boolean;
|
|
116
|
+
};
|
|
107
117
|
}
|
|
108
118
|
export interface DoctorOptions {
|
|
109
119
|
/** Family id or Account ref; all Accounts when omitted. */
|
|
@@ -232,4 +242,10 @@ export declare function createRuntime(opts?: {
|
|
|
232
242
|
env?: Env;
|
|
233
243
|
builtins?: BuiltinPlugin[];
|
|
234
244
|
now?: () => Date;
|
|
245
|
+
/**
|
|
246
|
+
* Internal, for tests: replaces the machine the Desktop App calls use (platform, app folders and the programs
|
|
247
|
+
* `open`, `ps`, `osascript`, `plutil`, `mdfind`; spec §3.20). Omitted, the real machine is used. Not part of
|
|
248
|
+
* the plugin or Portal contract; it may change without notice.
|
|
249
|
+
*/
|
|
250
|
+
desktop?: Partial<DesktopDeps>;
|
|
235
251
|
}): Promise<Runtime>;
|
package/dist/src/runtime.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { aliasesByAccount, discoverAccounts, resolveRef } from './core/accounts.js';
|
|
3
3
|
import { addConfigAlias, aliasAccount, aliasArgs, aliasProblem, aliasRefs, handoffSettings, loadConfig, removeConfigAlias, } from './core/config.js';
|
|
4
4
|
import { createAccount } from './core/create.js';
|
|
5
|
+
import { bindDesktopDeps, checkAppData } from './core/desktop-app.js';
|
|
5
6
|
import { applyFixes, runDoctor } from './core/doctor.js';
|
|
6
7
|
import { UserError } from './core/errors.js';
|
|
7
8
|
import { planHandoff } from './core/handoff.js';
|
|
@@ -254,6 +255,8 @@ export async function createRuntime(opts = {}) {
|
|
|
254
255
|
// Checked for every Family in the run, also when Doctor is aimed at one Account, so the saved result
|
|
255
256
|
// of that Family keeps its skill warning.
|
|
256
257
|
skillVersion: packageVersion(),
|
|
258
|
+
// Desktop App data folders sideby made for an Account (spec §3.20).
|
|
259
|
+
extraChecks: (a) => checkAppData(loaded.plugins, paths.home, a),
|
|
257
260
|
};
|
|
258
261
|
// Auto Handoff warnings (spec §3.17) join the general Findings; they never fail Doctor.
|
|
259
262
|
const all = await accounts();
|
|
@@ -606,5 +609,7 @@ export async function createRuntime(opts = {}) {
|
|
|
606
609
|
},
|
|
607
610
|
readContext,
|
|
608
611
|
};
|
|
612
|
+
if (opts.desktop)
|
|
613
|
+
bindDesktopDeps(rt, opts.desktop);
|
|
609
614
|
return rt;
|
|
610
615
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sideby",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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)",
|
|
@@ -77,5 +77,5 @@
|
|
|
77
77
|
},
|
|
78
78
|
"packageManager": "pnpm@10.15.0",
|
|
79
79
|
"readmeFilename": "README.md",
|
|
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
|
+
"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 Claude Desktop or the Codex app as one account, several side by side (macOS) | `sideby desktop claude:work` (or the panel) | [Desktop apps](docs/guide/usage.md#desktop-apps) |\n| Open the panel from the Dock or app menu | `sideby app install` | [Panel app](docs/guide/panel.md#panel-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, panel 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"
|
|
81
81
|
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "sideby/desktop.json",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"required": [
|
|
6
|
+
"schemaVersion",
|
|
7
|
+
"ref",
|
|
8
|
+
"family",
|
|
9
|
+
"app",
|
|
10
|
+
"appPath",
|
|
11
|
+
"action",
|
|
12
|
+
"createdAppData",
|
|
13
|
+
"linked",
|
|
14
|
+
"notices"
|
|
15
|
+
],
|
|
16
|
+
"properties": {
|
|
17
|
+
"schemaVersion": {
|
|
18
|
+
"type": "number",
|
|
19
|
+
"const": 1
|
|
20
|
+
},
|
|
21
|
+
"ref": {
|
|
22
|
+
"type": "string"
|
|
23
|
+
},
|
|
24
|
+
"family": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"app": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "The desktop app, for example `Claude Desktop`."
|
|
30
|
+
},
|
|
31
|
+
"appPath": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"action": {
|
|
35
|
+
"anyOf": [
|
|
36
|
+
{
|
|
37
|
+
"type": "string",
|
|
38
|
+
"const": "started"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"type": "string",
|
|
42
|
+
"const": "focused"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"type": "string",
|
|
46
|
+
"const": "already-running"
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
"description": "`started`: a new window for this account; `focused`: its running window was brought to the front; `already-running`: it runs but could not be brought to the front."
|
|
50
|
+
},
|
|
51
|
+
"appData": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "This account's app data folder; absent for the main account, which uses the app's default."
|
|
54
|
+
},
|
|
55
|
+
"createdAppData": {
|
|
56
|
+
"type": "boolean"
|
|
57
|
+
},
|
|
58
|
+
"linked": {
|
|
59
|
+
"type": "array",
|
|
60
|
+
"items": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"description": "Files linked into a new app data folder from the main account's (claude_desktop_config.json)."
|
|
64
|
+
},
|
|
65
|
+
"command": {
|
|
66
|
+
"type": "array",
|
|
67
|
+
"items": {
|
|
68
|
+
"type": "string"
|
|
69
|
+
},
|
|
70
|
+
"description": "The `open` command sideby ran."
|
|
71
|
+
},
|
|
72
|
+
"notices": {
|
|
73
|
+
"type": "array",
|
|
74
|
+
"items": {
|
|
75
|
+
"type": "string"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
"title": "sideby desktop --json"
|
|
80
|
+
}
|
package/schemas/list.json
CHANGED
|
@@ -152,6 +152,26 @@
|
|
|
152
152
|
"items": {
|
|
153
153
|
"type": "string"
|
|
154
154
|
}
|
|
155
|
+
},
|
|
156
|
+
"desktopApp": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"required": [
|
|
159
|
+
"app",
|
|
160
|
+
"installed",
|
|
161
|
+
"running"
|
|
162
|
+
],
|
|
163
|
+
"properties": {
|
|
164
|
+
"app": {
|
|
165
|
+
"type": "string"
|
|
166
|
+
},
|
|
167
|
+
"installed": {
|
|
168
|
+
"type": "boolean"
|
|
169
|
+
},
|
|
170
|
+
"running": {
|
|
171
|
+
"type": "boolean"
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
"description": "The family's desktop app for this account (macOS, subscription accounts of claude and codex): whether it is installed and whether this account's own window runs now. Open it with `sideby desktop <account>`."
|
|
155
175
|
}
|
|
156
176
|
}
|
|
157
177
|
}
|
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.7.0"
|
|
6
6
|
requires:
|
|
7
7
|
bins: [sideby, node]
|
|
8
8
|
cliHelp: sideby --help
|
|
@@ -58,7 +58,9 @@ Read only the reference the task needs.
|
|
|
58
58
|
|
|
59
59
|
When a terminal manager such as Orca reopens a pane with a bare `claude --resume <id>` (or `codex resume <id>`, `grok --resume <id>`) and the Host says `No conversation found`, the session lives in another account. Suggest config `"resumeRouting": true` followed by `sideby shell-init --write` (or a new shell with `eval`): `shell-init` then defines Host-named functions that call `sideby resume <family>`, which starts the account holding that session. For a one-off, find it with `ls -d ~/.claude*/projects/*/<id>.jsonl` and run `sideby run <ref> -- --resume <id>`. When no account has that file, the session was opened but never got a message, so there is nothing to resume: `ls -d ~/.claude*/session-env/<id>` names the account that started it, and with routing on `sideby resume` starts a new session there by itself.
|
|
60
60
|
|
|
61
|
-
|
|
61
|
+
To open Claude Desktop or the Codex app as one account (macOS, subscription accounts), run `sideby desktop <ref> --json` yourself: it opens a new window for that account, or brings its open window to the front, and never closes anything. Relay its `notices`: the first Claude Desktop sign-in should happen while other Claude Desktop windows are closed, and a sign-in screen that suggests the main account ("Welcome back" in the Codex app) must not be used for another account.
|
|
62
|
+
|
|
63
|
+
For someone who would rather double-click than use a terminal, `sideby app install` adds a panel app (macOS `~/Applications/sideby.app`, Linux app menu) that runs `sideby ui --background`; `sideby ui --stop` stops it and `sideby app uninstall` removes the app. Ask before `app install`: it writes outside sideby's own directories.
|
|
62
64
|
|
|
63
65
|
## Keeping this skill current
|
|
64
66
|
|