@dst-justin/relay 2.3.0 → 2.3.1
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 +8 -0
- package/package.json +1 -1
- package/relay +244 -39
package/README.md
CHANGED
|
@@ -255,6 +255,14 @@ Sessions live in `~/.claude/projects/` and are shared across all accounts — af
|
|
|
255
255
|
|
|
256
256
|
## Changelog
|
|
257
257
|
|
|
258
|
+
### v2.3.1 — 2026-07-12
|
|
259
|
+
- Fix: autoswitch daemon now auto-redeploys and restarts after relay itself is updated — previously the daemon file was only regenerated by `relay autoswitch start`, so an already-running daemon would silently keep running stale code (missing new features and previously-fixed bugs) until manually restarted
|
|
260
|
+
- Fix: `relay reorder` no longer drops accounts omitted from the typed order — they're now appended in their prior relative order instead of being silently removed from autoswitch rotation
|
|
261
|
+
- Fix: autoswitch reorder wizard no longer discards a typed order; supports concatenated digit shorthand (e.g. `231`)
|
|
262
|
+
- Add: standalone `relay reorder` command
|
|
263
|
+
- Fix: lock/warmup auto-default configs, autoswitch daemon auto-default order, and `render_table` all respect the persisted account order file instead of falling back to alphabetical sort
|
|
264
|
+
- Fix: account order file stays in sync on add/save/remove/rename
|
|
265
|
+
|
|
258
266
|
### v2.3.0 — 2026-07-10
|
|
259
267
|
- Add scheduled warmup: `relay warmup add/remove/list/pause/resume` to pre-warm a 5hr session at set times
|
|
260
268
|
- Daemon warmup engine (`check_warmup`/`do_warmup`) fires scheduled warmups; always pings (`claude -p ping`) on fire rather than skipping when the account looks "already active" — an idle-overnight account was silently missing its pre-warm
|
package/package.json
CHANGED
package/relay
CHANGED
|
@@ -141,6 +141,86 @@ list_account_names() {
|
|
|
141
141
|
fi
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
# append name to ORDER_FILE if not already tracked (used by add/save)
|
|
145
|
+
add_to_order() {
|
|
146
|
+
local name="$1"
|
|
147
|
+
touch "${ORDER_FILE}"
|
|
148
|
+
grep -Fxq "${name}" "${ORDER_FILE}" 2>/dev/null && return 0
|
|
149
|
+
echo "${name}" >> "${ORDER_FILE}"
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
# remove name's line from ORDER_FILE (used by remove)
|
|
153
|
+
remove_from_order() {
|
|
154
|
+
local name="$1"
|
|
155
|
+
[[ -f "${ORDER_FILE}" ]] || return 0
|
|
156
|
+
local tmp="${ORDER_FILE}.tmp.$$"
|
|
157
|
+
grep -Fxv "${name}" "${ORDER_FILE}" > "${tmp}" 2>/dev/null || : > "${tmp}"
|
|
158
|
+
mv "${tmp}" "${ORDER_FILE}"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
# rename a tracked entry in place, preserving its position (used by rename)
|
|
162
|
+
rename_in_order() {
|
|
163
|
+
local old="$1" new="$2"
|
|
164
|
+
[[ -f "${ORDER_FILE}" ]] || return 0
|
|
165
|
+
local tmp="${ORDER_FILE}.tmp.$$"
|
|
166
|
+
awk -v old="${old}" -v new="${new}" '{ print ($0 == old) ? new : $0 }' "${ORDER_FILE}" > "${tmp}"
|
|
167
|
+
mv "${tmp}" "${ORDER_FILE}"
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
# interactive numbered reorder picker. Prints the account list + prompt to
|
|
171
|
+
# stderr (so it's visible even when the caller captures stdout), reads one
|
|
172
|
+
# line from stdin, resolves it to a comma-joined order, and echoes that
|
|
173
|
+
# order to stdout for the caller to capture.
|
|
174
|
+
prompt_reorder() {
|
|
175
|
+
local accounts=("$@")
|
|
176
|
+
echo "" >&2
|
|
177
|
+
printf " ${D}When an account hits its threshold, relay switches to the next one in order.${R}\n\n" >&2
|
|
178
|
+
local i=1 acct
|
|
179
|
+
for acct in "${accounts[@]}"; do
|
|
180
|
+
printf " ${D}%d${R} %s\n" "${i}" "${acct}" >&2
|
|
181
|
+
i=$((i+1))
|
|
182
|
+
done
|
|
183
|
+
echo "" >&2
|
|
184
|
+
printf " ${D}Type numbers in the order you want (e.g. ${CY}2 1${D}, or ${CY}21${D} for <=9 accounts, or Enter to keep the order above):${R}\n" >&2
|
|
185
|
+
printf " > " >&2
|
|
186
|
+
read -r order_input
|
|
187
|
+
|
|
188
|
+
local order_str; order_str=$("${PY}" - "${order_input}" "${accounts[@]}" <<'PYEOF'
|
|
189
|
+
import sys, re
|
|
190
|
+
raw = sys.argv[1].strip()
|
|
191
|
+
accts = sys.argv[2:]
|
|
192
|
+
if not raw:
|
|
193
|
+
result = accts
|
|
194
|
+
elif raw.isdigit() and not re.search(r'[\s,]', raw) and len(accts) <= 9:
|
|
195
|
+
# concatenated shorthand, e.g. "312" -> [3, 1, 2], only unambiguous for <=9 accounts
|
|
196
|
+
result = []
|
|
197
|
+
for ch in raw:
|
|
198
|
+
idx = int(ch) - 1
|
|
199
|
+
if 0 <= idx < len(accts):
|
|
200
|
+
result.append(accts[idx])
|
|
201
|
+
else:
|
|
202
|
+
tokens = re.split(r'[\s,]+', raw)
|
|
203
|
+
result = []
|
|
204
|
+
for t in tokens:
|
|
205
|
+
t = t.strip()
|
|
206
|
+
if t.isdigit():
|
|
207
|
+
idx = int(t) - 1
|
|
208
|
+
if 0 <= idx < len(accts):
|
|
209
|
+
result.append(accts[idx])
|
|
210
|
+
elif t:
|
|
211
|
+
result.append(t)
|
|
212
|
+
print(','.join(result))
|
|
213
|
+
PYEOF
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
local chain; chain=$(echo "${order_str}" | "${PY}" -c "
|
|
217
|
+
import sys; names=sys.stdin.read().strip().split(',')
|
|
218
|
+
print(' -> '.join(names) + ' -> (cycle)')")
|
|
219
|
+
printf " ${D}Order: ${CY}%s${R}\n" "${chain}" >&2
|
|
220
|
+
|
|
221
|
+
echo "${order_str}"
|
|
222
|
+
}
|
|
223
|
+
|
|
144
224
|
account_by_index() {
|
|
145
225
|
local idx="$1" i=1 name
|
|
146
226
|
while IFS= read -r name; do
|
|
@@ -228,7 +308,24 @@ def reset_in(iso):
|
|
|
228
308
|
except Exception:
|
|
229
309
|
return '—'
|
|
230
310
|
|
|
231
|
-
|
|
311
|
+
def read_order(creds_dir):
|
|
312
|
+
relay_dir = os.path.dirname(creds_dir)
|
|
313
|
+
order_file = os.path.join(relay_dir, 'order')
|
|
314
|
+
on_disk = set(f[:-5] for f in os.listdir(creds_dir) if f.endswith('.json')) if os.path.isdir(creds_dir) else set()
|
|
315
|
+
ordered = []
|
|
316
|
+
if os.path.exists(order_file):
|
|
317
|
+
for line in open(order_file):
|
|
318
|
+
n = line.strip()
|
|
319
|
+
if n in on_disk and n not in ordered:
|
|
320
|
+
ordered.append(n)
|
|
321
|
+
for n in sorted(on_disk):
|
|
322
|
+
if n not in ordered:
|
|
323
|
+
ordered.append(n)
|
|
324
|
+
with open(order_file, 'w') as f:
|
|
325
|
+
f.write('\n'.join(ordered) + ('\n' if ordered else ''))
|
|
326
|
+
return ordered
|
|
327
|
+
|
|
328
|
+
names = read_order(creds_dir)
|
|
232
329
|
if not names:
|
|
233
330
|
print(f' \033[33m⚠\033[0m No accounts yet. Run: {B}relay add <name>{R}')
|
|
234
331
|
sys.exit(0)
|
|
@@ -731,6 +828,7 @@ cmd_add() {
|
|
|
731
828
|
printf '%s' "${kc_creds}" > "$(account_creds "${name}")"
|
|
732
829
|
chmod 600 "$(account_creds "${name}")"
|
|
733
830
|
save_meta_email "${name}"
|
|
831
|
+
add_to_order "${name}"
|
|
734
832
|
echo "${name}" > "${CURRENT_FILE}"
|
|
735
833
|
ok "Account '${B}${name}${R}' added ${D}$(get_meta_email "${name}")${R}"
|
|
736
834
|
}
|
|
@@ -753,6 +851,7 @@ cmd_save() {
|
|
|
753
851
|
[[ ${saved} -eq 0 ]] && { err "No credentials found — log in first with: claude /login"; exit 1; }
|
|
754
852
|
|
|
755
853
|
save_meta_email "${name}"
|
|
854
|
+
add_to_order "${name}"
|
|
756
855
|
echo "${name}" > "${CURRENT_FILE}"
|
|
757
856
|
ok "Account '${B}${name}${R}' saved ${D}$(get_meta_email "${name}")${R}"
|
|
758
857
|
}
|
|
@@ -851,6 +950,7 @@ cmd_remove() {
|
|
|
851
950
|
read -r c
|
|
852
951
|
[[ "${c}" = "y" || "${c}" = "Y" ]] || { log "cancelled"; return 0; }
|
|
853
952
|
rm -f "$(account_creds "${name}")" "$(account_meta "${name}")"
|
|
953
|
+
remove_from_order "${name}"
|
|
854
954
|
[[ "$(current_name)" == "${name}" ]] && rm -f "${CURRENT_FILE}"
|
|
855
955
|
ok "Deleted '${name}' (sessions are unaffected)"
|
|
856
956
|
}
|
|
@@ -933,6 +1033,7 @@ cmd_rename() {
|
|
|
933
1033
|
|
|
934
1034
|
mv "$(account_creds "${old}")" "$(account_creds "${new}")"
|
|
935
1035
|
[[ -f "$(account_meta "${old}")" ]] && mv "$(account_meta "${old}")" "$(account_meta "${new}")"
|
|
1036
|
+
rename_in_order "${old}" "${new}"
|
|
936
1037
|
[[ "$(current_name)" == "${old}" ]] && echo "${new}" > "${CURRENT_FILE}"
|
|
937
1038
|
ok "Renamed '${B}${old}${R}' → '${B}${new}${R}'"
|
|
938
1039
|
}
|
|
@@ -1239,6 +1340,22 @@ def load_raw_config():
|
|
|
1239
1340
|
log_event('config_parse_error', error=str(e))
|
|
1240
1341
|
return {}
|
|
1241
1342
|
|
|
1343
|
+
def _read_order(creds_dir):
|
|
1344
|
+
order_file = os.path.join(os.path.dirname(creds_dir), 'order')
|
|
1345
|
+
on_disk = set(f[:-5] for f in os.listdir(creds_dir) if f.endswith('.json')) if os.path.isdir(creds_dir) else set()
|
|
1346
|
+
ordered = []
|
|
1347
|
+
if os.path.exists(order_file):
|
|
1348
|
+
for line in open(order_file):
|
|
1349
|
+
n = line.strip()
|
|
1350
|
+
if n in on_disk and n not in ordered:
|
|
1351
|
+
ordered.append(n)
|
|
1352
|
+
for n in sorted(on_disk):
|
|
1353
|
+
if n not in ordered:
|
|
1354
|
+
ordered.append(n)
|
|
1355
|
+
with open(order_file, 'w') as f:
|
|
1356
|
+
f.write('\n'.join(ordered) + ('\n' if ordered else ''))
|
|
1357
|
+
return ordered
|
|
1358
|
+
|
|
1242
1359
|
def load_config():
|
|
1243
1360
|
try:
|
|
1244
1361
|
return json.load(open(CONFIG_FILE))
|
|
@@ -1246,7 +1363,7 @@ def load_config():
|
|
|
1246
1363
|
# Auto-default: 2+ accounts → enable with 80% threshold, no explicit config needed
|
|
1247
1364
|
if not os.path.isdir(CREDS_DIR):
|
|
1248
1365
|
return None
|
|
1249
|
-
accounts =
|
|
1366
|
+
accounts = _read_order(CREDS_DIR)
|
|
1250
1367
|
if len(accounts) < 2:
|
|
1251
1368
|
return None
|
|
1252
1369
|
return {
|
|
@@ -1335,6 +1452,44 @@ if __name__ == '__main__':
|
|
|
1335
1452
|
main()
|
|
1336
1453
|
DAEMON_EOF
|
|
1337
1454
|
chmod 755 "${AUTOSWITCH_DAEMON}"
|
|
1455
|
+
_read_version > "${RELAY_DIR}/daemon_version"
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
# Restarts the already-installed daemon service in place (launchd/systemd/cron),
|
|
1459
|
+
# without touching plist/service file contents. Used after a silent redeploy.
|
|
1460
|
+
_restart_daemon_service() {
|
|
1461
|
+
if [[ "$(uname)" == "Darwin" ]] && [[ -f "${AUTOSWITCH_PLIST}" ]]; then
|
|
1462
|
+
launchctl unload "${AUTOSWITCH_PLIST}" 2>/dev/null || true
|
|
1463
|
+
launchctl load "${AUTOSWITCH_PLIST}"
|
|
1464
|
+
elif command -v systemctl >/dev/null 2>&1 && [[ -f "${AUTOSWITCH_SERVICE}" ]]; then
|
|
1465
|
+
systemctl --user restart relay-autoswitch
|
|
1466
|
+
else
|
|
1467
|
+
# cron fallback: no long-lived unit to restart — kill the running instance so
|
|
1468
|
+
# the next cron tick (daemon's own lock file) starts a fresh copy of the file
|
|
1469
|
+
# we just wrote via _extract_daemon.
|
|
1470
|
+
local pid; pid=$(cat "${RELAY_DIR}/autoswitch.lock" 2>/dev/null || echo "")
|
|
1471
|
+
[[ -n "${pid}" ]] && kill "${pid}" 2>/dev/null || true
|
|
1472
|
+
fi
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
# Runs on every invocation (cheap no-op unless autoswitch is actually running):
|
|
1476
|
+
# if relay itself was updated (npm/git/direct) since the daemon file on disk was
|
|
1477
|
+
# generated, silently regenerate it from the current script and restart it —
|
|
1478
|
+
# so daemon-side fixes (warmup engine, credential lock, etc.) don't require the
|
|
1479
|
+
# user to remember to run `relay autoswitch start` again after every update.
|
|
1480
|
+
_maybe_redeploy_daemon() {
|
|
1481
|
+
[[ -f "${AUTOSWITCH_DAEMON}" ]] || return 0
|
|
1482
|
+
local pid; pid=$(cat "${RELAY_DIR}/autoswitch.lock" 2>/dev/null || echo "")
|
|
1483
|
+
[[ -n "${pid}" ]] && kill -0 "${pid}" 2>/dev/null || return 0
|
|
1484
|
+
|
|
1485
|
+
local deployed; deployed=$(cat "${RELAY_DIR}/daemon_version" 2>/dev/null || echo "")
|
|
1486
|
+
local current; current=$(_read_version)
|
|
1487
|
+
[[ "${current}" == "unknown" ]] && return 0
|
|
1488
|
+
[[ "${deployed}" == "${current}" ]] && return 0
|
|
1489
|
+
|
|
1490
|
+
_extract_daemon
|
|
1491
|
+
_restart_daemon_service
|
|
1492
|
+
warn "relay updated to ${current} — autoswitch daemon redeployed and restarted"
|
|
1338
1493
|
}
|
|
1339
1494
|
|
|
1340
1495
|
cmd_autoswitch_config() {
|
|
@@ -1352,41 +1507,7 @@ cmd_autoswitch_config() {
|
|
|
1352
1507
|
# ── Step 1: switch order ───────────────────────────────────────
|
|
1353
1508
|
echo ""
|
|
1354
1509
|
printf " ${B}Step 1 / 3 — Switch order${R}\n"
|
|
1355
|
-
|
|
1356
|
-
local i=1
|
|
1357
|
-
for acct in "${accounts[@]}"; do
|
|
1358
|
-
printf " ${D}%d${R} %s\n" "${i}" "${acct}"
|
|
1359
|
-
i=$((i+1))
|
|
1360
|
-
done
|
|
1361
|
-
echo ""
|
|
1362
|
-
printf " ${D}Type numbers in the order you want (e.g. ${CY}2 1${D} or just Enter to keep the order above):${R}\n"
|
|
1363
|
-
printf " > "; read -r order_input
|
|
1364
|
-
|
|
1365
|
-
# resolve numbers (space or comma) to names; empty = default order
|
|
1366
|
-
local order_str; order_str=$(echo "${order_input}" | "${PY}" - "${accounts[@]}" <<'PYEOF'
|
|
1367
|
-
import sys, re
|
|
1368
|
-
raw = sys.stdin.read().strip()
|
|
1369
|
-
accts = sys.argv[1:]
|
|
1370
|
-
if not raw:
|
|
1371
|
-
print(','.join(accts))
|
|
1372
|
-
else:
|
|
1373
|
-
tokens = re.split(r'[\s,]+', raw)
|
|
1374
|
-
result = []
|
|
1375
|
-
for t in tokens:
|
|
1376
|
-
t = t.strip()
|
|
1377
|
-
if t.isdigit():
|
|
1378
|
-
idx = int(t) - 1
|
|
1379
|
-
if 0 <= idx < len(accts): result.append(accts[idx])
|
|
1380
|
-
elif t:
|
|
1381
|
-
result.append(t)
|
|
1382
|
-
print(','.join(result))
|
|
1383
|
-
PYEOF
|
|
1384
|
-
)
|
|
1385
|
-
# show resolved order as a visual chain
|
|
1386
|
-
local chain; chain=$(echo "${order_str}" | "${PY}" -c "
|
|
1387
|
-
import sys; names=sys.stdin.read().strip().split(',')
|
|
1388
|
-
print(' → '.join(names) + ' → (cycle)')")
|
|
1389
|
-
printf " ${D}Order: ${CY}%s${R}\n" "${chain}"
|
|
1510
|
+
local order_str; order_str=$(prompt_reorder "${accounts[@]}")
|
|
1390
1511
|
|
|
1391
1512
|
# ── Step 2: thresholds ────────────────────────────────────────
|
|
1392
1513
|
echo ""
|
|
@@ -1684,6 +1805,62 @@ cmd_autoswitch() {
|
|
|
1684
1805
|
esac
|
|
1685
1806
|
}
|
|
1686
1807
|
|
|
1808
|
+
cmd_reorder() {
|
|
1809
|
+
hdr "reorder accounts"
|
|
1810
|
+
|
|
1811
|
+
local accounts=()
|
|
1812
|
+
local name
|
|
1813
|
+
while IFS= read -r name; do accounts+=("${name}"); done < <(list_account_names)
|
|
1814
|
+
|
|
1815
|
+
if [[ ${#accounts[@]} -eq 0 ]]; then
|
|
1816
|
+
err "No accounts found. Run: relay add <name>"
|
|
1817
|
+
exit 1
|
|
1818
|
+
fi
|
|
1819
|
+
|
|
1820
|
+
local order_str; order_str=$(prompt_reorder "${accounts[@]}")
|
|
1821
|
+
|
|
1822
|
+
if [[ -z "${order_str}" ]]; then
|
|
1823
|
+
err "No valid order given — nothing changed"
|
|
1824
|
+
exit 1
|
|
1825
|
+
fi
|
|
1826
|
+
|
|
1827
|
+
local order_arr=()
|
|
1828
|
+
IFS=',' read -ra order_arr <<< "${order_str}"
|
|
1829
|
+
|
|
1830
|
+
local account ordered found
|
|
1831
|
+
for account in ${accounts[@]+"${accounts[@]}"}; do
|
|
1832
|
+
found=0
|
|
1833
|
+
for ordered in ${order_arr[@]+"${order_arr[@]}"}; do
|
|
1834
|
+
[[ "${ordered}" == "${account}" ]] && { found=1; break; }
|
|
1835
|
+
done
|
|
1836
|
+
[[ "${found}" -eq 0 ]] && order_arr+=("${account}")
|
|
1837
|
+
done
|
|
1838
|
+
|
|
1839
|
+
order_str=""
|
|
1840
|
+
for ordered in ${order_arr[@]+"${order_arr[@]}"}; do
|
|
1841
|
+
if [[ -z "${order_str}" ]]; then
|
|
1842
|
+
order_str="${ordered}"
|
|
1843
|
+
else
|
|
1844
|
+
order_str="${order_str},${ordered}"
|
|
1845
|
+
fi
|
|
1846
|
+
done
|
|
1847
|
+
|
|
1848
|
+
printf '%s\n' "${order_arr[@]}" > "${ORDER_FILE}"
|
|
1849
|
+
ok "Order saved to ${ORDER_FILE}"
|
|
1850
|
+
|
|
1851
|
+
local cfg="${RELAY_DIR}/autoswitch.json"
|
|
1852
|
+
if [[ -f "${cfg}" ]]; then
|
|
1853
|
+
"${PY}" - "${cfg}" "${order_str}" <<'PYEOF'
|
|
1854
|
+
import json, sys
|
|
1855
|
+
cfg_path, order_str = sys.argv[1], sys.argv[2]
|
|
1856
|
+
cfg = json.load(open(cfg_path))
|
|
1857
|
+
cfg['order'] = order_str.split(',')
|
|
1858
|
+
json.dump(cfg, open(cfg_path, 'w'), indent=2)
|
|
1859
|
+
PYEOF
|
|
1860
|
+
ok "Also updated order in ${cfg}"
|
|
1861
|
+
fi
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1687
1864
|
cmd_lock() {
|
|
1688
1865
|
local name="${1:-}"
|
|
1689
1866
|
local cfg="${RELAY_DIR}/autoswitch.json"
|
|
@@ -1717,7 +1894,19 @@ PYEOF
|
|
|
1717
1894
|
"${PY}" - "${CREDS_STORE}" "${cfg}" <<'PYEOF'
|
|
1718
1895
|
import json, sys, os
|
|
1719
1896
|
creds_dir, cfg_path = sys.argv[1], sys.argv[2]
|
|
1720
|
-
|
|
1897
|
+
order_file = os.path.join(os.path.dirname(creds_dir), 'order')
|
|
1898
|
+
on_disk = set(f[:-5] for f in os.listdir(creds_dir) if f.endswith('.json'))
|
|
1899
|
+
accounts = []
|
|
1900
|
+
if os.path.exists(order_file):
|
|
1901
|
+
for line in open(order_file):
|
|
1902
|
+
n = line.strip()
|
|
1903
|
+
if n in on_disk and n not in accounts:
|
|
1904
|
+
accounts.append(n)
|
|
1905
|
+
for n in sorted(on_disk):
|
|
1906
|
+
if n not in accounts:
|
|
1907
|
+
accounts.append(n)
|
|
1908
|
+
with open(order_file, 'w') as f:
|
|
1909
|
+
f.write('\n'.join(accounts) + ('\n' if accounts else ''))
|
|
1721
1910
|
config = {
|
|
1722
1911
|
'order': accounts,
|
|
1723
1912
|
'thresholds': {a: 80 for a in accounts},
|
|
@@ -1784,7 +1973,19 @@ _warmup_ensure_config() {
|
|
|
1784
1973
|
"${PY}" - "${CREDS_STORE}" "${cfg}" <<'PYEOF'
|
|
1785
1974
|
import json, sys, os
|
|
1786
1975
|
creds_dir, cfg_path = sys.argv[1], sys.argv[2]
|
|
1787
|
-
|
|
1976
|
+
on_disk = set(f[:-5] for f in os.listdir(creds_dir) if f.endswith('.json')) if os.path.isdir(creds_dir) else set()
|
|
1977
|
+
order_file = os.path.join(os.path.dirname(creds_dir), 'order')
|
|
1978
|
+
accounts = []
|
|
1979
|
+
if os.path.exists(order_file):
|
|
1980
|
+
for line in open(order_file):
|
|
1981
|
+
n = line.strip()
|
|
1982
|
+
if n in on_disk and n not in accounts:
|
|
1983
|
+
accounts.append(n)
|
|
1984
|
+
for n in sorted(on_disk):
|
|
1985
|
+
if n not in accounts:
|
|
1986
|
+
accounts.append(n)
|
|
1987
|
+
with open(order_file, 'w') as f:
|
|
1988
|
+
f.write('\n'.join(accounts) + ('\n' if accounts else ''))
|
|
1788
1989
|
config = {
|
|
1789
1990
|
'order': accounts,
|
|
1790
1991
|
'thresholds': {a: 80 for a in accounts},
|
|
@@ -2164,6 +2365,7 @@ cmd_help() {
|
|
|
2164
2365
|
printf " %-32s %s\n" " relay refresh-all" "silent OAuth refresh for all accounts"
|
|
2165
2366
|
printf " %-32s %s\n" " relay save <name>" "save current login state"
|
|
2166
2367
|
printf " %-32s %s\n" " relay rename <old> <new>" "rename an account"
|
|
2368
|
+
printf " %-32s %s\n" " relay reorder" "change account display/switch order"
|
|
2167
2369
|
printf " %-32s %s\n" " relay list" "full list with weekly usage"
|
|
2168
2370
|
printf " %-32s %s\n" " relay list -f" "live-refresh mode (Ctrl+C to exit)"
|
|
2169
2371
|
printf " %-32s %s\n" " relay list --no-usage" "list without querying API"
|
|
@@ -2194,6 +2396,8 @@ cmd_help() {
|
|
|
2194
2396
|
_show_update_notice
|
|
2195
2397
|
}
|
|
2196
2398
|
|
|
2399
|
+
_maybe_redeploy_daemon
|
|
2400
|
+
|
|
2197
2401
|
# ══════════════════════════════════════════════════════════════════
|
|
2198
2402
|
# Dispatch — single entry point, no fall-through
|
|
2199
2403
|
# ══════════════════════════════════════════════════════════════════
|
|
@@ -2222,6 +2426,7 @@ case "${CMD}" in
|
|
|
2222
2426
|
status|st) cmd_status ;;
|
|
2223
2427
|
remove|rm|del) cmd_remove "$@" ;;
|
|
2224
2428
|
rename|mv) cmd_rename "$@" ;;
|
|
2429
|
+
reorder) cmd_reorder "$@" ;;
|
|
2225
2430
|
sessions|sess) cmd_sessions ;;
|
|
2226
2431
|
autoswitch|as) cmd_autoswitch "$@" ;;
|
|
2227
2432
|
lock) cmd_lock "$@" ;;
|