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.
Files changed (85) hide show
  1. package/README.md +2 -1
  2. package/dist/build.json +1 -1
  3. package/dist/src/cli/commands.d.ts +4 -2
  4. package/dist/src/cli/commands.js +194 -11
  5. package/dist/src/cli/format.js +7 -3
  6. package/dist/src/cli/json-schemas.js +104 -3
  7. package/dist/src/cli/main.js +27 -7
  8. package/dist/src/core/config.d.ts +28 -0
  9. package/dist/src/core/config.js +54 -1
  10. package/dist/src/core/handoff-enable.js +10 -5
  11. package/dist/src/core/launch.d.ts +5 -0
  12. package/dist/src/core/launch.js +14 -3
  13. package/dist/src/core/quota-fetch.d.ts +22 -0
  14. package/dist/src/core/quota-fetch.js +98 -0
  15. package/dist/src/core/quota-levels.d.ts +3 -2
  16. package/dist/src/core/quota-levels.js +8 -4
  17. package/dist/src/core/quota-store.d.ts +19 -0
  18. package/dist/src/core/quota-store.js +62 -0
  19. package/dist/src/core/skill-check.d.ts +16 -0
  20. package/dist/src/core/skill-check.js +47 -3
  21. package/dist/src/core/update.d.ts +67 -1
  22. package/dist/src/core/update.js +147 -37
  23. package/dist/src/families/grok/handoff-hook.d.ts +6 -0
  24. package/dist/src/families/grok/handoff-hook.js +45 -2
  25. package/dist/src/families/grok/handoff.d.ts +6 -1
  26. package/dist/src/families/grok/handoff.js +30 -13
  27. package/dist/src/families/grok/index.js +5 -0
  28. package/dist/src/families/grok/quota-log.d.ts +30 -0
  29. package/dist/src/families/grok/quota-log.js +140 -0
  30. package/dist/src/families/grok/quota.d.ts +5 -0
  31. package/dist/src/families/grok/quota.js +112 -0
  32. package/dist/src/panel/handler.d.ts +13 -3
  33. package/dist/src/panel/handler.js +81 -8
  34. package/dist/src/panel/i18n.d.ts +138 -8
  35. package/dist/src/panel/i18n.js +301 -41
  36. package/dist/src/panel/page-logic.d.ts +59 -2
  37. package/dist/src/panel/page-logic.js +99 -6
  38. package/dist/src/panel/page-script.js +769 -175
  39. package/dist/src/panel/page-styles.js +83 -15
  40. package/dist/src/panel/page.js +3 -0
  41. package/dist/src/runtime.d.ts +32 -2
  42. package/dist/src/runtime.js +64 -21
  43. package/dist/src/types.d.ts +49 -1
  44. package/package.json +3 -2
  45. package/schemas/alias.json +1 -1
  46. package/schemas/app-install.json +1 -1
  47. package/schemas/app-uninstall.json +1 -1
  48. package/schemas/browser-open.json +1 -1
  49. package/schemas/browser-switch.json +1 -1
  50. package/schemas/browser.json +1 -1
  51. package/schemas/config.json +60 -0
  52. package/schemas/desktop-remove.json +1 -1
  53. package/schemas/desktop.json +1 -1
  54. package/schemas/doctor.json +1 -1
  55. package/schemas/error.json +1 -1
  56. package/schemas/families.json +1 -1
  57. package/schemas/handoff-disable.json +1 -1
  58. package/schemas/handoff-enable.json +1 -1
  59. package/schemas/handoff-ready.json +1 -1
  60. package/schemas/handoff-setup.json +1 -1
  61. package/schemas/handoff-status.json +282 -1
  62. package/schemas/handoff-teardown.json +1 -1
  63. package/schemas/list.json +6 -1
  64. package/schemas/model.json +1 -1
  65. package/schemas/new.json +1 -1
  66. package/schemas/next.json +1 -1
  67. package/schemas/order.json +148 -0
  68. package/schemas/pin.json +126 -0
  69. package/schemas/plugins.json +1 -1
  70. package/schemas/quota-check.json +88 -0
  71. package/schemas/quota-setup.json +1 -1
  72. package/schemas/quota-teardown.json +1 -1
  73. package/schemas/quota.json +15 -2
  74. package/schemas/remote-control.json +1 -1
  75. package/schemas/tune-change.json +1 -1
  76. package/schemas/tune.json +1 -1
  77. package/schemas/unpin.json +126 -0
  78. package/schemas/update-auto.json +84 -0
  79. package/schemas/update.json +79 -1
  80. package/skills/sideby/SKILL.md +7 -3
  81. package/skills/sideby/references/diagnose.md +2 -1
  82. package/skills/sideby/references/handoff.md +9 -1
  83. package/skills/sideby/references/panel.md +28 -0
  84. package/skills/sideby/references/quota.md +11 -1
  85. 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 q.windows) {
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 q.windows.length && max < 0 ? 0 : max;
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
- /** Sorted copy: `pressure` (fullest Quota first), `name`, or `recent` (last activity first). The Main Account leads ties. */
109
- export function sortAccounts(list, mode, now) {
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
- let max = q.windows.length ? 0 : -1;
117
- for (const w of q.windows) {
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