sideby 0.12.1 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +5 -2
- package/dist/src/cli/commands.js +165 -20
- package/dist/src/cli/json-schemas.js +100 -2
- package/dist/src/cli/main.js +72 -4
- package/dist/src/core/brand-mark.d.ts +5 -7
- package/dist/src/core/brand-mark.js +18 -5
- package/dist/src/core/browser-profiles.d.ts +71 -0
- package/dist/src/core/browser-profiles.js +361 -0
- package/dist/src/core/config.d.ts +15 -0
- package/dist/src/core/config.js +13 -1
- package/dist/src/core/create.js +2 -1
- package/dist/src/core/desktop-app.d.ts +3 -1
- package/dist/src/core/desktop-app.js +29 -8
- package/dist/src/core/launch.d.ts +7 -0
- package/dist/src/core/launch.js +22 -2
- package/dist/src/core/leveldb-keys.d.ts +14 -0
- package/dist/src/core/leveldb-keys.js +291 -0
- package/dist/src/core/remote-control.d.ts +17 -6
- package/dist/src/core/remote-control.js +40 -17
- package/dist/src/core/skill-check.d.ts +13 -0
- package/dist/src/core/skill-check.js +29 -1
- package/dist/src/core/update.d.ts +55 -3
- package/dist/src/core/update.js +124 -4
- package/dist/src/core/version.d.ts +11 -0
- package/dist/src/core/version.js +26 -0
- package/dist/src/desktop/app.d.ts +2 -0
- package/dist/src/desktop/app.js +39 -3
- package/dist/src/families/browser.d.ts +20 -0
- package/dist/src/families/browser.js +23 -0
- package/dist/src/families/claude/browser.d.ts +3 -0
- package/dist/src/families/claude/browser.js +14 -0
- package/dist/src/families/claude/desktop.js +1 -0
- package/dist/src/families/codex/browser.d.ts +7 -0
- package/dist/src/families/codex/browser.js +26 -0
- package/dist/src/families/desktop.d.ts +5 -1
- package/dist/src/families/pi/index.js +2 -0
- package/dist/src/panel/handler.d.ts +9 -1
- package/dist/src/panel/handler.js +54 -6
- package/dist/src/panel/i18n.d.ts +74 -4
- package/dist/src/panel/i18n.js +160 -20
- package/dist/src/panel/page-logic.d.ts +23 -0
- package/dist/src/panel/page-logic.js +44 -0
- package/dist/src/panel/page-script.js +587 -183
- package/dist/src/panel/page-styles.js +61 -18
- package/dist/src/panel/page.d.ts +5 -0
- package/dist/src/panel/page.js +10 -9
- package/dist/src/runtime.d.ts +6 -1
- package/dist/src/runtime.js +22 -11
- package/dist/src/types.d.ts +2 -0
- package/package.json +5 -3
- package/schemas/alias.json +62 -0
- package/schemas/app-install.json +62 -0
- package/schemas/app-uninstall.json +62 -0
- package/schemas/browser-open.json +109 -0
- package/schemas/browser-switch.json +104 -0
- package/schemas/browser.json +161 -0
- package/schemas/config.json +11 -0
- package/schemas/desktop-remove.json +62 -0
- package/schemas/desktop.json +62 -0
- package/schemas/doctor.json +62 -0
- package/schemas/error.json +62 -0
- package/schemas/families.json +68 -0
- package/schemas/handoff-disable.json +62 -0
- package/schemas/handoff-enable.json +62 -0
- package/schemas/handoff-ready.json +62 -0
- package/schemas/handoff-setup.json +62 -0
- package/schemas/handoff-status.json +62 -0
- package/schemas/handoff-teardown.json +62 -0
- package/schemas/list.json +68 -0
- package/schemas/model.json +62 -0
- package/schemas/new.json +62 -0
- package/schemas/next.json +62 -0
- package/schemas/plugins.json +62 -0
- package/schemas/quota-setup.json +62 -0
- package/schemas/quota-teardown.json +62 -0
- package/schemas/quota.json +62 -0
- package/schemas/remote-control.json +66 -0
- package/schemas/tune-change.json +62 -0
- package/schemas/tune.json +62 -0
- package/schemas/update.json +111 -1
- package/skills/sideby/SKILL.md +8 -4
- package/skills/sideby/references/diagnose.md +1 -1
- package/skills/sideby/references/setup.md +1 -1
- package/skills/sideby/references/update.md +16 -0
package/README.md
CHANGED
|
@@ -77,7 +77,7 @@ cc-switch's docs describe that switch: it writes the live settings file ([README
|
|
|
77
77
|
| See Claude quota (Codex needs no setup) | `sideby quota setup claude --yes` | [Claude quota](docs/guide/usage.md#claude-quota) |
|
|
78
78
|
| 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) |
|
|
79
79
|
| 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) |
|
|
80
|
-
|
|
|
80
|
+
| Choose whether Claude sessions connect to claude.ai by themselves (Remote Control), for every account at once | `sideby remote-control off` or `on` (or the panel's Settings) | [Remote Control](docs/guide/usage.md#remote-control) |
|
|
81
81
|
| Move on when an account hits its limit | `sideby next claude` | [Handoff](docs/guide/usage.md#when-an-account-hits-its-limit) |
|
|
82
82
|
| 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) |
|
|
83
83
|
| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |
|
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261016T161929-77384f"}
|
|
@@ -8,6 +8,7 @@ export interface Io {
|
|
|
8
8
|
/** The terminal Auto Handoff counts down on (spec §3.17); `process.stdin` when left out. */
|
|
9
9
|
stdin?: HandoffIo['stdin'];
|
|
10
10
|
}
|
|
11
|
+
export declare function setJsonNotice(n: object | null): void;
|
|
11
12
|
export declare function serializeDoctor(r: DoctorReport): {
|
|
12
13
|
status: "issues" | "ok";
|
|
13
14
|
accounts: {
|
|
@@ -44,8 +45,8 @@ export declare function cmdResume(a: ParsedArgs, io: Io): Promise<number>;
|
|
|
44
45
|
export declare function cmdNext(a: ParsedArgs, io: Io): Promise<number>;
|
|
45
46
|
export declare function cmdDoctor(a: ParsedArgs, io: Io): Promise<number>;
|
|
46
47
|
/**
|
|
47
|
-
* `sideby remote-control [off|undo] [--yes] [--json]` (spec §3.22, ADR-0017): whether Claude sessions connect to
|
|
48
|
-
* claude.ai by themselves, and turning that off for every Account (a preview with exit 10 until --yes).
|
|
48
|
+
* `sideby remote-control [on|off|undo] [--yes] [--json]` (spec §3.22, ADR-0017): whether Claude sessions connect to
|
|
49
|
+
* claude.ai by themselves, and turning that on or off for every Account (a preview with exit 10 until --yes).
|
|
49
50
|
*/
|
|
50
51
|
export declare function cmdRemoteControl(a: ParsedArgs, io: Io): Promise<number>;
|
|
51
52
|
/**
|
|
@@ -74,6 +75,8 @@ export declare function cmdApp(a: ParsedArgs, io: Io): Promise<number>;
|
|
|
74
75
|
* a Grok Bot window, new or open, and with `--remove` its removal (ADR-0019).
|
|
75
76
|
*/
|
|
76
77
|
export declare function cmdDesktop(a: ParsedArgs, io: Io): Promise<number>;
|
|
78
|
+
/** `sideby browser [--enable|--disable] [--yes]` and `sideby browser open <account> [--profile <p>]` (spec §3.24). */
|
|
79
|
+
export declare function cmdBrowser(a: ParsedArgs, io: Io): Promise<number>;
|
|
77
80
|
export declare function cmdAlias(a: ParsedArgs, io: Io): Promise<number>;
|
|
78
81
|
/** `sideby families [family]`: each Family's layout, sign-in and Shared Items, from its FamilyDef (spec §3.16). */
|
|
79
82
|
export declare function cmdFamilies(a: ParsedArgs, io: Io): Promise<number>;
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
3
3
|
import { listAppProfiles, openAppProfile, parseProfileRef, removeAppProfile } from '../core/app-profiles.js';
|
|
4
|
+
import { browserReport, openInBrowser, setBrowserSignIns } from '../core/browser-profiles.js';
|
|
4
5
|
import { aliasRefs } from '../core/config.js';
|
|
5
6
|
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
6
7
|
import { UserError } from '../core/errors.js';
|
|
7
8
|
import { requestHandoff } from '../core/handoff-chain.js';
|
|
8
9
|
import { explain, readyDecision, runWithHandoff } from '../core/handoff-run.js';
|
|
9
10
|
import { configureHandoff, handoffHint, handoffStatus } from '../core/handoff-status.js';
|
|
10
|
-
import { runHost } from '../core/launch.js';
|
|
11
|
+
import { installHint, runHost } from '../core/launch.js';
|
|
11
12
|
import { accountModel, setAccountModel } from '../core/model-choice.js';
|
|
12
13
|
import { resolvePaths, tildify } from '../core/paths.js';
|
|
13
14
|
import { USAGE_DAYS } from '../core/quota-levels.js';
|
|
14
|
-
import { groupsToTurnOff, groupsToUndo, } from '../core/remote-control.js';
|
|
15
|
+
import { groupsToTurnOff, groupsToTurnOn, groupsToUndo, } from '../core/remote-control.js';
|
|
15
16
|
import { SHELLS } from '../core/shell-init.js';
|
|
16
17
|
import { suggestAlias, suggestName } from '../panel/page-logic.js';
|
|
17
18
|
import { createRuntime, UnknownFamilyError } from '../runtime.js';
|
|
@@ -50,7 +51,12 @@ async function runAccountNow(rt, ref, args, io) {
|
|
|
50
51
|
}
|
|
51
52
|
return code;
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
+
// The `_notice` main.ts works out before a `--json` command (spec §3.23); every JSON output of this run carries it.
|
|
55
|
+
let notice = null;
|
|
56
|
+
export function setJsonNotice(n) {
|
|
57
|
+
notice = n;
|
|
58
|
+
}
|
|
59
|
+
const json = (io, data) => io.out(JSON.stringify({ schemaVersion: 1, ...data, ...(notice ? { _notice: notice } : {}) }, null, 2));
|
|
54
60
|
function stripFix(f) {
|
|
55
61
|
const { fix: _fix, ...rest } = f;
|
|
56
62
|
return rest;
|
|
@@ -85,12 +91,13 @@ export async function cmdList(a, io) {
|
|
|
85
91
|
io.out('No accounts found yet.');
|
|
86
92
|
const installed = families.filter((f) => f.installed);
|
|
87
93
|
if (installed.length === 0)
|
|
88
|
-
io.out(`Install a host first: ${families.map((f) => `${f.title} (${f
|
|
94
|
+
io.out(`Install a host first: ${families.map((f) => `${f.title} (${installHint(f)})`).join(', ')}`);
|
|
89
95
|
else
|
|
90
96
|
io.out(`Run ${installed.map((f) => f.bin).join(' or ')} once, then \`sideby new <family> <name>\`.`);
|
|
91
97
|
}
|
|
92
98
|
else {
|
|
93
99
|
// `= main`: the other Accounts of its Same Login, which share its Quota (spec §3.21).
|
|
100
|
+
const brokenOf = (id) => families.find((f) => f.id === id)?.brokenBin;
|
|
94
101
|
const sameMark = (refs) => refs?.length ? ` ${c.dim(`= ${refs.map((r) => r.slice(r.indexOf(':') + 1)).join(', ')}`)}` : '';
|
|
95
102
|
const rows = statuses.map((s) => [
|
|
96
103
|
`${c.bold(s.ref)}${sameMark(s.sameLogin)}`,
|
|
@@ -104,10 +111,17 @@ export async function cmdList(a, io) {
|
|
|
104
111
|
: c.dim('?'),
|
|
105
112
|
s.identity?.email ?? c.dim('—'),
|
|
106
113
|
s.model ?? c.dim('—'),
|
|
107
|
-
s.hostInstalled
|
|
114
|
+
s.hostInstalled
|
|
115
|
+
? tildify(s.dir, home)
|
|
116
|
+
: `${tildify(s.dir, home)} ${c.yellow(brokenOf(s.family) ? '(host install broken)' : '(host not installed)')}`,
|
|
108
117
|
...(hasDesktop ? [s.desktopApp?.running ? c.green('open') : c.dim('—')] : []),
|
|
109
118
|
]);
|
|
110
119
|
io.out(table(rows, ['ACCOUNT', 'TYPE', 'LOGIN', 'EMAIL', 'MODEL', 'DIR', ...(hasDesktop ? ['DESKTOP'] : [])]));
|
|
120
|
+
// One line per Host that cannot run, with what to do: the rows only say which accounts it affects.
|
|
121
|
+
for (const f of families.filter((x) => !x.installed && statuses.some((s) => s.family === x.id)))
|
|
122
|
+
io.out(c.yellow(f.brokenBin
|
|
123
|
+
? `\n${f.title}'s install is broken (${tildify(f.brokenBin, home)} cannot run, often after an interrupted upgrade); reinstall it: ${installHint(f)}`
|
|
124
|
+
: `\n${f.title} is not installed; install it: ${installHint(f)}`));
|
|
111
125
|
if (statuses.some((s) => s.sameLogin?.length))
|
|
112
126
|
io.out(c.dim('\n= <account>: signed in as the same login as that account, so they share one quota.'));
|
|
113
127
|
io.out(c.dim('\nsideby run <account> · sideby new <family> <name> · sideby quota · sideby ui'));
|
|
@@ -471,13 +485,14 @@ function rcGroupLine(g, home) {
|
|
|
471
485
|
return ` ${paint(state)} ${c.bold(g.accounts.join(', '))} ${c.dim(where)}`;
|
|
472
486
|
}
|
|
473
487
|
/**
|
|
474
|
-
* `sideby remote-control [off|undo] [--yes] [--json]` (spec §3.22, ADR-0017): whether Claude sessions connect to
|
|
475
|
-
* claude.ai by themselves, and turning that off for every Account (a preview with exit 10 until --yes).
|
|
488
|
+
* `sideby remote-control [on|off|undo] [--yes] [--json]` (spec §3.22, ADR-0017): whether Claude sessions connect to
|
|
489
|
+
* claude.ai by themselves, and turning that on or off for every Account (a preview with exit 10 until --yes).
|
|
476
490
|
*/
|
|
477
491
|
export async function cmdRemoteControl(a, io) {
|
|
478
492
|
const [action, extra] = a.positionals;
|
|
479
|
-
if (extra !== undefined ||
|
|
480
|
-
|
|
493
|
+
if (extra !== undefined ||
|
|
494
|
+
(action !== undefined && action !== 'on' && action !== 'off' && action !== 'undo'))
|
|
495
|
+
throw new UsageError('usage: sideby remote-control [on|off|undo] [--yes] [--json]');
|
|
481
496
|
const wantJson = a.flags.has('json');
|
|
482
497
|
const rt = await createRuntime();
|
|
483
498
|
const home = rt.paths.home;
|
|
@@ -501,9 +516,15 @@ export async function cmdRemoteControl(a, io) {
|
|
|
501
516
|
io.out(c.cyan('\nRun `sideby remote-control off` so no session connects by itself (it shows the change first).'));
|
|
502
517
|
return 0;
|
|
503
518
|
}
|
|
504
|
-
const
|
|
519
|
+
const apply = () => action === 'off' ? rt.remoteControlOff() : action === 'on' ? rt.remoteControlOn() : rt.remoteControlUndo();
|
|
520
|
+
const todo = action === 'off'
|
|
521
|
+
? groupsToTurnOff(groups)
|
|
522
|
+
: action === 'on'
|
|
523
|
+
? groupsToTurnOn(groups)
|
|
524
|
+
: groupsToUndo(groups);
|
|
525
|
+
const verb = action === 'off' ? 'turn it off' : action === 'on' ? 'turn it on' : 'undo';
|
|
505
526
|
if (todo.length === 0) {
|
|
506
|
-
const r =
|
|
527
|
+
const r = await apply();
|
|
507
528
|
if (wantJson)
|
|
508
529
|
out({ applied: false, groups: show(groups), message: r.message });
|
|
509
530
|
else
|
|
@@ -515,26 +536,30 @@ export async function cmdRemoteControl(a, io) {
|
|
|
515
536
|
out({
|
|
516
537
|
applied: false,
|
|
517
538
|
groups: show(todo),
|
|
518
|
-
message: `Run again with --yes to ${
|
|
539
|
+
message: `Run again with --yes to ${verb}.`,
|
|
519
540
|
});
|
|
520
541
|
else {
|
|
521
542
|
io.out(action === 'off'
|
|
522
543
|
? 'Turn off Remote Control at startup?'
|
|
523
|
-
:
|
|
544
|
+
: action === 'on'
|
|
545
|
+
? 'Turn on Remote Control at startup?'
|
|
546
|
+
: 'Put back the Remote Control setting sideby replaced?');
|
|
524
547
|
for (const g of todo) {
|
|
525
|
-
const to = action === 'off' ? 'false' : 'what it was before';
|
|
548
|
+
const to = action === 'off' ? 'false' : action === 'on' ? 'true' : 'what it was before';
|
|
526
549
|
const from = g.state === 'default' ? 'not set' : g.state === 'on' ? 'true' : 'false';
|
|
527
550
|
io.out(` ${tildify(g.file, home)}: remoteControlAtStartup ${from} -> ${to}`);
|
|
528
551
|
io.out(c.dim(` used by ${g.accounts.join(', ')}`));
|
|
529
552
|
}
|
|
530
553
|
io.out(action === 'off'
|
|
531
554
|
? 'Sessions already running stay connected until you restart them. You can still start it in a session with /remote-control, and undo this with `sideby remote-control undo`.'
|
|
532
|
-
:
|
|
533
|
-
|
|
555
|
+
: action === 'on'
|
|
556
|
+
? 'Every new session then connects to claude.ai and the Claude app, where every device signed in to that account can read it. Undo this with `sideby remote-control undo`.'
|
|
557
|
+
: 'New sessions go back to the previous setting.');
|
|
558
|
+
io.out(`Run again with --yes to ${verb}.`);
|
|
534
559
|
}
|
|
535
560
|
return 10;
|
|
536
561
|
}
|
|
537
|
-
const r =
|
|
562
|
+
const r = await apply();
|
|
538
563
|
const results = r.results.map((x) => ({ ...x, file: tildify(x.file, home) }));
|
|
539
564
|
if (wantJson)
|
|
540
565
|
out({ applied: r.ok, results, message: r.message });
|
|
@@ -1031,11 +1056,41 @@ export async function cmdUpdate(a, io) {
|
|
|
1031
1056
|
const asJson = a.flags.has('json');
|
|
1032
1057
|
const plan = await u.planUpdate(paths, process.env);
|
|
1033
1058
|
const shown = u.shownPlan(plan);
|
|
1059
|
+
const sk = plan.skill;
|
|
1060
|
+
const skillLine = (s) => s.canRun
|
|
1061
|
+
? `the sideby skill (for ${s.installed.map((v) => v ?? 'no version').join(', ')}) is updated too:\n ${s.command}`
|
|
1062
|
+
: `the sideby skill is not updated by sideby: ${s.reason}.\n ${s.command}`;
|
|
1034
1063
|
if (!plan.available) {
|
|
1064
|
+
// The CLI is current; an outdated skill is the one thing left to update.
|
|
1065
|
+
if (sk?.outdated && sk.canRun) {
|
|
1066
|
+
if (!a.flags.has('yes')) {
|
|
1067
|
+
if (asJson)
|
|
1068
|
+
json(io, { ...shown, updated: false });
|
|
1069
|
+
else {
|
|
1070
|
+
io.out(`${c.green('✓')} sideby ${plan.current} is the latest version.`);
|
|
1071
|
+
io.out(`\nUpdating ${skillLine(sk)}`);
|
|
1072
|
+
io.out(c.cyan('\nRun it with `sideby update --yes`.'));
|
|
1073
|
+
}
|
|
1074
|
+
return 10;
|
|
1075
|
+
}
|
|
1076
|
+
if (!asJson)
|
|
1077
|
+
io.out(c.dim(`$ ${sk.command}`));
|
|
1078
|
+
const r = await u.applySkillUpdate(sk, process.env, asJson ? undefined : (t) => process.stderr.write(t));
|
|
1079
|
+
if (asJson)
|
|
1080
|
+
json(io, { ...shown, updated: false, skillUpdated: r?.ok ?? false, message: r?.message ?? '' });
|
|
1081
|
+
else if (r?.ok)
|
|
1082
|
+
io.out(`${c.green('✓')} ${r.message}; agents load it in their next session.`);
|
|
1083
|
+
else
|
|
1084
|
+
io.err(c.red(`✗ ${r?.message}`));
|
|
1085
|
+
return r?.ok ? 0 : 1;
|
|
1086
|
+
}
|
|
1035
1087
|
if (asJson)
|
|
1036
1088
|
json(io, { ...shown, updated: false });
|
|
1037
|
-
else
|
|
1089
|
+
else {
|
|
1038
1090
|
io.out(`${c.green('✓')} sideby ${plan.current} is the latest version.`);
|
|
1091
|
+
if (sk?.outdated)
|
|
1092
|
+
io.out(c.yellow(skillLine(sk)));
|
|
1093
|
+
}
|
|
1039
1094
|
return 0;
|
|
1040
1095
|
}
|
|
1041
1096
|
if (!plan.canRun) {
|
|
@@ -1053,6 +1108,8 @@ export async function cmdUpdate(a, io) {
|
|
|
1053
1108
|
else {
|
|
1054
1109
|
io.out(`sideby ${c.bold(plan.latest)} is available (you have ${plan.current}). What changed: ${plan.releaseNotes}`);
|
|
1055
1110
|
io.out(`\nUpdating runs:\n ${plan.command}`);
|
|
1111
|
+
if (sk?.outdated)
|
|
1112
|
+
io.out(`Then ${skillLine(sk)}`);
|
|
1056
1113
|
io.out(c.cyan('\nRun it with `sideby update --yes`.'));
|
|
1057
1114
|
}
|
|
1058
1115
|
return 10;
|
|
@@ -1069,7 +1126,9 @@ export async function cmdUpdate(a, io) {
|
|
|
1069
1126
|
updated: r.ok,
|
|
1070
1127
|
message: r.message,
|
|
1071
1128
|
restartedPanel: r.restartedPanel,
|
|
1129
|
+
refreshedApp: r.refreshedApp,
|
|
1072
1130
|
skill: SKILL_INSTALL,
|
|
1131
|
+
...(r.skillUpdated === undefined ? {} : { skillUpdated: r.skillUpdated }),
|
|
1073
1132
|
...(r.ok ? {} : { output: r.output }),
|
|
1074
1133
|
});
|
|
1075
1134
|
return r.ok ? 0 : 1;
|
|
@@ -1081,8 +1140,17 @@ export async function cmdUpdate(a, io) {
|
|
|
1081
1140
|
io.out(`${c.green('✓')} ${r.message}.`);
|
|
1082
1141
|
if (r.restartedPanel)
|
|
1083
1142
|
io.out(' The background panel is restarting with the new version; open pages reload by themselves.');
|
|
1143
|
+
if (r.refreshedApp)
|
|
1144
|
+
io.out(' The desktop app now starts the new version and shows its icon.');
|
|
1084
1145
|
io.out(' A `sideby ui` running in a terminal and sessions already running keep the old version until you restart them.');
|
|
1085
|
-
|
|
1146
|
+
if (r.skillUpdated)
|
|
1147
|
+
io.out(' The sideby skill was updated too; agents load it in their next session.');
|
|
1148
|
+
else if (r.skillUpdated === false)
|
|
1149
|
+
io.err(c.yellow(` ${r.skillMessage}`));
|
|
1150
|
+
else if (sk?.outdated)
|
|
1151
|
+
io.out(c.yellow(` ${skillLine(sk)}`));
|
|
1152
|
+
else if (!sk)
|
|
1153
|
+
io.out(` If you use the sideby skill, install it with: ${SKILL_INSTALL}`);
|
|
1086
1154
|
return 0;
|
|
1087
1155
|
}
|
|
1088
1156
|
/** The internal `sideby update-check` the CLI starts detached: one Update Check, then Auto Update if it is on. */
|
|
@@ -1180,6 +1248,83 @@ export async function cmdDesktop(a, io) {
|
|
|
1180
1248
|
io.err(c.cyan(n));
|
|
1181
1249
|
return 0;
|
|
1182
1250
|
}
|
|
1251
|
+
/** `sideby browser [--enable|--disable] [--yes]` and `sideby browser open <account> [--profile <p>]` (spec §3.24). */
|
|
1252
|
+
export async function cmdBrowser(a, io) {
|
|
1253
|
+
const [sub, ref, ...extra] = a.positionals;
|
|
1254
|
+
const rt = await createRuntime();
|
|
1255
|
+
const home = rt.paths.home;
|
|
1256
|
+
if (sub === 'open') {
|
|
1257
|
+
if (!ref || extra.length || a.rest.length)
|
|
1258
|
+
throw new UsageError('usage: sideby browser open <account> [--profile <folder|name>]');
|
|
1259
|
+
const profile = a.flags.get('profile');
|
|
1260
|
+
const r = await openInBrowser(rt, await rt.resolve(ref), typeof profile === 'string' ? profile : undefined);
|
|
1261
|
+
if (a.flags.has('json'))
|
|
1262
|
+
json(io, r);
|
|
1263
|
+
else
|
|
1264
|
+
io.out(`${c.green('✓')} opened ${r.url} in Chrome as ${c.bold(r.profile.name)} for ${c.bold(r.ref)}`);
|
|
1265
|
+
return 0;
|
|
1266
|
+
}
|
|
1267
|
+
if (sub !== undefined || a.rest.length)
|
|
1268
|
+
throw new UsageError('usage: sideby browser [--enable|--disable] [--yes] | sideby browser open <account>');
|
|
1269
|
+
const enable = a.flags.has('enable');
|
|
1270
|
+
if (enable || a.flags.has('disable')) {
|
|
1271
|
+
if (enable && a.flags.has('disable'))
|
|
1272
|
+
throw new UsageError('pass --enable or --disable, not both');
|
|
1273
|
+
const r = await setBrowserSignIns(rt, enable, { apply: a.flags.has('yes') });
|
|
1274
|
+
if (a.flags.has('json'))
|
|
1275
|
+
json(io, r);
|
|
1276
|
+
else if (!r.changed)
|
|
1277
|
+
io.out(`Finding browser sign-ins is already ${enable ? 'on' : 'off'}; nothing to change.`);
|
|
1278
|
+
else if (!r.applied) {
|
|
1279
|
+
if (enable) {
|
|
1280
|
+
io.out('sideby will read, on this computer only (nothing is sent anywhere):');
|
|
1281
|
+
for (const x of r.reads)
|
|
1282
|
+
io.out(` - ${x}`);
|
|
1283
|
+
io.out('It never opens cookies, saved passwords or the Keychain, and never changes Chrome.');
|
|
1284
|
+
}
|
|
1285
|
+
io.out(`\nChange to ${tildify(r.file, home)}: browser.signIns ${enable ? 'off → on' : 'on → off'}`);
|
|
1286
|
+
io.out(c.cyan(`Apply it with \`sideby browser --${enable ? 'enable' : 'disable'} --yes\`.`));
|
|
1287
|
+
}
|
|
1288
|
+
else
|
|
1289
|
+
io.out(`${c.green('✓')} finding browser sign-ins is ${enable ? 'on' : 'off'}.`);
|
|
1290
|
+
return r.changed && !r.applied ? 10 : 0;
|
|
1291
|
+
}
|
|
1292
|
+
const accounts = await rt.accounts();
|
|
1293
|
+
const r = await browserReport(rt, accounts);
|
|
1294
|
+
if (a.flags.has('json')) {
|
|
1295
|
+
json(io, r);
|
|
1296
|
+
return 0;
|
|
1297
|
+
}
|
|
1298
|
+
for (const p of r.problems)
|
|
1299
|
+
io.err(c.yellow(p));
|
|
1300
|
+
if (!r.enabled) {
|
|
1301
|
+
io.out('Finding which Chrome browser user signed in to each account is off.');
|
|
1302
|
+
io.out(c.cyan('Turn it on with `sideby browser --enable` (it shows what it reads first).'));
|
|
1303
|
+
io.out(c.dim('Without it, open a website in a browser user you name: sideby browser open <account> --profile <name>'));
|
|
1304
|
+
return 0;
|
|
1305
|
+
}
|
|
1306
|
+
if (!r.found) {
|
|
1307
|
+
io.out('Google Chrome has no data on this computer yet; open it once, then try again.');
|
|
1308
|
+
return 0;
|
|
1309
|
+
}
|
|
1310
|
+
const signedIn = new Set(r.profiles.flatMap((p) => p.signIns));
|
|
1311
|
+
const now = new Date();
|
|
1312
|
+
io.out(table(r.profiles.map((p) => [
|
|
1313
|
+
p.name,
|
|
1314
|
+
p.email ?? c.dim('-'),
|
|
1315
|
+
p.dir,
|
|
1316
|
+
p.signIns.join(', ') || c.dim('-'),
|
|
1317
|
+
p.changedAt ? clock(p.changedAt, now) : '-',
|
|
1318
|
+
]), ['Browser user', 'Google account', 'Folder', 'Signed in to', 'Changed']));
|
|
1319
|
+
const missing = accounts.filter((x) => r.families.matched.includes(x.family) && x.kind !== 'api' && !signedIn.has(x.ref));
|
|
1320
|
+
if (missing.length)
|
|
1321
|
+
io.out(c.dim(`\nNo browser user found for: ${missing.map((x) => x.ref).join(', ')}`));
|
|
1322
|
+
if (r.families.openOnly.length)
|
|
1323
|
+
io.out(c.dim(`${r.families.openOnly.join(', ')}: the website keeps no id to match; open it with --profile.`));
|
|
1324
|
+
io.out(c.dim('\nRead from Chrome on this computer only; a browser user that signed out may still show.'));
|
|
1325
|
+
io.out(c.dim('Open one: sideby browser open <account> [--profile <name>]'));
|
|
1326
|
+
return 0;
|
|
1327
|
+
}
|
|
1183
1328
|
export async function cmdAlias(a, io) {
|
|
1184
1329
|
const [sub, alias, target, ...more] = a.positionals;
|
|
1185
1330
|
const usage = 'usage: sideby alias add <short-command> <account> [-- host args] | sideby alias rm <short-command>';
|
|
@@ -1221,7 +1366,7 @@ export async function cmdFamilies(a, io) {
|
|
|
1221
1366
|
families.forEach((f, i) => {
|
|
1222
1367
|
if (i)
|
|
1223
1368
|
io.out('');
|
|
1224
|
-
io.out(`${c.bold(f.title)} (${f.id}) ${f.installed ? c.green('installed') : c.yellow(`not installed: ${f
|
|
1369
|
+
io.out(`${c.bold(f.title)} (${f.id}) ${f.installed ? c.green('installed') : c.yellow(f.brokenBin ? `install broken (${tildify(f.brokenBin, rt.paths.home)} cannot run); reinstall: ${installHint(f)}` : `not installed: ${installHint(f)}`)}`);
|
|
1225
1370
|
io.out(` Accounts ~/${f.layout.main} (main), ~/${f.layout.account}; selected with ${f.selectVar}`);
|
|
1226
1371
|
io.out(` Sign in sideby login ${f.id}:<name> ${c.dim(f.login.args ? `(runs ${[f.bin, ...f.login.args].join(' ')})` : `(${f.login.hint})`)}`);
|
|
1227
1372
|
const yes = (b) => (b ? c.green('yes') : c.dim('no public source'));
|
|
@@ -50,7 +50,9 @@ const FamilyInfo = Type.Object({
|
|
|
50
50
|
title: Str,
|
|
51
51
|
bin: Str,
|
|
52
52
|
installUrl: Str,
|
|
53
|
+
installCommand: Type.Optional(Str),
|
|
53
54
|
installed: Type.Boolean(),
|
|
55
|
+
brokenBin: Type.Optional(Str),
|
|
54
56
|
plugin: Str,
|
|
55
57
|
tuning: Type.Boolean(),
|
|
56
58
|
});
|
|
@@ -210,6 +212,30 @@ const AppProfile = Type.Object({
|
|
|
210
212
|
}, {
|
|
211
213
|
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
214
|
});
|
|
215
|
+
/**
|
|
216
|
+
* On any `--json` output when there is something to mention once the task is done (spec §3.23): a newer sideby in the
|
|
217
|
+
* cached Update Check, or an installed sideby skill for another version. Off with SIDEBY_NO_UPDATE_NOTIFIER.
|
|
218
|
+
*/
|
|
219
|
+
const Notice = Type.Object({
|
|
220
|
+
update: Type.Optional(Type.Object({
|
|
221
|
+
current: Str,
|
|
222
|
+
latest: Str,
|
|
223
|
+
command: Type.String({
|
|
224
|
+
description: 'Run it after the user agrees: it shows the plan and exits 10; then --yes.',
|
|
225
|
+
}),
|
|
226
|
+
message: Str,
|
|
227
|
+
})),
|
|
228
|
+
skill: Type.Optional(Type.Object({
|
|
229
|
+
installed: Type.Union([Str, Type.Null()], {
|
|
230
|
+
description: "The skill's version; null when it has none.",
|
|
231
|
+
}),
|
|
232
|
+
current: Str,
|
|
233
|
+
command: Type.String({
|
|
234
|
+
description: 'Updates the installed sideby skill (run it after the user agrees).',
|
|
235
|
+
}),
|
|
236
|
+
message: Str,
|
|
237
|
+
})),
|
|
238
|
+
}, { description: 'Not the answer to the task: mention it briefly afterwards and offer to update.' });
|
|
213
239
|
export const OUTPUT_SCHEMAS = {
|
|
214
240
|
list: Type.Object({
|
|
215
241
|
schemaVersion: Version,
|
|
@@ -256,7 +282,12 @@ export const OUTPUT_SCHEMAS = {
|
|
|
256
282
|
}, { title: 'sideby tune apply|undo --json' }),
|
|
257
283
|
'remote-control': Type.Object({
|
|
258
284
|
schemaVersion: Version,
|
|
259
|
-
action: Type.Union([
|
|
285
|
+
action: Type.Union([
|
|
286
|
+
Type.Literal('status'),
|
|
287
|
+
Type.Literal('on'),
|
|
288
|
+
Type.Literal('off'),
|
|
289
|
+
Type.Literal('undo'),
|
|
290
|
+
]),
|
|
260
291
|
applied: Type.Optional(Type.Boolean({ description: 'True once the change was written (or undone).' })),
|
|
261
292
|
groups: Type.Optional(Type.Array(Type.Object({
|
|
262
293
|
file: Str,
|
|
@@ -491,6 +522,47 @@ export const OUTPUT_SCHEMAS = {
|
|
|
491
522
|
description: 'False when only the plan was shown; run again with --yes (exit 10).',
|
|
492
523
|
}),
|
|
493
524
|
}, { title: 'sideby desktop grok-bot:<name> --remove --json' }),
|
|
525
|
+
browser: Type.Object({
|
|
526
|
+
schemaVersion: Version,
|
|
527
|
+
enabled: Type.Boolean({
|
|
528
|
+
description: 'config `browser.signIns`. When false nothing of the browser was read and `profiles` is empty.',
|
|
529
|
+
}),
|
|
530
|
+
browser: Type.String({ description: 'The browser read, `Google Chrome`.' }),
|
|
531
|
+
found: Type.Boolean({ description: "Chrome's user data folder exists on this Mac." }),
|
|
532
|
+
profiles: Type.Array(Type.Object({
|
|
533
|
+
dir: Type.String({ description: "The browser user's folder, such as `Default` or `Profile 7`." }),
|
|
534
|
+
name: Type.String({ description: 'The name Chrome shows; it may be an email.' }),
|
|
535
|
+
email: Type.Optional(Type.String({ description: 'The Google account this browser user is signed in to Chrome with.' })),
|
|
536
|
+
signIns: Type.Array(Str, {
|
|
537
|
+
description: "Refs of the accounts this browser user signed in to on the vendor's website, as far as its local data shows; it may be left over from before a sign-out.",
|
|
538
|
+
}),
|
|
539
|
+
changedAt: Type.Optional(Type.String({ description: 'When its Local Storage last changed (ISO 8601).' })),
|
|
540
|
+
}), { description: 'Most recently changed first.' }),
|
|
541
|
+
families: Type.Object({
|
|
542
|
+
matched: Type.Array(Str, { description: 'Families whose accounts can be matched (claude, codex).' }),
|
|
543
|
+
openOnly: Type.Array(Str, {
|
|
544
|
+
description: 'Families that can only be opened in a browser user given with --profile (grok).',
|
|
545
|
+
}),
|
|
546
|
+
}),
|
|
547
|
+
problems: Type.Array(Str),
|
|
548
|
+
}, { title: 'sideby browser --json' }),
|
|
549
|
+
'browser-switch': Type.Object({
|
|
550
|
+
schemaVersion: Version,
|
|
551
|
+
enabled: Type.Boolean({ description: 'The value asked for.' }),
|
|
552
|
+
changed: Type.Boolean(),
|
|
553
|
+
applied: Type.Boolean({
|
|
554
|
+
description: 'False when only the change was shown; run again with --yes (exit 10).',
|
|
555
|
+
}),
|
|
556
|
+
file: Type.String({ description: 'The config file.' }),
|
|
557
|
+
reads: Type.Array(Str, { description: 'What sideby reads while it is on, all on this computer.' }),
|
|
558
|
+
}, { title: 'sideby browser --enable|--disable --json' }),
|
|
559
|
+
'browser-open': Type.Object({
|
|
560
|
+
schemaVersion: Version,
|
|
561
|
+
ref: Str,
|
|
562
|
+
url: Str,
|
|
563
|
+
profile: Type.Object({ dir: Str, name: Str }),
|
|
564
|
+
command: Type.Array(Str, { description: 'The `open` command sideby ran.' }),
|
|
565
|
+
}, { title: 'sideby browser open --json' }),
|
|
494
566
|
update: Type.Object({
|
|
495
567
|
schemaVersion: Version,
|
|
496
568
|
current: Type.String({ description: 'The running sideby version.' }),
|
|
@@ -513,7 +585,28 @@ export const OUTPUT_SCHEMAS = {
|
|
|
513
585
|
restartedPanel: Type.Optional(Type.Boolean({
|
|
514
586
|
description: 'A background Panel was running and starts again with the new version.',
|
|
515
587
|
})),
|
|
516
|
-
|
|
588
|
+
refreshedApp: Type.Optional(Type.Boolean({
|
|
589
|
+
description: 'The desktop app (`sideby app install`) was installed and was written again by the new version.',
|
|
590
|
+
})),
|
|
591
|
+
skill: Type.Optional(Type.String({ description: 'The command that installs the sideby skill by hand.' })),
|
|
592
|
+
skillUpdate: Type.Optional(Type.Object({
|
|
593
|
+
installed: Type.Array(Type.Union([Str, Type.Null()]), {
|
|
594
|
+
description: 'Versions of the installed sideby skills; null for one without a version.',
|
|
595
|
+
}),
|
|
596
|
+
outdated: Type.Boolean({
|
|
597
|
+
description: 'Some installed skill is for another version than sideby will run.',
|
|
598
|
+
}),
|
|
599
|
+
command: Str,
|
|
600
|
+
canRun: Type.Boolean({
|
|
601
|
+
description: 'False when sideby leaves the skill alone (see reason): give the user command.',
|
|
602
|
+
}),
|
|
603
|
+
reason: Type.Optional(Str),
|
|
604
|
+
}, {
|
|
605
|
+
description: 'The installed sideby skill, which the Update brings along; absent when none is installed.',
|
|
606
|
+
})),
|
|
607
|
+
skillUpdated: Type.Optional(Type.Boolean({
|
|
608
|
+
description: 'The skill was updated too (true) or that failed (false); absent when not tried.',
|
|
609
|
+
})),
|
|
517
610
|
output: Type.Optional(Type.String({ description: 'What the failed install command printed.' })),
|
|
518
611
|
}, { title: 'sideby update --json' }),
|
|
519
612
|
error: Type.Object({
|
|
@@ -525,3 +618,8 @@ export const OUTPUT_SCHEMAS = {
|
|
|
525
618
|
}, { title: 'sideby error --json' }),
|
|
526
619
|
config: ConfigSchema,
|
|
527
620
|
};
|
|
621
|
+
for (const [name, schema] of Object.entries(OUTPUT_SCHEMAS)) {
|
|
622
|
+
const props = schema.properties;
|
|
623
|
+
if (name !== 'config' && props)
|
|
624
|
+
props._notice = Type.Optional(Notice);
|
|
625
|
+
}
|
package/dist/src/cli/main.js
CHANGED
|
@@ -34,7 +34,7 @@ Usage:
|
|
|
34
34
|
from your last 7 days of sessions (read-only)
|
|
35
35
|
sideby model <account> [<model>|default] [--effort <level>|default] [--yes]
|
|
36
36
|
show or set the model and effort sideby starts this account with
|
|
37
|
-
sideby remote-control [off|undo] [--yes]
|
|
37
|
+
sideby remote-control [on|off|undo] [--yes]
|
|
38
38
|
whether Claude sessions connect to claude.ai by themselves;
|
|
39
39
|
off stops that for every account (shows the change first)
|
|
40
40
|
sideby handoff status what Auto Handoff still needs on this machine
|
|
@@ -52,6 +52,10 @@ Usage:
|
|
|
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
54
|
sideby desktop grok-bot:<name> open a Grok Bot window, a new one for a new name (macOS)
|
|
55
|
+
sideby browser [--enable [--yes]] which Chrome browser user signed in to each account's website
|
|
56
|
+
(read on this computer only; off until you enable it)
|
|
57
|
+
sideby browser open <account> [--profile <name>]
|
|
58
|
+
open claude.ai, chatgpt.com or grok.com in that browser user
|
|
55
59
|
sideby app install [--url <url>] add a sideby app that opens the panel (macOS, Linux)
|
|
56
60
|
sideby app uninstall remove that app
|
|
57
61
|
sideby shell-init zsh|bash print shell functions, e.g. eval "$(sideby shell-init zsh)"
|
|
@@ -62,8 +66,8 @@ Usage:
|
|
|
62
66
|
|
|
63
67
|
Options:
|
|
64
68
|
--json machine-readable output (list, new, next, alias, doctor, quota, tune, model, remote-control, handoff,
|
|
65
|
-
families, plugins, app, desktop, update)
|
|
66
|
-
-h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop
|
|
69
|
+
families, plugins, app, desktop, browser, update)
|
|
70
|
+
-h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop, browser
|
|
67
71
|
-v, --version show the version
|
|
68
72
|
|
|
69
73
|
An <account> is <family>:<name> (claude:work) or just <name> when it is unique.
|
|
@@ -164,6 +168,30 @@ Examples:
|
|
|
164
168
|
sideby desktop grok-bot:002 --remove --yes
|
|
165
169
|
Exit: 0 opened, brought to the front or removed; 1 not macOS, no desktop app for that host, API account, app not
|
|
166
170
|
installed, a bad window name, or the app did not open; 10 --remove without --yes; 2 malformed command line.`,
|
|
171
|
+
browser: `sideby browser [--enable|--disable] [--yes] [--json]
|
|
172
|
+
sideby browser open <account> [--profile <folder|name>] [--json]
|
|
173
|
+
|
|
174
|
+
Find which Google Chrome browser user signed in to each account's website (claude.ai for claude accounts,
|
|
175
|
+
chatgpt.com for codex accounts), and open the website in that browser user. macOS only for now.
|
|
176
|
+
|
|
177
|
+
Everything is read on this computer and sent nowhere. sideby reads each browser user's name, the key names (never
|
|
178
|
+
the values) claude.ai and chatgpt.com keep in that browser user's Local Storage, and the account id in each Claude and
|
|
179
|
+
Codex login file, only to compare. It never opens cookies, saved passwords or the Keychain, and never changes Chrome.
|
|
180
|
+
It is off until you turn it on: --enable shows what it reads and changes config browser.signIns after --yes.
|
|
181
|
+
|
|
182
|
+
"Signed in to" means that browser user's data shows a sign-in as that account; it may be left over from before a
|
|
183
|
+
sign-out. grok.com keeps no id to match, so grok accounts can only be opened with --profile.
|
|
184
|
+
|
|
185
|
+
open uses the one browser user that signed in to the account, or the one --profile names (folder such as
|
|
186
|
+
"Profile 7", or the name Chrome shows). It runs: open -na "Google Chrome" --args --profile-directory=<folder> <url>.
|
|
187
|
+
|
|
188
|
+
Examples:
|
|
189
|
+
sideby browser --enable --yes
|
|
190
|
+
sideby browser
|
|
191
|
+
sideby browser open cc008
|
|
192
|
+
sideby browser open grok:main --profile Work
|
|
193
|
+
Exit: 0 shown, changed or opened; 1 not macOS, Chrome not installed, no or several browser users to choose from, or
|
|
194
|
+
Chrome did not open; 10 --enable/--disable without --yes; 2 malformed command line.`,
|
|
167
195
|
families: `sideby families [family] [--json]
|
|
168
196
|
|
|
169
197
|
For each host (built in or from a plugin): where its accounts live, the variable that selects one, how to sign
|
|
@@ -204,6 +232,7 @@ const COMMANDS = {
|
|
|
204
232
|
ui: { flags: ['no-open', 'background', 'stop', 'serve-detached'], valued: ['port'] },
|
|
205
233
|
app: { flags: ['json'], valued: ['url'] },
|
|
206
234
|
desktop: { flags: ['json', 'remove', 'yes'] },
|
|
235
|
+
browser: { flags: ['json', 'enable', 'disable', 'yes'], valued: ['profile'] },
|
|
207
236
|
'shell-init': { flags: ['write'] },
|
|
208
237
|
plugins: { flags: ['json'] },
|
|
209
238
|
families: { flags: ['json'] },
|
|
@@ -220,6 +249,38 @@ function noticeAllowed(cmd, parsed) {
|
|
|
220
249
|
return false;
|
|
221
250
|
return true;
|
|
222
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* Before a `--json` command (spec §3.23): the `_notice` its output carries, from the cached Update Check and the
|
|
254
|
+
* installed sideby skills, so an agent that reads JSON learns of a newer version too. Starts the next check detached
|
|
255
|
+
* when the last one is a day old, as afterCommand does on a terminal. Never fails the command.
|
|
256
|
+
*/
|
|
257
|
+
async function jsonNoticeFor() {
|
|
258
|
+
try {
|
|
259
|
+
const u = await import('../core/update.js');
|
|
260
|
+
const { resolvePaths } = await import('../core/paths.js');
|
|
261
|
+
const { loadConfig } = await import('../core/config.js');
|
|
262
|
+
const { packageVersion } = await import('../core/version.js');
|
|
263
|
+
const { installedSkillVersions, SKILL_INSTALL } = await import('../core/skill-check.js');
|
|
264
|
+
const paths = resolvePaths(process.env);
|
|
265
|
+
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
266
|
+
const cache = await u.readUpdateCache(paths);
|
|
267
|
+
const skills = await installedSkillVersions(paths.home);
|
|
268
|
+
const n = u.jsonNotice(cache, packageVersion(), config, process.env, skills, SKILL_INSTALL);
|
|
269
|
+
if (!u.checksOff(process.env, config) && u.checkDue(cache, new Date())) {
|
|
270
|
+
const { spawn } = await import('node:child_process');
|
|
271
|
+
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update-check'], {
|
|
272
|
+
detached: true,
|
|
273
|
+
stdio: 'ignore',
|
|
274
|
+
});
|
|
275
|
+
child.on('error', () => { });
|
|
276
|
+
child.unref();
|
|
277
|
+
}
|
|
278
|
+
return n;
|
|
279
|
+
}
|
|
280
|
+
catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
223
284
|
/**
|
|
224
285
|
* After a command (spec §3.23): print the Update Notice and the result of an Auto Update, each at most once, and
|
|
225
286
|
* start the next Update Check detached when the last one is a day old. Only on a terminal; never fails the command.
|
|
@@ -290,6 +351,7 @@ export async function main(argv) {
|
|
|
290
351
|
// Only sideby's own options count: arguments after `--` belong to the host.
|
|
291
352
|
const own = args.includes('--') ? args.slice(0, args.indexOf('--')) : args;
|
|
292
353
|
const wantsJson = Boolean(spec?.flags.includes('json')) && own.includes('--json');
|
|
354
|
+
let notice = null;
|
|
293
355
|
try {
|
|
294
356
|
if (!spec)
|
|
295
357
|
throw new UsageError(`unknown command "${cmd}"; run \`sideby --help\``);
|
|
@@ -325,6 +387,10 @@ export async function main(argv) {
|
|
|
325
387
|
return 0;
|
|
326
388
|
}
|
|
327
389
|
const cmds = await import('./commands.js');
|
|
390
|
+
if (wantsJson && noticeAllowed(cmd, parsed)) {
|
|
391
|
+
notice = await jsonNoticeFor();
|
|
392
|
+
cmds.setJsonNotice(notice);
|
|
393
|
+
}
|
|
328
394
|
const code = await dispatch(cmd, parsed, io, cmds);
|
|
329
395
|
if (!wantsJson && noticeAllowed(cmd, parsed))
|
|
330
396
|
await afterCommand();
|
|
@@ -336,7 +402,7 @@ export async function main(argv) {
|
|
|
336
402
|
if (wantsJson) {
|
|
337
403
|
const { UserError } = await import('../core/errors.js');
|
|
338
404
|
const code = usage ? 'usage' : err instanceof UserError && err.code ? err.code : 'error';
|
|
339
|
-
console.log(JSON.stringify({ schemaVersion: 1, error: message, code }, null, 2));
|
|
405
|
+
console.log(JSON.stringify({ schemaVersion: 1, error: message, code, ...(notice ? { _notice: notice } : {}) }, null, 2));
|
|
340
406
|
}
|
|
341
407
|
else
|
|
342
408
|
console.error(`sideby: ${message}`);
|
|
@@ -379,6 +445,8 @@ async function dispatch(cmd, parsed, io, cmds) {
|
|
|
379
445
|
return await cmds.cmdApp(parsed, io);
|
|
380
446
|
case 'desktop':
|
|
381
447
|
return await cmds.cmdDesktop(parsed, io);
|
|
448
|
+
case 'browser':
|
|
449
|
+
return await cmds.cmdBrowser(parsed, io);
|
|
382
450
|
case 'shell-init':
|
|
383
451
|
return await cmds.cmdShellInit(parsed, io);
|
|
384
452
|
case 'plugins':
|