sideby 0.14.0 → 0.16.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 +1 -0
- package/dist/build.json +1 -1
- package/dist/src/cli/commands.d.ts +7 -0
- package/dist/src/cli/commands.js +259 -9
- package/dist/src/cli/json-schemas.js +109 -2
- package/dist/src/cli/main.js +30 -6
- package/dist/src/core/config.d.ts +1 -0
- package/dist/src/core/config.js +8 -2
- package/dist/src/core/local-build.d.ts +63 -0
- package/dist/src/core/local-build.js +189 -0
- package/dist/src/core/skill-check.d.ts +14 -0
- package/dist/src/core/skill-check.js +29 -3
- package/dist/src/core/update.d.ts +73 -1
- package/dist/src/core/update.js +155 -23
- package/dist/src/panel/handler.d.ts +12 -1
- package/dist/src/panel/handler.js +84 -5
- package/dist/src/panel/i18n.d.ts +125 -5
- package/dist/src/panel/i18n.js +280 -40
- package/dist/src/panel/page-logic.d.ts +41 -0
- package/dist/src/panel/page-logic.js +46 -0
- package/dist/src/panel/page-script.js +616 -162
- package/dist/src/panel/page-styles.js +74 -23
- package/dist/src/panel/page.js +3 -0
- package/dist/src/runtime.d.ts +2 -0
- 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 +15 -1
- 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/local.json +165 -0
- 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 +1 -1
- 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 +8 -3
- package/skills/sideby/references/handoff.md +8 -0
- package/skills/sideby/references/panel.md +28 -0
- package/skills/sideby/references/quota.md +6 -0
- package/skills/sideby/references/update.md +15 -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) |
|
package/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261017T131419-98017e"}
|
|
@@ -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;
|
|
@@ -69,6 +71,11 @@ export declare function cmdUi(a: ParsedArgs, io: Io): Promise<number>;
|
|
|
69
71
|
export declare function cmdUpdate(a: ParsedArgs, io: Io): Promise<number>;
|
|
70
72
|
/** The internal `sideby update-check` the CLI starts detached: one Update Check, then Auto Update if it is on. */
|
|
71
73
|
export declare function cmdUpdateCheck(_a: ParsedArgs, _io: Io): Promise<number>;
|
|
74
|
+
/**
|
|
75
|
+
* `sideby local [on|off]` (ADR-0024): which side the global `sideby` runs, and a switch between the newest Local
|
|
76
|
+
* Build (`pnpm local` builds it) and the npm package. Shows the change first and exits 10 without `--yes`.
|
|
77
|
+
*/
|
|
78
|
+
export declare function cmdLocal(a: ParsedArgs, io: Io): Promise<number>;
|
|
72
79
|
export declare function cmdApp(a: ParsedArgs, io: Io): Promise<number>;
|
|
73
80
|
/**
|
|
74
81
|
* `sideby desktop <account>`: the Family's Desktop App as that Account (spec §3.20); `sideby desktop grok-bot:<name>`:
|
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 { inDisplayOrder, 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,18 +74,155 @@ export function serializeDoctor(r) {
|
|
|
73
74
|
function tilde(text, home) {
|
|
74
75
|
return text.split(`${home}/`).join('~/');
|
|
75
76
|
}
|
|
76
|
-
/** Accounts in the Display Order the user gave them in the Panel (spec §3.25): Families first, then each one's. */
|
|
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. */
|
|
77
78
|
function displayOrdered(accounts, rt) {
|
|
78
79
|
const d = rt.displayOrder();
|
|
79
80
|
const ids = inDisplayOrder([...new Set(accounts.map((x) => x.family))], (id) => id, d.families);
|
|
80
|
-
return ids.flatMap((id) => inDisplayOrder(accounts.filter((x) => x.family === id), (x) => x.name, d.accounts?.[id]));
|
|
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]');
|
|
112
|
+
const rt = await createRuntime();
|
|
113
|
+
const account = await rt.resolve(a.positionals[0]);
|
|
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';
|
|
81
212
|
}
|
|
82
213
|
export async function cmdList(a, io) {
|
|
83
214
|
const rt = await createRuntime();
|
|
84
215
|
const accounts = displayOrdered(await rt.accounts(), rt);
|
|
85
216
|
const [statuses, appProfiles] = await Promise.all([
|
|
86
|
-
withDesktopApps(rt, accounts, Promise.all(accounts.map((x) => rt.status(x)))),
|
|
87
|
-
|
|
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
|
+
}),
|
|
88
226
|
]);
|
|
89
227
|
const hasDesktop = statuses.some((s) => s.desktopApp);
|
|
90
228
|
const families = inDisplayOrder(await rt.familyInfo(), (f) => f.id, rt.displayOrder().families);
|
|
@@ -682,6 +820,22 @@ export async function cmdTune(a, io) {
|
|
|
682
820
|
}
|
|
683
821
|
export async function cmdQuota(a, io) {
|
|
684
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
|
+
}
|
|
685
839
|
if (sub === 'setup' || sub === 'teardown')
|
|
686
840
|
return cmdQuotaSetup(a, io, sub);
|
|
687
841
|
const rt = await createRuntime();
|
|
@@ -774,9 +928,10 @@ async function runSetup(a, io, rt, command, sub, familyId, setup) {
|
|
|
774
928
|
}
|
|
775
929
|
/** `sideby handoff status` (spec §3.17): what Auto Handoff still needs, with the commands to run. */
|
|
776
930
|
async function cmdHandoffStatus(a, io) {
|
|
777
|
-
const
|
|
931
|
+
const rt = await createRuntime();
|
|
932
|
+
const st = await handoffStatus(rt);
|
|
778
933
|
if (a.flags.has('json')) {
|
|
779
|
-
json(io, st);
|
|
934
|
+
json(io, { ...st, overview: await handoffOverview(rt) });
|
|
780
935
|
return 0;
|
|
781
936
|
}
|
|
782
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}%)`);
|
|
@@ -1065,6 +1220,18 @@ export async function cmdUi(a, io) {
|
|
|
1065
1220
|
export async function cmdUpdate(a, io) {
|
|
1066
1221
|
if (a.positionals.length)
|
|
1067
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
|
+
}
|
|
1068
1235
|
const u = await import('../core/update.js');
|
|
1069
1236
|
const { SKILL_INSTALL } = await import('../core/skill-check.js');
|
|
1070
1237
|
const { backgroundPanelPort } = await import('../panel/background.js');
|
|
@@ -1091,7 +1258,7 @@ export async function cmdUpdate(a, io) {
|
|
|
1091
1258
|
}
|
|
1092
1259
|
if (!asJson)
|
|
1093
1260
|
io.out(c.dim(`$ ${sk.command}`));
|
|
1094
|
-
const r = await u.
|
|
1261
|
+
const r = await u.applySkillOnly(paths, sk, process.env, asJson ? undefined : (t) => process.stderr.write(t));
|
|
1095
1262
|
if (asJson)
|
|
1096
1263
|
json(io, { ...shown, updated: false, skillUpdated: r?.ok ?? false, message: r?.message ?? '' });
|
|
1097
1264
|
else if (r?.ok)
|
|
@@ -1179,6 +1346,89 @@ export async function cmdUpdateCheck(_a, _io) {
|
|
|
1179
1346
|
await backgroundCheck(paths, process.env, config, () => backgroundPanelPort(paths));
|
|
1180
1347
|
return 0;
|
|
1181
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* `sideby local [on|off]` (ADR-0024): which side the global `sideby` runs, and a switch between the newest Local
|
|
1351
|
+
* Build (`pnpm local` builds it) and the npm package. Shows the change first and exits 10 without `--yes`.
|
|
1352
|
+
*/
|
|
1353
|
+
export async function cmdLocal(a, io) {
|
|
1354
|
+
const usage = 'usage: sideby local [on|off] [--yes] [--json]';
|
|
1355
|
+
const sub = a.positionals[0];
|
|
1356
|
+
if (a.positionals.length > 1 || (sub !== undefined && sub !== 'on' && sub !== 'off'))
|
|
1357
|
+
throw new UsageError(usage);
|
|
1358
|
+
const lb = await import('../core/local-build.js');
|
|
1359
|
+
const asJson = a.flags.has('json');
|
|
1360
|
+
const d = await lb.localDirs(process.env);
|
|
1361
|
+
if (!d)
|
|
1362
|
+
throw new UserError('`npm prefix -g` failed; switching needs npm on PATH and an npm global install of sideby');
|
|
1363
|
+
const s = await lb.localStatus(d);
|
|
1364
|
+
const now = new Date();
|
|
1365
|
+
const build = (l) => `${l.version}-dev · ${l.commit || 'no commit'}${l.dirty ? '+' : ''}${l.builtAt ? `, built ${clock(l.builtAt, now)}` : ''}`;
|
|
1366
|
+
if (sub === undefined) {
|
|
1367
|
+
if (asJson)
|
|
1368
|
+
json(io, { ...s, switched: false });
|
|
1369
|
+
else {
|
|
1370
|
+
io.out(s.running === 'local' && s.local
|
|
1371
|
+
? `The global sideby runs the local build ${build(s.local)}.`
|
|
1372
|
+
: s.running === 'npm'
|
|
1373
|
+
? 'The global sideby runs the npm package.'
|
|
1374
|
+
: 'The global sideby is not an npm global install; switching supports `npm install -g sideby` only.');
|
|
1375
|
+
if (s.running === 'local')
|
|
1376
|
+
io.out(c.dim('Back to the release: sideby local off'));
|
|
1377
|
+
else if (s.running === 'npm' && s.local)
|
|
1378
|
+
io.out(c.dim(`A local build is kept (${build(s.local)}): sideby local on`));
|
|
1379
|
+
}
|
|
1380
|
+
return 0;
|
|
1381
|
+
}
|
|
1382
|
+
const to = sub === 'on' ? 'local' : 'npm';
|
|
1383
|
+
const blocked = lb.switchBlocker(s, to);
|
|
1384
|
+
if (blocked) {
|
|
1385
|
+
if (asJson)
|
|
1386
|
+
json(io, { ...s, to, switched: false, blocked });
|
|
1387
|
+
else
|
|
1388
|
+
io.err(c.yellow(`sideby cannot switch: ${blocked}`));
|
|
1389
|
+
return 1;
|
|
1390
|
+
}
|
|
1391
|
+
if (s.running === to) {
|
|
1392
|
+
if (asJson)
|
|
1393
|
+
json(io, { ...s, to, switched: false });
|
|
1394
|
+
else
|
|
1395
|
+
io.out(`${c.green('✓')} The global sideby already runs the ${to === 'local' ? 'local build' : 'npm package'}.`);
|
|
1396
|
+
return 0;
|
|
1397
|
+
}
|
|
1398
|
+
if (!a.flags.has('yes')) {
|
|
1399
|
+
if (asJson)
|
|
1400
|
+
json(io, { ...s, to, switched: false });
|
|
1401
|
+
else {
|
|
1402
|
+
io.out(to === 'local' && s.local
|
|
1403
|
+
? `Switching runs the local build ${build(s.local)} instead of the npm package. It is not rebuilt: \`pnpm local\` in the checkout builds a new one.`
|
|
1404
|
+
: `Switching goes back to the npm package: the kept ${s.kept ?? 'copy'} when it is the latest, else \`npm install -g sideby@latest\`.`);
|
|
1405
|
+
io.out(c.cyan(`\nRun it with \`sideby local ${sub} --yes\`.`));
|
|
1406
|
+
}
|
|
1407
|
+
return 10;
|
|
1408
|
+
}
|
|
1409
|
+
const { backgroundPanelPort } = await import('../panel/background.js');
|
|
1410
|
+
const paths = resolvePaths(process.env);
|
|
1411
|
+
const r = await lb.switchLocal(paths, d, to, process.env, {
|
|
1412
|
+
panelPort: () => backgroundPanelPort(paths),
|
|
1413
|
+
...(asJson ? {} : { onOutput: (t) => process.stderr.write(t) }),
|
|
1414
|
+
});
|
|
1415
|
+
if (asJson)
|
|
1416
|
+
json(io, {
|
|
1417
|
+
...(await lb.localStatus(d)),
|
|
1418
|
+
to,
|
|
1419
|
+
switched: r.ok,
|
|
1420
|
+
message: r.message,
|
|
1421
|
+
restartedPanel: r.restartedPanel,
|
|
1422
|
+
});
|
|
1423
|
+
else if (r.ok) {
|
|
1424
|
+
io.out(`${c.green('✓')} ${r.message}.`);
|
|
1425
|
+
if (r.restartedPanel)
|
|
1426
|
+
io.out(c.dim('The background panel restarted with it; open panels reload by themselves.'));
|
|
1427
|
+
}
|
|
1428
|
+
else
|
|
1429
|
+
io.err(c.red(`✗ ${r.message}`));
|
|
1430
|
+
return r.ok ? 0 : 1;
|
|
1431
|
+
}
|
|
1182
1432
|
export async function cmdApp(a, io) {
|
|
1183
1433
|
const sub = a.positionals[0];
|
|
1184
1434
|
const usage = 'usage: sideby app install [--url <url>] | sideby app uninstall';
|
|
@@ -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>`.",
|
|
@@ -244,12 +249,109 @@ const Notice = Type.Object({
|
|
|
244
249
|
}),
|
|
245
250
|
current: Str,
|
|
246
251
|
command: Type.String({
|
|
247
|
-
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.',
|
|
248
253
|
}),
|
|
249
254
|
message: Str,
|
|
250
255
|
})),
|
|
251
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
|
+
});
|
|
252
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() }),
|
|
336
|
+
local: Type.Object({
|
|
337
|
+
schemaVersion: Version,
|
|
338
|
+
running: Type.Union([Type.Literal('local'), Type.Literal('npm'), Type.Null()], {
|
|
339
|
+
description: 'What the global sideby runs: the Local Build, an npm package, or neither (not an npm global install).',
|
|
340
|
+
}),
|
|
341
|
+
local: Type.Union([Type.Object({ commit: Str, dirty: Type.Boolean(), builtAt: Str, version: Str }), Type.Null()], {
|
|
342
|
+
description: 'The newest Local Build, the one `on` switches to; null when `pnpm local` never ran.',
|
|
343
|
+
}),
|
|
344
|
+
kept: Type.Union([Str, Type.Null()], {
|
|
345
|
+
description: 'The npm package version kept aside while a Local Build runs.',
|
|
346
|
+
}),
|
|
347
|
+
to: Type.Optional(Type.Union([Type.Literal('local'), Type.Literal('npm')])),
|
|
348
|
+
switched: Type.Boolean({
|
|
349
|
+
description: 'A switch ran and succeeded; false for status, a preview (exit 10) or a failure.',
|
|
350
|
+
}),
|
|
351
|
+
blocked: Type.Optional(Type.String({ description: 'Why the switch cannot run, with what to do.' })),
|
|
352
|
+
message: Type.Optional(Str),
|
|
353
|
+
restartedPanel: Type.Optional(Type.Boolean()),
|
|
354
|
+
}, { title: 'sideby local --json' }),
|
|
253
355
|
list: Type.Object({
|
|
254
356
|
schemaVersion: Version,
|
|
255
357
|
families: Type.Array(FamilyInfo),
|
|
@@ -396,6 +498,7 @@ export const OUTPUT_SCHEMAS = {
|
|
|
396
498
|
'quota-setup': Type.Object({ schemaVersion: Version, family: Str, applied: Type.Boolean(), plan: QuotaSetupPlan }, { title: 'sideby quota setup --json' }),
|
|
397
499
|
'handoff-status': Type.Object({
|
|
398
500
|
schemaVersion: Version,
|
|
501
|
+
overview: Type.Optional(Overview),
|
|
399
502
|
auto: Type.Boolean(),
|
|
400
503
|
sameFamily: Type.Boolean(),
|
|
401
504
|
prepareAt: Type.Number(),
|
|
@@ -582,9 +685,11 @@ export const OUTPUT_SCHEMAS = {
|
|
|
582
685
|
latest: Type.String({ description: 'The `latest` version on the npm registry.' }),
|
|
583
686
|
available: Type.Boolean({ description: '`latest` is newer than `current`.' }),
|
|
584
687
|
method: Type.Object({
|
|
585
|
-
kind: Type.Union(['npm', 'pnpm', 'bun', 'volta', 'npx', 'project', 'source'].map((k) => Type.Literal(k)), { description: 'How this copy was installed.' }),
|
|
688
|
+
kind: Type.Union(['npm', 'pnpm', 'bun', 'volta', 'npx', 'project', 'source', 'local'].map((k) => Type.Literal(k)), { description: 'How this copy was installed.' }),
|
|
586
689
|
canUpdate: Type.Boolean({ description: 'sideby can run the Update itself.' }),
|
|
587
690
|
reason: Type.Optional(Type.String({ description: 'Why it cannot.' })),
|
|
691
|
+
reasonCode: Type.Optional(ReasonCode),
|
|
692
|
+
reasonAt: Type.Optional(Type.String({ description: 'The folders that are not writable (`not-writable`).' })),
|
|
588
693
|
}),
|
|
589
694
|
command: Type.String({
|
|
590
695
|
description: 'What an Update runs, or what to run by hand when `canRun` is false.',
|
|
@@ -614,6 +719,8 @@ export const OUTPUT_SCHEMAS = {
|
|
|
614
719
|
description: 'False when sideby leaves the skill alone (see reason): give the user command.',
|
|
615
720
|
}),
|
|
616
721
|
reason: Type.Optional(Str),
|
|
722
|
+
reasonCode: Type.Optional(ReasonCode),
|
|
723
|
+
reasonAt: Type.Optional(Type.String({ description: 'The git repository that owns the skill (`skill-in-git`).' })),
|
|
617
724
|
}, {
|
|
618
725
|
description: 'The installed sideby skill, which the Update brings along; absent when none is installed.',
|
|
619
726
|
})),
|
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]
|
|
@@ -28,7 +36,7 @@ Usage:
|
|
|
28
36
|
sideby doctor [target] [--fix] [--force]
|
|
29
37
|
check shared items and permissions; --fix repairs safe issues
|
|
30
38
|
sideby quota [account|family] [--refresh]
|
|
31
|
-
quota and 7-day token usage; --refresh asks
|
|
39
|
+
quota and 7-day token usage; --refresh asks every fetching family again now
|
|
32
40
|
sideby quota setup claude [--yes] turn on Claude quota (shows the change first)
|
|
33
41
|
sideby quota teardown claude undo it, restoring the original file
|
|
34
42
|
sideby tune [account|family] estimate the cheapest auto-compact threshold per model
|
|
@@ -64,10 +72,12 @@ Usage:
|
|
|
64
72
|
rewrite the config's shellInitFile with them
|
|
65
73
|
sideby plugins list loaded plugins and load errors
|
|
66
74
|
sideby update [--yes] update sideby to the newest version (shows the command first)
|
|
75
|
+
sideby local [on|off] [--yes] for sideby developers: switch the global sideby between the
|
|
76
|
+
last local build (pnpm local) and the npm package
|
|
67
77
|
|
|
68
78
|
Options:
|
|
69
79
|
--json machine-readable output (list, new, next, alias, doctor, quota, tune, model, remote-control, handoff,
|
|
70
|
-
families, plugins, app, desktop, browser, update)
|
|
80
|
+
families, plugins, app, desktop, browser, update, local)
|
|
71
81
|
-h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop, browser
|
|
72
82
|
-v, --version show the version
|
|
73
83
|
|
|
@@ -215,6 +225,9 @@ export function nodeTooOld(version = process.versions.node) {
|
|
|
215
225
|
}
|
|
216
226
|
const COMMANDS = {
|
|
217
227
|
list: { flags: ['json'] },
|
|
228
|
+
pin: { flags: ['json'] },
|
|
229
|
+
unpin: { flags: ['json'] },
|
|
230
|
+
order: { flags: ['json', 'families', 'reset'] },
|
|
218
231
|
run: { flags: [] },
|
|
219
232
|
resume: { flags: [] },
|
|
220
233
|
login: { flags: [] },
|
|
@@ -237,7 +250,8 @@ const COMMANDS = {
|
|
|
237
250
|
'shell-init': { flags: ['write'] },
|
|
238
251
|
plugins: { flags: ['json'] },
|
|
239
252
|
families: { flags: ['json'] },
|
|
240
|
-
update: { flags: ['json', 'yes'] },
|
|
253
|
+
update: { flags: ['json', 'yes'], valued: ['auto'] },
|
|
254
|
+
local: { flags: ['json', 'yes'] },
|
|
241
255
|
'update-check': { flags: [] },
|
|
242
256
|
};
|
|
243
257
|
/** Commands that never print an Update Notice: their output is read by a shell or an agent, or they are internal. */
|
|
@@ -261,12 +275,13 @@ async function jsonNoticeFor() {
|
|
|
261
275
|
const { resolvePaths } = await import('../core/paths.js');
|
|
262
276
|
const { loadConfig } = await import('../core/config.js');
|
|
263
277
|
const { packageVersion } = await import('../core/version.js');
|
|
264
|
-
const { installedSkillVersions
|
|
278
|
+
const { installedSkillVersions } = await import('../core/skill-check.js');
|
|
265
279
|
const paths = resolvePaths(process.env);
|
|
266
280
|
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
267
281
|
const cache = await u.readUpdateCache(paths);
|
|
268
282
|
const skills = await installedSkillVersions(paths.home);
|
|
269
|
-
|
|
283
|
+
// `sideby update` brings the skill along, so it is the one command for both (spec §3.23).
|
|
284
|
+
const n = u.jsonNotice(cache, packageVersion(), config, process.env, skills, 'sideby update');
|
|
270
285
|
if (!u.checksOff(process.env, config) && u.checkDue(cache, new Date())) {
|
|
271
286
|
const { spawn } = await import('node:child_process');
|
|
272
287
|
const child = spawn(process.execPath, [fileURLToPath(import.meta.url), 'update-check'], {
|
|
@@ -297,7 +312,9 @@ async function afterCommand() {
|
|
|
297
312
|
const paths = resolvePaths(process.env);
|
|
298
313
|
const config = await loadConfig(paths.configFile).catch(() => ({}));
|
|
299
314
|
const cache = await u.readUpdateCache(paths);
|
|
300
|
-
const
|
|
315
|
+
const current = packageVersion();
|
|
316
|
+
const skill = u.skillBehind(await u.skillUpdatePlan(paths.home, current).catch(() => null), current);
|
|
317
|
+
const plan = u.afterCommandPlan(cache, current, config, process.env, new Date(), skill);
|
|
301
318
|
const dim = process.env.NO_COLOR ? (t) => t : (t) => `\x1b[2m${t}\x1b[22m`;
|
|
302
319
|
for (const l of plan.lines)
|
|
303
320
|
console.error(dim(l));
|
|
@@ -414,6 +431,11 @@ export async function main(argv) {
|
|
|
414
431
|
}
|
|
415
432
|
async function dispatch(cmd, parsed, io, cmds) {
|
|
416
433
|
switch (cmd) {
|
|
434
|
+
case 'pin':
|
|
435
|
+
case 'unpin':
|
|
436
|
+
return await cmds.cmdPin(parsed, io, cmd === 'pin');
|
|
437
|
+
case 'order':
|
|
438
|
+
return await cmds.cmdOrder(parsed, io);
|
|
417
439
|
case 'list':
|
|
418
440
|
return await cmds.cmdList(parsed, io);
|
|
419
441
|
case 'run':
|
|
@@ -458,6 +480,8 @@ async function dispatch(cmd, parsed, io, cmds) {
|
|
|
458
480
|
return await cmds.cmdUpdate(parsed, io);
|
|
459
481
|
case 'update-check':
|
|
460
482
|
return await cmds.cmdUpdateCheck(parsed, io);
|
|
483
|
+
case 'local':
|
|
484
|
+
return await cmds.cmdLocal(parsed, io);
|
|
461
485
|
}
|
|
462
486
|
return 2;
|
|
463
487
|
}
|
|
@@ -72,6 +72,7 @@ export declare const ConfigSchema: Type.TObject<{
|
|
|
72
72
|
display: Type.TOptional<Type.TObject<{
|
|
73
73
|
families: Type.TOptional<Type.TArray<Type.TString>>;
|
|
74
74
|
accounts: Type.TOptional<Type.TRecord<"^.*$", Type.TArray<Type.TString>>>;
|
|
75
|
+
pinned: Type.TOptional<Type.TRecord<"^.*$", Type.TArray<Type.TString>>>;
|
|
75
76
|
}>>;
|
|
76
77
|
quota: Type.TOptional<Type.TObject<{
|
|
77
78
|
check: Type.TOptional<Type.TRecord<"^.*$", Type.TBoolean>>;
|