@dst-justin/relay 2.1.0 → 2.1.2
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 +16 -0
- package/package.json +3 -2
- package/postinstall.js +84 -0
- package/relay +179 -39
package/README.md
CHANGED
|
@@ -218,3 +218,19 @@ Sessions live in `~/.claude/projects/` and are shared across all accounts — af
|
|
|
218
218
|
| `relay.ps1` | Full PowerShell implementation |
|
|
219
219
|
| `relay.cmd` | Thin CMD wrapper — delegates to `relay.ps1` |
|
|
220
220
|
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Changelog
|
|
225
|
+
|
|
226
|
+
### v2.1.1 — 2026-06-24
|
|
227
|
+
- Display current version and latest version at the end of `list`, `status`, `relay` (menu), `sessions`, and `help` commands
|
|
228
|
+
- Background version check (24h cache) — non-blocking, never slows down output
|
|
229
|
+
- `relay update` now detects original install method: uses `npm install -g` for npm installs, `git pull` for git clones, and direct GitHub download for bare script copies
|
|
230
|
+
- `npm install -g` post-install script automatically patches `~/.bashrc`, `~/.zshrc`, `~/.profile`, and fish `config.fish` if the npm bin dir is missing from PATH
|
|
231
|
+
|
|
232
|
+
### v2.1.0 — 2026-06-24
|
|
233
|
+
- Upgraded GitHub Actions workflow to `actions/checkout@v6` and `actions/setup-node@v6` (Node 24 runtime, removes Node 20 deprecation warning)
|
|
234
|
+
|
|
235
|
+
### v2.0.2 — 2026-06-23
|
|
236
|
+
- Skip `npm install` during `relay update` when already on the latest version or when version check fails
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dst-justin/relay",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Multi-account switcher for Claude Code — instant credential swap across macOS, Linux, and Windows",
|
|
5
5
|
"bin": {
|
|
6
6
|
"relay": "./relay.js"
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
"relay.js",
|
|
11
11
|
"relay.ps1",
|
|
12
12
|
"relay.cmd",
|
|
13
|
+
"postinstall.js",
|
|
13
14
|
"README.md"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
|
-
"postinstall": "node -e \"if(process.platform!=='win32'){try{require('fs').chmodSync(require('path').join(__dirname,'relay'),'755')}catch(_){}}\""
|
|
17
|
+
"postinstall": "node -e \"if(process.platform!=='win32'){try{require('fs').chmodSync(require('path').join(__dirname,'relay'),'755')}catch(_){}}\" && node postinstall.js"
|
|
17
18
|
},
|
|
18
19
|
"engines": {
|
|
19
20
|
"node": ">=16"
|
package/postinstall.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Runs after `npm install -g @dst-justin/relay`.
|
|
3
|
+
// If the npm global bin dir is not in PATH, appends it to shell config files.
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const { existsSync, readFileSync, appendFileSync, mkdirSync, writeFileSync } = require('fs');
|
|
8
|
+
const { join } = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
// ── Windows ───────────────────────────────────────────────────────────────────
|
|
12
|
+
// npm on Windows already manages PATH via the installer; nothing to do here.
|
|
13
|
+
if (process.platform === 'win32') {
|
|
14
|
+
// Verify relay.js can reach relay.ps1 (sanity check)
|
|
15
|
+
if (!existsSync(join(__dirname, 'relay.ps1'))) {
|
|
16
|
+
console.warn(' relay: warning — relay.ps1 not found, Windows support may be broken');
|
|
17
|
+
}
|
|
18
|
+
process.exit(0);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Get npm global bin dir ────────────────────────────────────────────────────
|
|
22
|
+
// npm sets npm_config_prefix during install; fall back to `npm prefix -g`
|
|
23
|
+
let prefix = process.env.npm_config_prefix;
|
|
24
|
+
if (!prefix) {
|
|
25
|
+
try {
|
|
26
|
+
prefix = execSync('npm prefix -g', { encoding: 'utf8', stdio: ['pipe','pipe','pipe'] }).trim();
|
|
27
|
+
} catch (_) {
|
|
28
|
+
process.exit(0); // can't determine, give up silently
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const binDir = join(prefix, 'bin');
|
|
32
|
+
|
|
33
|
+
// Already in PATH — nothing to do
|
|
34
|
+
const pathDirs = (process.env.PATH || '').split(':');
|
|
35
|
+
if (pathDirs.includes(binDir)) process.exit(0);
|
|
36
|
+
|
|
37
|
+
const home = os.homedir();
|
|
38
|
+
|
|
39
|
+
// ── POSIX shells: bash / zsh / sh ────────────────────────────────────────────
|
|
40
|
+
const exportLine = `export PATH="${binDir}:$PATH"`;
|
|
41
|
+
const marker = '# added by relay';
|
|
42
|
+
const block = `\n${exportLine} ${marker}\n`;
|
|
43
|
+
|
|
44
|
+
const rcFiles = ['.bashrc', '.zshrc', '.profile'].map(f => join(home, f));
|
|
45
|
+
let patched = false;
|
|
46
|
+
|
|
47
|
+
for (const rc of rcFiles) {
|
|
48
|
+
if (!existsSync(rc)) continue;
|
|
49
|
+
try {
|
|
50
|
+
if (readFileSync(rc, 'utf8').includes(marker)) continue; // idempotent
|
|
51
|
+
appendFileSync(rc, block);
|
|
52
|
+
console.log(` relay: added ${binDir} to PATH in ~/${require('path').basename(rc)}`);
|
|
53
|
+
patched = true;
|
|
54
|
+
} catch (_) {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If no shell rc found, create ~/.bashrc as a last resort
|
|
58
|
+
if (!patched) {
|
|
59
|
+
const bashrc = join(home, '.bashrc');
|
|
60
|
+
try {
|
|
61
|
+
appendFileSync(bashrc, block);
|
|
62
|
+
console.log(` relay: created ~/.bashrc with PATH entry for ${binDir}`);
|
|
63
|
+
patched = true;
|
|
64
|
+
} catch (_) {}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// ── Fish shell ────────────────────────────────────────────────────────────────
|
|
68
|
+
const fishConfig = join(home, '.config', 'fish', 'config.fish');
|
|
69
|
+
const fishMarker = '# added by relay';
|
|
70
|
+
const fishLine = `\nset -gx PATH "${binDir}" $PATH ${fishMarker}\n`;
|
|
71
|
+
|
|
72
|
+
if (existsSync(fishConfig)) {
|
|
73
|
+
try {
|
|
74
|
+
if (!readFileSync(fishConfig, 'utf8').includes(fishMarker)) {
|
|
75
|
+
appendFileSync(fishConfig, fishLine);
|
|
76
|
+
console.log(` relay: added ${binDir} to PATH in ~/.config/fish/config.fish`);
|
|
77
|
+
patched = true;
|
|
78
|
+
}
|
|
79
|
+
} catch (_) {}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (patched) {
|
|
83
|
+
console.log(` relay: restart your shell (or 'source ~/.bashrc' / 'source ~/.zshrc') then try: relay list`);
|
|
84
|
+
}
|
package/relay
CHANGED
|
@@ -12,6 +12,7 @@ RELAY_DIR="${HOME}/.claude-relay"
|
|
|
12
12
|
CREDS_STORE="${RELAY_DIR}/credentials"
|
|
13
13
|
META_STORE="${RELAY_DIR}/meta"
|
|
14
14
|
CURRENT_FILE="${RELAY_DIR}/current"
|
|
15
|
+
UPDATE_CACHE="${RELAY_DIR}/.update_cache"
|
|
15
16
|
CLAUDE_DIR="${HOME}/.claude"
|
|
16
17
|
CLAUDE_JSON="${HOME}/.claude.json"
|
|
17
18
|
REAL_CLAUDE=$(command -v claude 2>/dev/null || echo "")
|
|
@@ -371,14 +372,21 @@ _sync_current_creds() {
|
|
|
371
372
|
[[ -n "${live}" ]] && printf '%s' "${live}" > "$(account_creds "${cur}")"
|
|
372
373
|
}
|
|
373
374
|
|
|
374
|
-
cmd_quick() {
|
|
375
|
+
cmd_quick() {
|
|
376
|
+
_check_update_bg
|
|
377
|
+
_sync_current_creds
|
|
378
|
+
render_table quick "${CREDS_STORE}" "${META_STORE}" "$(current_name)" "$@"
|
|
379
|
+
_show_update_notice
|
|
380
|
+
}
|
|
375
381
|
|
|
376
382
|
cmd_list() {
|
|
383
|
+
_check_update_bg
|
|
377
384
|
hdr "Account List"
|
|
378
385
|
_sync_current_creds
|
|
379
386
|
render_table full "${CREDS_STORE}" "${META_STORE}" "$(current_name)" "$@"
|
|
380
387
|
echo ""
|
|
381
388
|
ok "Inside Claude Code: ${CY}!relay <index>${R} to switch"
|
|
389
|
+
_show_update_notice
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
cmd_status() {
|
|
@@ -450,6 +458,8 @@ EOF
|
|
|
450
458
|
[[ -d "${CLAUDE_DIR}/projects" ]] && \
|
|
451
459
|
n=$(find "${CLAUDE_DIR}/projects" -name "*.jsonl" 2>/dev/null | wc -l | tr -d ' ')
|
|
452
460
|
printf "\n ${B}Sessions:${R} %s (shared across all accounts in ~/.claude/projects/)\n" "${n}"
|
|
461
|
+
_check_update_bg
|
|
462
|
+
_show_update_notice
|
|
453
463
|
}
|
|
454
464
|
|
|
455
465
|
cmd_add() {
|
|
@@ -599,6 +609,8 @@ print()
|
|
|
599
609
|
print(f' {total} session(s)' if total else ' No sessions found')
|
|
600
610
|
EOF
|
|
601
611
|
log "${CY}claude -c${R} resume last ${D}|${R} ${CY}claude -r${R} pick one ${D}|${R} ${CY}claude --resume <id>${R}"
|
|
612
|
+
_check_update_bg
|
|
613
|
+
_show_update_notice
|
|
602
614
|
}
|
|
603
615
|
|
|
604
616
|
cmd_install() {
|
|
@@ -896,47 +908,74 @@ cmd_autoswitch_config() {
|
|
|
896
908
|
exit 1
|
|
897
909
|
fi
|
|
898
910
|
|
|
911
|
+
# ── Step 1: switch order ───────────────────────────────────────
|
|
899
912
|
echo ""
|
|
900
|
-
printf "
|
|
901
|
-
|
|
902
|
-
|
|
913
|
+
printf " ${B}Step 1 / 3 — Switch order${R}\n"
|
|
914
|
+
printf " ${D}When an account hits its threshold, relay switches to the next one in order.${R}\n\n"
|
|
915
|
+
local i=1
|
|
916
|
+
for acct in "${accounts[@]}"; do
|
|
917
|
+
printf " ${D}%d${R} %s\n" "${i}" "${acct}"
|
|
918
|
+
i=$((i+1))
|
|
919
|
+
done
|
|
920
|
+
echo ""
|
|
921
|
+
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"
|
|
903
922
|
printf " > "; read -r order_input
|
|
904
|
-
local order_str; order_str=$(echo "${order_input}" | "${PY}" -c "
|
|
905
|
-
import sys,re
|
|
906
|
-
raw=sys.stdin.read().strip()
|
|
907
|
-
names=[x.strip() for x in raw.split(',') if x.strip()]
|
|
908
|
-
print(','.join(names))")
|
|
909
923
|
|
|
924
|
+
# resolve numbers (space or comma) to names; empty = default order
|
|
925
|
+
local order_str; order_str=$(echo "${order_input}" | "${PY}" - "${accounts[@]}" <<'PYEOF'
|
|
926
|
+
import sys, re
|
|
927
|
+
raw = sys.stdin.read().strip()
|
|
928
|
+
accts = sys.argv[1:]
|
|
929
|
+
if not raw:
|
|
930
|
+
print(','.join(accts))
|
|
931
|
+
else:
|
|
932
|
+
tokens = re.split(r'[\s,]+', raw)
|
|
933
|
+
result = []
|
|
934
|
+
for t in tokens:
|
|
935
|
+
t = t.strip()
|
|
936
|
+
if t.isdigit():
|
|
937
|
+
idx = int(t) - 1
|
|
938
|
+
if 0 <= idx < len(accts): result.append(accts[idx])
|
|
939
|
+
elif t:
|
|
940
|
+
result.append(t)
|
|
941
|
+
print(','.join(result))
|
|
942
|
+
PYEOF
|
|
943
|
+
)
|
|
944
|
+
# show resolved order as a visual chain
|
|
945
|
+
local chain; chain=$(echo "${order_str}" | "${PY}" -c "
|
|
946
|
+
import sys; names=sys.stdin.read().strip().split(',')
|
|
947
|
+
print(' → '.join(names) + ' → (cycle)')")
|
|
948
|
+
printf " ${D}Order: ${CY}%s${R}\n" "${chain}"
|
|
949
|
+
|
|
950
|
+
# ── Step 2: thresholds ────────────────────────────────────────
|
|
910
951
|
echo ""
|
|
911
|
-
printf " ${B}
|
|
952
|
+
printf " ${B}Step 2 / 3 — Thresholds${R}\n"
|
|
953
|
+
printf " ${D}Autoswitch triggers when an account's 5-hr usage exceeds this %%.${R}\n"
|
|
954
|
+
printf " ${D}Enter a number (e.g. 70), or press Enter for default 80%%.${R}\n\n"
|
|
955
|
+
|
|
912
956
|
local thresholds_json="{"
|
|
913
957
|
local order_json="["
|
|
914
|
-
local
|
|
915
|
-
local first_thresh=1
|
|
958
|
+
local first=1
|
|
916
959
|
IFS=',' read -ra order_arr <<< "${order_str}"
|
|
917
960
|
for acct in "${order_arr[@]}"; do
|
|
918
|
-
printf " %s threshold
|
|
961
|
+
printf " ${CY}%s${R} threshold [80%%]: " "${acct}"
|
|
919
962
|
read -r val
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
fi
|
|
923
|
-
[[ ${first_order} -eq 0 ]] && order_json+=","
|
|
963
|
+
[[ -z "${val}" ]] && val="80"
|
|
964
|
+
[[ ${first} -eq 0 ]] && { order_json+=","; thresholds_json+=","; }
|
|
924
965
|
order_json+="\"${acct}\""
|
|
925
|
-
first_order=0
|
|
926
|
-
[[ ${first_thresh} -eq 0 ]] && thresholds_json+=","
|
|
927
966
|
thresholds_json+="\"${acct}\":${val}"
|
|
928
|
-
|
|
967
|
+
first=0
|
|
929
968
|
done
|
|
930
969
|
order_json+="]"
|
|
931
970
|
thresholds_json+="}"
|
|
932
971
|
|
|
972
|
+
# ── Step 3: poll interval ─────────────────────────────────────
|
|
933
973
|
echo ""
|
|
934
|
-
printf " ${B}
|
|
935
|
-
|
|
936
|
-
printf "
|
|
937
|
-
read -r high_min; high_min="${high_min:-2}"
|
|
938
|
-
printf "
|
|
939
|
-
read -r high_thr; high_thr="${high_thr:-50}"
|
|
974
|
+
printf " ${B}Step 3 / 3 — Check interval${R}\n"
|
|
975
|
+
printf " ${D}How often the daemon checks usage. Switches to faster polling near the threshold.${R}\n\n"
|
|
976
|
+
printf " Normal check every N minutes [10]: "; read -r low_min; low_min="${low_min:-10}"
|
|
977
|
+
printf " Fast check every N minutes [2]: "; read -r high_min; high_min="${high_min:-2}"
|
|
978
|
+
printf " Switch to fast polling at [50%%]: "; read -r high_thr; high_thr="${high_thr:-50}"
|
|
940
979
|
|
|
941
980
|
local cfg_file="${RELAY_DIR}/autoswitch.json"
|
|
942
981
|
printf '{"order":%s,"thresholds":%s,"poll":{"low_minutes":%s,"high_minutes":%s,"high_threshold":%s}}' \
|
|
@@ -945,7 +984,19 @@ print(','.join(names))")
|
|
|
945
984
|
> "${cfg_file}"
|
|
946
985
|
|
|
947
986
|
echo ""
|
|
948
|
-
ok "
|
|
987
|
+
ok "Config saved to ${cfg_file}"
|
|
988
|
+
echo ""
|
|
989
|
+
"${PY}" -c "
|
|
990
|
+
import json, sys
|
|
991
|
+
cfg = json.load(open(sys.argv[1]))
|
|
992
|
+
R='\033[0m'; B='\033[1m'; D='\033[2m'; CY='\033[36m'
|
|
993
|
+
for i, name in enumerate(cfg['order'], 1):
|
|
994
|
+
thr = cfg['thresholds'].get(name, '?')
|
|
995
|
+
print(f' {D}{i}.{R} {name:<14} → switch at {CY}{thr}%{R}')
|
|
996
|
+
p = cfg['poll']
|
|
997
|
+
print(f\"\n {D}polling: {p[\"low_minutes\"]}min normal / {p[\"high_minutes\"]}min fast (fast above {p[\"high_threshold\"]}%){R}\")
|
|
998
|
+
" "${cfg_file}"
|
|
999
|
+
echo ""
|
|
949
1000
|
log "Run ${CY}relay autoswitch start${R} to activate"
|
|
950
1001
|
}
|
|
951
1002
|
|
|
@@ -1190,6 +1241,68 @@ cmd_version() {
|
|
|
1190
1241
|
printf "relay %s\n" "$(_read_version)"
|
|
1191
1242
|
}
|
|
1192
1243
|
|
|
1244
|
+
# ── Update notification helpers ───────────────────────────────────────────────
|
|
1245
|
+
# Cache format: "<epoch>:<version>" TTL = 24h
|
|
1246
|
+
_check_update_bg() {
|
|
1247
|
+
(
|
|
1248
|
+
local ttl=86400
|
|
1249
|
+
if [[ -f "${UPDATE_CACHE}" ]]; then
|
|
1250
|
+
local cached; cached=$(cat "${UPDATE_CACHE}" 2>/dev/null)
|
|
1251
|
+
local ts="${cached%%:*}"
|
|
1252
|
+
local now; now=$(date +%s)
|
|
1253
|
+
[[ $(( now - ts )) -lt ${ttl} ]] && exit 0
|
|
1254
|
+
fi
|
|
1255
|
+
local ver
|
|
1256
|
+
ver=$("${PY}" - 2>/dev/null <<'PYEOF'
|
|
1257
|
+
import urllib.request, json, sys
|
|
1258
|
+
def fetch(url, h={}):
|
|
1259
|
+
r = urllib.request.Request(url, headers=h)
|
|
1260
|
+
with urllib.request.urlopen(r, timeout=6) as resp:
|
|
1261
|
+
return json.loads(resp.read())
|
|
1262
|
+
try:
|
|
1263
|
+
d = fetch('https://api.github.com/repos/darkstar1227/relay/releases/latest',
|
|
1264
|
+
{'User-Agent': 'relay-update'})
|
|
1265
|
+
print(d['tag_name'].lstrip('v')); sys.exit(0)
|
|
1266
|
+
except Exception: pass
|
|
1267
|
+
try:
|
|
1268
|
+
d = fetch('https://registry.npmjs.org/@dst-justin%2frelay/latest')
|
|
1269
|
+
print(d['version'])
|
|
1270
|
+
except Exception: sys.exit(1)
|
|
1271
|
+
PYEOF
|
|
1272
|
+
)
|
|
1273
|
+
[[ -n "${ver}" ]] && printf '%s:%s' "$(date +%s)" "${ver}" > "${UPDATE_CACHE}"
|
|
1274
|
+
) >/dev/null 2>&1 &
|
|
1275
|
+
disown 2>/dev/null || true
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
_show_update_notice() {
|
|
1279
|
+
local current; current=$(_read_version)
|
|
1280
|
+
local latest=""
|
|
1281
|
+
if [[ -f "${UPDATE_CACHE}" ]]; then
|
|
1282
|
+
local cached; cached=$(cat "${UPDATE_CACHE}" 2>/dev/null)
|
|
1283
|
+
latest="${cached#*:}"
|
|
1284
|
+
fi
|
|
1285
|
+
if [[ -n "${latest}" && "${latest}" != "${current}" ]]; then
|
|
1286
|
+
printf "\n ${D}relay version: ${B}${current}${R}${D} → ${CY}${B}${latest}${R}${D} available — run ${CY}relay update${R}${D} to install${R}\n"
|
|
1287
|
+
else
|
|
1288
|
+
local ver_display="${latest:-${current}}"
|
|
1289
|
+
printf "\n ${D}relay version: ${B}${ver_display}${R}${D} (up to date)${R}\n"
|
|
1290
|
+
fi
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
# Detect how relay was originally installed:
|
|
1294
|
+
# npm — package.json present in script dir (npm unpacks the full package)
|
|
1295
|
+
# git — .git dir present in script dir
|
|
1296
|
+
# direct — bare script copy (no package.json, no .git)
|
|
1297
|
+
_detect_install_method() {
|
|
1298
|
+
local d; d=$(_script_dir)
|
|
1299
|
+
# .git check first: git clone has both .git AND package.json; npm publish strips .git
|
|
1300
|
+
if [[ -d "${d}/.git" ]]; then echo "git"
|
|
1301
|
+
elif [[ -f "${d}/package.json" ]]; then echo "npm"
|
|
1302
|
+
else echo "direct"
|
|
1303
|
+
fi
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1193
1306
|
cmd_update() {
|
|
1194
1307
|
hdr "Update relay"
|
|
1195
1308
|
|
|
@@ -1229,18 +1342,43 @@ PYEOF
|
|
|
1229
1342
|
fi
|
|
1230
1343
|
|
|
1231
1344
|
local relay_dir; relay_dir=$(_script_dir)
|
|
1232
|
-
local
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
npm
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1345
|
+
local method; method=$(_detect_install_method)
|
|
1346
|
+
|
|
1347
|
+
case "${method}" in
|
|
1348
|
+
npm)
|
|
1349
|
+
local npm_cmd; npm_cmd=$(command -v npm 2>/dev/null)
|
|
1350
|
+
if [[ -n "${npm_cmd}" ]]; then
|
|
1351
|
+
log "Updating via npm (original install method)..."
|
|
1352
|
+
npm install -g @dst-justin/relay@latest
|
|
1353
|
+
ok "Updated to $(_read_version)"
|
|
1354
|
+
else
|
|
1355
|
+
err "npm not found — reinstall npm and retry"
|
|
1356
|
+
log "Or update manually: ${CY}npm install -g @dst-justin/relay@latest${R}"
|
|
1357
|
+
fi ;;
|
|
1358
|
+
git)
|
|
1359
|
+
log "Updating via git pull (original install method)..."
|
|
1360
|
+
git -C "${relay_dir}" pull ;;
|
|
1361
|
+
direct)
|
|
1362
|
+
log "Updating via direct download (original install method)..."
|
|
1363
|
+
local script_path; script_path=$(readlink -f "$0" 2>/dev/null || echo "$0")
|
|
1364
|
+
"${PY}" - "${script_path}" "${latest}" <<'PYEOF'
|
|
1365
|
+
import urllib.request, sys, os, stat
|
|
1366
|
+
script_path, version = sys.argv[1], sys.argv[2]
|
|
1367
|
+
url = f'https://raw.githubusercontent.com/darkstar1227/relay/v{version}/relay'
|
|
1368
|
+
try:
|
|
1369
|
+
with urllib.request.urlopen(url, timeout=15) as r:
|
|
1370
|
+
content = r.read()
|
|
1371
|
+
tmp = script_path + '.tmp'
|
|
1372
|
+
with open(tmp, 'wb') as f: f.write(content)
|
|
1373
|
+
os.chmod(tmp, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
|
1374
|
+
os.replace(tmp, script_path)
|
|
1375
|
+
except Exception as e:
|
|
1376
|
+
print(f' download failed: {e}', file=sys.stderr); sys.exit(1)
|
|
1377
|
+
PYEOF
|
|
1378
|
+
ok "Updated ${script_path} to ${latest}" ;;
|
|
1379
|
+
esac
|
|
1380
|
+
# Invalidate update cache so next display shows fresh state
|
|
1381
|
+
rm -f "${UPDATE_CACHE}" 2>/dev/null || true
|
|
1244
1382
|
}
|
|
1245
1383
|
|
|
1246
1384
|
cmd_uninstall() {
|
|
@@ -1297,6 +1435,8 @@ cmd_help() {
|
|
|
1297
1435
|
echo ""
|
|
1298
1436
|
printf " ${D}switches the OAuth credential (macOS Keychain / Linux ~/.claude/.credentials.json); sessions are shared${R}\n"
|
|
1299
1437
|
printf " ${D}after switching: claude -c to resume, claude --resume <id> for a specific session${R}\n\n"
|
|
1438
|
+
_check_update_bg
|
|
1439
|
+
_show_update_notice
|
|
1300
1440
|
}
|
|
1301
1441
|
|
|
1302
1442
|
# ══════════════════════════════════════════════════════════════════
|