sideby 0.16.0 → 0.17.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 +5 -3
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +16 -0
- package/dist/src/cli/commands.js +460 -5
- package/dist/src/cli/json-schemas.js +115 -0
- package/dist/src/cli/main.js +93 -3
- package/dist/src/core/ask.d.ts +150 -0
- package/dist/src/core/ask.js +365 -0
- package/dist/src/core/attachments.d.ts +50 -0
- package/dist/src/core/attachments.js +442 -0
- package/dist/src/core/autostart.d.ts +88 -0
- package/dist/src/core/autostart.js +256 -0
- package/dist/src/core/config.d.ts +97 -0
- package/dist/src/core/config.js +278 -10
- package/dist/src/core/launch.d.ts +15 -4
- package/dist/src/core/launch.js +27 -23
- package/dist/src/core/local-build.d.ts +17 -0
- package/dist/src/core/local-build.js +39 -3
- package/dist/src/core/probe.d.ts +78 -0
- package/dist/src/core/probe.js +127 -0
- package/dist/src/core/same-login.d.ts +9 -4
- package/dist/src/core/same-login.js +9 -4
- package/dist/src/core/secret-file.d.ts +26 -0
- package/dist/src/core/secret-file.js +126 -7
- package/dist/src/core/terminal.d.ts +93 -0
- package/dist/src/core/terminal.js +371 -0
- package/dist/src/core/update.d.ts +17 -0
- package/dist/src/core/update.js +63 -1
- package/dist/src/families/claude/index.js +3 -0
- package/dist/src/families/claude/quota-cache.d.ts +24 -7
- package/dist/src/families/claude/quota-cache.js +72 -21
- package/dist/src/families/codex/index.js +7 -0
- package/dist/src/families/codex/login-key.d.ts +8 -0
- package/dist/src/families/codex/login-key.js +33 -0
- package/dist/src/families/grok/index.js +3 -0
- package/dist/src/families/login-keys.d.ts +6 -0
- package/dist/src/families/login-keys.js +6 -0
- package/dist/src/families/pi/index.js +4 -0
- package/dist/src/panel/background.js +3 -2
- package/dist/src/panel/handler.d.ts +29 -2
- package/dist/src/panel/handler.js +396 -9
- package/dist/src/panel/i18n.d.ts +175 -2
- package/dist/src/panel/i18n.js +381 -35
- package/dist/src/panel/page-logic.d.ts +214 -0
- package/dist/src/panel/page-logic.js +394 -0
- package/dist/src/panel/page-script.js +984 -134
- package/dist/src/panel/page-styles.js +69 -19
- package/dist/src/panel/page.d.ts +5 -0
- package/dist/src/panel/page.js +2 -0
- package/dist/src/panel/server.d.ts +16 -2
- package/dist/src/panel/server.js +41 -13
- package/dist/src/plugins/check.d.ts +47 -0
- package/dist/src/plugins/check.js +544 -0
- package/dist/src/plugins/scaffold.d.ts +21 -0
- package/dist/src/plugins/scaffold.js +123 -0
- package/dist/src/quota/statusline-tap.js +1 -1
- package/dist/src/runtime.d.ts +10 -0
- package/dist/src/runtime.js +47 -16
- package/dist/src/types.d.ts +19 -0
- package/package.json +2 -2
- package/schemas/ask.json +180 -0
- package/schemas/autostart.json +165 -0
- package/schemas/config-get.json +132 -0
- package/schemas/config-set.json +107 -0
- package/schemas/config.json +73 -6
- package/schemas/key.json +93 -0
- package/schemas/local.json +4 -0
- package/schemas/plugins-check.json +113 -0
- package/schemas/plugins-new.json +87 -0
- package/schemas/terminal.json +152 -0
- package/schemas/try.json +109 -0
- package/schemas/update.json +4 -0
- package/skills/sideby/SKILL.md +12 -6
- package/skills/sideby/references/accounts.md +1 -1
- package/skills/sideby/references/ai-tasks.md +116 -0
- package/skills/sideby/references/panel.md +14 -0
- package/skills/sideby/references/update.md +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The Claude Quota cache: one module owns where it lives, how the Account is named, and how it is written,
|
|
2
2
|
// read and validated. The status line tap writes it on every refresh, so this module imports only leaf
|
|
3
3
|
// modules and Node built-ins (never the Family index, the Runtime or core/accounts.ts).
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
4
5
|
import { mkdir, readFile } from 'node:fs/promises';
|
|
5
6
|
import { homedir } from 'node:os';
|
|
6
7
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
@@ -8,6 +9,9 @@ import { ACCOUNT_NAME } from '../../core/account-name.js';
|
|
|
8
9
|
import { writeFileAtomic } from '../../core/fs-safe.js';
|
|
9
10
|
import { resolvePaths } from '../../core/paths.js';
|
|
10
11
|
import { CLAUDE_ACCOUNT_PREFIX, CLAUDE_MAIN_DIR } from './layout.js';
|
|
12
|
+
const MAX_SESSIONS = 32;
|
|
13
|
+
const SESSION_DAYS = 8;
|
|
14
|
+
const SESSION_ID = /^[A-Za-z0-9._-]{1,128}$/;
|
|
11
15
|
const WINDOWS = [
|
|
12
16
|
{ key: 'five_hour', label: '5h', minutes: 300 },
|
|
13
17
|
{ key: 'seven_day', label: '7d', minutes: 10_080 },
|
|
@@ -32,7 +36,7 @@ export function accountNameFromEnv(env) {
|
|
|
32
36
|
const name = base.slice(CLAUDE_ACCOUNT_PREFIX.length);
|
|
33
37
|
return name !== 'main' && ACCOUNT_NAME.test(name) ? name : null;
|
|
34
38
|
}
|
|
35
|
-
/** Extracts the 5h and 7d windows from status line JSON; null when there is nothing usable. */
|
|
39
|
+
/** Extracts the 5h and 7d windows and the session id from status line JSON; null when there is nothing usable. */
|
|
36
40
|
export function parseRateLimits(input, observedAt) {
|
|
37
41
|
let data;
|
|
38
42
|
try {
|
|
@@ -62,7 +66,15 @@ export function parseRateLimits(input, observedAt) {
|
|
|
62
66
|
resetsAt: new Date(resets * 1000).toISOString(),
|
|
63
67
|
});
|
|
64
68
|
}
|
|
65
|
-
|
|
69
|
+
if (!windows.length)
|
|
70
|
+
return null;
|
|
71
|
+
const id = data.session_id;
|
|
72
|
+
return {
|
|
73
|
+
version: 1,
|
|
74
|
+
observedAt: observedAt.toISOString(),
|
|
75
|
+
windows,
|
|
76
|
+
...(typeof id === 'string' && SESSION_ID.test(id) ? { sessionId: id } : {}),
|
|
77
|
+
};
|
|
66
78
|
}
|
|
67
79
|
/** Two readings belong to one window when their resets are less than a quarter of the window apart. */
|
|
68
80
|
function sameWindow(a, b) {
|
|
@@ -76,33 +88,56 @@ const order = (label) => {
|
|
|
76
88
|
const i = WINDOWS.findIndex((w) => w.label === label);
|
|
77
89
|
return i < 0 ? WINDOWS.length : i;
|
|
78
90
|
};
|
|
91
|
+
/** What a session reported, as a short hash: equal hashes mean the session has made no request since. */
|
|
92
|
+
function windowsHash(windows) {
|
|
93
|
+
const key = windows.map((w) => [w.label, w.usedPercent, w.resetsAt]);
|
|
94
|
+
return createHash('sha256').update(JSON.stringify(key)).digest('hex').slice(0, 16);
|
|
95
|
+
}
|
|
96
|
+
/** The newest {@link MAX_SESSIONS} sessions seen in the last {@link SESSION_DAYS} days before `now`. */
|
|
97
|
+
function pruneSessions(sessions, now) {
|
|
98
|
+
const oldest = Date.parse(now) - SESSION_DAYS * 86_400_000;
|
|
99
|
+
const kept = Object.entries(sessions)
|
|
100
|
+
.filter(([, s]) => Date.parse(s.seenAt) >= oldest)
|
|
101
|
+
.sort((a, b) => Date.parse(b[1].seenAt) - Date.parse(a[1].seenAt))
|
|
102
|
+
.slice(0, MAX_SESSIONS);
|
|
103
|
+
return Object.fromEntries(kept);
|
|
104
|
+
}
|
|
79
105
|
/**
|
|
80
|
-
* Merges a status line reading into the
|
|
81
|
-
* idle session of the same Account
|
|
82
|
-
*
|
|
83
|
-
*
|
|
106
|
+
* Merges a status line reading into the cache (spec §3.7). Each session reports the Quota of its own last request, so
|
|
107
|
+
* an idle session of the same Account keeps repeating older numbers. A reading whose session reported exactly these
|
|
108
|
+
* windows before carries nothing new: null, so nothing is written. A reading that changed, or the first of a session,
|
|
109
|
+
* is a fresh request result and wins for every window it has, the vendor's resets included; a window it lacks is
|
|
110
|
+
* kept. A reading without a session id falls back to: within one window the higher use wins, otherwise the reading
|
|
111
|
+
* wins; null when that only repeats the cache.
|
|
84
112
|
*/
|
|
85
113
|
export function mergeQuotaCache(cached, next) {
|
|
114
|
+
const { sessionId, ...reading } = next;
|
|
115
|
+
const before = cached?.windows ?? [];
|
|
116
|
+
const keepLacking = (windows) => {
|
|
117
|
+
for (const c of before)
|
|
118
|
+
if (!windows.some((w) => w.label === c.label))
|
|
119
|
+
windows.push(c);
|
|
120
|
+
return windows.sort((a, b) => order(a.label) - order(b.label));
|
|
121
|
+
};
|
|
122
|
+
if (sessionId) {
|
|
123
|
+
const hash = windowsHash(reading.windows);
|
|
124
|
+
if (cached?.sessions?.[sessionId]?.hash === hash)
|
|
125
|
+
return null;
|
|
126
|
+
const sessions = pruneSessions({ ...cached?.sessions, [sessionId]: { hash, seenAt: reading.observedAt } }, reading.observedAt);
|
|
127
|
+
return { ...reading, windows: keepLacking([...reading.windows]), sessions };
|
|
128
|
+
}
|
|
86
129
|
let older = false;
|
|
87
|
-
const windows =
|
|
88
|
-
const c =
|
|
89
|
-
|
|
90
|
-
return w;
|
|
91
|
-
const keep = sameWindow(c, w)
|
|
92
|
-
? c.usedPercent > w.usedPercent
|
|
93
|
-
: Date.parse(c.resetsAt) > Date.parse(w.resetsAt);
|
|
130
|
+
const windows = keepLacking(reading.windows.map((w) => {
|
|
131
|
+
const c = before.find((x) => x.label === w.label);
|
|
132
|
+
const keep = !!c && sameWindow(c, w) && c.usedPercent > w.usedPercent;
|
|
94
133
|
if (keep)
|
|
95
134
|
older = true;
|
|
96
135
|
return keep ? c : w;
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
if (!windows.some((w) => w.label === c.label))
|
|
100
|
-
windows.push(c);
|
|
101
|
-
windows.sort((a, b) => order(a.label) - order(b.label));
|
|
102
|
-
const unchanged = windows.length === cached.length && windows.every((w) => cached.some((c) => sameReading(c, w)));
|
|
136
|
+
}));
|
|
137
|
+
const unchanged = windows.length === before.length && windows.every((w) => before.some((c) => sameReading(c, w)));
|
|
103
138
|
if (older && unchanged)
|
|
104
139
|
return null;
|
|
105
|
-
return { ...
|
|
140
|
+
return { ...reading, windows, ...(cached?.sessions ? { sessions: cached.sessions } : {}) };
|
|
106
141
|
}
|
|
107
142
|
/** Writes the cache atomically; creates the private parent directory first (writeFileAtomic does not). */
|
|
108
143
|
export async function writeQuotaCache(stateDir, name, cache) {
|
|
@@ -147,5 +182,21 @@ export async function readQuotaCache(stateDir, name) {
|
|
|
147
182
|
!Number.isFinite(Date.parse(cache.observedAt)) ||
|
|
148
183
|
!windows.length)
|
|
149
184
|
return { status: 'unrecognized', detail: `${file} has no usable quota windows` };
|
|
150
|
-
|
|
185
|
+
const sessions = validSessions(cache.sessions);
|
|
186
|
+
return { status: 'ok', observedAt: cache.observedAt, windows, ...(sessions ? { sessions } : {}) };
|
|
187
|
+
}
|
|
188
|
+
/** The well-formed session entries of a cache file; undefined when it has none (older files). */
|
|
189
|
+
function validSessions(raw) {
|
|
190
|
+
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
191
|
+
return undefined;
|
|
192
|
+
const out = {};
|
|
193
|
+
for (const [id, v] of Object.entries(raw)) {
|
|
194
|
+
const s = v;
|
|
195
|
+
if (SESSION_ID.test(id) &&
|
|
196
|
+
typeof s?.hash === 'string' &&
|
|
197
|
+
typeof s.seenAt === 'string' &&
|
|
198
|
+
Number.isFinite(Date.parse(s.seenAt)))
|
|
199
|
+
out[id] = { hash: s.hash, seenAt: s.seenAt };
|
|
200
|
+
}
|
|
201
|
+
return Object.keys(out).length ? out : undefined;
|
|
151
202
|
}
|
|
@@ -31,6 +31,13 @@ export const codexFamily = {
|
|
|
31
31
|
layout: { main: '.codex', account: '.codex-<name>' },
|
|
32
32
|
hijackVars: ['OPENAI_API_KEY', 'OPENAI_BASE_URL', 'CODEX_API_BASE_URL'],
|
|
33
33
|
apiVars: ['OPENAI_BASE_URL', 'OPENAI_API_KEY'],
|
|
34
|
+
secretVar: 'OPENAI_API_KEY',
|
|
35
|
+
// `exec` refuses to run outside a git repo unless this flag is set (Codex 0.163.0 --help:
|
|
36
|
+
// allow running outside a Git repository). The probe's cwd is a temp directory.
|
|
37
|
+
probe: { args: ['exec', '--skip-git-repo-check', 'Reply with OK'] },
|
|
38
|
+
// `-i, --image <FILE>...` takes several values (Codex 0.163.0 --help); AI Tasks pass `--image=<path>`, one value
|
|
39
|
+
// per flag, so the prompt after it stays a positional argument.
|
|
40
|
+
imageArg: '--image',
|
|
34
41
|
sharedItems: [
|
|
35
42
|
{ path: 'AGENTS.md', mode: 'link' },
|
|
36
43
|
{ path: 'hooks.json', mode: 'link' },
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Account } from '../../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* A hash of `chatgpt_account_id` (the workspace) and `chatgpt_user_id` under the auth claim of `tokens.id_token`,
|
|
4
|
+
* cut to 16 hex characters, only for comparing two Accounts while the Runtime groups a Same Login. The payload is
|
|
5
|
+
* decoded as for the email (core/identity.ts); the token, both ids and every other claim are dropped at once.
|
|
6
|
+
* Undefined on any problem: the Account then simply does not group.
|
|
7
|
+
*/
|
|
8
|
+
export declare function codexLoginKey(account: Account): Promise<string | undefined>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Codex's Login Key (ADR-0028): which ChatGPT workspace and user an Account is signed in as, kept only as a hash.
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const AUTH_CLAIM = 'https://api.openai.com/auth';
|
|
6
|
+
// The shapes Codex writes; anything else gives no key rather than a key built from an unexpected value.
|
|
7
|
+
const ACCOUNT_ID = /^[A-Za-z0-9-]{8,64}$/;
|
|
8
|
+
const USER_ID = /^user-[A-Za-z0-9]{8,64}$/;
|
|
9
|
+
/**
|
|
10
|
+
* A hash of `chatgpt_account_id` (the workspace) and `chatgpt_user_id` under the auth claim of `tokens.id_token`,
|
|
11
|
+
* cut to 16 hex characters, only for comparing two Accounts while the Runtime groups a Same Login. The payload is
|
|
12
|
+
* decoded as for the email (core/identity.ts); the token, both ids and every other claim are dropped at once.
|
|
13
|
+
* Undefined on any problem: the Account then simply does not group.
|
|
14
|
+
*/
|
|
15
|
+
export async function codexLoginKey(account) {
|
|
16
|
+
try {
|
|
17
|
+
const token = JSON.parse(await readFile(join(account.dir, 'auth.json'), 'utf8'))?.tokens?.id_token;
|
|
18
|
+
if (typeof token !== 'string')
|
|
19
|
+
return undefined;
|
|
20
|
+
const parts = token.split('.');
|
|
21
|
+
if (parts.length !== 3 || !parts[1])
|
|
22
|
+
return undefined;
|
|
23
|
+
const auth = JSON.parse(Buffer.from(parts[1], 'base64url').toString('utf8'))?.[AUTH_CLAIM];
|
|
24
|
+
const ws = auth?.chatgpt_account_id;
|
|
25
|
+
const user = auth?.chatgpt_user_id;
|
|
26
|
+
if (typeof ws !== 'string' || !ACCOUNT_ID.test(ws) || typeof user !== 'string' || !USER_ID.test(user))
|
|
27
|
+
return undefined;
|
|
28
|
+
return createHash('sha256').update(`codex\0${ws}\0${user}`).digest('hex').slice(0, 16);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -49,6 +49,9 @@ export const grokFamily = {
|
|
|
49
49
|
layout: { main: '.grok', account: '.grok-<name>' },
|
|
50
50
|
hijackVars: ['XAI_API_KEY', 'GROK_MODELS_BASE_URL'],
|
|
51
51
|
apiVars: ['GROK_MODELS_BASE_URL', 'XAI_API_KEY'],
|
|
52
|
+
secretVar: 'XAI_API_KEY',
|
|
53
|
+
// `-p` is one non-interactive prompt (Grok Build 1.0.60 --help, 2026-10-17).
|
|
54
|
+
probe: { args: ['-p', 'Reply with OK'] },
|
|
52
55
|
sharedItems: [
|
|
53
56
|
{ path: 'skills', mode: 'link' },
|
|
54
57
|
{ path: 'installed-plugins', mode: 'link' },
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Account } from '../types.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Family id → its Login Key reader. Must list exactly the Families of `LOGIN_KEY_FAMILIES` (core/same-login.ts);
|
|
4
|
+
* a test keeps the two in step.
|
|
5
|
+
*/
|
|
6
|
+
export declare const LOGIN_KEY_READERS: ReadonlyMap<string, (account: Account) => Promise<string | undefined>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { codexLoginKey } from './codex/login-key.js';
|
|
2
|
+
/**
|
|
3
|
+
* Family id → its Login Key reader. Must list exactly the Families of `LOGIN_KEY_FAMILIES` (core/same-login.ts);
|
|
4
|
+
* a test keeps the two in step.
|
|
5
|
+
*/
|
|
6
|
+
export const LOGIN_KEY_READERS = new Map([['codex', codexLoginKey]]);
|
|
@@ -41,6 +41,10 @@ export const piFamily = {
|
|
|
41
41
|
'OPENAI_BASE_URL',
|
|
42
42
|
],
|
|
43
43
|
apiVars: ['ANTHROPIC_API_KEY', 'ANTHROPIC_BASE_URL', 'OPENAI_API_KEY', 'OPENAI_BASE_URL'],
|
|
44
|
+
// First key in the proxy.env template `new --api` writes. The OpenAI key is still set with `--var`.
|
|
45
|
+
secretVar: 'ANTHROPIC_API_KEY',
|
|
46
|
+
// `-p` is one non-interactive prompt (pi 1.3.0 --help, 2026-10-17).
|
|
47
|
+
probe: { args: ['-p', 'Reply with OK'] },
|
|
44
48
|
sharedItems: [
|
|
45
49
|
{ path: 'AGENTS.md', mode: 'link' },
|
|
46
50
|
{ path: 'auth.json', mode: 'info', credential: true },
|
|
@@ -7,7 +7,7 @@ import { mkdir, open, readFile, rename, rm, stat, writeFile } from 'node:fs/prom
|
|
|
7
7
|
import { join } from 'node:path';
|
|
8
8
|
import { loginShellPath } from '../core/login-path.js';
|
|
9
9
|
import { buildId } from '../core/version.js';
|
|
10
|
-
import { DEFAULT_PANEL_PORT, isSidebyPanel, panelIdentity, startPanelServer, } from './server.js';
|
|
10
|
+
import { DEFAULT_PANEL_PORT, isSidebyPanel, panelIdentity, panelOwner, startPanelServer, } from './server.js';
|
|
11
11
|
export const DETACHED_FLAG = 'serve-detached';
|
|
12
12
|
const START_TIMEOUT_MS = 20_000;
|
|
13
13
|
const LOCK_STALE_MS = 30_000;
|
|
@@ -85,7 +85,7 @@ export async function findRunningPanel(p, port) {
|
|
|
85
85
|
if (check.status === 'stale')
|
|
86
86
|
await rm(pidFilePath(p), { force: true });
|
|
87
87
|
}
|
|
88
|
-
return (await isSidebyPanel(port)) ? `http://127.0.0.1:${port}/` : undefined;
|
|
88
|
+
return (await isSidebyPanel(port, panelOwner(p.home))) ? `http://127.0.0.1:${port}/` : undefined;
|
|
89
89
|
}
|
|
90
90
|
/**
|
|
91
91
|
* Stops this state directory's background Panel when it runs another build than `build` (or is older than build
|
|
@@ -209,6 +209,7 @@ export async function serveDetached(opts) {
|
|
|
209
209
|
const srv = await startPanelServer({
|
|
210
210
|
runtimeFactory: async () => opts.runtimeFactory(await envReady),
|
|
211
211
|
instance,
|
|
212
|
+
owner: panelOwner(opts.paths.home),
|
|
212
213
|
...(opts.port ? { port: opts.port } : {}),
|
|
213
214
|
open: false,
|
|
214
215
|
log,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
import { type AppProfileStatus } from '../core/app-profiles.ts';
|
|
3
|
+
import { type AutostartContext, type AutostartStatus } from '../core/autostart.ts';
|
|
3
4
|
import { type BrowserReport } from '../core/browser-profiles.ts';
|
|
4
|
-
import { type DisplayConfig, type HandoffSettings } from '../core/config.ts';
|
|
5
|
+
import { type DisplayConfig, type HandoffSettings, type PanelConfig } from '../core/config.ts';
|
|
5
6
|
import { type AccountModel } from '../core/model-choice.ts';
|
|
6
7
|
import type { RemoteControlGroup } from '../core/remote-control.ts';
|
|
7
8
|
import * as update from '../core/update.ts';
|
|
@@ -68,9 +69,15 @@ export declare function healthByAccount(history: DoctorHistory | undefined): Map
|
|
|
68
69
|
fail: number;
|
|
69
70
|
warn: number;
|
|
70
71
|
}>;
|
|
71
|
-
/** A Family as the page sees it: FamilyInfo plus the mark from its FamilyDef. */
|
|
72
|
+
/** A Family as the page sees it: FamilyInfo plus the mark from its FamilyDef, and what the key box needs. */
|
|
72
73
|
export type PanelFamily = FamilyInfo & {
|
|
73
74
|
logo?: FamilyLogo;
|
|
75
|
+
/** The variable the key box writes by default (spec §3.28); null when the Family names none. */
|
|
76
|
+
keyVar?: string | null;
|
|
77
|
+
/** The Secret File holds keys even for a subscription (pi), so its accounts offer the key box too. */
|
|
78
|
+
keyAlways?: boolean;
|
|
79
|
+
/** What "Try it" runs (`sideby try`): the Host and the probe arguments; absent when it cannot be tried. */
|
|
80
|
+
tryCommand?: string[];
|
|
74
81
|
};
|
|
75
82
|
/** What the Panel shows about updates (spec §3.23); absent when the handler was created with `updates: false`. */
|
|
76
83
|
export interface PanelUpdate {
|
|
@@ -155,7 +162,27 @@ export interface PanelState {
|
|
|
155
162
|
display: DisplayConfig;
|
|
156
163
|
/** Families that ask for Quota in a way that costs a request (spec §3.26), with their switch; absent when none. */
|
|
157
164
|
quotaChecks?: QuotaCheckInfo[];
|
|
165
|
+
/** Config `panel` (spec §3.29): the theme and language the Panel shows; absent when neither is set. */
|
|
166
|
+
panel?: PanelConfig;
|
|
158
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* Internal, for tests: replaces parts of the machine the Panel's start-at-login switch uses (platform, launcher,
|
|
170
|
+
* `exec`, login home). Not part of the plugin or Portal contract.
|
|
171
|
+
*/
|
|
172
|
+
export declare function bindPanelAutostart(ctx: Partial<AutostartContext>): () => void;
|
|
173
|
+
/**
|
|
174
|
+
* The start-at-login state as the page shows it (spec §3.29): the agent's path and what it starts, with the home
|
|
175
|
+
* folder as `~`, and nothing else of the machine.
|
|
176
|
+
*/
|
|
177
|
+
export declare function autostartView(s: AutostartStatus, home: string): {
|
|
178
|
+
supported: boolean;
|
|
179
|
+
reason?: string | undefined;
|
|
180
|
+
state: import("../core/autostart.ts").AutostartState;
|
|
181
|
+
path: string;
|
|
182
|
+
current: boolean;
|
|
183
|
+
programArguments?: string[] | undefined;
|
|
184
|
+
missing?: string[] | undefined;
|
|
185
|
+
};
|
|
159
186
|
/** The `handoff` map of the Panel state, from the statuses and Quota already read for the page. */
|
|
160
187
|
export declare function handoffByFamily(rt: Runtime, accounts: readonly PanelAccount[], now: Date): Record<string, string>;
|
|
161
188
|
export type RemoteControlView = Omit<RemoteControlGroup, 'file'> & {
|