sideby 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -18
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +11 -1
- package/dist/src/cli/commands.js +144 -7
- package/dist/src/cli/json-schemas.js +56 -1
- package/dist/src/cli/main.js +119 -48
- package/dist/src/core/app-profiles.d.ts +50 -0
- package/dist/src/core/app-profiles.js +206 -0
- package/dist/src/core/brand-mark.d.ts +83 -0
- package/dist/src/core/brand-mark.js +88 -0
- package/dist/src/core/config.d.ts +16 -0
- package/dist/src/core/config.js +17 -2
- package/dist/src/core/desktop-app.d.ts +18 -1
- package/dist/src/core/desktop-app.js +22 -12
- package/dist/src/core/handoff-chain.d.ts +16 -2
- package/dist/src/core/handoff-chain.js +19 -6
- package/dist/src/core/handoff-run.js +43 -18
- package/dist/src/core/handoff-select.d.ts +4 -0
- package/dist/src/core/handoff-select.js +3 -2
- package/dist/src/core/update.d.ts +209 -0
- package/dist/src/core/update.js +631 -0
- package/dist/src/desktop/icon.js +293 -62
- package/dist/src/families/claude/handoff.js +10 -0
- package/dist/src/families/desktop.d.ts +25 -0
- package/dist/src/families/desktop.js +8 -2
- package/dist/src/families/grok/bot.d.ts +8 -0
- package/dist/src/families/grok/bot.js +18 -0
- package/dist/src/handoff/hook.js +21 -4
- package/dist/src/handoff/messages.d.ts +4 -0
- package/dist/src/handoff/messages.js +12 -0
- package/dist/src/panel/background.d.ts +2 -0
- package/dist/src/panel/background.js +7 -0
- package/dist/src/panel/handler.d.ts +35 -0
- package/dist/src/panel/handler.js +132 -6
- package/dist/src/panel/i18n.d.ts +59 -0
- package/dist/src/panel/i18n.js +118 -0
- package/dist/src/panel/page-script.js +282 -6
- package/dist/src/panel/page-styles.js +21 -2
- package/dist/src/panel/page.d.ts +2 -0
- package/dist/src/panel/page.js +6 -3
- package/dist/src/runtime.d.ts +3 -1
- package/dist/src/runtime.js +7 -0
- package/dist/src/types.d.ts +5 -0
- package/dist/testing/index.js +2 -0
- package/package.json +2 -2
- package/schemas/config.json +15 -0
- package/schemas/desktop-remove.json +33 -0
- package/schemas/desktop.json +2 -1
- package/schemas/handoff-ready.json +4 -0
- package/schemas/list.json +47 -0
- package/schemas/update.json +115 -0
- package/skills/sideby/SKILL.md +7 -1
- package/skills/sideby/references/handoff.md +1 -1
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type AppProfileStatus } from '../core/app-profiles.ts';
|
|
2
3
|
import { type HandoffSettings } from '../core/config.ts';
|
|
3
4
|
import { type AccountModel } from '../core/model-choice.ts';
|
|
4
5
|
import type { RemoteControlGroup } from '../core/remote-control.ts';
|
|
6
|
+
import * as update from '../core/update.ts';
|
|
5
7
|
import type { AccountStatus, DoctorHistory, FamilyInfo, Runtime } from '../runtime.ts';
|
|
6
8
|
import type { Account, FamilyLogo, QuotaResult, QuotaSetupPlan, UsageResult } from '../types.ts';
|
|
7
9
|
import { type PanelTheme } from './theme.ts';
|
|
@@ -30,6 +32,12 @@ export interface PanelHandlerOptions {
|
|
|
30
32
|
* any other value throws a TypeError. A Portal that changes language creates the handler again.
|
|
31
33
|
*/
|
|
32
34
|
lang?: 'auto' | 'en' | 'zh';
|
|
35
|
+
/**
|
|
36
|
+
* Whether the Panel offers sideby updates (spec §3.23, ADR-0021): the "new version" label, the Update button and
|
|
37
|
+
* the Auto Update switch. Default true; a Portal that manages sideby's version itself passes false, and the
|
|
38
|
+
* Panel then refuses `/api/update*` with 403.
|
|
39
|
+
*/
|
|
40
|
+
updates?: boolean;
|
|
33
41
|
}
|
|
34
42
|
export type PanelHandler = ((req: IncomingMessage, res: ServerResponse) => Promise<boolean>) & {
|
|
35
43
|
/** Per-instance secret the page sends as `x-sideby-token`. */
|
|
@@ -63,6 +71,29 @@ export declare function healthByAccount(history: DoctorHistory | undefined): Map
|
|
|
63
71
|
export type PanelFamily = FamilyInfo & {
|
|
64
72
|
logo?: FamilyLogo;
|
|
65
73
|
};
|
|
74
|
+
/** What the Panel shows about updates (spec §3.23); absent when the handler was created with `updates: false`. */
|
|
75
|
+
export interface PanelUpdate {
|
|
76
|
+
current: string;
|
|
77
|
+
/** The newest version the last Update Check saw; absent before the first one. */
|
|
78
|
+
latest?: string;
|
|
79
|
+
available: boolean;
|
|
80
|
+
checkedAt?: string;
|
|
81
|
+
/** Why Update Checks are off; absent when they are on. */
|
|
82
|
+
checksOff?: string;
|
|
83
|
+
/** Config `update.auto`. */
|
|
84
|
+
auto: boolean;
|
|
85
|
+
method: {
|
|
86
|
+
kind: update.InstallKind;
|
|
87
|
+
canUpdate: boolean;
|
|
88
|
+
reason?: string;
|
|
89
|
+
};
|
|
90
|
+
/** The command an Update runs, or the one to run by hand when `canRun` is false. */
|
|
91
|
+
command?: string;
|
|
92
|
+
canRun: boolean;
|
|
93
|
+
releaseNotes?: string;
|
|
94
|
+
/** The command that updates the sideby skill, which sideby never runs itself. */
|
|
95
|
+
skill: string;
|
|
96
|
+
}
|
|
66
97
|
export interface PanelState {
|
|
67
98
|
schemaVersion: 1;
|
|
68
99
|
version: string;
|
|
@@ -97,6 +128,10 @@ export interface PanelState {
|
|
|
97
128
|
};
|
|
98
129
|
/** Claude settings files grouped as `sideby remote-control` shows them (spec §3.22); absent without Claude Accounts. */
|
|
99
130
|
remoteControl?: RemoteControlView[];
|
|
131
|
+
/** Grok Bot windows (ADR-0019), data folders shown as `~/…`; absent when there are none to show. */
|
|
132
|
+
appProfiles?: AppProfileStatus[];
|
|
133
|
+
/** Update Check result and Auto Update setting (spec §3.23); absent with `updates: false`. */
|
|
134
|
+
update?: PanelUpdate;
|
|
100
135
|
}
|
|
101
136
|
/** The `handoff` map of the Panel state, from the statuses and Quota already read for the page. */
|
|
102
137
|
export declare function handoffByFamily(rt: Runtime, accounts: readonly PanelAccount[], now: Date): Record<string, string>;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// HTTP handler for the local Panel. Mountable under any basePath; standalone and embedded use the same rules.
|
|
2
2
|
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
3
3
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
4
|
-
import {
|
|
4
|
+
import { listAppProfiles, openAppProfile, removeAppProfile, } from '../core/app-profiles.js';
|
|
5
|
+
import { FAVICON_SVG, ICON_SVG } from '../core/brand-mark.js';
|
|
6
|
+
import { aliasArgs, aliasRefs, editConfigUpdate } from '../core/config.js';
|
|
5
7
|
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
6
8
|
import { UserError } from '../core/errors.js';
|
|
7
9
|
import { planHandoff } from '../core/handoff.js';
|
|
@@ -10,7 +12,9 @@ import { handoffOverview } from '../core/handoff-overview.js';
|
|
|
10
12
|
import { configureHandoff, handoffStatus } from '../core/handoff-status.js';
|
|
11
13
|
import { accountModel, setAccountModel } from '../core/model-choice.js';
|
|
12
14
|
import { tildify } from '../core/paths.js';
|
|
15
|
+
import { SKILL_INSTALL } from '../core/skill-check.js';
|
|
13
16
|
import { MIN_CALLS } from '../core/tuning.js';
|
|
17
|
+
import * as update from '../core/update.js';
|
|
14
18
|
import { buildId, packageVersion } from '../core/version.js';
|
|
15
19
|
import { HOOK_FAMILIES } from '../handoff/hook.js';
|
|
16
20
|
import { renderPage } from './page.js';
|
|
@@ -39,6 +43,36 @@ export function healthByAccount(history) {
|
|
|
39
43
|
});
|
|
40
44
|
return out;
|
|
41
45
|
}
|
|
46
|
+
// One Update Check at a time per process, started by a state read when one is due (the cache also keeps a failed
|
|
47
|
+
// one from being tried again within the hour, though the page asks every 20 s).
|
|
48
|
+
let checking = null;
|
|
49
|
+
/**
|
|
50
|
+
* The `update` part of the state. A Panel that may write starts an Update Check in the background when one is due;
|
|
51
|
+
* a read-only one only shows what the cache holds.
|
|
52
|
+
*/
|
|
53
|
+
async function updateState(rt, readOnly) {
|
|
54
|
+
const current = packageVersion();
|
|
55
|
+
const off = update.checksOff(rt.env, rt.config);
|
|
56
|
+
const cache = await update.readUpdateCache(rt.paths);
|
|
57
|
+
if (!off && !readOnly && !checking && update.checkDue(cache, new Date()))
|
|
58
|
+
checking = update.refreshUpdateCheck(rt.paths, rt.env).finally(() => {
|
|
59
|
+
checking = null;
|
|
60
|
+
});
|
|
61
|
+
const method = await update.installMethod(rt.env);
|
|
62
|
+
const plan = cache.latest ? update.shownPlan(update.planFrom(method, current, cache.latest, rt.env)) : null;
|
|
63
|
+
return {
|
|
64
|
+
current,
|
|
65
|
+
...(cache.latest ? { latest: cache.latest } : {}),
|
|
66
|
+
available: Boolean(plan?.available),
|
|
67
|
+
...(cache.checkedAt ? { checkedAt: cache.checkedAt } : {}),
|
|
68
|
+
...(off ? { checksOff: off } : {}),
|
|
69
|
+
auto: rt.config.update?.auto === true,
|
|
70
|
+
method: update.shownMethod(method),
|
|
71
|
+
...(plan ? { command: plan.command, releaseNotes: plan.releaseNotes } : {}),
|
|
72
|
+
canRun: Boolean(plan?.canRun),
|
|
73
|
+
skill: SKILL_INSTALL,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
42
76
|
/** The `handoff` map of the Panel state, from the statuses and Quota already read for the page. */
|
|
43
77
|
export function handoffByFamily(rt, accounts, now) {
|
|
44
78
|
const out = {};
|
|
@@ -202,17 +236,21 @@ function rcView(groups, home) {
|
|
|
202
236
|
}
|
|
203
237
|
async function buildState(rt, opts) {
|
|
204
238
|
const ctx = rt.readContext();
|
|
205
|
-
const [families, accounts, history, quotaSetups, remoteControl] = await Promise.all([
|
|
239
|
+
const [families, accounts, history, quotaSetups, remoteControl, upd] = await Promise.all([
|
|
206
240
|
rt.familyInfo(),
|
|
207
241
|
rt.accounts(),
|
|
208
242
|
rt.doctorHistory(),
|
|
209
243
|
rt.quotaSetups(),
|
|
210
244
|
rt.remoteControl(),
|
|
245
|
+
opts.updates ? updateState(rt, opts.readOnly).catch(() => undefined) : Promise.resolve(undefined),
|
|
211
246
|
]);
|
|
212
247
|
const health = healthByAccount(history);
|
|
213
248
|
const aliases = aliasesByAccount(aliasRefs(rt.config.aliases), accounts);
|
|
214
249
|
// Desktop Apps (spec §3.20): one process listing for every Account, run alongside the per-Account reads.
|
|
215
|
-
const states = await
|
|
250
|
+
const [states, appProfiles] = await Promise.all([
|
|
251
|
+
withDesktopApps(rt, accounts, Promise.all(accounts.map((a) => accountState(rt, a, families)))),
|
|
252
|
+
listAppProfiles(rt),
|
|
253
|
+
]);
|
|
216
254
|
const list = states.map((st) => {
|
|
217
255
|
const h = health.get(st.ref);
|
|
218
256
|
const names = aliases.get(st.ref);
|
|
@@ -244,12 +282,18 @@ async function buildState(rt, opts) {
|
|
|
244
282
|
...(rt.config.aliases ? { configAliases: aliasRefs(rt.config.aliases) } : {}),
|
|
245
283
|
...(Object.keys(handoff).length ? { handoff } : {}),
|
|
246
284
|
...(remoteControl.length ? { remoteControl: rcView(remoteControl, ctx.home) } : {}),
|
|
285
|
+
...(appProfiles.length
|
|
286
|
+
? {
|
|
287
|
+
appProfiles: appProfiles.map((p) => p.appData ? { ...p, appData: tildify(p.appData, rt.paths.home) } : p),
|
|
288
|
+
}
|
|
289
|
+
: {}),
|
|
247
290
|
autoHandoff: {
|
|
248
291
|
settings: rt.handoffSettings(),
|
|
249
292
|
starters: [...rt.families.values()]
|
|
250
293
|
.filter((f) => f.handoff?.starts.length && HOOK_FAMILIES.includes(f.id))
|
|
251
294
|
.map((f) => f.id),
|
|
252
295
|
},
|
|
296
|
+
...(upd ? { update: upd } : {}),
|
|
253
297
|
};
|
|
254
298
|
}
|
|
255
299
|
/** The `change` of a /api/handoff/configure body, with every field checked; unknown fields are refused. */
|
|
@@ -309,6 +353,7 @@ export function createPanelHandler(opts) {
|
|
|
309
353
|
const build = buildId();
|
|
310
354
|
const theme = resolveTheme(opts.theme);
|
|
311
355
|
const lang = resolveLang(opts.lang);
|
|
356
|
+
const updates = opts.updates !== false;
|
|
312
357
|
const token = randomBytes(32).toString('base64url');
|
|
313
358
|
const serial = writeQueue();
|
|
314
359
|
const getRuntime = typeof opts.runtime === 'function' ? opts.runtime : async () => opts.runtime;
|
|
@@ -479,6 +524,33 @@ export function createPanelHandler(opts) {
|
|
|
479
524
|
};
|
|
480
525
|
},
|
|
481
526
|
},
|
|
527
|
+
// Opens a Grok Bot window, creating it when the name is new, or brings it to the front (ADR-0019).
|
|
528
|
+
'/api/app-profile/open': {
|
|
529
|
+
async run(body) {
|
|
530
|
+
const ref = optString(body, 'ref');
|
|
531
|
+
if (!ref)
|
|
532
|
+
throw new HttpError(400, '"ref" is required');
|
|
533
|
+
const rt = await getRuntime();
|
|
534
|
+
const r = await openAppProfile(rt, ref);
|
|
535
|
+
const home = rt.paths.home;
|
|
536
|
+
return {
|
|
537
|
+
...r,
|
|
538
|
+
appPath: tildify(r.appPath, home),
|
|
539
|
+
...(r.appData ? { appData: tildify(r.appData, home) } : {}),
|
|
540
|
+
};
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
// Removes a Grok Bot window's data folder (ADR-0019). The page asks first, so this call is the confirmation.
|
|
544
|
+
'/api/app-profile/remove': {
|
|
545
|
+
async run(body) {
|
|
546
|
+
const ref = optString(body, 'ref');
|
|
547
|
+
if (!ref)
|
|
548
|
+
throw new HttpError(400, '"ref" is required');
|
|
549
|
+
const rt = await getRuntime();
|
|
550
|
+
const r = await removeAppProfile(rt, ref, { yes: true });
|
|
551
|
+
return { ...r, appData: tildify(r.appData, rt.paths.home) };
|
|
552
|
+
},
|
|
553
|
+
},
|
|
482
554
|
// The Auto Handoff page (spec §3.17): where work would go next and the last hand-overs. Reads only.
|
|
483
555
|
'/api/handoff/overview': {
|
|
484
556
|
writes: () => false,
|
|
@@ -507,7 +579,51 @@ export function createPanelHandler(opts) {
|
|
|
507
579
|
return { ...r, plan: show(r.plan) };
|
|
508
580
|
},
|
|
509
581
|
},
|
|
510
|
-
//
|
|
582
|
+
// Update (spec §3.23, ADR-0021): asks the registry, and runs the plan only with `confirm` and the `version` the
|
|
583
|
+
// person saw. When the registry names another version now, nothing runs and the new plan comes back (`changed`).
|
|
584
|
+
// It writes the cache even without `confirm`, so a read-only Panel refuses it (writes defaults to true).
|
|
585
|
+
'/api/update': {
|
|
586
|
+
async run(body) {
|
|
587
|
+
if (!updates)
|
|
588
|
+
throw new HttpError(403, 'updates are managed by the page that shows this panel');
|
|
589
|
+
const rt = await getRuntime();
|
|
590
|
+
const plan = await update.planUpdate(rt.paths, rt.env);
|
|
591
|
+
const shown = update.shownPlan(plan);
|
|
592
|
+
if (!optBool(body, 'confirm'))
|
|
593
|
+
return { ...shown, updated: false };
|
|
594
|
+
// A confirmation that runs nothing always says why: the registry names another version (or none newer) now.
|
|
595
|
+
if (body.version !== plan.latest || !plan.available)
|
|
596
|
+
return { ...shown, updated: false, changed: true };
|
|
597
|
+
const { backgroundPanelPort } = await import('./background.js');
|
|
598
|
+
const r = await update.applyUpdate(rt.paths, plan, rt.env, {
|
|
599
|
+
panelPort: () => backgroundPanelPort(rt.paths),
|
|
600
|
+
});
|
|
601
|
+
if (!r.ok)
|
|
602
|
+
throw new UserError(r.message);
|
|
603
|
+
return { ...shown, updated: true, message: r.message, restartedPanel: r.restartedPanel };
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
// The Auto Update switch: config `update.auto`, shown as a diff first like every config change.
|
|
607
|
+
'/api/update/settings': {
|
|
608
|
+
writes: (body) => body.confirm === true,
|
|
609
|
+
async run(body) {
|
|
610
|
+
if (!updates)
|
|
611
|
+
throw new HttpError(403, 'updates are managed by the page that shows this panel');
|
|
612
|
+
if (typeof body.auto !== 'boolean')
|
|
613
|
+
throw new HttpError(400, '"auto" must be true or false');
|
|
614
|
+
const auto = body.auto;
|
|
615
|
+
const rt = await getRuntime();
|
|
616
|
+
const r = await editConfigUpdate(rt.paths.configFile, (cur) => ({ ...cur, auto }), {
|
|
617
|
+
dryRun: !optBool(body, 'confirm'),
|
|
618
|
+
});
|
|
619
|
+
return {
|
|
620
|
+
...r,
|
|
621
|
+
applied: optBool(body, 'confirm') && r.changed,
|
|
622
|
+
file: tildify(rt.paths.configFile, rt.paths.home),
|
|
623
|
+
};
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
// Undo for the quota setup above; the Runtime turns a teardown that throws into `{ ok: false, message }`.
|
|
511
627
|
'/api/quota/teardown': {
|
|
512
628
|
async run(body) {
|
|
513
629
|
const family = optString(body, 'family');
|
|
@@ -555,7 +671,17 @@ export function createPanelHandler(opts) {
|
|
|
555
671
|
});
|
|
556
672
|
res.end(method === 'HEAD'
|
|
557
673
|
? undefined
|
|
558
|
-
: renderPage({ basePath, token, nonce, version, build, readOnly, theme, lang }));
|
|
674
|
+
: renderPage({ basePath, token, nonce, version, build, readOnly, theme, lang, updates }));
|
|
675
|
+
return true;
|
|
676
|
+
}
|
|
677
|
+
// The mark, for a Portal's tab and for this page's favicon (ADR-0006 amendment).
|
|
678
|
+
if (sub === '/icon.svg' || sub === '/favicon.svg') {
|
|
679
|
+
res.writeHead(200, {
|
|
680
|
+
'content-type': 'image/svg+xml; charset=utf-8',
|
|
681
|
+
'cache-control': 'no-cache',
|
|
682
|
+
'x-content-type-options': 'nosniff',
|
|
683
|
+
});
|
|
684
|
+
res.end(method === 'HEAD' ? undefined : sub === '/icon.svg' ? ICON_SVG : FAVICON_SVG);
|
|
559
685
|
return true;
|
|
560
686
|
}
|
|
561
687
|
if (sub === '/api/health') {
|
|
@@ -571,7 +697,7 @@ export function createPanelHandler(opts) {
|
|
|
571
697
|
return true;
|
|
572
698
|
}
|
|
573
699
|
if (sub === '/api/state') {
|
|
574
|
-
send(res, 200, await buildState(await getRuntime(), { version, build, readOnly }));
|
|
700
|
+
send(res, 200, await buildState(await getRuntime(), { version, build, readOnly, updates }));
|
|
575
701
|
return true;
|
|
576
702
|
}
|
|
577
703
|
if (Object.hasOwn(routes, sub))
|
package/dist/src/panel/i18n.d.ts
CHANGED
|
@@ -8,6 +8,35 @@ declare const en: {
|
|
|
8
8
|
'top.updatedTitle': string;
|
|
9
9
|
'top.language': string;
|
|
10
10
|
'foot.text': string;
|
|
11
|
+
'foot.textChecks': string;
|
|
12
|
+
'upd.label': string;
|
|
13
|
+
'upd.labelLong': string;
|
|
14
|
+
'upd.title': string;
|
|
15
|
+
'upd.loading': string;
|
|
16
|
+
'upd.current': string;
|
|
17
|
+
'upd.available': string;
|
|
18
|
+
'upd.notes': string;
|
|
19
|
+
'upd.runs': string;
|
|
20
|
+
'upd.after': string;
|
|
21
|
+
'upd.cannot': string;
|
|
22
|
+
'upd.skill': string;
|
|
23
|
+
'upd.checksOff': string;
|
|
24
|
+
'upd.latest': string;
|
|
25
|
+
'upd.checked': string;
|
|
26
|
+
'upd.notYet': string;
|
|
27
|
+
'upd.auto': string;
|
|
28
|
+
'upd.autoHint': string;
|
|
29
|
+
'upd.autoNo': string;
|
|
30
|
+
'upd.autoChange': string;
|
|
31
|
+
'upd.save': string;
|
|
32
|
+
'upd.changed': string;
|
|
33
|
+
'upd.autoOnSaved': string;
|
|
34
|
+
'upd.autoOffSaved': string;
|
|
35
|
+
'upd.now': string;
|
|
36
|
+
'upd.updating': string;
|
|
37
|
+
'upd.done': string;
|
|
38
|
+
'upd.reloading': string;
|
|
39
|
+
'upd.restartHint': string;
|
|
11
40
|
'acct.title': string;
|
|
12
41
|
'theme.system': string;
|
|
13
42
|
'theme.light': string;
|
|
@@ -247,6 +276,36 @@ declare const en: {
|
|
|
247
276
|
'desk.started': string;
|
|
248
277
|
'desk.focused': string;
|
|
249
278
|
'desk.failed': string;
|
|
279
|
+
'app.apart': string;
|
|
280
|
+
'app.apartTip': string;
|
|
281
|
+
'app.mainName': string;
|
|
282
|
+
'app.mainData': string;
|
|
283
|
+
'app.isOpen': string;
|
|
284
|
+
'app.signedIn': string;
|
|
285
|
+
'app.signedOut': string;
|
|
286
|
+
'app.signedInHint': string;
|
|
287
|
+
'app.openBtn': string;
|
|
288
|
+
'app.show': string;
|
|
289
|
+
'app.openHint': string;
|
|
290
|
+
'app.showHint': string;
|
|
291
|
+
'app.started': string;
|
|
292
|
+
'app.failed': string;
|
|
293
|
+
'app.remove': string;
|
|
294
|
+
'app.removeHint': string;
|
|
295
|
+
'app.removeRunning': string;
|
|
296
|
+
'app.removeAsk': string;
|
|
297
|
+
'app.removeYes': string;
|
|
298
|
+
'app.removing': string;
|
|
299
|
+
'app.removed': string;
|
|
300
|
+
'app.removeFailed': string;
|
|
301
|
+
'app.another': string;
|
|
302
|
+
'app.anotherHint': string;
|
|
303
|
+
'app.nameLabel': string;
|
|
304
|
+
'app.create': string;
|
|
305
|
+
'app.newHint': string;
|
|
306
|
+
'app.exists': string;
|
|
307
|
+
'app.nameEmpty': string;
|
|
308
|
+
'app.nameBad': string;
|
|
250
309
|
'md.title': string;
|
|
251
310
|
'md.intro': string;
|
|
252
311
|
'md.model': string;
|
package/dist/src/panel/i18n.js
CHANGED
|
@@ -10,6 +10,35 @@ const en = {
|
|
|
10
10
|
'top.updatedTitle': 'Last loaded at {at}. Refreshes on its own every {s} s while this page is open.',
|
|
11
11
|
'top.language': 'Switch language',
|
|
12
12
|
'foot.text': 'runs on this machine; nothing leaves it.',
|
|
13
|
+
'foot.textChecks': 'runs on this machine; once a day it asks npm for the newest sideby version, nothing else leaves it.',
|
|
14
|
+
'upd.label': 'v{v} available',
|
|
15
|
+
'upd.labelLong': 'sideby {v} is available (you have {cur}). Click to see what updating does.',
|
|
16
|
+
'upd.title': 'sideby updates',
|
|
17
|
+
'upd.loading': 'Loading…',
|
|
18
|
+
'upd.current': 'You have sideby {v}.',
|
|
19
|
+
'upd.available': 'sideby {v} is available.',
|
|
20
|
+
'upd.notes': 'What changed',
|
|
21
|
+
'upd.runs': 'Updating runs:',
|
|
22
|
+
'upd.after': 'A background panel restarts by itself and this page reloads. A sideby ui running in a terminal and sessions already running keep the old version until you restart them.',
|
|
23
|
+
'upd.cannot': 'sideby cannot update itself here: {why}. Run this yourself:',
|
|
24
|
+
'upd.skill': 'If you use the sideby skill, update it too:',
|
|
25
|
+
'upd.checksOff': 'Update checks are off ({why}).',
|
|
26
|
+
'upd.latest': 'This is the newest version.',
|
|
27
|
+
'upd.checked': 'checked {ago}',
|
|
28
|
+
'upd.notYet': 'sideby has not checked for a new version yet. It checks once a day.',
|
|
29
|
+
'upd.auto': 'Update by itself',
|
|
30
|
+
'upd.autoHint': 'Installs a new version after the daily check, when no sideby session is running. Never a major version.',
|
|
31
|
+
'upd.autoNo': 'Not available for this install: sideby cannot update itself here.',
|
|
32
|
+
'upd.autoChange': 'Sets update.auto to {value} in {file}.',
|
|
33
|
+
'upd.save': 'Save',
|
|
34
|
+
'upd.changed': 'A newer version, {v}, came out while this was open. Nothing was installed; look again and update.',
|
|
35
|
+
'upd.autoOnSaved': 'sideby now updates by itself',
|
|
36
|
+
'upd.autoOffSaved': 'sideby no longer updates by itself',
|
|
37
|
+
'upd.now': 'Update to {v}',
|
|
38
|
+
'upd.updating': 'Updating…',
|
|
39
|
+
'upd.done': 'Updated to sideby {v}.',
|
|
40
|
+
'upd.reloading': 'Waiting for the new version; this page reloads by itself.',
|
|
41
|
+
'upd.restartHint': 'This page still runs the old version. Restart sideby ui (or reload the page in your portal) to use the new one.',
|
|
13
42
|
'acct.title': 'Accounts',
|
|
14
43
|
'theme.system': 'Theme: follow system',
|
|
15
44
|
'theme.light': 'Theme: light',
|
|
@@ -249,6 +278,36 @@ const en = {
|
|
|
249
278
|
'desk.started': 'Opened {app} as {ref}.',
|
|
250
279
|
'desk.focused': '{app} for {ref} is in front.',
|
|
251
280
|
'desk.failed': 'Could not open the desktop app for {ref}: {msg}',
|
|
281
|
+
'app.apart': 'Signs in on its own',
|
|
282
|
+
'app.apartTip': '{app} signs in with a Cursor account, not with any account above. Each window here keeps its own sign-in, so several can be open at once.',
|
|
283
|
+
'app.mainName': 'main',
|
|
284
|
+
'app.mainData': 'the app’s own data',
|
|
285
|
+
'app.isOpen': 'Open',
|
|
286
|
+
'app.signedIn': 'Signed in',
|
|
287
|
+
'app.signedOut': 'Not signed in',
|
|
288
|
+
'app.signedInHint': 'What the app last reported. Which account is signed in is not shown: sideby does not read sign-in files.',
|
|
289
|
+
'app.openBtn': 'Open',
|
|
290
|
+
'app.show': 'Switch to it',
|
|
291
|
+
'app.openHint': 'Open {app} as {ref}, in its own window next to the others',
|
|
292
|
+
'app.showHint': '{app} is open as {ref}; click to bring it to the front',
|
|
293
|
+
'app.started': 'Opened {app} as {ref}.',
|
|
294
|
+
'app.failed': 'Could not open {ref}: {msg}',
|
|
295
|
+
'app.remove': 'Remove',
|
|
296
|
+
'app.removeHint': 'Remove this {app} window: signs it out and deletes its local data on this Mac',
|
|
297
|
+
'app.removeRunning': 'Quit this {app} window first, then remove it',
|
|
298
|
+
'app.removeAsk': 'Remove {ref}? This signs that window out and deletes its local data on this Mac.',
|
|
299
|
+
'app.removeYes': 'Remove',
|
|
300
|
+
'app.removing': 'Removing…',
|
|
301
|
+
'app.removed': 'Removed {ref}.',
|
|
302
|
+
'app.removeFailed': 'Could not remove {ref}: {msg}',
|
|
303
|
+
'app.another': 'Open another {app}',
|
|
304
|
+
'app.anotherHint': 'A new window with its own sign-in.',
|
|
305
|
+
'app.nameLabel': 'Name for the new window',
|
|
306
|
+
'app.create': 'Create and open',
|
|
307
|
+
'app.newHint': 'Sign in inside the new window with the account you want for it. Close the other {app} windows while you sign in.',
|
|
308
|
+
'app.exists': '{ref} already exists; this opens it.',
|
|
309
|
+
'app.nameEmpty': 'Enter a name.',
|
|
310
|
+
'app.nameBad': 'Use lowercase letters, digits and dashes, starting with a letter or digit.',
|
|
252
311
|
'md.title': 'Model and effort',
|
|
253
312
|
'md.intro': 'Applies to new sessions of {ref} that you start with sideby (a short command or sideby run). It is saved in sideby’s config; {host}’s own settings stay as they are.',
|
|
254
313
|
'md.model': 'Model',
|
|
@@ -489,6 +548,35 @@ const zh = {
|
|
|
489
548
|
'top.updatedTitle': '上次加载于 {at}。页面打开时每 {s} 秒自动刷新。',
|
|
490
549
|
'top.language': '切换语言',
|
|
491
550
|
'foot.text': '只在本机运行,数据不会离开这台电脑。',
|
|
551
|
+
'foot.textChecks': '只在本机运行;每天向 npm 查一次 sideby 最新版本号,除此之外没有数据离开这台电脑。',
|
|
552
|
+
'upd.label': 'v{v} 可更新',
|
|
553
|
+
'upd.labelLong': 'sideby {v} 已发布(当前 {cur})。点击查看更新会做什么。',
|
|
554
|
+
'upd.title': 'sideby 更新',
|
|
555
|
+
'upd.loading': '加载中…',
|
|
556
|
+
'upd.current': '当前版本 sideby {v}。',
|
|
557
|
+
'upd.available': 'sideby {v} 已发布。',
|
|
558
|
+
'upd.notes': '更新内容',
|
|
559
|
+
'upd.runs': '更新会运行:',
|
|
560
|
+
'upd.after': '后台面板会自己重启,本页随后自动刷新。终端里运行的 sideby ui 和已经在跑的会话要重启后才用新版本。',
|
|
561
|
+
'upd.cannot': '这种安装方式下 sideby 不能自己更新:{why}。请自己运行:',
|
|
562
|
+
'upd.skill': '如果你装了 sideby skill,也更新一下:',
|
|
563
|
+
'upd.checksOff': '检查更新已关闭({why})。',
|
|
564
|
+
'upd.latest': '已是最新版本。',
|
|
565
|
+
'upd.checked': '{ago}检查',
|
|
566
|
+
'upd.notYet': 'sideby 还没检查过新版本,每天检查一次。',
|
|
567
|
+
'upd.auto': '自动更新',
|
|
568
|
+
'upd.autoHint': '每天检查后,在没有 sideby 会话运行时安装新版本。不会自动装大版本。',
|
|
569
|
+
'upd.autoNo': '这种安装方式不支持:sideby 不能自己更新。',
|
|
570
|
+
'upd.autoChange': '会把 {file} 里的 update.auto 改成 {value}。',
|
|
571
|
+
'upd.save': '保存',
|
|
572
|
+
'upd.changed': '对话框打开期间又发布了新版本 {v}。什么都没安装;请再确认一次后更新。',
|
|
573
|
+
'upd.autoOnSaved': '已开启自动更新',
|
|
574
|
+
'upd.autoOffSaved': '已关闭自动更新',
|
|
575
|
+
'upd.now': '更新到 {v}',
|
|
576
|
+
'upd.updating': '更新中…',
|
|
577
|
+
'upd.done': '已更新到 sideby {v}。',
|
|
578
|
+
'upd.reloading': '等待新版本启动,本页会自动刷新。',
|
|
579
|
+
'upd.restartHint': '本页仍在运行旧版本。重启 sideby ui(在 Portal 里就刷新页面)后使用新版本。',
|
|
492
580
|
'acct.title': '账号',
|
|
493
581
|
'theme.system': '主题:跟随系统',
|
|
494
582
|
'theme.light': '主题:浅色',
|
|
@@ -728,6 +816,36 @@ const zh = {
|
|
|
728
816
|
'desk.started': '已以 {ref} 打开 {app}。',
|
|
729
817
|
'desk.focused': '{ref} 的 {app} 已切到前台。',
|
|
730
818
|
'desk.failed': '没能打开 {ref} 的桌面应用:{msg}',
|
|
819
|
+
'app.apart': '单独登录',
|
|
820
|
+
'app.apartTip': '{app} 用 Cursor 账号登录,和上面的账号都无关。这里每个窗口各自保存登录,可以同时开几个。',
|
|
821
|
+
'app.mainName': 'main',
|
|
822
|
+
'app.mainData': '应用自己的数据',
|
|
823
|
+
'app.isOpen': '已打开',
|
|
824
|
+
'app.signedIn': '已登录',
|
|
825
|
+
'app.signedOut': '未登录',
|
|
826
|
+
'app.signedInHint': '应用最后一次报告的状态。不显示登录的是哪个账号:sideby 不读登录文件。',
|
|
827
|
+
'app.openBtn': '打开',
|
|
828
|
+
'app.show': '切到窗口',
|
|
829
|
+
'app.openHint': '以 {ref} 打开 {app},和其他窗口同时开着',
|
|
830
|
+
'app.showHint': '{app} 已经以 {ref} 打开,点一下切到前台',
|
|
831
|
+
'app.started': '已以 {ref} 打开 {app}。',
|
|
832
|
+
'app.failed': '没能打开 {ref}:{msg}',
|
|
833
|
+
'app.remove': '删除',
|
|
834
|
+
'app.removeHint': '删除这个 {app} 窗口:退出它的登录,删掉它在这台 Mac 上的数据',
|
|
835
|
+
'app.removeRunning': '先退出这个 {app} 窗口,再删除',
|
|
836
|
+
'app.removeAsk': '删除 {ref}?会退出这个窗口的登录,删掉它在这台 Mac 上的数据。',
|
|
837
|
+
'app.removeYes': '删除',
|
|
838
|
+
'app.removing': '正在删除…',
|
|
839
|
+
'app.removed': '已删除 {ref}。',
|
|
840
|
+
'app.removeFailed': '没能删除 {ref}:{msg}',
|
|
841
|
+
'app.another': '再开一个 {app}',
|
|
842
|
+
'app.anotherHint': '新窗口单独登录。',
|
|
843
|
+
'app.nameLabel': '新窗口的名字',
|
|
844
|
+
'app.create': '新建并打开',
|
|
845
|
+
'app.newHint': '在新窗口里用你想给它的账号登录。登录时先关掉其他 {app} 窗口。',
|
|
846
|
+
'app.exists': '{ref} 已经有了,点了会直接打开它。',
|
|
847
|
+
'app.nameEmpty': '请填一个名字。',
|
|
848
|
+
'app.nameBad': '只能用小写字母、数字和横线,以字母或数字开头。',
|
|
731
849
|
'md.title': '模型和 effort',
|
|
732
850
|
'md.intro': '只影响之后用 sideby 启动的 {ref} 新会话(短命令或 sideby run)。保存在 sideby 的配置里,不改 {host} 自己的设置。',
|
|
733
851
|
'md.model': '模型',
|