sideby 0.6.0 → 0.8.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 +44 -5
- package/dist/src/cli/json-schemas.js +37 -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 +382 -0
- package/dist/src/core/doctor.d.ts +6 -1
- package/dist/src/core/doctor.js +19 -0
- package/dist/src/core/handoff-run.d.ts +2 -0
- package/dist/src/core/handoff-run.js +10 -4
- package/dist/src/core/handoff-select.d.ts +9 -0
- package/dist/src/core/handoff-select.js +10 -2
- package/dist/src/core/handoff-status.js +12 -0
- package/dist/src/core/handoff.d.ts +12 -0
- package/dist/src/core/handoff.js +13 -4
- package/dist/src/core/same-login.d.ts +24 -0
- package/dist/src/core/same-login.js +52 -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 +28 -6
- package/dist/src/panel/i18n.d.ts +11 -0
- package/dist/src/panel/i18n.js +22 -0
- package/dist/src/panel/page-logic.d.ts +6 -1
- package/dist/src/panel/page-logic.js +5 -4
- package/dist/src/panel/page-script.js +37 -3
- package/dist/src/runtime.d.ts +26 -0
- package/dist/src/runtime.js +89 -23
- package/dist/src/types.d.ts +5 -0
- package/package.json +2 -2
- package/schemas/desktop.json +80 -0
- package/schemas/list.json +27 -0
- package/schemas/next.json +12 -0
- package/schemas/quota.json +4 -0
- package/skills/sideby/SKILL.md +4 -2
- package/skills/sideby/references/handoff.md +1 -1
- package/skills/sideby/references/quota.md +2 -0
package/README.md
CHANGED
|
@@ -76,7 +76,8 @@ The agent checks Node and your Host CLIs, installs sideby and the skill, creates
|
|
|
76
76
|
| 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) |
|
|
77
77
|
| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |
|
|
78
78
|
| 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) |
|
|
79
|
-
| Open
|
|
79
|
+
| 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) |
|
|
80
|
+
| Open the panel from the Dock or app menu | `sideby app install` | [Panel app](docs/guide/panel.md#panel-app) |
|
|
80
81
|
| Show the panel inside your own local page | `createPanelHandler()` | [Embed the panel](docs/guide/panel.md#embed-the-panel) |
|
|
81
82
|
| Add a Host or a pre-launch check | a local plugin | [Plugins](docs/guide/plugins.md) |
|
|
82
83
|
|
|
@@ -114,7 +115,7 @@ Usage questions and ideas go in [Discussions](https://github.com/Adonis0123/side
|
|
|
114
115
|
- [Using sideby](docs/guide/usage.md): accounts, API accounts, Doctor, quota, Handoff, Host notes
|
|
115
116
|
- [Commands](docs/guide/commands.md): every command, exit codes and `--json`
|
|
116
117
|
- [Configuration](docs/guide/configuration.md): `config.json`, aliases, shell functions
|
|
117
|
-
- [Panel](docs/guide/panel.md): the panel,
|
|
118
|
+
- [Panel](docs/guide/panel.md): the panel, panel app and embedding
|
|
118
119
|
- [Plugins](docs/guide/plugins.md): write your own Family or launch hook
|
|
119
120
|
- [FAQ](docs/guide/faq.md): terms of service, data locations, alternatives, uninstall
|
|
120
121
|
- [Changelog](https://github.com/Adonis0123/sideby/releases)
|
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261014T102906-2ab079"}
|
|
@@ -57,6 +57,8 @@ export declare function cmdPlugins(a: ParsedArgs, io: Io): Promise<number>;
|
|
|
57
57
|
export declare function cmdShellInit(a: ParsedArgs, io: Io): Promise<number>;
|
|
58
58
|
export declare function cmdUi(a: ParsedArgs, io: Io): Promise<number>;
|
|
59
59
|
export declare function cmdApp(a: ParsedArgs, io: Io): Promise<number>;
|
|
60
|
+
/** `sideby desktop <account>`: the Family's Desktop App as that Account (spec §3.20). */
|
|
61
|
+
export declare function cmdDesktop(a: ParsedArgs, io: Io): Promise<number>;
|
|
60
62
|
export declare function cmdAlias(a: ParsedArgs, io: Io): Promise<number>;
|
|
61
63
|
/** `sideby families [family]`: each Family's layout, sign-in and Shared Items, from its FamilyDef (spec §3.16). */
|
|
62
64
|
export declare function cmdFamilies(a: ParsedArgs, io: Io): Promise<number>;
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { fileURLToPath } from 'node:url';
|
|
2
2
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
3
3
|
import { aliasRefs } from '../core/config.js';
|
|
4
|
+
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
4
5
|
import { UserError } from '../core/errors.js';
|
|
5
6
|
import { requestHandoff } from '../core/handoff-chain.js';
|
|
6
7
|
import { explain, readyDecision, runWithHandoff } from '../core/handoff-run.js';
|
|
@@ -53,7 +54,8 @@ function tilde(text, home) {
|
|
|
53
54
|
export async function cmdList(a, io) {
|
|
54
55
|
const rt = await createRuntime();
|
|
55
56
|
const accounts = await rt.accounts();
|
|
56
|
-
const statuses = await Promise.all(accounts.map((x) => rt.status(x)));
|
|
57
|
+
const statuses = await withDesktopApps(rt, accounts, Promise.all(accounts.map((x) => rt.status(x))));
|
|
58
|
+
const hasDesktop = statuses.some((s) => s.desktopApp);
|
|
57
59
|
const families = await rt.familyInfo();
|
|
58
60
|
if (a.flags.has('json')) {
|
|
59
61
|
json(io, { families, accounts: statuses, pluginErrors: [...rt.pluginErrors] });
|
|
@@ -69,8 +71,10 @@ export async function cmdList(a, io) {
|
|
|
69
71
|
io.out(`Run ${installed.map((f) => f.bin).join(' or ')} once, then \`sideby new <family> <name>\`.`);
|
|
70
72
|
}
|
|
71
73
|
else {
|
|
74
|
+
// `= main`: the other Accounts of its Same Login, which share its Quota (spec §3.21).
|
|
75
|
+
const sameMark = (refs) => refs?.length ? ` ${c.dim(`= ${refs.map((r) => r.slice(r.indexOf(':') + 1)).join(', ')}`)}` : '';
|
|
72
76
|
const rows = statuses.map((s) => [
|
|
73
|
-
c.bold(s.ref)
|
|
77
|
+
`${c.bold(s.ref)}${sameMark(s.sameLogin)}`,
|
|
74
78
|
s.isMain ? 'main' : s.kind === 'api' ? c.cyan('api') : 'sub',
|
|
75
79
|
s.login === 'logged-in'
|
|
76
80
|
? c.green('✓')
|
|
@@ -82,8 +86,11 @@ export async function cmdList(a, io) {
|
|
|
82
86
|
s.identity?.email ?? c.dim('—'),
|
|
83
87
|
s.model ?? c.dim('—'),
|
|
84
88
|
s.hostInstalled ? tildify(s.dir, home) : `${tildify(s.dir, home)} ${c.yellow('(host not installed)')}`,
|
|
89
|
+
...(hasDesktop ? [s.desktopApp?.running ? c.green('open') : c.dim('—')] : []),
|
|
85
90
|
]);
|
|
86
|
-
io.out(table(rows, ['ACCOUNT', 'TYPE', 'LOGIN', 'EMAIL', 'MODEL', 'DIR']));
|
|
91
|
+
io.out(table(rows, ['ACCOUNT', 'TYPE', 'LOGIN', 'EMAIL', 'MODEL', 'DIR', ...(hasDesktop ? ['DESKTOP'] : [])]));
|
|
92
|
+
if (statuses.some((s) => s.sameLogin?.length))
|
|
93
|
+
io.out(c.dim('\n= <account>: signed in as the same login as that account, so they share one quota.'));
|
|
87
94
|
io.out(c.dim('\nsideby run <account> · sideby new <family> <name> · sideby quota · sideby ui'));
|
|
88
95
|
}
|
|
89
96
|
for (const s of statuses)
|
|
@@ -213,6 +220,8 @@ export async function cmdNext(a, io) {
|
|
|
213
220
|
}
|
|
214
221
|
const now = rt.readContext().now;
|
|
215
222
|
const note = (e) => {
|
|
223
|
+
if (e.sameLoginAs)
|
|
224
|
+
return `same login as ${e.sameLoginAs}, which shares its quota; skipped`;
|
|
216
225
|
if (e.state === 'ready')
|
|
217
226
|
return `fullest window ${Math.round(e.pressure ?? 0)}%`;
|
|
218
227
|
if (e.state === 'unknown')
|
|
@@ -236,11 +245,13 @@ export async function cmdNext(a, io) {
|
|
|
236
245
|
const why = plan.earliestReset
|
|
237
246
|
? `every ${family} account is at its limit; ${plan.earliestReset.ref} is back at ${clock(plan.earliestReset.at, now)}`
|
|
238
247
|
: `no ${family} account can be used now`;
|
|
248
|
+
const same = plan.accounts.filter((e) => e.sameLoginAs).map((e) => e.ref);
|
|
249
|
+
const sameNote = same.length ? `; ${same.join(', ')} share the quota of ${plan.from}` : '';
|
|
239
250
|
const next = [
|
|
240
251
|
...(out.length ? [`sign in with \`${out.join('` or `')}\``] : []),
|
|
241
252
|
...(apiLeft ? ['add --include-api to use an API account'] : []),
|
|
242
253
|
];
|
|
243
|
-
io.err(c.yellow(`\n${why}${next.length ? `; ${next.join(', or ')}` : ''}.`));
|
|
254
|
+
io.err(c.yellow(`\n${why}${sameNote}${next.length ? `; ${next.join(', or ')}` : ''}.`));
|
|
244
255
|
return 1;
|
|
245
256
|
}
|
|
246
257
|
const picked = plan.accounts.find((e) => e.ref === plan.pick);
|
|
@@ -527,7 +538,11 @@ export async function cmdQuota(a, io) {
|
|
|
527
538
|
return 0;
|
|
528
539
|
}
|
|
529
540
|
const now = new Date();
|
|
530
|
-
io.out(table(rows.map((r) => [
|
|
541
|
+
io.out(table(rows.map((r) => [
|
|
542
|
+
c.bold(r.ref),
|
|
543
|
+
`${quotaText(r.quota, now)}${r.quota.status === 'ok' && r.quota.from ? c.dim(` read from ${r.quota.from} (same login)`) : ''}`,
|
|
544
|
+
usageText(r.usage),
|
|
545
|
+
]), ['ACCOUNT', 'QUOTA', `USAGE (${USAGE_DAYS}d)`]));
|
|
531
546
|
for (const r of rows)
|
|
532
547
|
if (r.quota.status === 'unavailable' && r.quota.detail)
|
|
533
548
|
io.out(c.dim(`${r.ref}: ${r.quota.detail}`));
|
|
@@ -897,6 +912,30 @@ export async function cmdApp(a, io) {
|
|
|
897
912
|
}
|
|
898
913
|
return r.skipped.length ? 1 : 0;
|
|
899
914
|
}
|
|
915
|
+
/** `sideby desktop <account>`: the Family's Desktop App as that Account (spec §3.20). */
|
|
916
|
+
export async function cmdDesktop(a, io) {
|
|
917
|
+
const [ref, ...extra] = a.positionals;
|
|
918
|
+
if (!ref || extra.length || a.rest.length)
|
|
919
|
+
throw new UsageError('usage: sideby desktop <account>');
|
|
920
|
+
const rt = await createRuntime();
|
|
921
|
+
const r = await openDesktopApp(rt, ref);
|
|
922
|
+
if (a.flags.has('json')) {
|
|
923
|
+
json(io, r);
|
|
924
|
+
return 0;
|
|
925
|
+
}
|
|
926
|
+
const home = rt.paths.home;
|
|
927
|
+
if (r.action === 'started')
|
|
928
|
+
io.out(`${c.green('✓')} opened ${r.app} as ${c.bold(r.ref)}`);
|
|
929
|
+
else if (r.action === 'focused')
|
|
930
|
+
io.out(`${c.green('✓')} ${r.app} is already open as ${c.bold(r.ref)}; brought it to the front`);
|
|
931
|
+
if (r.appData)
|
|
932
|
+
io.out(c.dim(` its data: ${tildify(r.appData, home)}`));
|
|
933
|
+
for (const f of r.linked)
|
|
934
|
+
io.out(c.dim(` linked ${f} to the main account's`));
|
|
935
|
+
for (const n of r.notices)
|
|
936
|
+
io.err(c.cyan(n));
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
900
939
|
export async function cmdAlias(a, io) {
|
|
901
940
|
const [sub, alias, target, ...more] = a.positionals;
|
|
902
941
|
const usage = 'usage: sideby alias add <short-command> <account> [-- host args] | sideby alias rm <short-command>';
|
|
@@ -36,7 +36,13 @@ const AccountStatus = Type.Intersect([
|
|
|
36
36
|
identity: Type.Optional(Type.Object({ email: Type.Optional(Str), org: Type.Optional(Str) }, {
|
|
37
37
|
description: "Who the account is signed in as, from the host's login file: identity fields only, never a token (ADR-0003).",
|
|
38
38
|
})),
|
|
39
|
+
sameLogin: Type.Optional(Type.Array(Str, {
|
|
40
|
+
description: 'The other accounts signed in as the same login (same family, email and organization), which share one quota (spec §3.21). Absent when there are none.',
|
|
41
|
+
})),
|
|
39
42
|
problems: Type.Optional(Type.Array(Str)),
|
|
43
|
+
desktopApp: Type.Optional(Type.Object({ app: Str, installed: Type.Boolean(), running: Type.Boolean() }, {
|
|
44
|
+
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>`.",
|
|
45
|
+
})),
|
|
40
46
|
}),
|
|
41
47
|
]);
|
|
42
48
|
const FamilyInfo = Type.Object({
|
|
@@ -103,6 +109,9 @@ const Quota = Type.Union([
|
|
|
103
109
|
observedAt: Str,
|
|
104
110
|
source: Str,
|
|
105
111
|
plan: Type.Optional(Str),
|
|
112
|
+
from: Type.Optional(Type.String({
|
|
113
|
+
description: 'The account this reading was taken from, when it is another account of the same login (spec §3.21); observedAt is its time.',
|
|
114
|
+
})),
|
|
106
115
|
}),
|
|
107
116
|
Type.Object({ status: Type.Literal('unavailable'), reason: Str, detail: Type.Optional(Str) }),
|
|
108
117
|
]);
|
|
@@ -260,6 +269,9 @@ export const OUTPUT_SCHEMAS = {
|
|
|
260
269
|
next: Type.Object({
|
|
261
270
|
schemaVersion: Version,
|
|
262
271
|
family: Str,
|
|
272
|
+
from: Type.Optional(Type.String({
|
|
273
|
+
description: 'The account `sideby next` runs in (SIDEBY_ACCOUNT, set by sideby for the sessions it starts), when it belongs to this family.',
|
|
274
|
+
})),
|
|
263
275
|
pick: Type.Union([Str, Type.Null()], {
|
|
264
276
|
description: 'The recommended account; null when none can be used now (exit code 1).',
|
|
265
277
|
}),
|
|
@@ -280,6 +292,12 @@ export const OUTPUT_SCHEMAS = {
|
|
|
280
292
|
}),
|
|
281
293
|
resetsAt: Type.Optional(Type.String({ description: 'For full: when its last full window resets.' })),
|
|
282
294
|
observedAt: Type.Optional(Type.String({ description: "When the account's quota was recorded (its last session)." })),
|
|
295
|
+
quotaFrom: Type.Optional(Type.String({
|
|
296
|
+
description: 'The account the quota was read from, when it is another account of the same login.',
|
|
297
|
+
})),
|
|
298
|
+
sameLoginAs: Type.Optional(Type.String({
|
|
299
|
+
description: 'The current account (`from`), when this one is signed in as the same login: it shares that quota and is never picked.',
|
|
300
|
+
})),
|
|
283
301
|
}), { description: 'Every account of the family, best first.' }),
|
|
284
302
|
hasQuota: Type.Boolean({
|
|
285
303
|
description: 'False when no account has quota data: the pick is then a guess, and `sideby next` does not start it (exit 1).',
|
|
@@ -395,6 +413,25 @@ export const OUTPUT_SCHEMAS = {
|
|
|
395
413
|
removed: Type.Array(Str),
|
|
396
414
|
skipped: Type.Array(Type.Object({ path: Str, reason: Str })),
|
|
397
415
|
}, { title: 'sideby app uninstall --json' }),
|
|
416
|
+
desktop: Type.Object({
|
|
417
|
+
schemaVersion: Version,
|
|
418
|
+
ref: Str,
|
|
419
|
+
family: Str,
|
|
420
|
+
app: Type.String({ description: 'The desktop app, for example `Claude Desktop`.' }),
|
|
421
|
+
appPath: Str,
|
|
422
|
+
action: Type.Union([Type.Literal('started'), Type.Literal('focused'), Type.Literal('already-running')], {
|
|
423
|
+
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.',
|
|
424
|
+
}),
|
|
425
|
+
appData: Type.Optional(Type.String({
|
|
426
|
+
description: "This account's app data folder; absent for the main account, which uses the app's default.",
|
|
427
|
+
})),
|
|
428
|
+
createdAppData: Type.Boolean(),
|
|
429
|
+
linked: Type.Array(Str, {
|
|
430
|
+
description: "Files linked into a new app data folder from the main account's (claude_desktop_config.json).",
|
|
431
|
+
}),
|
|
432
|
+
command: Type.Optional(Type.Array(Str, { description: 'The `open` command sideby ran.' })),
|
|
433
|
+
notices: Type.Array(Str),
|
|
434
|
+
}, { title: 'sideby desktop --json' }),
|
|
398
435
|
error: Type.Object({
|
|
399
436
|
schemaVersion: Version,
|
|
400
437
|
error: Type.String({ description: 'What went wrong and what to do next.' }),
|
package/dist/src/cli/main.js
CHANGED
|
@@ -45,6 +45,8 @@ Usage:
|
|
|
45
45
|
sideby ui [--port n] [--no-open] open the local panel
|
|
46
46
|
sideby ui --background same, without a terminal: keeps running after this command exits
|
|
47
47
|
sideby ui --stop stop the background panel
|
|
48
|
+
sideby desktop <account> open the desktop app (Claude Desktop, the Codex app) as this
|
|
49
|
+
account, next to the others (macOS)
|
|
48
50
|
sideby app install [--url <url>] add a sideby app that opens the panel (macOS, Linux)
|
|
49
51
|
sideby app uninstall remove that app
|
|
50
52
|
sideby shell-init zsh|bash print shell functions, e.g. eval "$(sideby shell-init zsh)"
|
|
@@ -53,8 +55,9 @@ Usage:
|
|
|
53
55
|
sideby plugins list loaded plugins and load errors
|
|
54
56
|
|
|
55
57
|
Options:
|
|
56
|
-
--json machine-readable output (list, new, next, alias, doctor, quota, tune, handoff, families, plugins, app
|
|
57
|
-
|
|
58
|
+
--json machine-readable output (list, new, next, alias, doctor, quota, tune, handoff, families, plugins, app,
|
|
59
|
+
desktop)
|
|
60
|
+
-h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop
|
|
58
61
|
-v, --version show the version
|
|
59
62
|
|
|
60
63
|
An <account> is <family>:<name> (claude:work) or just <name> when it is unique.
|
|
@@ -125,6 +128,24 @@ Examples:
|
|
|
125
128
|
sideby doctor --json
|
|
126
129
|
sideby doctor claude:008 --fix
|
|
127
130
|
Exit: 0 when nothing fails (warnings allowed); 1 when a check fails.`,
|
|
131
|
+
desktop: `sideby desktop <account> [--json]
|
|
132
|
+
|
|
133
|
+
Open the host's desktop app as one account: Claude Desktop for claude accounts, the Codex app (ChatGPT.app) for codex
|
|
134
|
+
accounts. Each account gets its own window, side by side with the others; macOS only for now.
|
|
135
|
+
|
|
136
|
+
The main account opens the app as usual. Any other subscription account opens a new window with that account's
|
|
137
|
+
directory (CLAUDE_CONFIG_DIR, CODEX_HOME) and its own app data folder,
|
|
138
|
+
~/Library/Application Support/sideby/<family>/<name>/. When that account's window is already open, sideby brings it
|
|
139
|
+
to the front instead of starting another one. API accounts are refused: the apps sign in with a subscription only.
|
|
140
|
+
|
|
141
|
+
sideby never copies or changes the app, and never closes a window. The first time, sign in inside Claude Desktop;
|
|
142
|
+
close other Claude Desktop windows while you do, because the sign-in may come back to one of them.
|
|
143
|
+
|
|
144
|
+
Examples:
|
|
145
|
+
sideby desktop claude:work
|
|
146
|
+
sideby desktop cc008 --json
|
|
147
|
+
Exit: 0 opened or brought to the front; 1 not macOS, no desktop app for that host, API account, app not installed,
|
|
148
|
+
or the app did not open; 2 malformed command line.`,
|
|
128
149
|
families: `sideby families [family] [--json]
|
|
129
150
|
|
|
130
151
|
For each host (built in or from a plugin): where its accounts live, the variable that selects one, how to sign
|
|
@@ -163,6 +184,7 @@ const COMMANDS = {
|
|
|
163
184
|
},
|
|
164
185
|
ui: { flags: ['no-open', 'background', 'stop', 'serve-detached'], valued: ['port'] },
|
|
165
186
|
app: { flags: ['json'], valued: ['url'] },
|
|
187
|
+
desktop: { flags: ['json'] },
|
|
166
188
|
'shell-init': { flags: ['write'] },
|
|
167
189
|
plugins: { flags: ['json'] },
|
|
168
190
|
families: { flags: ['json'] },
|
|
@@ -266,6 +288,8 @@ export async function main(argv) {
|
|
|
266
288
|
return await cmds.cmdUi(parsed, io);
|
|
267
289
|
case 'app':
|
|
268
290
|
return await cmds.cmdApp(parsed, io);
|
|
291
|
+
case 'desktop':
|
|
292
|
+
return await cmds.cmdDesktop(parsed, io);
|
|
269
293
|
case 'shell-init':
|
|
270
294
|
return await cmds.cmdShellInit(parsed, io);
|
|
271
295
|
case 'plugins':
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type DesktopAppDef } from '../families/desktop.ts';
|
|
2
|
+
import type { LoadedPlugin } from '../plugins/loader.ts';
|
|
3
|
+
import type { Runtime } from '../runtime.ts';
|
|
4
|
+
import type { Account, CheckResult, Env } from '../types.ts';
|
|
5
|
+
export interface ExecResult {
|
|
6
|
+
code: number;
|
|
7
|
+
stdout: string;
|
|
8
|
+
stderr: string;
|
|
9
|
+
}
|
|
10
|
+
/** What opening a Desktop App needs from the machine; tests replace all of it. */
|
|
11
|
+
export interface DesktopDeps {
|
|
12
|
+
platform: NodeJS.Platform;
|
|
13
|
+
/** Folders searched for the app, in order (normally /Applications and ~/Applications). */
|
|
14
|
+
appDirs: readonly string[];
|
|
15
|
+
/** Runs a program and resolves with its exit code and output; never rejects. */
|
|
16
|
+
exec(cmd: string, args: readonly string[], env?: Env): Promise<ExecResult>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Internal: makes every Desktop App call made with this Runtime use `deps` instead of the machine. Only
|
|
20
|
+
* `createRuntime` calls it, from its `desktop` option, which is the one way to replace the machine (tests).
|
|
21
|
+
*/
|
|
22
|
+
export declare function bindDesktopDeps(rt: Runtime, deps: Partial<DesktopDeps>): void;
|
|
23
|
+
/** The Desktop App of a Family, only when a built-in Plugin provides that Family (a plugin may not claim one). */
|
|
24
|
+
export declare function desktopAppOf(plugins: readonly LoadedPlugin[], familyId: string): DesktopAppDef | undefined;
|
|
25
|
+
/** Where a non-main Account's App Data lives: `~/Library/Application Support/sideby/<family>/<name>`. */
|
|
26
|
+
export declare function appDataDir(home: string, account: Pick<Account, 'family' | 'name'>): string;
|
|
27
|
+
export interface FoundApp {
|
|
28
|
+
path: string;
|
|
29
|
+
/** `Contents/MacOS/<executable>`: the app's main process runs this. */
|
|
30
|
+
executable: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Finds the app by bundle id: the usual names in the app folders first, then (with `spotlight`) Spotlight. Listing
|
|
34
|
+
* Accounts leaves Spotlight out to stay fast; opening one asks it too.
|
|
35
|
+
*/
|
|
36
|
+
export declare function findApp(def: DesktopAppDef, deps: DesktopDeps, opts?: {
|
|
37
|
+
spotlight?: boolean;
|
|
38
|
+
}): Promise<FoundApp | null>;
|
|
39
|
+
export interface Proc {
|
|
40
|
+
pid: number;
|
|
41
|
+
command: string;
|
|
42
|
+
}
|
|
43
|
+
/** The running instance of this Account: the one with its App Data, or for the Main Account the one without any. */
|
|
44
|
+
export declare function instanceOf(app: FoundApp, appData: string | null, procs: readonly Proc[]): Proc | undefined;
|
|
45
|
+
export interface DesktopOpenResult {
|
|
46
|
+
ref: string;
|
|
47
|
+
family: string;
|
|
48
|
+
/** The app as messages name it, for example `Claude Desktop`. */
|
|
49
|
+
app: string;
|
|
50
|
+
appPath: string;
|
|
51
|
+
/** `started`: a new instance; `focused`: the running one is in front; `already-running`: it runs but stayed behind. */
|
|
52
|
+
action: 'started' | 'focused' | 'already-running';
|
|
53
|
+
/** The Account's App Data; absent for the Main Account, which uses the app's default. */
|
|
54
|
+
appData?: string;
|
|
55
|
+
/** True when this call created the App Data folder. */
|
|
56
|
+
createdAppData: boolean;
|
|
57
|
+
/** Files linked into a new App Data from the Main Account's default one. */
|
|
58
|
+
linked: string[];
|
|
59
|
+
/** The `open` arguments, when sideby started the app. */
|
|
60
|
+
command?: string[];
|
|
61
|
+
/** What the user should know: sign in first, close other windows while signing in. */
|
|
62
|
+
notices: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Opens the Account's Desktop App, or brings its running instance to the front. Never starts a second instance of the
|
|
66
|
+
* same Account, never ends a process, never touches the app bundle or the Host account directory.
|
|
67
|
+
*/
|
|
68
|
+
export declare function openDesktopApp(rt: Runtime, ref: string): Promise<DesktopOpenResult>;
|
|
69
|
+
export interface DesktopAppStatus {
|
|
70
|
+
/** The app as messages name it. */
|
|
71
|
+
app: string;
|
|
72
|
+
installed: boolean;
|
|
73
|
+
/** That Account's own instance runs now. */
|
|
74
|
+
running: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Desktop App state per Account ref for `sideby list` and the Panel: only on macOS, only Subscription Accounts of a
|
|
78
|
+
* Family that has one. One process listing for all of them; never throws.
|
|
79
|
+
*/
|
|
80
|
+
export declare function desktopStatuses(rt: Runtime, accounts: readonly Account[]): Promise<Map<string, DesktopAppStatus>>;
|
|
81
|
+
/**
|
|
82
|
+
* Adds `desktopApp` to each status whose Account has one, for `sideby list` and the Panel. The process listing runs
|
|
83
|
+
* while `statuses` is still being built; the order of the statuses is kept.
|
|
84
|
+
*/
|
|
85
|
+
export declare function withDesktopApps<T extends {
|
|
86
|
+
ref: string;
|
|
87
|
+
}>(rt: Runtime, accounts: readonly Account[], statuses: Promise<readonly T[]>): Promise<(T & {
|
|
88
|
+
desktopApp?: DesktopAppStatus;
|
|
89
|
+
})[]>;
|
|
90
|
+
/**
|
|
91
|
+
* Doctor's extra checks for an Account's App Data (spec §3.20): the Desktop App's shared files under `link` rules,
|
|
92
|
+
* only when that Account's App Data folder exists, never for the Main Account (it uses the app's default) or an API
|
|
93
|
+
* Account. Results are named `app-data/<path>` and are not counted in `shared`. The App Data is the repair boundary:
|
|
94
|
+
* a fix may only add a missing link inside it, never replace a real file.
|
|
95
|
+
*/
|
|
96
|
+
export declare function checkAppData(plugins: readonly LoadedPlugin[], home: string, account: Account): Promise<CheckResult[]>;
|