sideby 0.13.1 → 0.15.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 +2 -1
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +4 -2
- package/dist/src/cli/commands.js +194 -11
- package/dist/src/cli/format.js +7 -3
- package/dist/src/cli/json-schemas.js +104 -3
- package/dist/src/cli/main.js +27 -7
- package/dist/src/core/config.d.ts +28 -0
- package/dist/src/core/config.js +54 -1
- package/dist/src/core/handoff-enable.js +10 -5
- package/dist/src/core/launch.d.ts +5 -0
- package/dist/src/core/launch.js +14 -3
- package/dist/src/core/quota-fetch.d.ts +22 -0
- package/dist/src/core/quota-fetch.js +98 -0
- package/dist/src/core/quota-levels.d.ts +3 -2
- package/dist/src/core/quota-levels.js +8 -4
- package/dist/src/core/quota-store.d.ts +19 -0
- package/dist/src/core/quota-store.js +62 -0
- package/dist/src/core/skill-check.d.ts +16 -0
- package/dist/src/core/skill-check.js +47 -3
- package/dist/src/core/update.d.ts +67 -1
- package/dist/src/core/update.js +147 -37
- package/dist/src/families/grok/handoff-hook.d.ts +6 -0
- package/dist/src/families/grok/handoff-hook.js +45 -2
- package/dist/src/families/grok/handoff.d.ts +6 -1
- package/dist/src/families/grok/handoff.js +30 -13
- package/dist/src/families/grok/index.js +5 -0
- package/dist/src/families/grok/quota-log.d.ts +30 -0
- package/dist/src/families/grok/quota-log.js +140 -0
- package/dist/src/families/grok/quota.d.ts +5 -0
- package/dist/src/families/grok/quota.js +112 -0
- package/dist/src/panel/handler.d.ts +13 -3
- package/dist/src/panel/handler.js +81 -8
- package/dist/src/panel/i18n.d.ts +138 -8
- package/dist/src/panel/i18n.js +301 -41
- package/dist/src/panel/page-logic.d.ts +59 -2
- package/dist/src/panel/page-logic.js +99 -6
- package/dist/src/panel/page-script.js +769 -175
- package/dist/src/panel/page-styles.js +83 -15
- package/dist/src/panel/page.js +3 -0
- package/dist/src/runtime.d.ts +32 -2
- package/dist/src/runtime.js +64 -21
- package/dist/src/types.d.ts +49 -1
- package/package.json +3 -2
- package/schemas/alias.json +1 -1
- package/schemas/app-install.json +1 -1
- package/schemas/app-uninstall.json +1 -1
- package/schemas/browser-open.json +1 -1
- package/schemas/browser-switch.json +1 -1
- package/schemas/browser.json +1 -1
- package/schemas/config.json +60 -0
- package/schemas/desktop-remove.json +1 -1
- package/schemas/desktop.json +1 -1
- package/schemas/doctor.json +1 -1
- package/schemas/error.json +1 -1
- package/schemas/families.json +1 -1
- package/schemas/handoff-disable.json +1 -1
- package/schemas/handoff-enable.json +1 -1
- package/schemas/handoff-ready.json +1 -1
- package/schemas/handoff-setup.json +1 -1
- package/schemas/handoff-status.json +282 -1
- package/schemas/handoff-teardown.json +1 -1
- package/schemas/list.json +6 -1
- package/schemas/model.json +1 -1
- package/schemas/new.json +1 -1
- package/schemas/next.json +1 -1
- package/schemas/order.json +148 -0
- package/schemas/pin.json +126 -0
- package/schemas/plugins.json +1 -1
- package/schemas/quota-check.json +88 -0
- package/schemas/quota-setup.json +1 -1
- package/schemas/quota-teardown.json +1 -1
- package/schemas/quota.json +15 -2
- package/schemas/remote-control.json +1 -1
- package/schemas/tune-change.json +1 -1
- package/schemas/tune.json +1 -1
- package/schemas/unpin.json +126 -0
- package/schemas/update-auto.json +84 -0
- package/schemas/update.json +79 -1
- package/skills/sideby/SKILL.md +7 -3
- package/skills/sideby/references/diagnose.md +2 -1
- package/skills/sideby/references/handoff.md +9 -1
- package/skills/sideby/references/panel.md +28 -0
- package/skills/sideby/references/quota.md +11 -1
- package/skills/sideby/references/update.md +6 -2
package/README.md
CHANGED
|
@@ -78,6 +78,7 @@ cc-switch's docs describe that switch: it writes the live settings file ([README
|
|
|
78
78
|
| Find the cheapest auto-compact size from your own sessions, and use it in one click | `sideby tune` (or the panel's Context tuning tab) | [Context tuning](docs/guide/usage.md#context-tuning) |
|
|
79
79
|
| See each account's model and effort, and set them per account | `sideby model <account>` (or click the model in the panel) | [Model and effort](docs/guide/usage.md#model-and-effort) |
|
|
80
80
|
| Choose whether Claude sessions connect to claude.ai by themselves (Remote Control), for every account at once | `sideby remote-control off` or `on` (or the panel's Settings) | [Remote Control](docs/guide/usage.md#remote-control) |
|
|
81
|
+
| Pin the accounts you use most and put tools and accounts in your own order | `sideby pin claude:work`, `sideby order` (or the panel's Settings) | [Your own order](docs/guide/usage.md#your-own-order) |
|
|
81
82
|
| Move on when an account hits its limit | `sideby next claude` | [Handoff](docs/guide/usage.md#when-an-account-hits-its-limit) |
|
|
82
83
|
| 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) |
|
|
83
84
|
| Short commands such as `ccw` | `sideby alias add ccw claude:work` | [Shell functions](docs/guide/configuration.md#shell-functions) |
|
|
@@ -94,7 +95,7 @@ cc-switch's docs describe that switch: it writes the live settings file ([README
|
|
|
94
95
|
|---|---|---|---|---|
|
|
95
96
|
| Claude Code | `~/.claude`, `~/.claude-<name>` | `claude auth login` | ✓ after `quota setup claude` | ✓ |
|
|
96
97
|
| Codex CLI | `~/.codex`, `~/.codex-<name>` | `codex login` | ✓ | ✓ |
|
|
97
|
-
| Grok Build | `~/.grok`, `~/.grok-<name>` | `grok login` |
|
|
98
|
+
| Grok Build | `~/.grok`, `~/.grok-<name>` | `grok login` | weekly, asked from the `grok` CLI | no public source |
|
|
98
99
|
| pi | `~/.pi/agent`, `~/.pi-<name>/agent` | `/login` inside pi | no public source | no public source |
|
|
99
100
|
|
|
100
101
|
sideby starts the official binary on your `PATH` and never patches it. Tested versions and per-Host notes are in the [guide](docs/guide/usage.md#host-notes).
|
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261017T115526-c6b039"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DoctorReport } from '../core/doctor.ts';
|
|
2
2
|
import { type HandoffIo } from '../core/handoff-run.ts';
|
|
3
|
-
import type { Finding } from '../types.ts';
|
|
3
|
+
import type { Account, Finding } from '../types.ts';
|
|
4
4
|
import { type ParsedArgs } from './args.ts';
|
|
5
5
|
export interface Io {
|
|
6
6
|
out(s: string): void;
|
|
@@ -15,7 +15,7 @@ export declare function serializeDoctor(r: DoctorReport): {
|
|
|
15
15
|
ref: string;
|
|
16
16
|
family: string;
|
|
17
17
|
name: string;
|
|
18
|
-
kind:
|
|
18
|
+
kind: Account['kind'];
|
|
19
19
|
dir: string;
|
|
20
20
|
shared: {
|
|
21
21
|
ok: number;
|
|
@@ -27,6 +27,8 @@ export declare function serializeDoctor(r: DoctorReport): {
|
|
|
27
27
|
general: Omit<Finding, "fix">[];
|
|
28
28
|
fixes: import("../core/doctor.ts").FixOutcome[];
|
|
29
29
|
};
|
|
30
|
+
export declare function cmdPin(a: ParsedArgs, io: Io, pin: boolean): Promise<number>;
|
|
31
|
+
export declare function cmdOrder(a: ParsedArgs, io: Io): Promise<number>;
|
|
30
32
|
export declare function cmdList(a: ParsedArgs, io: Io): Promise<number>;
|
|
31
33
|
export interface NewSuggestion {
|
|
32
34
|
name: string;
|
package/dist/src/cli/commands.js
CHANGED
|
@@ -2,10 +2,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
2
2
|
import { aliasesByAccount } from '../core/accounts.js';
|
|
3
3
|
import { listAppProfiles, openAppProfile, parseProfileRef, removeAppProfile } from '../core/app-profiles.js';
|
|
4
4
|
import { browserReport, openInBrowser, setBrowserSignIns } from '../core/browser-profiles.js';
|
|
5
|
-
import { aliasRefs } from '../core/config.js';
|
|
5
|
+
import { aliasRefs, editConfigUpdate } from '../core/config.js';
|
|
6
6
|
import { openDesktopApp, withDesktopApps } from '../core/desktop-app.js';
|
|
7
7
|
import { UserError } from '../core/errors.js';
|
|
8
8
|
import { requestHandoff } from '../core/handoff-chain.js';
|
|
9
|
+
import { handoffOverview } from '../core/handoff-overview.js';
|
|
9
10
|
import { explain, readyDecision, runWithHandoff } from '../core/handoff-run.js';
|
|
10
11
|
import { configureHandoff, handoffHint, handoffStatus } from '../core/handoff-status.js';
|
|
11
12
|
import { installHint, runHost } from '../core/launch.js';
|
|
@@ -14,7 +15,7 @@ import { resolvePaths, tildify } from '../core/paths.js';
|
|
|
14
15
|
import { USAGE_DAYS } from '../core/quota-levels.js';
|
|
15
16
|
import { groupsToTurnOff, groupsToTurnOn, groupsToUndo, } from '../core/remote-control.js';
|
|
16
17
|
import { SHELLS } from '../core/shell-init.js';
|
|
17
|
-
import { suggestAlias, suggestName } from '../panel/page-logic.js';
|
|
18
|
+
import { inDisplayOrder, pinnedIn, suggestAlias, suggestName, withPinned } from '../panel/page-logic.js';
|
|
18
19
|
import { createRuntime, UnknownFamilyError } from '../runtime.js';
|
|
19
20
|
import { UsageError } from './args.js';
|
|
20
21
|
import { c, clock, compactNumber, quotaText, table, tuningLines, usageText } from './format.js';
|
|
@@ -73,15 +74,158 @@ export function serializeDoctor(r) {
|
|
|
73
74
|
function tilde(text, home) {
|
|
74
75
|
return text.split(`${home}/`).join('~/');
|
|
75
76
|
}
|
|
76
|
-
|
|
77
|
+
/** Accounts in the Display Order the user gave them in the Panel (spec §3.25): Families first, then each one's, Pinned Accounts leading. */
|
|
78
|
+
function displayOrdered(accounts, rt) {
|
|
79
|
+
const d = rt.displayOrder();
|
|
80
|
+
const ids = inDisplayOrder([...new Set(accounts.map((x) => x.family))], (id) => id, d.families);
|
|
81
|
+
return ids.flatMap((id) => withPinned(inDisplayOrder(accounts.filter((x) => x.family === id), (x) => x.name, d.accounts?.[id]), d.pinned?.[id]));
|
|
82
|
+
}
|
|
83
|
+
/** Drop deleted accounts and unloaded Families only when saving a display change. */
|
|
84
|
+
/**
|
|
85
|
+
* Groups the Display Order may name (spec §3.25): every Family, and every desktop app with windows of its own (Grok
|
|
86
|
+
* Bot, `grok-bot`), each with the names it holds.
|
|
87
|
+
*/
|
|
88
|
+
async function orderGroups(rt, accounts) {
|
|
89
|
+
const out = new Map();
|
|
90
|
+
for (const id of rt.families.keys())
|
|
91
|
+
out.set(id, accounts.filter((a) => a.family === id).map((a) => a.name));
|
|
92
|
+
for (const p of await listAppProfiles(rt)) {
|
|
93
|
+
const [id, name] = [p.ref.slice(0, p.ref.indexOf(':')), p.ref.slice(p.ref.indexOf(':') + 1)];
|
|
94
|
+
out.set(id, [...(out.get(id) ?? []), name]);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function cleanDisplay(rt, groups) {
|
|
99
|
+
const d = rt.displayOrder();
|
|
100
|
+
const clean = (lists) => Object.fromEntries(Object.entries(lists ?? {})
|
|
101
|
+
.filter(([id]) => groups.has(id))
|
|
102
|
+
.map(([id, names]) => [id, names.filter((name) => groups.get(id)?.includes(name))]));
|
|
103
|
+
return {
|
|
104
|
+
families: d.families?.filter((id) => groups.has(id)),
|
|
105
|
+
accounts: clean(d.accounts),
|
|
106
|
+
pinned: clean(d.pinned),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export async function cmdPin(a, io, pin) {
|
|
110
|
+
if (a.positionals.length !== 1 || a.rest.length)
|
|
111
|
+
throw new UsageError('usage: sideby pin|unpin <account|short command> [--json]');
|
|
77
112
|
const rt = await createRuntime();
|
|
113
|
+
const account = await rt.resolve(a.positionals[0]);
|
|
78
114
|
const accounts = await rt.accounts();
|
|
115
|
+
const d = cleanDisplay(rt, await orderGroups(rt, accounts));
|
|
116
|
+
const peers = new Set([account.ref, ...(await rt.sameLogin(account))]);
|
|
117
|
+
const names = accounts.filter((x) => peers.has(x.ref)).map((x) => x.name);
|
|
118
|
+
const pins = d.pinned?.[account.family] ?? [];
|
|
119
|
+
const already = names.some((name) => pins.includes(name));
|
|
120
|
+
if (pin && !already) {
|
|
121
|
+
const units = new Set();
|
|
122
|
+
for (const x of accounts.filter((x) => x.family === account.family && pins.includes(x.name))) {
|
|
123
|
+
units.add([x.ref, ...(await rt.sameLogin(x))].sort().join('|'));
|
|
124
|
+
}
|
|
125
|
+
if (units.size >= 3)
|
|
126
|
+
throw new UserError('At most 3 pinned logins per family; unpin one first with sideby unpin <account>.');
|
|
127
|
+
}
|
|
128
|
+
const next = pin
|
|
129
|
+
? [...pins, ...names.filter((name) => !pins.includes(name))]
|
|
130
|
+
: pins.filter((name) => !names.includes(name));
|
|
131
|
+
d.pinned = { ...d.pinned, [account.family]: next };
|
|
132
|
+
const before = JSON.stringify(rt.displayOrder());
|
|
133
|
+
const display = await rt.setDisplayOrder(d);
|
|
134
|
+
const changed = before !== JSON.stringify(display);
|
|
135
|
+
const message = pin
|
|
136
|
+
? already
|
|
137
|
+
? 'Already pinned.'
|
|
138
|
+
: 'Pinned.'
|
|
139
|
+
: already
|
|
140
|
+
? 'Unpinned.'
|
|
141
|
+
: 'Account is not pinned; nothing to change.';
|
|
142
|
+
if (a.flags.has('json'))
|
|
143
|
+
json(io, { ref: account.ref, pinned: pin, changed, message, display });
|
|
144
|
+
else
|
|
145
|
+
io.out(`${account.ref}: ${message}`);
|
|
146
|
+
return 0;
|
|
147
|
+
}
|
|
148
|
+
export async function cmdOrder(a, io) {
|
|
149
|
+
const reset = a.flags.has('reset');
|
|
150
|
+
const families = a.flags.has('families');
|
|
151
|
+
if (a.rest.length ||
|
|
152
|
+
(reset && (families || a.positionals.length)) ||
|
|
153
|
+
(families && !a.positionals.length) ||
|
|
154
|
+
(!reset && !families && a.positionals.length === 1))
|
|
155
|
+
throw new UsageError('usage: sideby order [--json] | <family> <name>... | --families <id>... | --reset');
|
|
156
|
+
const rt = await createRuntime();
|
|
157
|
+
const accounts = await rt.accounts();
|
|
158
|
+
if (reset || families || a.positionals.length) {
|
|
159
|
+
const groups = await orderGroups(rt, accounts);
|
|
160
|
+
let d = cleanDisplay(rt, groups);
|
|
161
|
+
if (reset)
|
|
162
|
+
d = { pinned: d.pinned };
|
|
163
|
+
else {
|
|
164
|
+
const [id, ...names] = a.positionals;
|
|
165
|
+
const requested = families ? a.positionals : names;
|
|
166
|
+
const unknown = families
|
|
167
|
+
? requested.filter((x) => !groups.has(x))
|
|
168
|
+
: !groups.has(id)
|
|
169
|
+
? [id]
|
|
170
|
+
: names.filter((name) => !groups.get(id)?.includes(name));
|
|
171
|
+
if (unknown.length)
|
|
172
|
+
throw new UserError(`Unknown ${families || !groups.has(id) ? 'families' : 'account names'}: ${unknown.join(', ')}; run sideby families or sideby list to choose existing names.`);
|
|
173
|
+
if (new Set(requested).size !== requested.length)
|
|
174
|
+
throw new UsageError('Names must not repeat; list each name once.');
|
|
175
|
+
if (families)
|
|
176
|
+
d.families = requested;
|
|
177
|
+
else
|
|
178
|
+
d.accounts = { ...d.accounts, [id]: names };
|
|
179
|
+
}
|
|
180
|
+
await rt.setDisplayOrder(d);
|
|
181
|
+
}
|
|
182
|
+
const display = rt.displayOrder();
|
|
183
|
+
const ordered = displayOrdered(accounts, rt);
|
|
184
|
+
const groups = await orderGroups(rt, accounts);
|
|
185
|
+
const rows = inDisplayOrder([...groups.keys()], (id) => id, display.families).map((family) => ({
|
|
186
|
+
family,
|
|
187
|
+
accounts: rt.families.has(family)
|
|
188
|
+
? ordered
|
|
189
|
+
.filter((x) => x.family === family)
|
|
190
|
+
.map((x) => ({
|
|
191
|
+
name: x.name,
|
|
192
|
+
ref: x.ref,
|
|
193
|
+
pinned: display.pinned?.[family]?.includes(x.name) ?? false,
|
|
194
|
+
}))
|
|
195
|
+
: inDisplayOrder(groups.get(family) ?? [], (n) => n, display.accounts?.[family]).map((name) => ({
|
|
196
|
+
name,
|
|
197
|
+
ref: `${family}:${name}`,
|
|
198
|
+
pinned: false,
|
|
199
|
+
})),
|
|
200
|
+
}));
|
|
201
|
+
if (a.flags.has('json'))
|
|
202
|
+
json(io, { display, families: rows });
|
|
203
|
+
else
|
|
204
|
+
for (const row of rows)
|
|
205
|
+
io.out(`${row.family}: ${row.accounts.map((x) => `${x.name}${x.pinned ? ' (pinned)' : ''}`).join(', ') || '(no accounts)'}`);
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
function onOff(value, usage) {
|
|
209
|
+
if (value !== 'on' && value !== 'off')
|
|
210
|
+
throw new UsageError(usage);
|
|
211
|
+
return value === 'on';
|
|
212
|
+
}
|
|
213
|
+
export async function cmdList(a, io) {
|
|
214
|
+
const rt = await createRuntime();
|
|
215
|
+
const accounts = displayOrdered(await rt.accounts(), rt);
|
|
79
216
|
const [statuses, appProfiles] = await Promise.all([
|
|
80
|
-
withDesktopApps(rt, accounts, Promise.all(accounts.map((x) => rt.status(x)))),
|
|
81
|
-
|
|
217
|
+
withDesktopApps(rt, accounts, Promise.all(accounts.map((x) => rt.status(x)))).then((list) => list.map((s) => pinnedIn(s, rt.displayOrder().pinned?.[s.family] ?? []) ? { ...s, pinned: true } : s)),
|
|
218
|
+
// Desktop app windows in the Display Order too (spec §3.25), the main window first by default.
|
|
219
|
+
listAppProfiles(rt).then((list) => {
|
|
220
|
+
const d = rt.displayOrder();
|
|
221
|
+
const appOf = (ref) => ref.slice(0, ref.indexOf(':'));
|
|
222
|
+
const nameOf = (ref) => ref.slice(ref.indexOf(':') + 1);
|
|
223
|
+
const apps = inDisplayOrder([...new Set(list.map((p) => appOf(p.ref)))], (id) => id, d.families);
|
|
224
|
+
return apps.flatMap((app) => inDisplayOrder(list.filter((p) => appOf(p.ref) === app), (p) => nameOf(p.ref), d.accounts?.[app]));
|
|
225
|
+
}),
|
|
82
226
|
]);
|
|
83
227
|
const hasDesktop = statuses.some((s) => s.desktopApp);
|
|
84
|
-
const families = await rt.familyInfo();
|
|
228
|
+
const families = inDisplayOrder(await rt.familyInfo(), (f) => f.id, rt.displayOrder().families);
|
|
85
229
|
if (a.flags.has('json')) {
|
|
86
230
|
json(io, { families, accounts: statuses, appProfiles, pluginErrors: [...rt.pluginErrors] });
|
|
87
231
|
return 0;
|
|
@@ -676,10 +820,33 @@ export async function cmdTune(a, io) {
|
|
|
676
820
|
}
|
|
677
821
|
export async function cmdQuota(a, io) {
|
|
678
822
|
const sub = a.positionals[0];
|
|
823
|
+
if (sub === 'check') {
|
|
824
|
+
const usage = 'usage: sideby quota check <family> on|off [--json]';
|
|
825
|
+
if (a.positionals.length !== 3 || a.rest.length || a.flags.has('refresh') || a.flags.has('yes'))
|
|
826
|
+
throw new UsageError(usage);
|
|
827
|
+
const on = onOff(a.positionals[2], usage);
|
|
828
|
+
const family = a.positionals[1];
|
|
829
|
+
const rt = await createRuntime();
|
|
830
|
+
if (!rt.quotaChecks().some((x) => x.family === family))
|
|
831
|
+
throw new UserError(`${family} has no quota fetcher; run sideby families to choose a family that fetches quota.`);
|
|
832
|
+
const r = await rt.setQuotaCheck(family, on);
|
|
833
|
+
if (a.flags.has('json'))
|
|
834
|
+
json(io, { family, on, ...r });
|
|
835
|
+
else
|
|
836
|
+
io.out(`${family}: quota checks ${on ? 'on' : 'off'}.`);
|
|
837
|
+
return 0;
|
|
838
|
+
}
|
|
679
839
|
if (sub === 'setup' || sub === 'teardown')
|
|
680
840
|
return cmdQuotaSetup(a, io, sub);
|
|
681
841
|
const rt = await createRuntime();
|
|
682
|
-
const accounts = sub
|
|
842
|
+
const accounts = !sub
|
|
843
|
+
? displayOrdered(await rt.accounts(), rt)
|
|
844
|
+
: rt.families.has(sub)
|
|
845
|
+
? displayOrdered(await rt.accounts(), rt).filter((x) => x.family === sub)
|
|
846
|
+
: [await rt.resolve(sub)];
|
|
847
|
+
// Asks the Families that fetch Quota again now (spec §3.26); a check that is turned off stays off.
|
|
848
|
+
if (a.flags.has('refresh'))
|
|
849
|
+
await rt.refreshQuota(accounts);
|
|
683
850
|
const rows = await Promise.all(accounts.map(async (x) => ({
|
|
684
851
|
ref: x.ref,
|
|
685
852
|
kind: x.kind,
|
|
@@ -700,9 +867,12 @@ export async function cmdQuota(a, io) {
|
|
|
700
867
|
`${quotaText(r.quota, now)}${r.quota.status === 'ok' && r.quota.from ? c.dim(` read from ${r.quota.from} (same login)`) : ''}`,
|
|
701
868
|
usageText(r.usage),
|
|
702
869
|
]), ['ACCOUNT', 'QUOTA', `USAGE (${USAGE_DAYS}d)`]));
|
|
703
|
-
for (const r of rows)
|
|
870
|
+
for (const r of rows) {
|
|
704
871
|
if (r.quota.status === 'unavailable' && r.quota.detail)
|
|
705
872
|
io.out(c.dim(`${r.ref}: ${r.quota.detail}`));
|
|
873
|
+
if (r.quota.status === 'ok' && r.quota.fetchError)
|
|
874
|
+
io.out(c.dim(`${r.ref}: the latest check failed: ${r.quota.fetchError}`));
|
|
875
|
+
}
|
|
706
876
|
// Suggest a setup only where it would help: the Family has one and its plan can be applied.
|
|
707
877
|
const off = new Set(rows
|
|
708
878
|
.filter((r) => r.quota.status === 'unavailable' && r.quota.reason === 'not-enabled')
|
|
@@ -758,9 +928,10 @@ async function runSetup(a, io, rt, command, sub, familyId, setup) {
|
|
|
758
928
|
}
|
|
759
929
|
/** `sideby handoff status` (spec §3.17): what Auto Handoff still needs, with the commands to run. */
|
|
760
930
|
async function cmdHandoffStatus(a, io) {
|
|
761
|
-
const
|
|
931
|
+
const rt = await createRuntime();
|
|
932
|
+
const st = await handoffStatus(rt);
|
|
762
933
|
if (a.flags.has('json')) {
|
|
763
|
-
json(io, st);
|
|
934
|
+
json(io, { ...st, overview: await handoffOverview(rt) });
|
|
764
935
|
return 0;
|
|
765
936
|
}
|
|
766
937
|
io.out(`Auto Handoff: ${st.auto ? c.green('on') : c.yellow('off')} (same family ${st.sameFamily ? 'on' : 'off'}; brief at ${st.prepareAt}%, hand over at ${st.threshold}%)`);
|
|
@@ -1049,6 +1220,18 @@ export async function cmdUi(a, io) {
|
|
|
1049
1220
|
export async function cmdUpdate(a, io) {
|
|
1050
1221
|
if (a.positionals.length)
|
|
1051
1222
|
throw new UsageError('usage: sideby update [--yes] [--json]');
|
|
1223
|
+
if (a.flags.has('auto')) {
|
|
1224
|
+
if (a.rest.length)
|
|
1225
|
+
throw new UsageError('usage: sideby update --auto on|off [--json]');
|
|
1226
|
+
const auto = onOff(a.flags.get('auto'), 'usage: sideby update --auto on|off [--json]');
|
|
1227
|
+
const paths = resolvePaths(process.env);
|
|
1228
|
+
const r = await editConfigUpdate(paths.configFile, (cur) => ({ ...cur, auto }));
|
|
1229
|
+
if (a.flags.has('json'))
|
|
1230
|
+
json(io, { auto, changed: r.changed });
|
|
1231
|
+
else
|
|
1232
|
+
io.out(`Auto Update ${auto ? 'on' : 'off'}${r.changed ? '.' : ' (unchanged).'}`);
|
|
1233
|
+
return 0;
|
|
1234
|
+
}
|
|
1052
1235
|
const u = await import('../core/update.js');
|
|
1053
1236
|
const { SKILL_INSTALL } = await import('../core/skill-check.js');
|
|
1054
1237
|
const { backgroundPanelPort } = await import('../panel/background.js');
|
|
@@ -1075,7 +1258,7 @@ export async function cmdUpdate(a, io) {
|
|
|
1075
1258
|
}
|
|
1076
1259
|
if (!asJson)
|
|
1077
1260
|
io.out(c.dim(`$ ${sk.command}`));
|
|
1078
|
-
const r = await u.
|
|
1261
|
+
const r = await u.applySkillOnly(paths, sk, process.env, asJson ? undefined : (t) => process.stderr.write(t));
|
|
1079
1262
|
if (asJson)
|
|
1080
1263
|
json(io, { ...shown, updated: false, skillUpdated: r?.ok ?? false, message: r?.message ?? '' });
|
|
1081
1264
|
else if (r?.ok)
|
package/dist/src/cli/format.js
CHANGED
|
@@ -44,12 +44,14 @@ export function clock(iso, now) {
|
|
|
44
44
|
return sameDay ? hm : `${d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })} ${hm}`;
|
|
45
45
|
}
|
|
46
46
|
function windowText(w, now) {
|
|
47
|
+
// A window that covers only some models goes by their name (spec §3.26).
|
|
48
|
+
const label = w.name ?? w.label;
|
|
47
49
|
if (Date.parse(w.resetsAt) <= now.getTime())
|
|
48
|
-
return `${
|
|
50
|
+
return `${label} reset`;
|
|
49
51
|
const pct = Math.round(w.usedPercent);
|
|
50
52
|
const level = quotaLevel(pct);
|
|
51
53
|
const color = level === 'fail' ? c.red : level === 'warn' ? c.yellow : c.green;
|
|
52
|
-
return `${
|
|
54
|
+
return `${label} ${color(`${pct}%`)} ${c.dim(`→ ${clock(w.resetsAt, now)}`)}`;
|
|
53
55
|
}
|
|
54
56
|
export const QUOTA_REASON = {
|
|
55
57
|
'not-enabled': 'off',
|
|
@@ -57,11 +59,13 @@ export const QUOTA_REASON = {
|
|
|
57
59
|
unrecognized: 'unrecognized format',
|
|
58
60
|
'no-source': 'no public source',
|
|
59
61
|
'api-account': 'API account',
|
|
62
|
+
'fetch-failed': 'could not get it',
|
|
60
63
|
};
|
|
61
64
|
export function quotaText(q, now) {
|
|
62
65
|
if (q.status === 'unavailable')
|
|
63
66
|
return c.dim(QUOTA_REASON[q.reason] ?? q.reason);
|
|
64
|
-
|
|
67
|
+
const failed = q.fetchError ? c.yellow(' (latest check failed)') : '';
|
|
68
|
+
return `${q.windows.map((w) => windowText(w, now)).join(' ')} ${c.dim(ago(q.observedAt, now))}${failed}`;
|
|
65
69
|
}
|
|
66
70
|
export function compactNumber(n) {
|
|
67
71
|
if (n < 1000)
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { Type } from 'typebox';
|
|
4
4
|
import { ConfigSchema } from '../core/config.js';
|
|
5
5
|
const Str = Type.String();
|
|
6
|
+
/** Why sideby cannot do something itself (spec §3.23); `reason` keeps the English text. */
|
|
7
|
+
const ReasonCode = Type.Union(['local-build', 'npx', 'volta', 'not-writable', 'project', 'source', 'skill-in-git'].map((k) => Type.Literal(k)), { description: 'Why, as a code a page can say in its own language.' });
|
|
6
8
|
const Level = Type.Union([Type.Literal('ok'), Type.Literal('warn'), Type.Literal('fail')]);
|
|
7
9
|
const Kind = Type.Union([Type.Literal('subscription'), Type.Literal('api')]);
|
|
8
10
|
const Version = Type.Literal(1);
|
|
@@ -39,6 +41,9 @@ const AccountStatus = Type.Intersect([
|
|
|
39
41
|
sameLogin: Type.Optional(Type.Array(Str, {
|
|
40
42
|
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
43
|
})),
|
|
44
|
+
pinned: Type.Optional(Type.Literal(true, {
|
|
45
|
+
description: 'Pinned to the top of its family in the Panel and `sideby list` (spec §3.25). Display only; absent when not pinned.',
|
|
46
|
+
})),
|
|
42
47
|
problems: Type.Optional(Type.Array(Str)),
|
|
43
48
|
desktopApp: Type.Optional(Type.Object({ app: Str, installed: Type.Boolean(), running: Type.Boolean() }, {
|
|
44
49
|
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>`.",
|
|
@@ -100,6 +105,9 @@ const Finding = Type.Object({
|
|
|
100
105
|
});
|
|
101
106
|
const QuotaWindow = Type.Object({
|
|
102
107
|
label: Str,
|
|
108
|
+
name: Type.Optional(Type.String({
|
|
109
|
+
description: 'The group of models this limit covers, when it covers only some (spec §3.26); it does not count toward Quota Pressure.',
|
|
110
|
+
})),
|
|
103
111
|
windowMinutes: Type.Number(),
|
|
104
112
|
usedPercent: Type.Number(),
|
|
105
113
|
resetsAt: Str,
|
|
@@ -114,8 +122,18 @@ const Quota = Type.Union([
|
|
|
114
122
|
from: Type.Optional(Type.String({
|
|
115
123
|
description: 'The account this reading was taken from, when it is another account of the same login (spec §3.21); observedAt is its time.',
|
|
116
124
|
})),
|
|
125
|
+
fetched: Type.Optional(Type.Boolean({
|
|
126
|
+
description: 'This reading is the kept answer of a quota check that costs a request (spec §3.26).',
|
|
127
|
+
})),
|
|
128
|
+
fetchError: Type.Optional(Type.String({ description: 'Why the latest quota check failed, while this older reading is shown.' })),
|
|
129
|
+
}),
|
|
130
|
+
Type.Object({
|
|
131
|
+
status: Type.Literal('unavailable'),
|
|
132
|
+
reason: Type.String({
|
|
133
|
+
description: 'not-enabled, no-session, unrecognized, no-source, api-account or fetch-failed.',
|
|
134
|
+
}),
|
|
135
|
+
detail: Type.Optional(Str),
|
|
117
136
|
}),
|
|
118
|
-
Type.Object({ status: Type.Literal('unavailable'), reason: Str, detail: Type.Optional(Str) }),
|
|
119
137
|
]);
|
|
120
138
|
const TuningModel = Type.Object({
|
|
121
139
|
model: Str,
|
|
@@ -231,12 +249,90 @@ const Notice = Type.Object({
|
|
|
231
249
|
}),
|
|
232
250
|
current: Str,
|
|
233
251
|
command: Type.String({
|
|
234
|
-
description: '
|
|
252
|
+
description: '`sideby update`, which updates the skill with sideby: run it after the user agrees; it shows the plan and exits 10, then --yes.',
|
|
235
253
|
}),
|
|
236
254
|
message: Str,
|
|
237
255
|
})),
|
|
238
256
|
}, { description: 'Not the answer to the task: mention it briefly afterwards and offer to update.' });
|
|
257
|
+
const Display = Type.Object({
|
|
258
|
+
families: Type.Optional(Type.Array(Str)),
|
|
259
|
+
accounts: Type.Optional(Type.Record(Str, Type.Array(Str))),
|
|
260
|
+
pinned: Type.Optional(Type.Record(Str, Type.Array(Str))),
|
|
261
|
+
});
|
|
262
|
+
const PinResult = Type.Object({
|
|
263
|
+
schemaVersion: Version,
|
|
264
|
+
ref: Str,
|
|
265
|
+
pinned: Type.Boolean(),
|
|
266
|
+
changed: Type.Boolean(),
|
|
267
|
+
message: Str,
|
|
268
|
+
display: Display,
|
|
269
|
+
});
|
|
270
|
+
const Skipped = Type.Optional(Type.Array(Type.Object({ ref: Str, reason: Str })));
|
|
271
|
+
const Overview = Type.Object({
|
|
272
|
+
settings: Type.Object({
|
|
273
|
+
auto: Type.Boolean(),
|
|
274
|
+
sameFamily: Type.Boolean(),
|
|
275
|
+
prepareAt: Type.Number(),
|
|
276
|
+
threshold: Type.Number(),
|
|
277
|
+
waitIfResetWithinMinutes: Type.Number(),
|
|
278
|
+
countdownSeconds: Type.Number(),
|
|
279
|
+
crossOrganization: Type.Array(Str),
|
|
280
|
+
families: Type.Record(Str, Type.Object({
|
|
281
|
+
policy: Type.Union([Type.Literal('pressure'), Type.Literal('order')]),
|
|
282
|
+
order: Type.Array(Str),
|
|
283
|
+
})),
|
|
284
|
+
}),
|
|
285
|
+
takeover: Type.Array(Type.Object({
|
|
286
|
+
ref: Str,
|
|
287
|
+
family: Str,
|
|
288
|
+
starts: Type.Array(Type.Union([Type.Literal('quota'), Type.Literal('limit')])),
|
|
289
|
+
decision: Type.Union([
|
|
290
|
+
Type.Object({ kind: Type.Literal('pick'), ref: Str, skipped: Skipped }),
|
|
291
|
+
Type.Object({ kind: Type.Literal('wait'), until: Str }),
|
|
292
|
+
Type.Object({
|
|
293
|
+
kind: Type.Literal('none'),
|
|
294
|
+
reason: Str,
|
|
295
|
+
earliestReset: Type.Optional(Type.Object({ ref: Str, at: Str })),
|
|
296
|
+
skipped: Skipped,
|
|
297
|
+
}),
|
|
298
|
+
]),
|
|
299
|
+
})),
|
|
300
|
+
recent: Type.Array(Type.Object({
|
|
301
|
+
from: Str,
|
|
302
|
+
to: Str,
|
|
303
|
+
at: Str,
|
|
304
|
+
trigger: Type.Union([
|
|
305
|
+
Type.Literal('threshold'),
|
|
306
|
+
Type.Literal('limit'),
|
|
307
|
+
Type.Literal('manual'),
|
|
308
|
+
Type.Null(),
|
|
309
|
+
]),
|
|
310
|
+
briefSource: Type.Union([
|
|
311
|
+
Type.Literal('agent'),
|
|
312
|
+
Type.Literal('sideby'),
|
|
313
|
+
Type.Literal('user'),
|
|
314
|
+
Type.Null(),
|
|
315
|
+
]),
|
|
316
|
+
})),
|
|
317
|
+
});
|
|
239
318
|
export const OUTPUT_SCHEMAS = {
|
|
319
|
+
pin: PinResult,
|
|
320
|
+
unpin: PinResult,
|
|
321
|
+
order: Type.Object({
|
|
322
|
+
schemaVersion: Version,
|
|
323
|
+
display: Display,
|
|
324
|
+
families: Type.Array(Type.Object({
|
|
325
|
+
family: Str,
|
|
326
|
+
accounts: Type.Array(Type.Object({ name: Str, ref: Str, pinned: Type.Boolean() })),
|
|
327
|
+
})),
|
|
328
|
+
}),
|
|
329
|
+
'quota-check': Type.Object({
|
|
330
|
+
schemaVersion: Version,
|
|
331
|
+
family: Str,
|
|
332
|
+
on: Type.Boolean(),
|
|
333
|
+
changed: Type.Boolean(),
|
|
334
|
+
}),
|
|
335
|
+
'update-auto': Type.Object({ schemaVersion: Version, auto: Type.Boolean(), changed: Type.Boolean() }),
|
|
240
336
|
list: Type.Object({
|
|
241
337
|
schemaVersion: Version,
|
|
242
338
|
families: Type.Array(FamilyInfo),
|
|
@@ -383,6 +479,7 @@ export const OUTPUT_SCHEMAS = {
|
|
|
383
479
|
'quota-setup': Type.Object({ schemaVersion: Version, family: Str, applied: Type.Boolean(), plan: QuotaSetupPlan }, { title: 'sideby quota setup --json' }),
|
|
384
480
|
'handoff-status': Type.Object({
|
|
385
481
|
schemaVersion: Version,
|
|
482
|
+
overview: Type.Optional(Overview),
|
|
386
483
|
auto: Type.Boolean(),
|
|
387
484
|
sameFamily: Type.Boolean(),
|
|
388
485
|
prepareAt: Type.Number(),
|
|
@@ -569,9 +666,11 @@ export const OUTPUT_SCHEMAS = {
|
|
|
569
666
|
latest: Type.String({ description: 'The `latest` version on the npm registry.' }),
|
|
570
667
|
available: Type.Boolean({ description: '`latest` is newer than `current`.' }),
|
|
571
668
|
method: Type.Object({
|
|
572
|
-
kind: Type.Union(['npm', 'pnpm', 'bun', 'volta', 'npx', 'project', 'source'].map((k) => Type.Literal(k)), { description: 'How this copy was installed.' }),
|
|
669
|
+
kind: Type.Union(['npm', 'pnpm', 'bun', 'volta', 'npx', 'project', 'source', 'local'].map((k) => Type.Literal(k)), { description: 'How this copy was installed.' }),
|
|
573
670
|
canUpdate: Type.Boolean({ description: 'sideby can run the Update itself.' }),
|
|
574
671
|
reason: Type.Optional(Type.String({ description: 'Why it cannot.' })),
|
|
672
|
+
reasonCode: Type.Optional(ReasonCode),
|
|
673
|
+
reasonAt: Type.Optional(Type.String({ description: 'The folders that are not writable (`not-writable`).' })),
|
|
575
674
|
}),
|
|
576
675
|
command: Type.String({
|
|
577
676
|
description: 'What an Update runs, or what to run by hand when `canRun` is false.',
|
|
@@ -601,6 +700,8 @@ export const OUTPUT_SCHEMAS = {
|
|
|
601
700
|
description: 'False when sideby leaves the skill alone (see reason): give the user command.',
|
|
602
701
|
}),
|
|
603
702
|
reason: Type.Optional(Str),
|
|
703
|
+
reasonCode: Type.Optional(ReasonCode),
|
|
704
|
+
reasonAt: Type.Optional(Type.String({ description: 'The git repository that owns the skill (`skill-in-git`).' })),
|
|
604
705
|
}, {
|
|
605
706
|
description: 'The installed sideby skill, which the Update brings along; absent when none is installed.',
|
|
606
707
|
})),
|
package/dist/src/cli/main.js
CHANGED
|
@@ -13,6 +13,14 @@ Usage:
|
|
|
13
13
|
create an account (families: claude, codex, grok, pi);
|
|
14
14
|
--next picks the next name and short command (cc008);
|
|
15
15
|
--alias also adds a short command such as cc008
|
|
16
|
+
sideby pin|unpin <account|short command> [--json]
|
|
17
|
+
pin or unpin a Same Login (at most 3 per family)
|
|
18
|
+
sideby order [--json] show display order, with pinned accounts marked
|
|
19
|
+
sideby order <family> <name>... set account order within a family
|
|
20
|
+
sideby order --families <id>... set family order
|
|
21
|
+
sideby order --reset restore default order, keeping pins
|
|
22
|
+
sideby quota check <family> on|off turn quota fetching on or off
|
|
23
|
+
sideby update --auto on|off set Auto Update without running an update
|
|
16
24
|
sideby families [family] what each host's accounts share with the main account,
|
|
17
25
|
where they live and how they sign in
|
|
18
26
|
sideby alias add <short> <account> [-- args]
|
|
@@ -23,11 +31,12 @@ Usage:
|
|
|
23
31
|
sideby resume <family> [-- args] run the host; a session resumed by id (claude --resume <id>,
|
|
24
32
|
codex resume <id>) starts in the account that holds it
|
|
25
33
|
sideby next <family> [--dry-run] [--include-api] [-- args]
|
|
26
|
-
recommend the account with the most quota left (claude, codex)
|
|
34
|
+
recommend the account with the most quota left (claude, codex, grok)
|
|
27
35
|
and start it; --dry-run only recommends
|
|
28
36
|
sideby doctor [target] [--fix] [--force]
|
|
29
37
|
check shared items and permissions; --fix repairs safe issues
|
|
30
|
-
sideby quota [account]
|
|
38
|
+
sideby quota [account|family] [--refresh]
|
|
39
|
+
quota and 7-day token usage; --refresh asks every fetching family again now
|
|
31
40
|
sideby quota setup claude [--yes] turn on Claude quota (shows the change first)
|
|
32
41
|
sideby quota teardown claude undo it, restoring the original file
|
|
33
42
|
sideby tune [account|family] estimate the cheapest auto-compact threshold per model
|
|
@@ -214,6 +223,9 @@ export function nodeTooOld(version = process.versions.node) {
|
|
|
214
223
|
}
|
|
215
224
|
const COMMANDS = {
|
|
216
225
|
list: { flags: ['json'] },
|
|
226
|
+
pin: { flags: ['json'] },
|
|
227
|
+
unpin: { flags: ['json'] },
|
|
228
|
+
order: { flags: ['json', 'families', 'reset'] },
|
|
217
229
|
run: { flags: [] },
|
|
218
230
|
resume: { flags: [] },
|
|
219
231
|
login: { flags: [] },
|
|
@@ -221,7 +233,7 @@ const COMMANDS = {
|
|
|
221
233
|
new: { flags: ['api', 'json', 'next'], valued: ['alias'] },
|
|
222
234
|
alias: { flags: ['json'] },
|
|
223
235
|
doctor: { flags: ['fix', 'force', 'json'] },
|
|
224
|
-
quota: { flags: ['json', 'yes'] },
|
|
236
|
+
quota: { flags: ['json', 'yes', 'refresh'] },
|
|
225
237
|
tune: { flags: ['json', 'yes'] },
|
|
226
238
|
model: { flags: ['json', 'yes'], valued: ['effort'] },
|
|
227
239
|
'remote-control': { flags: ['json', 'yes'] },
|
|
@@ -236,7 +248,7 @@ const COMMANDS = {
|
|
|
236
248
|
'shell-init': { flags: ['write'] },
|
|
237
249
|
plugins: { flags: ['json'] },
|
|
238
250
|
families: { flags: ['json'] },
|
|
239
|
-
update: { flags: ['json', 'yes'] },
|
|
251
|
+
update: { flags: ['json', 'yes'], valued: ['auto'] },
|
|
240
252
|
'update-check': { flags: [] },
|
|
241
253
|
};
|
|
242
254
|
/** Commands that never print an Update Notice: their output is read by a shell or an agent, or they are internal. */
|
|
@@ -260,12 +272,13 @@ async function jsonNoticeFor() {
|
|
|
260
272
|
const { resolvePaths } = await import('../core/paths.js');
|
|
261
273
|
const { loadConfig } = await import('../core/config.js');
|
|
262
274
|
const { packageVersion } = await import('../core/version.js');
|
|
263
|
-
const { installedSkillVersions
|
|
275
|
+
const { installedSkillVersions } = await import('../core/skill-check.js');
|
|
264
276
|
const paths = resolvePaths(process.env);
|
|
265
277
|
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
266
278
|
const cache = await u.readUpdateCache(paths);
|
|
267
279
|
const skills = await installedSkillVersions(paths.home);
|
|
268
|
-
|
|
280
|
+
// `sideby update` brings the skill along, so it is the one command for both (spec §3.23).
|
|
281
|
+
const n = u.jsonNotice(cache, packageVersion(), config, process.env, skills, 'sideby update');
|
|
269
282
|
if (!u.checksOff(process.env, config) && u.checkDue(cache, new Date())) {
|
|
270
283
|
const { spawn } = await import('node:child_process');
|
|
271
284
|
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update-check'], {
|
|
@@ -296,7 +309,9 @@ async function afterCommand() {
|
|
|
296
309
|
const paths = resolvePaths(process.env);
|
|
297
310
|
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
298
311
|
const cache = await u.readUpdateCache(paths);
|
|
299
|
-
const
|
|
312
|
+
const current = packageVersion();
|
|
313
|
+
const skill = u.skillBehind(await u.skillUpdatePlan(paths.home, current).catch(() => null), current);
|
|
314
|
+
const plan = u.afterCommandPlan(cache, current, config, process.env, new Date(), skill);
|
|
300
315
|
const dim = process.env.NO_COLOR ? (t) => t : (t) => `\x1b[2m${t}\x1b[22m`;
|
|
301
316
|
for (const l of plan.lines)
|
|
302
317
|
console.error(dim(l));
|
|
@@ -413,6 +428,11 @@ export async function main(argv) {
|
|
|
413
428
|
}
|
|
414
429
|
async function dispatch(cmd, parsed, io, cmds) {
|
|
415
430
|
switch (cmd) {
|
|
431
|
+
case 'pin':
|
|
432
|
+
case 'unpin':
|
|
433
|
+
return await cmds.cmdPin(parsed, io, cmd === 'pin');
|
|
434
|
+
case 'order':
|
|
435
|
+
return await cmds.cmdOrder(parsed, io);
|
|
416
436
|
case 'list':
|
|
417
437
|
return await cmds.cmdList(parsed, io);
|
|
418
438
|
case 'run':
|
|
@@ -69,6 +69,14 @@ export declare const ConfigSchema: Type.TObject<{
|
|
|
69
69
|
browser: Type.TOptional<Type.TObject<{
|
|
70
70
|
signIns: Type.TOptional<Type.TBoolean>;
|
|
71
71
|
}>>;
|
|
72
|
+
display: Type.TOptional<Type.TObject<{
|
|
73
|
+
families: Type.TOptional<Type.TArray<Type.TString>>;
|
|
74
|
+
accounts: Type.TOptional<Type.TRecord<"^.*$", Type.TArray<Type.TString>>>;
|
|
75
|
+
pinned: Type.TOptional<Type.TRecord<"^.*$", Type.TArray<Type.TString>>>;
|
|
76
|
+
}>>;
|
|
77
|
+
quota: Type.TOptional<Type.TObject<{
|
|
78
|
+
check: Type.TOptional<Type.TRecord<"^.*$", Type.TBoolean>>;
|
|
79
|
+
}>>;
|
|
72
80
|
shellInitFile: Type.TOptional<Type.TObject<{
|
|
73
81
|
zsh: Type.TOptional<Type.TString>;
|
|
74
82
|
bash: Type.TOptional<Type.TString>;
|
|
@@ -116,6 +124,26 @@ export declare function editConfigBrowser(file: string, edit: (current: BrowserC
|
|
|
116
124
|
before: BrowserConfig | undefined;
|
|
117
125
|
after: BrowserConfig | undefined;
|
|
118
126
|
}>;
|
|
127
|
+
export type DisplayConfig = NonNullable<Config['display']>;
|
|
128
|
+
/**
|
|
129
|
+
* The Panel's drag and drop (spec §3.25): replaces the config `display` key, or removes it when `display` is undefined
|
|
130
|
+
* or empty (see `editConfigKey` for how the file is written).
|
|
131
|
+
*/
|
|
132
|
+
export declare function editConfigDisplay(file: string, display: DisplayConfig | undefined): Promise<{
|
|
133
|
+
changed: boolean;
|
|
134
|
+
before: DisplayConfig | undefined;
|
|
135
|
+
after: DisplayConfig | undefined;
|
|
136
|
+
}>;
|
|
137
|
+
export type QuotaConfig = NonNullable<Config['quota']>;
|
|
138
|
+
/**
|
|
139
|
+
* The Panel's Quota check switch (spec §3.26): `false` writes `quota.check.<family>: false`, `true` removes that key
|
|
140
|
+
* (on is the default), and an empty `quota` goes away with it (see `editConfigKey` for how the file is written).
|
|
141
|
+
*/
|
|
142
|
+
export declare function editConfigQuotaCheck(file: string, family: string, on: boolean): Promise<{
|
|
143
|
+
changed: boolean;
|
|
144
|
+
before: QuotaConfig | undefined;
|
|
145
|
+
after: QuotaConfig | undefined;
|
|
146
|
+
}>;
|
|
119
147
|
export type AccountsConfig = NonNullable<Config['accounts']>;
|
|
120
148
|
/**
|
|
121
149
|
* `sideby model` (spec §3.19): changes the config `accounts` key with `edit` (see `editConfigKey` for how the file is
|