sideby 0.12.0 → 0.13.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 (60) hide show
  1. package/README.md +1 -1
  2. package/dist/build.json +1 -1
  3. package/dist/src/cli/commands.d.ts +4 -2
  4. package/dist/src/cli/commands.js +117 -17
  5. package/dist/src/cli/json-schemas.js +52 -1
  6. package/dist/src/cli/main.js +34 -3
  7. package/dist/src/core/brand-mark.d.ts +5 -7
  8. package/dist/src/core/brand-mark.js +18 -5
  9. package/dist/src/core/browser-profiles.d.ts +71 -0
  10. package/dist/src/core/browser-profiles.js +361 -0
  11. package/dist/src/core/config.d.ts +15 -0
  12. package/dist/src/core/config.js +13 -1
  13. package/dist/src/core/create.js +2 -1
  14. package/dist/src/core/desktop-app.d.ts +3 -1
  15. package/dist/src/core/desktop-app.js +29 -8
  16. package/dist/src/core/launch.d.ts +7 -0
  17. package/dist/src/core/launch.js +22 -2
  18. package/dist/src/core/leveldb-keys.d.ts +14 -0
  19. package/dist/src/core/leveldb-keys.js +291 -0
  20. package/dist/src/core/remote-control.d.ts +17 -6
  21. package/dist/src/core/remote-control.js +40 -17
  22. package/dist/src/core/update.d.ts +10 -3
  23. package/dist/src/core/update.js +38 -3
  24. package/dist/src/core/version.d.ts +11 -0
  25. package/dist/src/core/version.js +26 -0
  26. package/dist/src/desktop/app.d.ts +2 -0
  27. package/dist/src/desktop/app.js +39 -3
  28. package/dist/src/families/browser.d.ts +20 -0
  29. package/dist/src/families/browser.js +23 -0
  30. package/dist/src/families/claude/browser.d.ts +3 -0
  31. package/dist/src/families/claude/browser.js +14 -0
  32. package/dist/src/families/claude/desktop.js +1 -0
  33. package/dist/src/families/codex/browser.d.ts +7 -0
  34. package/dist/src/families/codex/browser.js +26 -0
  35. package/dist/src/families/desktop.d.ts +5 -1
  36. package/dist/src/families/pi/index.js +2 -0
  37. package/dist/src/panel/handler.d.ts +6 -0
  38. package/dist/src/panel/handler.js +49 -6
  39. package/dist/src/panel/i18n.d.ts +75 -4
  40. package/dist/src/panel/i18n.js +162 -20
  41. package/dist/src/panel/page-logic.d.ts +23 -0
  42. package/dist/src/panel/page-logic.js +44 -0
  43. package/dist/src/panel/page-script.js +609 -180
  44. package/dist/src/panel/page-styles.js +72 -17
  45. package/dist/src/panel/page.d.ts +5 -0
  46. package/dist/src/panel/page.js +10 -9
  47. package/dist/src/runtime.d.ts +6 -1
  48. package/dist/src/runtime.js +22 -11
  49. package/dist/src/types.d.ts +2 -0
  50. package/package.json +5 -3
  51. package/schemas/browser-open.json +47 -0
  52. package/schemas/browser-switch.json +42 -0
  53. package/schemas/browser.json +99 -0
  54. package/schemas/config.json +11 -0
  55. package/schemas/families.json +6 -0
  56. package/schemas/list.json +6 -0
  57. package/schemas/remote-control.json +4 -0
  58. package/schemas/update.json +4 -0
  59. package/skills/sideby/SKILL.md +5 -3
  60. package/skills/sideby/references/setup.md +1 -1
