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.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/build.json +1 -1
  3. package/dist/src/cli/commands.d.ts +2 -0
  4. package/dist/src/cli/commands.js +44 -5
  5. package/dist/src/cli/json-schemas.js +37 -0
  6. package/dist/src/cli/main.js +26 -2
  7. package/dist/src/core/desktop-app.d.ts +96 -0
  8. package/dist/src/core/desktop-app.js +382 -0
  9. package/dist/src/core/doctor.d.ts +6 -1
  10. package/dist/src/core/doctor.js +19 -0
  11. package/dist/src/core/handoff-run.d.ts +2 -0
  12. package/dist/src/core/handoff-run.js +10 -4
  13. package/dist/src/core/handoff-select.d.ts +9 -0
  14. package/dist/src/core/handoff-select.js +10 -2
  15. package/dist/src/core/handoff-status.js +12 -0
  16. package/dist/src/core/handoff.d.ts +12 -0
  17. package/dist/src/core/handoff.js +13 -4
  18. package/dist/src/core/same-login.d.ts +24 -0
  19. package/dist/src/core/same-login.js +52 -0
  20. package/dist/src/core/tuning-apply.js +1 -1
  21. package/dist/src/families/claude/desktop.d.ts +6 -0
  22. package/dist/src/families/claude/desktop.js +28 -0
  23. package/dist/src/families/codex/desktop.d.ts +7 -0
  24. package/dist/src/families/codex/desktop.js +25 -0
  25. package/dist/src/families/desktop.d.ts +27 -0
  26. package/dist/src/families/desktop.js +9 -0
  27. package/dist/src/panel/handler.js +28 -6
  28. package/dist/src/panel/i18n.d.ts +11 -0
  29. package/dist/src/panel/i18n.js +22 -0
  30. package/dist/src/panel/page-logic.d.ts +6 -1
  31. package/dist/src/panel/page-logic.js +5 -4
  32. package/dist/src/panel/page-script.js +37 -3
  33. package/dist/src/runtime.d.ts +26 -0
  34. package/dist/src/runtime.js +89 -23
  35. package/dist/src/types.d.ts +5 -0
  36. package/package.json +2 -2
  37. package/schemas/desktop.json +80 -0
  38. package/schemas/list.json +27 -0
  39. package/schemas/next.json +12 -0
  40. package/schemas/quota.json +4 -0
  41. package/skills/sideby/SKILL.md +4 -2
  42. package/skills/sideby/references/handoff.md +1 -1
  43. 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 the panel from the Dock or app menu | `sideby app install` | [Desktop app](docs/guide/panel.md#desktop-app) |
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, desktop app and embedding
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":"20261014T070544-66900c"}
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>;
@@ -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) => [c.bold(r.ref), quotaText(r.quota, now), usageText(r.usage)]), ['ACCOUNT', 'QUOTA', `USAGE (${USAGE_DAYS}d)`]));
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.' }),
@@ -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
- -h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families
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[]>;