sideby 0.10.0 → 0.12.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 +24 -18
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +11 -1
- package/dist/src/cli/commands.js +144 -7
- package/dist/src/cli/json-schemas.js +56 -1
- package/dist/src/cli/main.js +119 -48
- package/dist/src/core/app-profiles.d.ts +50 -0
- package/dist/src/core/app-profiles.js +206 -0
- package/dist/src/core/brand-mark.d.ts +83 -0
- package/dist/src/core/brand-mark.js +88 -0
- package/dist/src/core/config.d.ts +16 -0
- package/dist/src/core/config.js +17 -2
- package/dist/src/core/desktop-app.d.ts +18 -1
- package/dist/src/core/desktop-app.js +22 -12
- package/dist/src/core/handoff-chain.d.ts +16 -2
- package/dist/src/core/handoff-chain.js +19 -6
- package/dist/src/core/handoff-run.js +43 -18
- package/dist/src/core/handoff-select.d.ts +4 -0
- package/dist/src/core/handoff-select.js +3 -2
- package/dist/src/core/update.d.ts +209 -0
- package/dist/src/core/update.js +631 -0
- package/dist/src/desktop/icon.js +293 -62
- package/dist/src/families/claude/handoff.js +10 -0
- package/dist/src/families/desktop.d.ts +25 -0
- package/dist/src/families/desktop.js +8 -2
- package/dist/src/families/grok/bot.d.ts +8 -0
- package/dist/src/families/grok/bot.js +18 -0
- package/dist/src/handoff/hook.js +21 -4
- package/dist/src/handoff/messages.d.ts +4 -0
- package/dist/src/handoff/messages.js +12 -0
- package/dist/src/panel/background.d.ts +2 -0
- package/dist/src/panel/background.js +7 -0
- package/dist/src/panel/handler.d.ts +35 -0
- package/dist/src/panel/handler.js +132 -6
- package/dist/src/panel/i18n.d.ts +59 -0
- package/dist/src/panel/i18n.js +118 -0
- package/dist/src/panel/page-script.js +282 -6
- package/dist/src/panel/page-styles.js +21 -2
- package/dist/src/panel/page.d.ts +2 -0
- package/dist/src/panel/page.js +6 -3
- package/dist/src/runtime.d.ts +3 -1
- package/dist/src/runtime.js +7 -0
- package/dist/src/types.d.ts +5 -0
- package/dist/testing/index.js +2 -0
- package/package.json +2 -2
- package/schemas/config.json +15 -0
- package/schemas/desktop-remove.json +33 -0
- package/schemas/desktop.json +2 -1
- package/schemas/handoff-ready.json +4 -0
- package/schemas/list.json +47 -0
- package/schemas/update.json +115 -0
- package/skills/sideby/SKILL.md +7 -1
- package/skills/sideby/references/handoff.md +1 -1
package/README.md
CHANGED
|
@@ -16,31 +16,20 @@
|
|
|
16
16
|
|
|
17
17
|
<p align="center">English | <a href="README.zh-CN.md">中文</a></p>
|
|
18
18
|
|
|
19
|
-
## Why not just an alias?
|
|
20
|
-
|
|
21
|
-
`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)
|
|
22
|
-
|
|
23
|
-
| | At the same time | Skills, hooks, settings | Quota and usage | Setup |
|
|
24
|
-
|---|---|---|---|---|
|
|
25
|
-
| `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 |
|
|
26
|
-
| [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 |
|
|
27
|
-
| 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 |
|
|
28
|
-
|
|
29
|
-
cc-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.
|
|
30
|
-
|
|
31
19
|
## Quick start
|
|
32
20
|
|
|
33
21
|
Requires Node 22.18+ and at least one Host CLI (Claude Code, Codex, Grok Build or pi) installed. AI agents: follow the setup steps in [`llms.txt`](llms.txt) ("Set up sideby for a user") instead.
|
|
34
22
|
|
|
35
23
|
```sh
|
|
36
24
|
npm i -g sideby
|
|
37
|
-
sideby new claude work
|
|
38
|
-
sideby login claude:work
|
|
39
|
-
sideby run work
|
|
40
|
-
sideby
|
|
25
|
+
sideby new claude work # create ~/.claude-work, sharing ~/.claude's skills and hooks
|
|
26
|
+
sideby login claude:work # sign in once
|
|
27
|
+
sideby run work # Claude Code on the work account
|
|
28
|
+
sideby quota setup claude --yes # Claude quota in the panel; undo: sideby quota teardown claude
|
|
29
|
+
sideby ui # every account's quota at http://127.0.0.1:17420
|
|
41
30
|
```
|
|
42
31
|
|
|
43
|
-
`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`.
|
|
32
|
+
`sideby update` installs a newer sideby when there is one; sideby says so after a command and in the panel. `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`.
|
|
44
33
|
|
|
45
34
|
## Set up with your AI agent
|
|
46
35
|
|
|
@@ -55,6 +44,10 @@ The agent checks Node and your Host CLIs, installs sideby and the skill, creates
|
|
|
55
44
|
|
|
56
45
|
## What it looks like
|
|
57
46
|
|
|
47
|
+
The 25-second story: one account buried in tasks, then accounts side by side sharing one setup, and a task moving on when one runs out.
|
|
48
|
+
|
|
49
|
+
https://github.com/user-attachments/assets/3a470c03-9009-4f1d-b7f6-8b4625d74958
|
|
50
|
+
|
|
58
51
|
<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">
|
|
59
52
|
|
|
60
53
|
<picture>
|
|
@@ -63,6 +56,18 @@ The agent checks Node and your Host CLIs, installs sideby and the skill, creates
|
|
|
63
56
|
<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">
|
|
64
57
|
</picture>
|
|
65
58
|
|
|
59
|
+
## Why not just an alias?
|
|
60
|
+
|
|
61
|
+
`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)
|
|
62
|
+
|
|
63
|
+
| | At the same time | Skills, hooks, settings | Quota and usage | Setup |
|
|
64
|
+
|---|---|---|---|---|
|
|
65
|
+
| `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 |
|
|
66
|
+
| [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 |
|
|
67
|
+
| 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 |
|
|
68
|
+
|
|
69
|
+
cc-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.
|
|
70
|
+
|
|
66
71
|
## What else it does
|
|
67
72
|
|
|
68
73
|
| You want to | Run | Guide |
|
|
@@ -78,6 +83,7 @@ The agent checks Node and your Host CLIs, installs sideby and the skill, creates
|
|
|
78
83
|
| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |
|
|
79
84
|
| 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) |
|
|
80
85
|
| 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) |
|
|
86
|
+
| Keep several Grok Bot windows open, each signed in to its own account (macOS) | `sideby desktop grok-bot:002` (or the panel) | [Grok Bot windows](docs/guide/usage.md#grok-bot-windows) |
|
|
81
87
|
| Open the panel from the Dock or app menu | `sideby app install` | [Panel app](docs/guide/panel.md#panel-app) |
|
|
82
88
|
| Show the panel inside your own local page | `createPanelHandler()` | [Embed the panel](docs/guide/panel.md#embed-the-panel) |
|
|
83
89
|
| Add a Host or a pre-launch check | a local plugin | [Plugins](docs/guide/plugins.md) |
|
|
@@ -104,7 +110,7 @@ macOS and Linux. CI runs the suite on `ubuntu-latest` and `macos-latest`, Node 2
|
|
|
104
110
|
## Safe by design
|
|
105
111
|
|
|
106
112
|
- 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.
|
|
107
|
-
- It never reads, prints or sends a credential value. Nothing leaves your machine: sideby
|
|
113
|
+
- It never reads, prints or sends a credential value. Nothing about your accounts leaves your machine: the only request sideby makes on its own is a daily check of the npm registry for its newest version (off with `NO_UPDATE_NOTIFIER=1`; see [Updates](docs/guide/configuration.md#updates)).
|
|
108
114
|
- `doctor --fix` never replaces a real file and never changes where a link points.
|
|
109
115
|
|
|
110
116
|
Details and the terms-of-service question are in the [FAQ](docs/guide/faq.md).
|
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261016T071613-e5f0f1"}
|
|
@@ -61,8 +61,18 @@ export declare function cmdHandoff(a: ParsedArgs, io: Io): Promise<number>;
|
|
|
61
61
|
export declare function cmdPlugins(a: ParsedArgs, io: Io): Promise<number>;
|
|
62
62
|
export declare function cmdShellInit(a: ParsedArgs, io: Io): Promise<number>;
|
|
63
63
|
export declare function cmdUi(a: ParsedArgs, io: Io): Promise<number>;
|
|
64
|
+
/**
|
|
65
|
+
* `sideby update [--yes] [--json]` (spec §3.23): asks the registry now, shows what an Update runs, and runs it with
|
|
66
|
+
* `--yes` (exit 10 before). An install sideby cannot update gets the command to run and exit 1.
|
|
67
|
+
*/
|
|
68
|
+
export declare function cmdUpdate(a: ParsedArgs, io: Io): Promise<number>;
|
|
69
|
+
/** The internal `sideby update-check` the CLI starts detached: one Update Check, then Auto Update if it is on. */
|
|
70
|
+
export declare function cmdUpdateCheck(_a: ParsedArgs, _io: Io): Promise<number>;
|
|
64
71
|
export declare function cmdApp(a: ParsedArgs, io: Io): Promise<number>;
|
|
65
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* `sideby desktop <account>`: the Family's Desktop App as that Account (spec §3.20); `sideby desktop grok-bot:<name>`:
|
|
74
|
+
* a Grok Bot window, new or open, and with `--remove` its removal (ADR-0019).
|
|
75
|
+
*/
|
|
66
76
|
export declare function cmdDesktop(a: ParsedArgs, io: Io): Promise<number>;
|
|
67
77
|
export declare function cmdAlias(a: ParsedArgs, io: Io): Promise<number>;
|
|
68
78
|
/** `sideby families [family]`: each Family's layout, sign-in and Shared Items, from its FamilyDef (spec §3.16). */
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
3
|
+
import { listAppProfiles, openAppProfile, parseProfileRef, removeAppProfile } from '../core/app-profiles.js';
|
|
3
4
|
import { aliasRefs } from '../core/config.js';
|
|
4
5
|
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
5
6
|
import { UserError } from '../core/errors.js';
|
|
@@ -16,8 +17,22 @@ import { suggestAlias, suggestName } from '../panel/page-logic.js';
|
|
|
16
17
|
import { createRuntime, UnknownFamilyError } from '../runtime.js';
|
|
17
18
|
import { UsageError } from './args.js';
|
|
18
19
|
import { c, clock, compactNumber, quotaText, table, tuningLines, usageText } from './format.js';
|
|
20
|
+
/** Runs `fn` while a record under the state directory says a Host runs (also for `login`), so an Auto Update waits (spec §3.23). */
|
|
21
|
+
async function whileRunning(rt, fn) {
|
|
22
|
+
const { markRunning } = await import('../core/update.js');
|
|
23
|
+
const done = await markRunning(rt.paths);
|
|
24
|
+
try {
|
|
25
|
+
return await fn();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
await done().catch(() => { });
|
|
29
|
+
}
|
|
30
|
+
}
|
|
19
31
|
/** Runs the Account with Auto Handoff when it applies (spec §3.17), else the Host as usual. */
|
|
20
32
|
async function runAccount(rt, ref, args, io) {
|
|
33
|
+
return whileRunning(rt, () => runAccountNow(rt, ref, args, io));
|
|
34
|
+
}
|
|
35
|
+
async function runAccountNow(rt, ref, args, io) {
|
|
21
36
|
const handed = await runWithHandoff(rt, ref, args, {
|
|
22
37
|
err: (l) => io.err(c.dim(l)),
|
|
23
38
|
stdin: io.stdin ?? process.stdin,
|
|
@@ -55,11 +70,14 @@ function tilde(text, home) {
|
|
|
55
70
|
export async function cmdList(a, io) {
|
|
56
71
|
const rt = await createRuntime();
|
|
57
72
|
const accounts = await rt.accounts();
|
|
58
|
-
const statuses = await
|
|
73
|
+
const [statuses, appProfiles] = await Promise.all([
|
|
74
|
+
withDesktopApps(rt, accounts, Promise.all(accounts.map((x) => rt.status(x)))),
|
|
75
|
+
listAppProfiles(rt),
|
|
76
|
+
]);
|
|
59
77
|
const hasDesktop = statuses.some((s) => s.desktopApp);
|
|
60
78
|
const families = await rt.familyInfo();
|
|
61
79
|
if (a.flags.has('json')) {
|
|
62
|
-
json(io, { families, accounts: statuses, pluginErrors: [...rt.pluginErrors] });
|
|
80
|
+
json(io, { families, accounts: statuses, appProfiles, pluginErrors: [...rt.pluginErrors] });
|
|
63
81
|
return 0;
|
|
64
82
|
}
|
|
65
83
|
const home = rt.paths.home;
|
|
@@ -94,6 +112,25 @@ export async function cmdList(a, io) {
|
|
|
94
112
|
io.out(c.dim('\n= <account>: signed in as the same login as that account, so they share one quota.'));
|
|
95
113
|
io.out(c.dim('\nsideby run <account> · sideby new <family> <name> · sideby quota · sideby ui'));
|
|
96
114
|
}
|
|
115
|
+
// App Profiles (ADR-0019): windows of an app that signs in apart from the Accounts above.
|
|
116
|
+
for (const app of new Set(appProfiles.map((p) => p.app))) {
|
|
117
|
+
const list = appProfiles.filter((p) => p.app === app);
|
|
118
|
+
io.out(`\n${c.bold(app)} ${c.dim('(signs in on its own, not with the accounts above)')}`);
|
|
119
|
+
io.out(table(list.map((p) => [
|
|
120
|
+
c.bold(p.ref),
|
|
121
|
+
p.running ? c.green('open') : c.dim('—'),
|
|
122
|
+
p.signedIn === true
|
|
123
|
+
? c.green('signed in')
|
|
124
|
+
: p.signedIn === false
|
|
125
|
+
? c.yellow('not signed in')
|
|
126
|
+
: c.dim('?'),
|
|
127
|
+
p.appData ? tildify(p.appData, home) : c.dim('default'),
|
|
128
|
+
]), ['WINDOW', 'STATE', 'SIGN-IN', 'DATA']));
|
|
129
|
+
if (!list[0]?.installed)
|
|
130
|
+
io.out(c.yellow(`${app} is not installed; these windows open again once it is`));
|
|
131
|
+
const id = list[0]?.ref.slice(0, list[0].ref.indexOf(':'));
|
|
132
|
+
io.out(c.dim(`sideby desktop ${id}:<name> opens one (a new name makes a new window)`));
|
|
133
|
+
}
|
|
97
134
|
for (const s of statuses)
|
|
98
135
|
for (const p of s.problems ?? [])
|
|
99
136
|
io.err(c.yellow(`${s.ref}: ${p}`));
|
|
@@ -185,7 +222,7 @@ export async function cmdRun(a, io, command) {
|
|
|
185
222
|
const prepared = await rt.prepareLaunch(ref, [...extra, ...a.rest], command);
|
|
186
223
|
if (prepared.notice)
|
|
187
224
|
io.err(c.cyan(prepared.notice));
|
|
188
|
-
return runHost(prepared);
|
|
225
|
+
return whileRunning(rt, () => runHost(prepared));
|
|
189
226
|
}
|
|
190
227
|
/**
|
|
191
228
|
* `sideby resume <family>`: what the Host-named shell function calls (spec §3.15). Starts the Account that holds the
|
|
@@ -201,7 +238,7 @@ export async function cmdResume(a, io) {
|
|
|
201
238
|
io.err(c.dim(`sideby: session ${route.sessionId} was never saved; starting a new session in ${route.account.ref}`));
|
|
202
239
|
else if (route.account)
|
|
203
240
|
io.err(c.dim(`sideby: session ${route.sessionId} is in ${route.account.ref}; starting it there`));
|
|
204
|
-
return runHost(route.launch);
|
|
241
|
+
return whileRunning(rt, () => runHost(route.launch));
|
|
205
242
|
}
|
|
206
243
|
/** Quotes a Host argument for a command line the user may paste; plain words stay as they are. */
|
|
207
244
|
function shellQuote(arg) {
|
|
@@ -860,7 +897,10 @@ export async function cmdHandoff(a, io) {
|
|
|
860
897
|
requested: true,
|
|
861
898
|
...(next ? { next } : {}),
|
|
862
899
|
...(request.brief ? { brief: request.brief } : {}),
|
|
900
|
+
...(request.now ? { now: true } : {}),
|
|
863
901
|
});
|
|
902
|
+
else if (request.now)
|
|
903
|
+
io.out(`This session was waiting for its quota; ${next ?? 'the next account'} takes over now.`);
|
|
864
904
|
else
|
|
865
905
|
io.out(`${next ? `${next} takes over` : 'The next account starts'} when this turn ends; finish the turn now.`);
|
|
866
906
|
return 0;
|
|
@@ -977,6 +1017,84 @@ export async function cmdUi(a, io) {
|
|
|
977
1017
|
});
|
|
978
1018
|
return 0;
|
|
979
1019
|
}
|
|
1020
|
+
/**
|
|
1021
|
+
* `sideby update [--yes] [--json]` (spec §3.23): asks the registry now, shows what an Update runs, and runs it with
|
|
1022
|
+
* `--yes` (exit 10 before). An install sideby cannot update gets the command to run and exit 1.
|
|
1023
|
+
*/
|
|
1024
|
+
export async function cmdUpdate(a, io) {
|
|
1025
|
+
if (a.positionals.length)
|
|
1026
|
+
throw new UsageError('usage: sideby update [--yes] [--json]');
|
|
1027
|
+
const u = await import('../core/update.js');
|
|
1028
|
+
const { SKILL_INSTALL } = await import('../core/skill-check.js');
|
|
1029
|
+
const { backgroundPanelPort } = await import('../panel/background.js');
|
|
1030
|
+
const paths = resolvePaths(process.env);
|
|
1031
|
+
const asJson = a.flags.has('json');
|
|
1032
|
+
const plan = await u.planUpdate(paths, process.env);
|
|
1033
|
+
const shown = u.shownPlan(plan);
|
|
1034
|
+
if (!plan.available) {
|
|
1035
|
+
if (asJson)
|
|
1036
|
+
json(io, { ...shown, updated: false });
|
|
1037
|
+
else
|
|
1038
|
+
io.out(`${c.green('✓')} sideby ${plan.current} is the latest version.`);
|
|
1039
|
+
return 0;
|
|
1040
|
+
}
|
|
1041
|
+
if (!plan.canRun) {
|
|
1042
|
+
if (asJson)
|
|
1043
|
+
json(io, { ...shown, updated: false });
|
|
1044
|
+
else {
|
|
1045
|
+
io.out(`sideby ${c.bold(plan.latest)} is available (you have ${plan.current}). What changed: ${plan.releaseNotes}`);
|
|
1046
|
+
io.err(c.yellow(`sideby cannot update itself: ${plan.method.reason}. Run it yourself:\n ${plan.command}`));
|
|
1047
|
+
}
|
|
1048
|
+
return 1;
|
|
1049
|
+
}
|
|
1050
|
+
if (!a.flags.has('yes')) {
|
|
1051
|
+
if (asJson)
|
|
1052
|
+
json(io, { ...shown, updated: false });
|
|
1053
|
+
else {
|
|
1054
|
+
io.out(`sideby ${c.bold(plan.latest)} is available (you have ${plan.current}). What changed: ${plan.releaseNotes}`);
|
|
1055
|
+
io.out(`\nUpdating runs:\n ${plan.command}`);
|
|
1056
|
+
io.out(c.cyan('\nRun it with `sideby update --yes`.'));
|
|
1057
|
+
}
|
|
1058
|
+
return 10;
|
|
1059
|
+
}
|
|
1060
|
+
if (!asJson)
|
|
1061
|
+
io.out(c.dim(`$ ${plan.command}`));
|
|
1062
|
+
const r = await u.applyUpdate(paths, plan, process.env, {
|
|
1063
|
+
...(asJson ? {} : { onOutput: (text) => process.stderr.write(text) }),
|
|
1064
|
+
panelPort: () => backgroundPanelPort(paths),
|
|
1065
|
+
});
|
|
1066
|
+
if (asJson) {
|
|
1067
|
+
json(io, {
|
|
1068
|
+
...shown,
|
|
1069
|
+
updated: r.ok,
|
|
1070
|
+
message: r.message,
|
|
1071
|
+
restartedPanel: r.restartedPanel,
|
|
1072
|
+
skill: SKILL_INSTALL,
|
|
1073
|
+
...(r.ok ? {} : { output: r.output }),
|
|
1074
|
+
});
|
|
1075
|
+
return r.ok ? 0 : 1;
|
|
1076
|
+
}
|
|
1077
|
+
if (!r.ok) {
|
|
1078
|
+
io.err(c.red(`✗ ${r.message}`));
|
|
1079
|
+
return 1;
|
|
1080
|
+
}
|
|
1081
|
+
io.out(`${c.green('✓')} ${r.message}.`);
|
|
1082
|
+
if (r.restartedPanel)
|
|
1083
|
+
io.out(' The background panel is restarting with the new version; open pages reload by themselves.');
|
|
1084
|
+
io.out(' A `sideby ui` running in a terminal and sessions already running keep the old version until you restart them.');
|
|
1085
|
+
io.out(` If you use the sideby skill, update it too: ${SKILL_INSTALL}`);
|
|
1086
|
+
return 0;
|
|
1087
|
+
}
|
|
1088
|
+
/** The internal `sideby update-check` the CLI starts detached: one Update Check, then Auto Update if it is on. */
|
|
1089
|
+
export async function cmdUpdateCheck(_a, _io) {
|
|
1090
|
+
const { backgroundCheck } = await import('../core/update.js');
|
|
1091
|
+
const { loadConfig } = await import('../core/config.js');
|
|
1092
|
+
const { backgroundPanelPort } = await import('../panel/background.js');
|
|
1093
|
+
const paths = resolvePaths(process.env);
|
|
1094
|
+
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
1095
|
+
await backgroundCheck(paths, process.env, config, () => backgroundPanelPort(paths));
|
|
1096
|
+
return 0;
|
|
1097
|
+
}
|
|
980
1098
|
export async function cmdApp(a, io) {
|
|
981
1099
|
const sub = a.positionals[0];
|
|
982
1100
|
const usage = 'usage: sideby app install [--url <url>] | sideby app uninstall';
|
|
@@ -1019,13 +1137,32 @@ export async function cmdApp(a, io) {
|
|
|
1019
1137
|
}
|
|
1020
1138
|
return r.skipped.length ? 1 : 0;
|
|
1021
1139
|
}
|
|
1022
|
-
/**
|
|
1140
|
+
/**
|
|
1141
|
+
* `sideby desktop <account>`: the Family's Desktop App as that Account (spec §3.20); `sideby desktop grok-bot:<name>`:
|
|
1142
|
+
* a Grok Bot window, new or open, and with `--remove` its removal (ADR-0019).
|
|
1143
|
+
*/
|
|
1023
1144
|
export async function cmdDesktop(a, io) {
|
|
1024
1145
|
const [ref, ...extra] = a.positionals;
|
|
1025
1146
|
if (!ref || extra.length || a.rest.length)
|
|
1026
|
-
throw new UsageError('usage: sideby desktop <account>');
|
|
1147
|
+
throw new UsageError('usage: sideby desktop <account> | sideby desktop grok-bot:<name> [--remove [--yes]]');
|
|
1027
1148
|
const rt = await createRuntime();
|
|
1028
|
-
const
|
|
1149
|
+
const profile = parseProfileRef(ref) !== undefined;
|
|
1150
|
+
if (a.flags.has('remove')) {
|
|
1151
|
+
if (!profile)
|
|
1152
|
+
throw new UsageError('--remove works on Grok Bot windows only: sideby desktop grok-bot:<name> --remove');
|
|
1153
|
+
const r = await removeAppProfile(rt, ref, { yes: a.flags.has('yes') });
|
|
1154
|
+
if (a.flags.has('json'))
|
|
1155
|
+
json(io, r);
|
|
1156
|
+
else if (r.removed)
|
|
1157
|
+
io.out(`${c.green('✓')} removed the ${r.app} window ${c.bold(r.ref)} and its data`);
|
|
1158
|
+
else {
|
|
1159
|
+
io.out(`Remove the ${r.app} window ${c.bold(r.ref)}?`);
|
|
1160
|
+
io.out(` deletes ${tildify(r.appData, rt.paths.home)}: its sign-in, bots and local data on this Mac`);
|
|
1161
|
+
io.out('Run again with --yes to remove it.');
|
|
1162
|
+
}
|
|
1163
|
+
return r.removed ? 0 : 10;
|
|
1164
|
+
}
|
|
1165
|
+
const r = profile ? await openAppProfile(rt, ref) : await openDesktopApp(rt, ref);
|
|
1029
1166
|
if (a.flags.has('json')) {
|
|
1030
1167
|
json(io, r);
|
|
1031
1168
|
return 0;
|
|
@@ -194,11 +194,30 @@ const ShellInitFiles = Type.Array(Type.Object({
|
|
|
194
194
|
action: Type.Optional(Type.Union([Type.Literal('written'), Type.Literal('unchanged')])),
|
|
195
195
|
message: Type.Optional(Str),
|
|
196
196
|
}), { description: 'One entry per config `shellInitFile`, rewritten after the change.' });
|
|
197
|
+
const AppProfile = Type.Object({
|
|
198
|
+
ref: Type.String({ description: "`grok-bot:<name>`; `grok-bot:main` is the app's own default data." }),
|
|
199
|
+
app: Type.String({ description: 'The app, for example `Grok Bot`.' }),
|
|
200
|
+
name: Str,
|
|
201
|
+
isMain: Type.Boolean(),
|
|
202
|
+
appData: Type.Optional(Type.String({
|
|
203
|
+
description: "This window's data folder; absent for the main one, which uses the app's default.",
|
|
204
|
+
})),
|
|
205
|
+
installed: Type.Boolean(),
|
|
206
|
+
running: Type.Boolean({ description: "This window's own instance runs now." }),
|
|
207
|
+
signedIn: Type.Optional(Type.Boolean({
|
|
208
|
+
description: 'What the app last wrote about its sign-in (it keeps the last value after the app quits); absent when unknown. Which account is signed in is not known.',
|
|
209
|
+
})),
|
|
210
|
+
}, {
|
|
211
|
+
description: 'A window of a desktop app that signs in apart from every account (Grok Bot, ADR-0019). Open or create one with `sideby desktop grok-bot:<name>`.',
|
|
212
|
+
});
|
|
197
213
|
export const OUTPUT_SCHEMAS = {
|
|
198
214
|
list: Type.Object({
|
|
199
215
|
schemaVersion: Version,
|
|
200
216
|
families: Type.Array(FamilyInfo),
|
|
201
217
|
accounts: Type.Array(AccountStatus),
|
|
218
|
+
appProfiles: Type.Optional(Type.Array(AppProfile, {
|
|
219
|
+
description: 'Grok Bot windows (macOS, when Grok Bot is installed or has windows), the main one first. Not accounts.',
|
|
220
|
+
})),
|
|
202
221
|
pluginErrors: Type.Array(PluginError),
|
|
203
222
|
}, { title: 'sideby list --json' }),
|
|
204
223
|
doctor: Type.Object({
|
|
@@ -380,6 +399,8 @@ export const OUTPUT_SCHEMAS = {
|
|
|
380
399
|
requested: Type.Literal(true),
|
|
381
400
|
next: Type.Optional(Str),
|
|
382
401
|
brief: Type.Optional(Str),
|
|
402
|
+
/** The session was waiting for its quota, so the handoff starts now instead of after a turn (ADR-0018). */
|
|
403
|
+
now: Type.Optional(Type.Literal(true)),
|
|
383
404
|
}, { title: 'sideby handoff ready --json' }),
|
|
384
405
|
'handoff-setup': Type.Object({ schemaVersion: Version, family: Str, applied: Type.Boolean(), plan: QuotaSetupPlan }, { title: 'sideby handoff setup --json' }),
|
|
385
406
|
'handoff-teardown': Type.Object({ schemaVersion: Version, family: Str, ok: Type.Boolean(), message: Str, diff: Type.Optional(Str) }, { title: 'sideby handoff teardown --json' }),
|
|
@@ -445,7 +466,7 @@ export const OUTPUT_SCHEMAS = {
|
|
|
445
466
|
desktop: Type.Object({
|
|
446
467
|
schemaVersion: Version,
|
|
447
468
|
ref: Str,
|
|
448
|
-
family:
|
|
469
|
+
family: Type.String({ description: 'The family, or `grok-bot` for a Grok Bot window.' }),
|
|
449
470
|
app: Type.String({ description: 'The desktop app, for example `Claude Desktop`.' }),
|
|
450
471
|
appPath: Str,
|
|
451
472
|
action: Type.Union([Type.Literal('started'), Type.Literal('focused'), Type.Literal('already-running')], {
|
|
@@ -461,6 +482,40 @@ export const OUTPUT_SCHEMAS = {
|
|
|
461
482
|
command: Type.Optional(Type.Array(Str, { description: 'The `open` command sideby ran.' })),
|
|
462
483
|
notices: Type.Array(Str),
|
|
463
484
|
}, { title: 'sideby desktop --json' }),
|
|
485
|
+
'desktop-remove': Type.Object({
|
|
486
|
+
schemaVersion: Version,
|
|
487
|
+
ref: Str,
|
|
488
|
+
app: Str,
|
|
489
|
+
appData: Type.String({ description: 'The folder that is (or would be) deleted.' }),
|
|
490
|
+
removed: Type.Boolean({
|
|
491
|
+
description: 'False when only the plan was shown; run again with --yes (exit 10).',
|
|
492
|
+
}),
|
|
493
|
+
}, { title: 'sideby desktop grok-bot:<name> --remove --json' }),
|
|
494
|
+
update: Type.Object({
|
|
495
|
+
schemaVersion: Version,
|
|
496
|
+
current: Type.String({ description: 'The running sideby version.' }),
|
|
497
|
+
latest: Type.String({ description: 'The `latest` version on the npm registry.' }),
|
|
498
|
+
available: Type.Boolean({ description: '`latest` is newer than `current`.' }),
|
|
499
|
+
method: Type.Object({
|
|
500
|
+
kind: Type.Union(['npm', 'pnpm', 'bun', 'volta', 'npx', 'project', 'source'].map((k) => Type.Literal(k)), { description: 'How this copy was installed.' }),
|
|
501
|
+
canUpdate: Type.Boolean({ description: 'sideby can run the Update itself.' }),
|
|
502
|
+
reason: Type.Optional(Type.String({ description: 'Why it cannot.' })),
|
|
503
|
+
}),
|
|
504
|
+
command: Type.String({
|
|
505
|
+
description: 'What an Update runs, or what to run by hand when `canRun` is false.',
|
|
506
|
+
}),
|
|
507
|
+
canRun: Type.Boolean(),
|
|
508
|
+
releaseNotes: Type.String({ description: 'The release notes of `latest`.' }),
|
|
509
|
+
updated: Type.Boolean({
|
|
510
|
+
description: 'False when only the plan was shown (exit 10 with --yes still to add) or nothing to do.',
|
|
511
|
+
}),
|
|
512
|
+
message: Type.Optional(Type.String()),
|
|
513
|
+
restartedPanel: Type.Optional(Type.Boolean({
|
|
514
|
+
description: 'A background Panel was running and starts again with the new version.',
|
|
515
|
+
})),
|
|
516
|
+
skill: Type.Optional(Type.String({ description: 'The command that updates the sideby skill; sideby never runs it.' })),
|
|
517
|
+
output: Type.Optional(Type.String({ description: 'What the failed install command printed.' })),
|
|
518
|
+
}, { title: 'sideby update --json' }),
|
|
464
519
|
error: Type.Object({
|
|
465
520
|
schemaVersion: Version,
|
|
466
521
|
error: Type.String({ description: 'What went wrong and what to do next.' }),
|
package/dist/src/cli/main.js
CHANGED
|
@@ -51,16 +51,18 @@ Usage:
|
|
|
51
51
|
sideby ui --stop stop the background panel
|
|
52
52
|
sideby desktop <account> open the desktop app (Claude Desktop, the Codex app) as this
|
|
53
53
|
account, next to the others (macOS)
|
|
54
|
+
sideby desktop grok-bot:<name> open a Grok Bot window, a new one for a new name (macOS)
|
|
54
55
|
sideby app install [--url <url>] add a sideby app that opens the panel (macOS, Linux)
|
|
55
56
|
sideby app uninstall remove that app
|
|
56
57
|
sideby shell-init zsh|bash print shell functions, e.g. eval "$(sideby shell-init zsh)"
|
|
57
58
|
sideby shell-init [zsh|bash] --write
|
|
58
59
|
rewrite the config's shellInitFile with them
|
|
59
60
|
sideby plugins list loaded plugins and load errors
|
|
61
|
+
sideby update [--yes] update sideby to the newest version (shows the command first)
|
|
60
62
|
|
|
61
63
|
Options:
|
|
62
64
|
--json machine-readable output (list, new, next, alias, doctor, quota, tune, model, remote-control, handoff,
|
|
63
|
-
families, plugins, app, desktop)
|
|
65
|
+
families, plugins, app, desktop, update)
|
|
64
66
|
-h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop
|
|
65
67
|
-v, --version show the version
|
|
66
68
|
|
|
@@ -72,7 +74,10 @@ For AI agents:
|
|
|
72
74
|
Setup steps and the full contract: https://github.com/Adonis0123/sideby/blob/main/llms.txt
|
|
73
75
|
Run everything else yourself, but hand the user these as one line each: \`sideby login <account>\`
|
|
74
76
|
(browser sign-in), filling in proxy.env, and \`sideby run\` or \`sideby next\` (interactive sessions).
|
|
75
|
-
Exit 10 means a change was shown: ask the user, then run it again with --yes
|
|
77
|
+
Exit 10 means a change was shown: ask the user, then run it again with --yes.
|
|
78
|
+
|
|
79
|
+
Updates: once a day sideby asks the npm registry for its newest version and says so after a command.
|
|
80
|
+
Turn it off with NO_UPDATE_NOTIFIER=1 or config "update": { "check": false }; "auto": true installs by itself.`;
|
|
76
81
|
/** `sideby <command> --help` for the commands agents use to set up and repair accounts (spec §3.16). */
|
|
77
82
|
const COMMAND_HELP = {
|
|
78
83
|
new: `sideby new <family> <name> [--api] [--alias <short>] [--json]
|
|
@@ -133,6 +138,7 @@ Examples:
|
|
|
133
138
|
sideby doctor claude:008 --fix
|
|
134
139
|
Exit: 0 when nothing fails (warnings allowed); 1 when a check fails.`,
|
|
135
140
|
desktop: `sideby desktop <account> [--json]
|
|
141
|
+
sideby desktop grok-bot:<name> [--remove [--yes]] [--json]
|
|
136
142
|
|
|
137
143
|
Open the host's desktop app as one account: Claude Desktop for claude accounts, the Codex app (ChatGPT.app) for codex
|
|
138
144
|
accounts. Each account gets its own window, side by side with the others; macOS only for now.
|
|
@@ -142,14 +148,22 @@ directory (CLAUDE_CONFIG_DIR, CODEX_HOME) and its own app data folder,
|
|
|
142
148
|
~/Library/Application Support/sideby/<family>/<name>/. When that account's window is already open, sideby brings it
|
|
143
149
|
to the front instead of starting another one. API accounts are refused: the apps sign in with a subscription only.
|
|
144
150
|
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
Grok Bot signs in with a Cursor account, apart from every account above, so its windows have names of their own:
|
|
152
|
+
grok-bot:<name> opens that window, and makes it first when the name is new (a new window, signed out). grok-bot or
|
|
153
|
+
grok-bot:main is Grok Bot's own default window. --remove deletes a window you made, which signs it out and deletes
|
|
154
|
+
its data on this Mac; it asks for --yes first (exit 10) and refuses while that window is open. \`sideby list\` shows
|
|
155
|
+
the Grok Bot windows.
|
|
156
|
+
|
|
157
|
+
sideby never copies or changes the app, and never closes a window. The first time, sign in inside the new window;
|
|
158
|
+
close the app's other windows while you do, because the sign-in may come back to one of them.
|
|
147
159
|
|
|
148
160
|
Examples:
|
|
149
161
|
sideby desktop claude:work
|
|
150
162
|
sideby desktop cc008 --json
|
|
151
|
-
|
|
152
|
-
|
|
163
|
+
sideby desktop grok-bot:002
|
|
164
|
+
sideby desktop grok-bot:002 --remove --yes
|
|
165
|
+
Exit: 0 opened, brought to the front or removed; 1 not macOS, no desktop app for that host, API account, app not
|
|
166
|
+
installed, a bad window name, or the app did not open; 10 --remove without --yes; 2 malformed command line.`,
|
|
153
167
|
families: `sideby families [family] [--json]
|
|
154
168
|
|
|
155
169
|
For each host (built in or from a plugin): where its accounts live, the variable that selects one, how to sign
|
|
@@ -189,11 +203,58 @@ const COMMANDS = {
|
|
|
189
203
|
},
|
|
190
204
|
ui: { flags: ['no-open', 'background', 'stop', 'serve-detached'], valued: ['port'] },
|
|
191
205
|
app: { flags: ['json'], valued: ['url'] },
|
|
192
|
-
desktop: { flags: ['json'] },
|
|
206
|
+
desktop: { flags: ['json', 'remove', 'yes'] },
|
|
193
207
|
'shell-init': { flags: ['write'] },
|
|
194
208
|
plugins: { flags: ['json'] },
|
|
195
209
|
families: { flags: ['json'] },
|
|
210
|
+
update: { flags: ['json', 'yes'] },
|
|
211
|
+
'update-check': { flags: [] },
|
|
196
212
|
};
|
|
213
|
+
/** Commands that never print an Update Notice: their output is read by a shell or an agent, or they are internal. */
|
|
214
|
+
function noticeAllowed(cmd, parsed) {
|
|
215
|
+
if (cmd === 'shell-init' || cmd === 'update' || cmd === 'update-check')
|
|
216
|
+
return false;
|
|
217
|
+
if (cmd === 'ui' && parsed.flags.has('serve-detached'))
|
|
218
|
+
return false;
|
|
219
|
+
if (cmd === 'handoff' && parsed.positionals[0] === 'ready')
|
|
220
|
+
return false;
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* After a command (spec §3.23): print the Update Notice and the result of an Auto Update, each at most once, and
|
|
225
|
+
* start the next Update Check detached when the last one is a day old. Only on a terminal; never fails the command.
|
|
226
|
+
*/
|
|
227
|
+
async function afterCommand() {
|
|
228
|
+
if (!process.stderr.isTTY)
|
|
229
|
+
return;
|
|
230
|
+
try {
|
|
231
|
+
const u = await import('../core/update.js');
|
|
232
|
+
const { resolvePaths } = await import('../core/paths.js');
|
|
233
|
+
const { loadConfig } = await import('../core/config.js');
|
|
234
|
+
const { packageVersion } = await import('../core/version.js');
|
|
235
|
+
const paths = resolvePaths(process.env);
|
|
236
|
+
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
237
|
+
const cache = await u.readUpdateCache(paths);
|
|
238
|
+
const plan = u.afterCommandPlan(cache, packageVersion(), config, process.env, new Date());
|
|
239
|
+
const dim = process.env.NO_COLOR ? (t) => t : (t) => `\x1b[2m${t}\x1b[22m`;
|
|
240
|
+
for (const l of plan.lines)
|
|
241
|
+
console.error(dim(l));
|
|
242
|
+
if (plan.record)
|
|
243
|
+
await u.updateUpdateCache(paths, plan.record);
|
|
244
|
+
if (plan.startCheck) {
|
|
245
|
+
const { spawn } = await import('node:child_process');
|
|
246
|
+
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update-check'], {
|
|
247
|
+
detached: true,
|
|
248
|
+
stdio: 'ignore',
|
|
249
|
+
});
|
|
250
|
+
child.on('error', () => { });
|
|
251
|
+
child.unref();
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
catch {
|
|
255
|
+
// an Update Notice is never worth failing a command
|
|
256
|
+
}
|
|
257
|
+
}
|
|
197
258
|
export async function main(argv) {
|
|
198
259
|
const [first, ...rest] = argv;
|
|
199
260
|
// Claude Code runs this on every status-line refresh. It must still launch the original command when Node is
|
|
@@ -264,47 +325,10 @@ export async function main(argv) {
|
|
|
264
325
|
return 0;
|
|
265
326
|
}
|
|
266
327
|
const cmds = await import('./commands.js');
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return await cmds.cmdRun(parsed, io, 'run');
|
|
272
|
-
case 'login':
|
|
273
|
-
return await cmds.cmdRun(parsed, io, 'login');
|
|
274
|
-
case 'resume':
|
|
275
|
-
return await cmds.cmdResume(parsed, io);
|
|
276
|
-
case 'next':
|
|
277
|
-
return await cmds.cmdNext(parsed, io);
|
|
278
|
-
case 'new':
|
|
279
|
-
return await cmds.cmdNew(parsed, io);
|
|
280
|
-
case 'alias':
|
|
281
|
-
return await cmds.cmdAlias(parsed, io);
|
|
282
|
-
case 'doctor':
|
|
283
|
-
return await cmds.cmdDoctor(parsed, io);
|
|
284
|
-
case 'quota':
|
|
285
|
-
return await cmds.cmdQuota(parsed, io);
|
|
286
|
-
case 'tune':
|
|
287
|
-
return await cmds.cmdTune(parsed, io);
|
|
288
|
-
case 'model':
|
|
289
|
-
return await cmds.cmdModel(parsed, io);
|
|
290
|
-
case 'remote-control':
|
|
291
|
-
return await cmds.cmdRemoteControl(parsed, io);
|
|
292
|
-
case 'handoff':
|
|
293
|
-
return await cmds.cmdHandoff(parsed, io);
|
|
294
|
-
case 'ui':
|
|
295
|
-
return await cmds.cmdUi(parsed, io);
|
|
296
|
-
case 'app':
|
|
297
|
-
return await cmds.cmdApp(parsed, io);
|
|
298
|
-
case 'desktop':
|
|
299
|
-
return await cmds.cmdDesktop(parsed, io);
|
|
300
|
-
case 'shell-init':
|
|
301
|
-
return await cmds.cmdShellInit(parsed, io);
|
|
302
|
-
case 'plugins':
|
|
303
|
-
return await cmds.cmdPlugins(parsed, io);
|
|
304
|
-
case 'families':
|
|
305
|
-
return await cmds.cmdFamilies(parsed, io);
|
|
306
|
-
}
|
|
307
|
-
return 2;
|
|
328
|
+
const code = await dispatch(cmd, parsed, io, cmds);
|
|
329
|
+
if (!wantsJson && noticeAllowed(cmd, parsed))
|
|
330
|
+
await afterCommand();
|
|
331
|
+
return code;
|
|
308
332
|
}
|
|
309
333
|
catch (err) {
|
|
310
334
|
const usage = err instanceof UsageError;
|
|
@@ -321,6 +345,53 @@ export async function main(argv) {
|
|
|
321
345
|
return usage ? 2 : 1;
|
|
322
346
|
}
|
|
323
347
|
}
|
|
348
|
+
async function dispatch(cmd, parsed, io, cmds) {
|
|
349
|
+
switch (cmd) {
|
|
350
|
+
case 'list':
|
|
351
|
+
return await cmds.cmdList(parsed, io);
|
|
352
|
+
case 'run':
|
|
353
|
+
return await cmds.cmdRun(parsed, io, 'run');
|
|
354
|
+
case 'login':
|
|
355
|
+
return await cmds.cmdRun(parsed, io, 'login');
|
|
356
|
+
case 'resume':
|
|
357
|
+
return await cmds.cmdResume(parsed, io);
|
|
358
|
+
case 'next':
|
|
359
|
+
return await cmds.cmdNext(parsed, io);
|
|
360
|
+
case 'new':
|
|
361
|
+
return await cmds.cmdNew(parsed, io);
|
|
362
|
+
case 'alias':
|
|
363
|
+
return await cmds.cmdAlias(parsed, io);
|
|
364
|
+
case 'doctor':
|
|
365
|
+
return await cmds.cmdDoctor(parsed, io);
|
|
366
|
+
case 'quota':
|
|
367
|
+
return await cmds.cmdQuota(parsed, io);
|
|
368
|
+
case 'tune':
|
|
369
|
+
return await cmds.cmdTune(parsed, io);
|
|
370
|
+
case 'model':
|
|
371
|
+
return await cmds.cmdModel(parsed, io);
|
|
372
|
+
case 'remote-control':
|
|
373
|
+
return await cmds.cmdRemoteControl(parsed, io);
|
|
374
|
+
case 'handoff':
|
|
375
|
+
return await cmds.cmdHandoff(parsed, io);
|
|
376
|
+
case 'ui':
|
|
377
|
+
return await cmds.cmdUi(parsed, io);
|
|
378
|
+
case 'app':
|
|
379
|
+
return await cmds.cmdApp(parsed, io);
|
|
380
|
+
case 'desktop':
|
|
381
|
+
return await cmds.cmdDesktop(parsed, io);
|
|
382
|
+
case 'shell-init':
|
|
383
|
+
return await cmds.cmdShellInit(parsed, io);
|
|
384
|
+
case 'plugins':
|
|
385
|
+
return await cmds.cmdPlugins(parsed, io);
|
|
386
|
+
case 'families':
|
|
387
|
+
return await cmds.cmdFamilies(parsed, io);
|
|
388
|
+
case 'update':
|
|
389
|
+
return await cmds.cmdUpdate(parsed, io);
|
|
390
|
+
case 'update-check':
|
|
391
|
+
return await cmds.cmdUpdateCheck(parsed, io);
|
|
392
|
+
}
|
|
393
|
+
return 2;
|
|
394
|
+
}
|
|
324
395
|
// Run when executed directly, also through symlinks (npm's bin shim, a user alias like `sb`).
|
|
325
396
|
function isEntry() {
|
|
326
397
|
const arg = process.argv[1];
|