@@ -39,7 +39,7 @@ export function launcherScript(s) {
39
39
  : `command -v notify-send >/dev/null 2>&1 && notify-send "sideby could not start" "Run sideby app install again in a terminal. Details: $LOG"`;
40
40
  const lines = [
41
41
  '#!/bin/sh',
42
- `# ${APP_MARKER}: written by \`sideby app install\`. Run that again after upgrading sideby or Node.`,
42
+ `# ${APP_MARKER}: written by \`sideby app install\` (\`sideby update\` runs it again). Run it yourself after upgrading Node.`,
43
43
  `NODE=${shq(s.node)}`,
44
44
  `ENTRY=${shq(s.entry)}`,
45
45
  `URL=${shq(s.url ?? '')}`,
@@ -185,6 +185,38 @@ async function ownership(entry) {
185
185
  }
186
186
  return markerTest(entry)(data) ? { state: 'ours' } : foreign(entry, NOT_OURS);
187
187
  }
188
+ /** The launcher script inside what install writes: the bundle's executable on macOS, the launcher file on Linux. */
189
+ function launcherPath(l, platform) {
190
+ return platform === 'darwin' ? join(l.primary, 'Contents', 'MacOS', APP_NAME) : l.linux.launcher;
191
+ }
192
+ /** The `URL='…'` an earlier install wrote into the launcher, when it is a valid http(s) address; never follows a link. */
193
+ async function recordedUrl(launcher) {
194
+ let text;
195
+ try {
196
+ text = (await readNoFollow(launcher)).toString('utf8');
197
+ }
198
+ catch {
199
+ return undefined;
200
+ }
201
+ if (text.split('\n', 2)[1]?.startsWith(`# ${APP_MARKER}: `) !== true)
202
+ return undefined;
203
+ const m = /^URL='((?:[^']|'\\'')*)'$/m.exec(text);
204
+ const value = m?.[1]?.replace(/'\\''/g, "'");
205
+ if (!value)
206
+ return undefined;
207
+ try {
208
+ return checkAppUrl(value);
209
+ }
210
+ catch {
211
+ return undefined;
212
+ }
213
+ }
214
+ /** Whether a sideby desktop app is installed for this home: its main file exists and carries APP_MARKER. */
215
+ export async function appInstalled(ctx) {
216
+ if (ctx.platform !== 'darwin' && ctx.platform !== 'linux')
217
+ return false;
218
+ return (await ownership(layout(ctx, ctx.platform).primary)).state === 'ours';
219
+ }
188
220
  /** Refuses to replace anything at `entry` that `sideby app install` did not write; true when it is ours. */
189
221
  async function assertOwned(entry, verb) {
190
222
  const o = await ownership(entry);
@@ -199,12 +231,14 @@ export async function installApp(ctx, opts) {
199
231
  const run = ctx.run ?? defaultRunner;
200
232
  if (!isAbsolute(opts.node) || !isAbsolute(opts.entry))
201
233
  throw new UserError('node and the sideby entry must be absolute paths');
202
- const url = opts.url === undefined ? undefined : checkAppUrl(opts.url);
203
234
  const l = layout(ctx, platform);
204
235
  let updated = false;
205
236
  for (const e of l.entries)
206
237
  if (await assertOwned(e, 'install'))
207
238
  updated = true;
239
+ // Without --url, a reinstall (by hand or after `sideby update`) keeps the address an earlier --url recorded.
240
+ const kept = opts.url === undefined && updated ? await recordedUrl(launcherPath(l, platform)) : undefined;
241
+ const url = opts.url === undefined ? kept : checkAppUrl(opts.url);
208
242
  const xdg = {};
209
243
  for (const k of ['XDG_CONFIG_HOME', 'XDG_STATE_HOME']) {
210
244
  const v = ctx.env[k];
@@ -274,8 +308,10 @@ export async function installApp(ctx, opts) {
274
308
  }
275
309
  if (url)
276
310
  hints.push(`The app opens ${url}.`);
311
+ if (kept)
312
+ hints.push('Kept the address from the earlier install; to start the panel instead, run `sideby app uninstall`, then `sideby app install`.');
277
313
  else
278
- hints.push('The app starts the panel in the background and opens it; stop the panel with `sideby ui --stop`.', 'Run `sideby app install` again after upgrading sideby or Node, so the app starts the new version.');
314
+ hints.push('The app starts the panel in the background and opens it; stop the panel with `sideby ui --stop`.', '`sideby update` refreshes the app; run `sideby app install` again after upgrading Node or installing sideby another way.');
279
315
  if (/[/\\]_npx[/\\]/.test(opts.entry))
280
316
  hints.push('sideby runs from the npx cache, which npm may clear; install it with `npm i -g sideby` and run this again.');
281
317
  return {
@@ -0,0 +1,20 @@
1
+ import type { Account } from '../types.ts';
2
+ export interface WebsiteDef {
3
+ /** The Family id. */
4
+ family: string;
5
+ /** Where "Open in browser" goes. */
6
+ url: string;
7
+ /**
8
+ * The Local Storage origin whose key names may hold `signInId`, and the reader of that id from the Host's login
9
+ * file. The id is compared in memory only; it is never shown, returned, logged or cached. Missing when the website
10
+ * keeps no id the Host also has (grok.com), so the Account can only be opened in a Browser Profile the user picks.
11
+ */
12
+ match?: {
13
+ origin: string;
14
+ signInId(account: Account): Promise<string | undefined>;
15
+ };
16
+ }
17
+ /** The website of a built-in Family id, if it has one. */
18
+ export declare function websiteDef(familyId: string): WebsiteDef | undefined;
19
+ /** Every website that can be matched, for the origins to read. */
20
+ export declare function matchedOrigins(): string[];
@@ -0,0 +1,23 @@
1
+ import { claudeSignInId } from './claude/browser.js';
2
+ import { codexSignInId } from './codex/browser.js';
3
+ const WEBSITES = new Map([
4
+ {
5
+ family: 'claude',
6
+ url: 'https://claude.ai',
7
+ match: { origin: 'https://claude.ai', signInId: claudeSignInId },
8
+ },
9
+ {
10
+ family: 'codex',
11
+ url: 'https://chatgpt.com',
12
+ match: { origin: 'https://chatgpt.com', signInId: codexSignInId },
13
+ },
14
+ { family: 'grok', url: 'https://grok.com' },
15
+ ].map((w) => [w.family, w]));
16
+ /** The website of a built-in Family id, if it has one. */
17
+ export function websiteDef(familyId) {
18
+ return WEBSITES.get(familyId);
19
+ }
20
+ /** Every website that can be matched, for the origins to read. */
21
+ export function matchedOrigins() {
22
+ return [...WEBSITES.values()].flatMap((w) => (w.match ? [w.match.origin] : []));
23
+ }
@@ -0,0 +1,3 @@
1
+ import type { Account } from '../../types.ts';
2
+ /** `oauthAccount.accountUuid` of `.claude.json`, only for matching; every other field is dropped at once. */
3
+ export declare function claudeSignInId(account: Account): Promise<string | undefined>;
@@ -0,0 +1,14 @@
1
+ // The Claude account id that claude.ai's Local Storage key names also carry (spec §3.24, ADR-0023).
2
+ import { readFile } from 'node:fs/promises';
3
+ import { claudeJsonOf } from './index.js';
4
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ /** `oauthAccount.accountUuid` of `.claude.json`, only for matching; every other field is dropped at once. */
6
+ export async function claudeSignInId(account) {
7
+ try {
8
+ const id = JSON.parse(await readFile(claudeJsonOf(account), 'utf8'))?.oauthAccount?.accountUuid;
9
+ return typeof id === 'string' && UUID.test(id) ? id.toLowerCase() : undefined;
10
+ }
11
+ catch {
12
+ return undefined;
13
+ }
14
+ }
@@ -23,6 +23,7 @@ export const claudeDesktop = {
23
23
  {
24
24
  path: 'claude_desktop_config.json',
25
25
  mainPath: 'Library/Application Support/Claude/claude_desktop_config.json',
26
+ key: 'mcpServers',
26
27
  },
27
28
  ],
28
29
  };
@@ -0,0 +1,7 @@
1
+ import type { Account } from '../../types.ts';
2
+ /**
3
+ * `chatgpt_user_id` (else `user_id`, as Codex itself falls back: codex-rs/login/src/token_data.rs) under the auth
4
+ * claim of `tokens.id_token`, only for matching. The payload is decoded as for the email (core/identity.ts); the
5
+ * token and every other claim are dropped at once. `tokens.account_id` is the workspace and is not used.
6
+ */
7
+ export declare function codexSignInId(account: Account): Promise<string | undefined>;
@@ -0,0 +1,26 @@
1
+ // The ChatGPT user id that chatgpt.com's Local Storage key names also carry (spec §3.24, ADR-0023).
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ const USER_ID = /^user-[A-Za-z0-9]{8,64}$/;
5
+ const AUTH_CLAIM = 'https://api.openai.com/auth';
6
+ /**
7
+ * `chatgpt_user_id` (else `user_id`, as Codex itself falls back: codex-rs/login/src/token_data.rs) under the auth
8
+ * claim of `tokens.id_token`, only for matching. The payload is decoded as for the email (core/identity.ts); the
9
+ * token and every other claim are dropped at once. `tokens.account_id` is the workspace and is not used.
10
+ */
11
+ export async function codexSignInId(account) {
12
+ try {
13
+ const token = JSON.parse(await readFile(join(account.dir, 'auth.json'), 'utf8'))?.tokens?.id_token;
14
+ if (typeof token !== 'string')
15
+ return undefined;
16
+ const payload = token.split('.')[1];
17
+ if (!payload)
18
+ return undefined;
19
+ const auth = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8'))?.[AUTH_CLAIM];
20
+ const id = auth?.chatgpt_user_id ?? auth?.user_id;
21
+ return typeof id === 'string' && USER_ID.test(id) ? id : undefined;
22
+ }
23
+ catch {
24
+ return undefined;
25
+ }
26
+ }
@@ -17,10 +17,14 @@ export interface DesktopAppDef {
17
17
  clearVars: readonly string[];
18
18
  /** The first sign-in happens inside the app, through a URL callback another running instance may catch. */
19
19
  signInInApp: boolean;
20
- /** Files of the App Data shared from the Main Account's default App Data as links (`mainPath` relative to HOME). */
20
+ /**
21
+ * Files of the App Data shared from the Main Account's default App Data as links (`mainPath` relative to HOME).
22
+ * With `key`, a real file the app made in place of the link is fine while that JSON key matches the main one.
23
+ */
21
24
  sharedFiles: readonly {
22
25
  path: string;
23
26
  mainPath: string;
27
+ key?: string;
24
28
  }[];
25
29
  }
26
30
  /** The Desktop App of a built-in Family id, if it has one. */
@@ -28,6 +28,8 @@ export const piFamily = {
28
28
  logo: BUILTIN_LOGOS.pi,
29
29
  bin: 'pi',
30
30
  installUrl: 'https://github.com/earendil-works/pi',
31
+ // The managed install stages and smoke-tests each release before switching to it; npm upgrades in place.
32
+ installCommand: 'curl -fsSL https://pi.dev/install.sh | sh',
31
33
  selectVar: 'PI_CODING_AGENT_DIR',
32
34
  secretFileMeansApi: false,
33
35
  layout: { main: '.pi/agent', account: '.pi-<name>/agent' },
@@ -1,5 +1,6 @@
1
1
  import type { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { type AppProfileStatus } from '../core/app-profiles.ts';
3
+ import { type BrowserReport } from '../core/browser-profiles.ts';
3
4
  import { type HandoffSettings } from '../core/config.ts';
4
5
  import { type AccountModel } from '../core/model-choice.ts';
5
6
  import type { RemoteControlGroup } from '../core/remote-control.ts';
@@ -132,6 +133,11 @@ export interface PanelState {
132
133
  appProfiles?: AppProfileStatus[];
133
134
  /** Update Check result and Auto Update setting (spec §3.23); absent with `updates: false`. */
134
135
  update?: PanelUpdate;
136
+ /**
137
+ * Browser Sign-ins (spec §3.24): which Chrome browser user signed in to which Account, read on this computer only.
138
+ * Absent without Claude, Codex or Grok subscription Accounts. Nothing of the browser is read while `enabled` is false.
139
+ */
140
+ browser?: BrowserReport;
135
141
  }
136
142
  /** The `handoff` map of the Panel state, from the statuses and Quota already read for the page. */
137
143
  export declare function handoffByFamily(rt: Runtime, accounts: readonly PanelAccount[], now: Date): Record<string, string>;
@@ -3,6 +3,7 @@ import { randomBytes, timingSafeEqual } from 'node:crypto';
3
3
  import { aliasesByAccount } from '../core/accounts.js';
4
4
  import { listAppProfiles, openAppProfile, removeAppProfile, } from '../core/app-profiles.js';
5
5
  import { FAVICON_SVG, ICON_SVG } from '../core/brand-mark.js';
6
+ import { browserReport, openInBrowser, setBrowserSignIns, } from '../core/browser-profiles.js';
6
7
  import { aliasArgs, aliasRefs, editConfigUpdate } from '../core/config.js';
7
8
  import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
8
9
  import { UserError } from '../core/errors.js';
@@ -15,7 +16,7 @@ import { tildify } from '../core/paths.js';
15
16
  import { SKILL_INSTALL } from '../core/skill-check.js';
16
17
  import { MIN_CALLS } from '../core/tuning.js';
17
18
  import * as update from '../core/update.js';
18
- import { buildId, packageVersion } from '../core/version.js';
19
+ import { buildId, localBuild, packageVersion } from '../core/version.js';
19
20
  import { HOOK_FAMILIES } from '../handoff/hook.js';
20
21
  import { renderPage } from './page.js';
21
22
  import { resolveTheme } from './theme.js';
@@ -265,6 +266,8 @@ async function buildState(rt, opts) {
265
266
  };
266
267
  });
267
268
  const handoff = handoffByFamily(rt, list, ctx.now);
269
+ const browser = await browserReport(rt, accounts).catch(() => undefined);
270
+ const showBrowser = browser && (browser.families.matched.length || browser.families.openOnly.length);
268
271
  return {
269
272
  schemaVersion: 1,
270
273
  version: opts.version,
@@ -273,7 +276,8 @@ async function buildState(rt, opts) {
273
276
  generatedAt: ctx.now.toISOString(),
274
277
  families: families.map((f) => {
275
278
  const logo = rt.families.get(f.id)?.logo;
276
- return logo ? { ...f, logo } : f;
279
+ const g = f.brokenBin ? { ...f, brokenBin: tildify(f.brokenBin, ctx.home) } : f;
280
+ return logo ? { ...g, logo } : g;
277
281
  }),
278
282
  accounts: list,
279
283
  pluginErrors: rt.pluginErrors.map((e) => ({ where: e.where, message: e.message })),
@@ -294,6 +298,7 @@ async function buildState(rt, opts) {
294
298
  .map((f) => f.id),
295
299
  },
296
300
  ...(upd ? { update: upd } : {}),
301
+ ...(showBrowser ? { browser } : {}),
297
302
  };
298
303
  }
299
304
  /** The `change` of a /api/handoff/configure body, with every field checked; unknown fields are refused. */
@@ -351,6 +356,7 @@ export function createPanelHandler(opts) {
351
356
  const readOnly = Boolean(opts.readOnly);
352
357
  const version = opts.version ?? PANEL_VERSION;
353
358
  const build = buildId();
359
+ const local = localBuild();
354
360
  const theme = resolveTheme(opts.theme);
355
361
  const lang = resolveLang(opts.lang);
356
362
  const updates = opts.updates !== false;
@@ -450,8 +456,8 @@ export function createPanelHandler(opts) {
450
456
  return { ref: found.ref, undone: r.ok, message: r.message };
451
457
  },
452
458
  },
453
- // Remote Control off by default (spec §3.22, ADR-0017): writes `remoteControlAtStartup: false` into each Claude
454
- // settings file that turns it on or leaves it to the default; the page shows the files first, from the state.
459
+ // Remote Control at startup (spec §3.22, ADR-0017): writes `remoteControlAtStartup` (false for off, true for on)
460
+ // into each Claude settings file that does not hold it yet; the page shows the files first, from the state.
455
461
  '/api/remote-control/off': {
456
462
  async run() {
457
463
  const rt = await getRuntime();
@@ -460,6 +466,14 @@ export function createPanelHandler(opts) {
460
466
  return { ...r, results: r.results.map((x) => ({ ...x, file: tildify(x.file, home) })) };
461
467
  },
462
468
  },
469
+ '/api/remote-control/on': {
470
+ async run() {
471
+ const rt = await getRuntime();
472
+ const r = await rt.remoteControlOn();
473
+ const home = rt.paths.home;
474
+ return { ...r, results: r.results.map((x) => ({ ...x, file: tildify(x.file, home) })) };
475
+ },
476
+ },
463
477
  '/api/remote-control/undo': {
464
478
  async run() {
465
479
  const rt = await getRuntime();
@@ -524,6 +538,29 @@ export function createPanelHandler(opts) {
524
538
  };
525
539
  },
526
540
  },
541
+ // Browser Sign-ins on or off (spec §3.24): config `browser.signIns`, shown first like every config change.
542
+ '/api/browser/settings': {
543
+ writes: (body) => body.confirm === true,
544
+ async run(body) {
545
+ if (typeof body.enabled !== 'boolean')
546
+ throw new HttpError(400, '"enabled" must be true or false');
547
+ const rt = await getRuntime();
548
+ const r = await setBrowserSignIns(rt, body.enabled, { apply: optBool(body, 'confirm') });
549
+ return { ...r, file: tildify(r.file, rt.paths.home) };
550
+ },
551
+ },
552
+ // Opens the Account's website in one Chrome browser user (spec §3.24). It starts a program, so a read-only Panel
553
+ // refuses it (writes defaults to true).
554
+ '/api/browser/open': {
555
+ async run(body) {
556
+ const account = optString(body, 'account');
557
+ if (!account)
558
+ throw new HttpError(400, '"account" is required');
559
+ const rt = await getRuntime();
560
+ const profile = optString(body, 'profile');
561
+ return openInBrowser(rt, await rt.resolve(account), profile);
562
+ },
563
+ },
527
564
  // Opens a Grok Bot window, creating it when the name is new, or brings it to the front (ADR-0019).
528
565
  '/api/app-profile/open': {
529
566
  async run(body) {
@@ -600,7 +637,13 @@ export function createPanelHandler(opts) {
600
637
  });
601
638
  if (!r.ok)
602
639
  throw new UserError(r.message);
603
- return { ...shown, updated: true, message: r.message, restartedPanel: r.restartedPanel };
640
+ return {
641
+ ...shown,
642
+ updated: true,
643
+ message: r.message,
644
+ restartedPanel: r.restartedPanel,
645
+ refreshedApp: r.refreshedApp,
646
+ };
604
647
  },
605
648
  },
606
649
  // The Auto Update switch: config `update.auto`, shown as a diff first like every config change.
@@ -671,7 +714,7 @@ export function createPanelHandler(opts) {
671
714
  });
672
715
  res.end(method === 'HEAD'
673
716
  ? undefined
674
- : renderPage({ basePath, token, nonce, version, build, readOnly, theme, lang, updates }));
717
+ : renderPage({ basePath, token, nonce, version, build, local, readOnly, theme, lang, updates }));
675
718
  return true;
676
719
  }
677
720
  // The mark, for a Portal's tab and for this page's favicon (ADR-0006 amendment).
@@ -6,7 +6,7 @@ declare const en: {
6
6
  'top.refreshing': string;
7
7
  'top.updated': string;
8
8
  'top.updatedTitle': string;
9
- 'top.language': string;
9
+ 'top.stale': string;
10
10
  'foot.text': string;
11
11
  'foot.textChecks': string;
12
12
  'upd.label': string;
@@ -22,6 +22,16 @@ declare const en: {
22
22
  'upd.skill': string;
23
23
  'upd.checksOff': string;
24
24
  'upd.latest': string;
25
+ 'upd.latestNow': string;
26
+ 'upd.check': string;
27
+ 'upd.checking': string;
28
+ 'upd.verTitle': string;
29
+ 'upd.local': string;
30
+ 'upd.localDirty': string;
31
+ 'upd.open': string;
32
+ 'upd.autoIsOn': string;
33
+ 'upd.autoIsOff': string;
34
+ 'upd.settings': string;
25
35
  'upd.checked': string;
26
36
  'upd.notYet': string;
27
37
  'upd.auto': string;
@@ -38,9 +48,6 @@ declare const en: {
38
48
  'upd.reloading': string;
39
49
  'upd.restartHint': string;
40
50
  'acct.title': string;
41
- 'theme.system': string;
42
- 'theme.light': string;
43
- 'theme.dark': string;
44
51
  'err.noAnswer': string;
45
52
  'err.session': string;
46
53
  'err.expired': string;
@@ -120,6 +127,7 @@ declare const en: {
120
127
  'why.health-fail': string;
121
128
  'why.signed-out': string;
122
129
  'why.not-installed': string;
130
+ 'why.broken': string;
123
131
  'why.quota-high': string;
124
132
  'why.health-warn': string;
125
133
  'tb.search': string;
@@ -134,6 +142,7 @@ declare const en: {
134
142
  'tb.used': string;
135
143
  'tb.left': string;
136
144
  'tb.view': string;
145
+ 'tb.display': string;
137
146
  'tb.list': string;
138
147
  'tb.cards': string;
139
148
  'tb.hideEmails': string;
@@ -151,6 +160,7 @@ declare const en: {
151
160
  'group.collapse': string;
152
161
  'row.collapse': string;
153
162
  'row.expand': string;
163
+ 'row.runs': string;
154
164
  'group.toggle': string;
155
165
  'group.attention.one': string;
156
166
  'group.attention.other': string;
@@ -172,6 +182,10 @@ declare const en: {
172
182
  'acct.installHost': string;
173
183
  'acct.nearLimit': string;
174
184
  'acct.installIt': string;
185
+ 'acct.installHostCmd': string;
186
+ 'acct.brokenHost': string;
187
+ 'acct.installDocs': string;
188
+ 'acct.broken': string;
175
189
  'quota.pctUsed': string;
176
190
  'quota.pctLeft': string;
177
191
  'quota.meterUsed': string;
@@ -182,6 +196,13 @@ declare const en: {
182
196
  'quota.dataFrom': string;
183
197
  'quota.asOf': string;
184
198
  'quota.sharedFrom': string;
199
+ 'quota.readFrom': string;
200
+ 'login.count': string;
201
+ 'login.label': string;
202
+ 'login.hint': string;
203
+ 'login.sharedAbove': string;
204
+ 'login.collapse': string;
205
+ 'login.expand': string;
185
206
  'quota.noWindows': string;
186
207
  'quota.off': string;
187
208
  'quota.turnOnFamily': string;
@@ -276,6 +297,32 @@ declare const en: {
276
297
  'desk.started': string;
277
298
  'desk.focused': string;
278
299
  'desk.failed': string;
300
+ 'br.open': string;
301
+ 'br.signedIn': string;
302
+ 'br.signedInMore': string;
303
+ 'br.hint': string;
304
+ 'br.title': string;
305
+ 'br.offIntro': string;
306
+ 'br.reads': string;
307
+ 'br.read1': string;
308
+ 'br.read2': string;
309
+ 'br.read3': string;
310
+ 'br.never': string;
311
+ 'br.enable': string;
312
+ 'br.enabled': string;
313
+ 'br.disable': string;
314
+ 'br.disabled': string;
315
+ 'br.local': string;
316
+ 'br.found': string;
317
+ 'br.none': string;
318
+ 'br.noMatch': string;
319
+ 'br.others': string;
320
+ 'br.all': string;
321
+ 'br.noChrome': string;
322
+ 'br.openIt': string;
323
+ 'br.opened': string;
324
+ 'br.failed': string;
325
+ 'br.changed': string;
279
326
  'app.apart': string;
280
327
  'app.apartTip': string;
281
328
  'app.mainName': string;
@@ -431,6 +478,7 @@ declare const en: {
431
478
  'nf.accounts.other': string;
432
479
  'nf.runOnce': string;
433
480
  'nf.notInstalled': string;
481
+ 'nf.broken': string;
434
482
  'nf.name': string;
435
483
  'nf.namePh': string;
436
484
  'nf.api': string;
@@ -531,6 +579,29 @@ declare const en: {
531
579
  'rc.confirm': string;
532
580
  'rc.done': string;
533
581
  'rc.failed': string;
582
+ 'set.button': string;
583
+ 'set.lang': string;
584
+ 'set.theme': string;
585
+ 'set.theme.system': string;
586
+ 'set.theme.light': string;
587
+ 'set.theme.dark': string;
588
+ 'set.rcOn': string;
589
+ 'rc.button': string;
590
+ 'rc.state.on': string;
591
+ 'rc.state.default': string;
592
+ 'rc.state.off': string;
593
+ 'rc.state.missing': string;
594
+ 'rc.state.blocked': string;
595
+ 'rc.dialogTitle': string;
596
+ 'rc.explain': string;
597
+ 'rc.allOff': string;
598
+ 'rc.undo': string;
599
+ 'rc.switch': string;
600
+ 'rc.confirmOn': string;
601
+ 'rc.onWarn': string;
602
+ 'rc.doneOn': string;
603
+ 'rc.failedOn': string;
604
+ 'rc.readOnlyBoth': string;
534
605
  'rc.undone': string;
535
606
  'banner.readOnly': string;
536
607
  'banner.plugins.one': string;