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
|
@@ -44,14 +44,16 @@ export function quotaPressure(a, now) {
|
|
|
44
44
|
const q = a.quota;
|
|
45
45
|
if (q?.status !== 'ok' || !q.windows)
|
|
46
46
|
return -1;
|
|
47
|
+
// A named window covers only some models and never counts (spec §3.26).
|
|
48
|
+
const windows = q.windows.filter((w) => !w.name);
|
|
47
49
|
let max = -1;
|
|
48
|
-
for (const w of
|
|
50
|
+
for (const w of windows) {
|
|
49
51
|
const reset = Date.parse(w.resetsAt);
|
|
50
52
|
if (Number.isFinite(reset) && reset <= now)
|
|
51
53
|
continue;
|
|
52
54
|
max = Math.max(max, Math.max(0, Math.min(100, Number(w.usedPercent) || 0)));
|
|
53
55
|
}
|
|
54
|
-
return
|
|
56
|
+
return windows.length && max < 0 ? 0 : max;
|
|
55
57
|
}
|
|
56
58
|
/**
|
|
57
59
|
* Why an Account needs a look, most serious first: `error`, `health-fail`, `signed-out`, `not-installed`,
|
|
@@ -71,6 +73,8 @@ export function attentionReasons(a, health, now, warn) {
|
|
|
71
73
|
const q = a.quota;
|
|
72
74
|
if (q && q.status === 'ok' && q.windows && !q.from)
|
|
73
75
|
for (const w of q.windows) {
|
|
76
|
+
if (w.name)
|
|
77
|
+
continue;
|
|
74
78
|
const reset = Date.parse(w.resetsAt);
|
|
75
79
|
if (Number.isFinite(reset) && reset <= now)
|
|
76
80
|
continue;
|
|
@@ -96,6 +100,8 @@ export function attentionWhy(a, health, now, warn) {
|
|
|
96
100
|
return { key: `why.${reason}`, vars: {} };
|
|
97
101
|
let top = null;
|
|
98
102
|
for (const w of a.quota?.windows ?? []) {
|
|
103
|
+
if (w.name)
|
|
104
|
+
continue;
|
|
99
105
|
const reset = Date.parse(w.resetsAt);
|
|
100
106
|
if (Number.isFinite(reset) && reset <= now)
|
|
101
107
|
continue;
|
|
@@ -105,16 +111,55 @@ export function attentionWhy(a, health, now, warn) {
|
|
|
105
111
|
}
|
|
106
112
|
return { key: 'why.quota-high', vars: { label: top?.label ?? '', pct: Math.round(top?.pct ?? 0), warn } };
|
|
107
113
|
}
|
|
108
|
-
/**
|
|
109
|
-
|
|
114
|
+
/**
|
|
115
|
+
* `items` in Display Order (spec §3.25): those whose key is in `order` first, in that order; the others after, in the
|
|
116
|
+
* order they came. Names no longer there are simply not met.
|
|
117
|
+
*/
|
|
118
|
+
export function inDisplayOrder(items, key, order) {
|
|
119
|
+
const at = new Map((order ?? []).map((k, i) => [k, i]));
|
|
120
|
+
return items
|
|
121
|
+
.map((t, i) => ({ t, i, r: at.has(key(t)) ? at.get(key(t)) : (order?.length ?? 0) }))
|
|
122
|
+
.sort((x, y) => x.r - y.r || x.i - y.i)
|
|
123
|
+
.map((x) => x.t);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* The Display Order after a drag (spec §3.25). `all` is every key of the list in the current Display Order, hidden
|
|
127
|
+
* ones included; `shown` is what the screen showed, in its order, as units (one key, or a Same Login group that moves
|
|
128
|
+
* as one). The unit at `from` moves to `to`; the shown keys then fill the places shown keys held in `all`, so keys a
|
|
129
|
+
* filter hides keep their place among themselves.
|
|
130
|
+
*/
|
|
131
|
+
export function reorder(all, shown, from, to) {
|
|
132
|
+
const units = shown.slice();
|
|
133
|
+
if (from < 0 || from >= units.length || to < 0 || to >= units.length)
|
|
134
|
+
return all.slice();
|
|
135
|
+
const [unit] = units.splice(from, 1);
|
|
136
|
+
units.splice(to, 0, unit);
|
|
137
|
+
const next = units.flat();
|
|
138
|
+
const visible = new Set(next);
|
|
139
|
+
const out = all.slice();
|
|
140
|
+
for (const k of next)
|
|
141
|
+
if (!out.includes(k))
|
|
142
|
+
out.push(k);
|
|
143
|
+
let i = 0;
|
|
144
|
+
for (let at = 0; at < out.length; at++)
|
|
145
|
+
if (visible.has(out[at]))
|
|
146
|
+
out[at] = next[i++];
|
|
147
|
+
return out;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Sorted copy: `pressure` (fullest Quota first), `name`, `recent` (last activity first), or `manual` (the Family's
|
|
151
|
+
* Display Order `order`, names not in it after). The Main Account leads ties.
|
|
152
|
+
*/
|
|
153
|
+
export function sortAccounts(list, mode, now, order) {
|
|
110
154
|
const byName = (x, y) => Number(Boolean(y.isMain)) - Number(Boolean(x.isMain)) ||
|
|
111
155
|
x.name.localeCompare(y.name, undefined, { numeric: true });
|
|
112
156
|
const pressure = (a) => {
|
|
113
157
|
const q = a.quota;
|
|
114
158
|
if (q?.status !== 'ok' || !q.windows)
|
|
115
159
|
return -1;
|
|
116
|
-
|
|
117
|
-
|
|
160
|
+
const windows = q.windows.filter((w) => !w.name);
|
|
161
|
+
let max = windows.length ? 0 : -1;
|
|
162
|
+
for (const w of windows) {
|
|
118
163
|
const reset = Date.parse(w.resetsAt);
|
|
119
164
|
if (!(Number.isFinite(reset) && reset <= now))
|
|
120
165
|
max = Math.max(max, Number(w.usedPercent) || 0);
|
|
@@ -128,12 +173,35 @@ export function sortAccounts(list, mode, now) {
|
|
|
128
173
|
const out = list.slice();
|
|
129
174
|
if (mode === 'name')
|
|
130
175
|
out.sort(byName);
|
|
176
|
+
else if (mode === 'manual')
|
|
177
|
+
return inDisplayOrder(out.sort(byName), (a) => a.name, order);
|
|
131
178
|
else if (mode === 'recent')
|
|
132
179
|
out.sort((x, y) => last(y) - last(x) || byName(x, y));
|
|
133
180
|
else
|
|
134
181
|
out.sort((x, y) => pressure(y) - pressure(x) || byName(x, y));
|
|
135
182
|
return out;
|
|
136
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Whether an Account is pinned (spec §3.25): its name is in `pinned`, or, for a Same Login, the name of any of its
|
|
186
|
+
* directories, so the login is pinned as a whole.
|
|
187
|
+
*/
|
|
188
|
+
export function pinnedIn(a, pinned) {
|
|
189
|
+
return (pinned.includes(a.name) || (a.sameLogin ?? []).some((r) => pinned.includes(r.slice(r.indexOf(':') + 1))));
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Pinned Accounts first (spec §3.25): the names in `pinned`, in that order, then the rest in the order they came.
|
|
193
|
+
* A Same Login follows any of its pinned directories, so it stays together. Names no longer there are simply not met.
|
|
194
|
+
*/
|
|
195
|
+
export function withPinned(list, pinned) {
|
|
196
|
+
if (!pinned?.length)
|
|
197
|
+
return list.slice();
|
|
198
|
+
const top = [];
|
|
199
|
+
for (const n of pinned)
|
|
200
|
+
for (const a of list)
|
|
201
|
+
if (!top.includes(a) && pinnedIn(a, [n]))
|
|
202
|
+
top.push(a);
|
|
203
|
+
return top.concat(list.filter((a) => !top.includes(a)));
|
|
204
|
+
}
|
|
137
205
|
/**
|
|
138
206
|
* The sorted list with each Same Login together (spec §3.21): an Account, or the shown members of one login, in
|
|
139
207
|
* order, at the place of its first member. A login with one shown member stays a plain Account.
|
|
@@ -289,6 +357,31 @@ export function panelHint(hint, fixable, action) {
|
|
|
289
357
|
return hint;
|
|
290
358
|
return hint.replace(/run `sideby doctor --fix`/g, (_m, at) => at === 0 ? action.charAt(0).toUpperCase() + action.slice(1) : action);
|
|
291
359
|
}
|
|
360
|
+
/**
|
|
361
|
+
* Whether the Panel fixes Finding `f` with a button of its own: a sideby skill behind this version while the
|
|
362
|
+
* Update plan says sideby may update it (`skillUpdatable`). Such Findings stay out of the prompt for an AI agent.
|
|
363
|
+
*/
|
|
364
|
+
export function panelFixes(f, skillUpdatable) {
|
|
365
|
+
return skillUpdatable && f.code === 'skill.outdated';
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* The prompt "Copy for AI" puts on the clipboard: `words.lead`, then each Finding that has no automatic fix
|
|
369
|
+
* (numbered, with its account, item, level, message and hint), then `words.fixFirst` when some Findings do have
|
|
370
|
+
* one, then `words.check`. Empty when every Finding has an automatic fix. `words.hint` labels a hint line.
|
|
371
|
+
*/
|
|
372
|
+
export function fixPrompt(findings, words) {
|
|
373
|
+
const manual = findings.filter((f) => !f.fixable);
|
|
374
|
+
if (!manual.length)
|
|
375
|
+
return '';
|
|
376
|
+
const items = manual.map((f, i) => {
|
|
377
|
+
const lines = [i + 1 + '. [' + f.level + '] ' + f.account + ' · ' + f.item, ' ' + f.message];
|
|
378
|
+
if (f.hint)
|
|
379
|
+
lines.push(' ' + words.hint + f.hint);
|
|
380
|
+
return lines.join('\n');
|
|
381
|
+
});
|
|
382
|
+
const tail = manual.length < findings.length ? [words.fixFirst, words.check] : [words.check];
|
|
383
|
+
return [words.lead, items.join('\n\n'), ...tail].join('\n\n');
|
|
384
|
+
}
|
|
292
385
|
/**
|
|
293
386
|
* A short command to offer for a new Account `name` of a Family, from the config aliases of its existing Accounts:
|
|
294
387
|
* an alias that is a prefix plus its Account's name (`cc001` for `claude:001`) gives the prefix, the most common
|