sideby 0.8.0 → 0.9.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/dist/build.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"id":"
|
|
1
|
+
{"id":"20261015T030631-4ed31c"}
|
package/dist/src/panel/i18n.d.ts
CHANGED
|
@@ -120,6 +120,8 @@ declare const en: {
|
|
|
120
120
|
'filter.clear': string;
|
|
121
121
|
'group.expand': string;
|
|
122
122
|
'group.collapse': string;
|
|
123
|
+
'row.collapse': string;
|
|
124
|
+
'row.expand': string;
|
|
123
125
|
'group.toggle': string;
|
|
124
126
|
'group.attention.one': string;
|
|
125
127
|
'group.attention.other': string;
|
package/dist/src/panel/i18n.js
CHANGED
|
@@ -122,6 +122,8 @@ const en = {
|
|
|
122
122
|
'filter.clear': 'Clear filters',
|
|
123
123
|
'group.expand': 'Show accounts',
|
|
124
124
|
'group.collapse': 'Hide accounts',
|
|
125
|
+
'row.collapse': 'Collapse {name}',
|
|
126
|
+
'row.expand': 'Expand {name}',
|
|
125
127
|
'group.toggle': '{title} ({count}): {action}',
|
|
126
128
|
'group.attention.one': '{n} needs attention',
|
|
127
129
|
'group.attention.other': '{n} need attention',
|
|
@@ -582,6 +584,8 @@ const zh = {
|
|
|
582
584
|
'filter.clear': '清除筛选',
|
|
583
585
|
'group.expand': '展开账号',
|
|
584
586
|
'group.collapse': '收起账号',
|
|
587
|
+
'row.collapse': '收起 {name}',
|
|
588
|
+
'row.expand': '展开 {name}',
|
|
585
589
|
'group.toggle': '{title}({count}):{action}',
|
|
586
590
|
'group.attention.one': '{n} 个需要处理',
|
|
587
591
|
'group.attention.other': '{n} 个需要处理',
|
|
@@ -65,13 +65,15 @@ const MAIN = String.raw `
|
|
|
65
65
|
function loadPrefs() {
|
|
66
66
|
let raw = {}
|
|
67
67
|
try { raw = JSON.parse(localStorage.getItem(PREFS_KEY) || '{}') || {} } catch {}
|
|
68
|
-
const p = { view: 'list', sort: 'pressure', quota: 'used', theme: 'system', lang: '', family: '', collapsed: {}, hideEmail: false }
|
|
68
|
+
const p = { view: 'list', sort: 'pressure', quota: 'used', theme: 'system', lang: '', family: '', collapsed: {}, rows: {}, hideEmail: false }
|
|
69
69
|
for (const k of Object.keys(CHOICES)) if (CHOICES[k].includes(raw[k])) p[k] = raw[k]
|
|
70
70
|
if (raw.hideEmail === true) p.hideEmail = true
|
|
71
71
|
if (typeof raw.ahHintAt === 'number') p.ahHintAt = raw.ahHintAt
|
|
72
72
|
if (typeof raw.family === 'string') p.family = raw.family
|
|
73
73
|
if (raw.collapsed && typeof raw.collapsed === 'object')
|
|
74
74
|
for (const k of Object.keys(raw.collapsed)) if (raw.collapsed[k] === true) p.collapsed[k] = true
|
|
75
|
+
if (raw.rows && typeof raw.rows === 'object')
|
|
76
|
+
for (const k of Object.keys(raw.rows)) if (raw.rows[k] === true) p.rows[k] = true
|
|
75
77
|
return p
|
|
76
78
|
}
|
|
77
79
|
function savePrefs() { try { localStorage.setItem(PREFS_KEY, JSON.stringify(prefs)) } catch {} }
|
|
@@ -954,12 +956,63 @@ const MAIN = String.raw `
|
|
|
954
956
|
}
|
|
955
957
|
// What a row or card shows depends on more than the account (health, view preferences, language); the
|
|
956
958
|
// fingerprint covers what its click handlers use.
|
|
957
|
-
function rowSig(a) { return sig([a, healthOf(a.ref), isNext(a), ui.desktopRef === a.ref]) }
|
|
959
|
+
function rowSig(a) { return sig([a, healthOf(a.ref), isNext(a), ui.desktopRef === a.ref, folded(a)]) }
|
|
960
|
+
// Each account can be folded to one line (its name) and opened again, like a family group; the choice is kept in
|
|
961
|
+
// this browser's preferences.
|
|
962
|
+
function folded(a) { return prefs.rows[a.ref] === true }
|
|
963
|
+
function rowToggle(a) {
|
|
964
|
+
const open = !folded(a)
|
|
965
|
+
const label = t(open ? 'row.collapse' : 'row.expand', { name: a.name })
|
|
966
|
+
return h('button', { class: 'icon-btn row-toggle', type: 'button', 'aria-expanded': String(open), 'aria-label': label, title: label, onclick: (e) => { e.stopPropagation(); toggleRow(a) } }, icon('chevron', 'chev'))
|
|
967
|
+
}
|
|
968
|
+
// The whole account cell (and a folded row anywhere) folds and opens it, not only the arrow; the buttons and links
|
|
969
|
+
// inside keep their own action, and a click that ends a text selection does nothing.
|
|
970
|
+
function foldClick(a) {
|
|
971
|
+
return (e) => {
|
|
972
|
+
if (e.target.closest('button, a, input, select, textarea, .tip')) return
|
|
973
|
+
if (window.getSelection && String(window.getSelection())) return
|
|
974
|
+
toggleRow(a)
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
// Folding animates: the details fade out, then the row's height eases to one line; opening eases the height out
|
|
978
|
+
// and fades the details in. The arrow turns with it. Reduced motion just switches.
|
|
979
|
+
const EASE = 'cubic-bezier(.2, 0, 0, 1)'
|
|
980
|
+
function rowDetails(el) { return Array.from(el.querySelectorAll(':scope > :not(.ident):not(.more):not(.card-head), .ident-email, .ident-sub')) }
|
|
981
|
+
function toggleRow(a) {
|
|
982
|
+
const closing = !folded(a)
|
|
983
|
+
const find = () => document.querySelector('[data-ref="' + CSS.escape(a.ref) + '"]')
|
|
984
|
+
const apply = () => {
|
|
985
|
+
if (closing) prefs.rows[a.ref] = true; else delete prefs.rows[a.ref]
|
|
986
|
+
savePrefs()
|
|
987
|
+
renderAccounts()
|
|
988
|
+
}
|
|
989
|
+
const old = find()
|
|
990
|
+
// A hidden page delays animation events; switch at once there too.
|
|
991
|
+
if (REDUCED || document.hidden || !old || !old.animate) { apply(); return }
|
|
992
|
+
const from = old.getBoundingClientRect().height
|
|
993
|
+
const fade = closing ? rowDetails(old).map((d) => d.animate([{ opacity: 1 }, { opacity: 0 }], { duration: 110, easing: 'ease-out', fill: 'forwards' }).finished) : []
|
|
994
|
+
Promise.all(fade).catch(() => {}).then(() => {
|
|
995
|
+
apply()
|
|
996
|
+
const el = find()
|
|
997
|
+
if (!el) return
|
|
998
|
+
const to = el.getBoundingClientRect().height
|
|
999
|
+
el.style.overflow = 'hidden'
|
|
1000
|
+
const done = () => { el.style.overflow = '' }
|
|
1001
|
+
el.animate([{ height: from + 'px' }, { height: to + 'px' }], { duration: 220, easing: EASE }).finished.then(done, done)
|
|
1002
|
+
const chev = el.querySelector('.row-toggle .chev')
|
|
1003
|
+
if (chev) chev.animate([{ transform: closing ? 'rotate(90deg)' : 'rotate(0deg)' }, { transform: closing ? 'rotate(0deg)' : 'rotate(90deg)' }], { duration: 220, easing: EASE })
|
|
1004
|
+
if (!closing) for (const d of rowDetails(el)) d.animate([{ opacity: 0, transform: 'translateY(-4px)' }, { opacity: 1, transform: 'none' }], { duration: 200, delay: 60, easing: 'ease-out', fill: 'backwards' })
|
|
1005
|
+
})
|
|
1006
|
+
}
|
|
958
1007
|
// The Account 'sideby next <family>' would pick; the server ranks it with the CLI's rule (spec §3.13).
|
|
959
1008
|
function isNext(a) { return Boolean(S.state && S.state.handoff && S.state.handoff[a.family] === a.ref) }
|
|
960
1009
|
function row(a, now, cols) {
|
|
1010
|
+
if (folded(a))
|
|
1011
|
+
return h('div', { class: 'row row-folded fold-hit' + (a.error ? ' row-error' : ''), role: 'listitem', 'data-ref': a.ref, 'data-key': a.ref, 'data-sig': rowSig(a), onclick: foldClick(a) },
|
|
1012
|
+
h('div', { class: 'ident' }, rowToggle(a), badge(a.family, 'sm'), h('div', { class: 'ident-text' }, nameLine(a))),
|
|
1013
|
+
moreButton(a))
|
|
961
1014
|
return h('div', { class: 'row' + (a.error ? ' row-error' : ''), role: 'listitem', 'data-ref': a.ref, 'data-key': a.ref, 'data-sig': rowSig(a) },
|
|
962
|
-
h('div', { class: 'ident' }, badge(a.family, 'sm'), h('div', { class: 'ident-text' }, nameLine(a), emailLine(a), subLine(a))),
|
|
1015
|
+
h('div', { class: 'ident fold-hit', onclick: foldClick(a) }, rowToggle(a), badge(a.family, 'sm'), h('div', { class: 'ident-text' }, nameLine(a), emailLine(a), subLine(a))),
|
|
963
1016
|
quotaBlock(a, now, cols),
|
|
964
1017
|
usageBlock(a),
|
|
965
1018
|
lastUsed(a),
|
|
@@ -968,8 +1021,12 @@ const MAIN = String.raw `
|
|
|
968
1021
|
notes(a))
|
|
969
1022
|
}
|
|
970
1023
|
function card(a, now, cols) {
|
|
1024
|
+
if (folded(a))
|
|
1025
|
+
return h('article', { class: 'card card-folded fold-hit' + (a.error ? ' card-error' : ''), 'data-ref': a.ref, 'data-key': a.ref, 'data-sig': rowSig(a), 'aria-label': a.ref, onclick: foldClick(a) },
|
|
1026
|
+
h('header', { class: 'card-head' }, rowToggle(a), badge(a.family, 'sm'), h('div', { class: 'ident-text' }, nameLine(a)), moreButton(a)))
|
|
971
1027
|
return h('article', { class: 'card' + (a.error ? ' card-error' : ''), 'data-ref': a.ref, 'data-key': a.ref, 'data-sig': rowSig(a), 'aria-label': a.ref },
|
|
972
|
-
h('header', { class: 'card-head' },
|
|
1028
|
+
h('header', { class: 'card-head fold-hit', onclick: foldClick(a) },
|
|
1029
|
+
rowToggle(a),
|
|
973
1030
|
badge(a.family, 'sm'),
|
|
974
1031
|
h('div', { class: 'ident-text' }, nameLine(a), emailLine(a), subLine(a)),
|
|
975
1032
|
moreButton(a)),
|
|
@@ -159,6 +159,16 @@ code, .mono { font-family: var(--mono); font-size: 0.92em; }
|
|
|
159
159
|
.row:first-child { border-top: 0; }
|
|
160
160
|
.row:last-child { border-radius: 0 0 var(--radius) var(--radius); }
|
|
161
161
|
.row-error .ident-name .name { color: var(--fail); }
|
|
162
|
+
/* One account folded to its name line, opened again with the arrow at its start. */
|
|
163
|
+
.row-toggle { width: 28px; height: 28px; margin: -4px -6px -4px -8px; }
|
|
164
|
+
.fold-hit { cursor: pointer; }
|
|
165
|
+
.row > .ident.fold-hit { align-self: stretch; margin: -11px 0 -11px -16px; padding: 11px 0 11px 16px; }
|
|
166
|
+
.fold-hit:hover .row-toggle { background: var(--hover); color: var(--text); }
|
|
167
|
+
.row-toggle[aria-expanded=true] .chev { transform: rotate(90deg); }
|
|
168
|
+
.row-toggle[aria-expanded=true]:not(:hover) { background: transparent; color: var(--muted); }
|
|
169
|
+
.fold-hit:hover .row-toggle[aria-expanded=true] { background: var(--hover); color: var(--text); }
|
|
170
|
+
.row-folded { grid-template-areas: "id id id id id more"; padding-top: 7px; padding-bottom: 7px; }
|
|
171
|
+
.card-folded .card-head { align-items: center; }
|
|
162
172
|
.col-head { position: sticky; top: 0; z-index: 2; padding: 7px 10px 7px 16px; border-top: 1px solid var(--border); background: var(--surface); color: var(--faint); font-size: 11.5px; font-weight: var(--strong-weight); line-height: 1.3; white-space: nowrap; }
|
|
163
173
|
.col-head > span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
|
|
164
174
|
.col-head .quota > span { overflow: hidden; text-overflow: ellipsis; }
|
|
@@ -515,6 +525,7 @@ select:is(:hover, :focus) { --chevron: var(--text); }
|
|
|
515
525
|
@media (max-width: 1100px) {
|
|
516
526
|
.col-head { display: none; }
|
|
517
527
|
.row { grid-template-columns: minmax(0, 1fr) auto var(--m-w); grid-template-areas: "id health more" "quota quota quota" "usage usage last" "notes notes notes"; row-gap: 10px; }
|
|
528
|
+
.row-folded { grid-template-areas: "id id more"; }
|
|
518
529
|
.row > .quota { grid-template-columns: repeat(var(--qn), minmax(0, 1fr)); max-width: 560px; }
|
|
519
530
|
.rows .m-label, .rows .m-unit, .rows .u-unit { display: inline; }
|
|
520
531
|
.rows .usage { justify-content: flex-start; }
|
package/package.json
CHANGED
package/skills/sideby/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: sideby
|
|
3
3
|
description: Use when the user runs, or wants to run, several AI coding accounts (Claude Code, Codex, Grok Build, pi) side by side. Covers installing sideby and setting up a new account ("install sideby", "set up another Claude account", "add cc008", "配置一个 cc00x", "再加一个 Codex 账号"), explaining what accounts share and what stays separate ("what do my accounts share?", "这些账号共享了什么"), showing which account a session is ("show [cc001] in the status line", "加上账号标识"), seeing accounts, quota and usage, moving on when one hits its 5-hour or weekly limit, adding a short command (alias), and repairing drifted account directories (skills, hooks, rules or settings not shared, wrong symlinks, credential file permissions). Drives the `sideby` CLI with `--json`; the agent does every step except browser sign-in, filling in secrets and starting interactive sessions, which it hands to the user as one command.
|
|
4
4
|
metadata:
|
|
5
|
-
version: "0.
|
|
5
|
+
version: "0.9.0"
|
|
6
6
|
requires:
|
|
7
7
|
bins: [sideby, node]
|
|
8
8
|
cliHelp: sideby --help
|