claude-multiacc 1.0.16 → 1.0.17
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 +20 -0
- package/bin/claude-accounts +89 -18
- package/bin/codex-accounts +14 -0
- package/package.json +1 -1
- package/tests/run-tests.sh +86 -0
package/README.md
CHANGED
|
@@ -435,6 +435,26 @@ Use `--token` when you specifically want one account usable on the server via th
|
|
|
435
435
|
sync (the ~1-year inference-only token that mirrors over). For everyday adds, the default
|
|
436
436
|
full login is smoother. `status` warns when a token nears end of life; re-run `login`.
|
|
437
437
|
|
|
438
|
+
### A setup token has no identity — name the account yourself
|
|
439
|
+
|
|
440
|
+
A setup token is minted with scope `user:inference` **alone**, so `claude auth status`
|
|
441
|
+
answers `{loggedIn, authMethod, apiProvider}` and no email — only an OAuth login reports
|
|
442
|
+
`email`/`orgId`/`subscriptionType`. Nothing on this side can therefore learn which account
|
|
443
|
+
approved a `setup-token` grant, and no future check will change that.
|
|
444
|
+
|
|
445
|
+
What follows, and what the CLI does about it:
|
|
446
|
+
|
|
447
|
+
- **`add <email> --token` requires the email** — there is nothing to read the account back
|
|
448
|
+
from. It registers under the name you gave and says so; naming no email is refused
|
|
449
|
+
outright rather than registering an anonymous slot.
|
|
450
|
+
- **`mint <acct-NN>` names the account in its prompt** (`approve in a browser signed in as
|
|
451
|
+
…`) and warns afterwards that the identity is unverifiable. Approving in a window signed
|
|
452
|
+
into a *different* account silently binds that account's subscription to the slot —
|
|
453
|
+
every machine then runs it under the wrong name. Sign in to the right account **first**,
|
|
454
|
+
in a fresh private window, then paste the link.
|
|
455
|
+
- `login <acct-NN> --token` compares against the manifest when an identity is readable
|
|
456
|
+
(it is not, today) and otherwise says what it is trusting.
|
|
457
|
+
|
|
438
458
|
Both accounts currently in the pool are live and verified: acct-01 (support@gowalkae.com,
|
|
439
459
|
Mac) and acct-02 (hasan@gowalkqa.com, server) — each adopted from its machine's existing
|
|
440
460
|
login, so each runs on the machine that holds its credential.
|
package/bin/claude-accounts
CHANGED
|
@@ -461,6 +461,13 @@ cmd_add() {
|
|
|
461
461
|
return 0
|
|
462
462
|
fi
|
|
463
463
|
fi
|
|
464
|
+
# A setup token reports no identity, so an unnamed --token add can never attribute the
|
|
465
|
+
# account. Refuse BEFORE the ceremony: running it first would mint a real 1-year grant
|
|
466
|
+
# on the account's behalf and then throw the token away, leaving a live credential
|
|
467
|
+
# issued for nothing.
|
|
468
|
+
if [ "$token" = "1" ] && [ -z "$email" ]; then
|
|
469
|
+
die "a setup token carries no identity, so the account cannot be read back — name it: claude-accounts add <email> --token"
|
|
470
|
+
fi
|
|
464
471
|
if [ ! -t 0 ] && [ -z "${CLAUDE_MULTIACC_FORCE_TTY:-}" ]; then
|
|
465
472
|
die "add is interactive (it completes sign-in before registering) — run it from a terminal"
|
|
466
473
|
fi
|
|
@@ -498,7 +505,14 @@ cmd_add() {
|
|
|
498
505
|
got="$(config_dir_email "$d")"
|
|
499
506
|
fi
|
|
500
507
|
if [ -z "$got" ]; then
|
|
501
|
-
if [
|
|
508
|
+
if [ "$token" = "1" ] && [ -n "$email" ]; then
|
|
509
|
+
# A setup token NEVER reports an identity (see token_email) — that is the grant's
|
|
510
|
+
# design, not a read that failed, so demanding --force here demanded a ceremony
|
|
511
|
+
# nobody can perform: it made the documented `add <email> --token` path die every
|
|
512
|
+
# single time. The named email is all there is; say so plainly and register it.
|
|
513
|
+
warn "a setup token carries no identity — registering as $email, the account you were asked to approve as"
|
|
514
|
+
got="$email"
|
|
515
|
+
elif [ -n "$email" ] && [ "$force" = "1" ]; then
|
|
502
516
|
warn "identity unverified — registering as $email because --force was given"
|
|
503
517
|
got="$email"
|
|
504
518
|
else
|
|
@@ -523,10 +537,18 @@ cmd_add() {
|
|
|
523
537
|
mutate_unlock
|
|
524
538
|
RESERVED_DIR="" # committed — the trap must not delete it now
|
|
525
539
|
trap - EXIT INT TERM
|
|
526
|
-
|
|
540
|
+
# Only the full-login path actually READ the identity back. Saying "verified" for a
|
|
541
|
+
# setup token would relaunder the very assumption this flow just warned about.
|
|
542
|
+
local verdict="sign-in verified"
|
|
543
|
+
if [ "$token" = "1" ]; then
|
|
544
|
+
verdict="identity unverifiable — trusted as the name you gave"
|
|
545
|
+
log_to ops.log "add $id $got (token; identity unverifiable)"
|
|
546
|
+
else
|
|
547
|
+
log_to ops.log "add $id $got (auth-verified)"
|
|
548
|
+
fi
|
|
527
549
|
auto_sync
|
|
528
550
|
cat <<EOF
|
|
529
|
-
Registered $id for $got (
|
|
551
|
+
Registered $id for $got ($verdict) — usable immediately.
|
|
530
552
|
Optional:
|
|
531
553
|
claude-accounts verify # confirm the 100% matrix
|
|
532
554
|
EOF
|
|
@@ -783,7 +805,23 @@ TIP
|
|
|
783
805
|
}
|
|
784
806
|
}
|
|
785
807
|
|
|
786
|
-
|
|
808
|
+
account_email() { # $1 = acct id; prints the email the manifest holds for it
|
|
809
|
+
"$PYBIN" - "$MANIFEST" "$1" <<'PYEOF'
|
|
810
|
+
import json, sys
|
|
811
|
+
for a in json.load(open(sys.argv[1])).get('accounts', []):
|
|
812
|
+
if a['id'] == sys.argv[2]:
|
|
813
|
+
print(a.get('email', ''))
|
|
814
|
+
break
|
|
815
|
+
PYEOF
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
token_email() { # $1 = token; prints the authenticated email — EMPTY for a setup token
|
|
819
|
+
# A setup token is minted with scope user:inference ALONE, so `claude auth status`
|
|
820
|
+
# answers {loggedIn, authMethod, apiProvider} and nothing else; an OAuth login also
|
|
821
|
+
# answers email/orgId/subscriptionType. There is therefore no way to learn which
|
|
822
|
+
# account approved a setup-token grant. Callers must read "" as "unknowable by
|
|
823
|
+
# design" — never as a transient failure worth retrying — and guard the ceremony by
|
|
824
|
+
# NAMING the expected account up front instead.
|
|
787
825
|
local real
|
|
788
826
|
real="$(find_real_claude "$_self")" || { echo ""; return 0; }
|
|
789
827
|
CLAUDE_CODE_OAUTH_TOKEN="$1" CLAUDE_SHIM_ACTIVE=1 "$real" auth status 2>/dev/null | "$PYBIN" -c '
|
|
@@ -838,30 +876,55 @@ TIP
|
|
|
838
876
|
}
|
|
839
877
|
|
|
840
878
|
cmd_mint() {
|
|
879
|
+
# The minted token is the ONLY credential that reaches the server and every peer, so a
|
|
880
|
+
# mint under the wrong browser session hands this slot another account's subscription —
|
|
881
|
+
# and nothing downstream can notice (a setup token reports no identity, see
|
|
882
|
+
# token_email). Naming the expected account before the ceremony opens is the only guard
|
|
883
|
+
# that exists; the old prompt said "THIS account" and named nobody.
|
|
841
884
|
require_manifest
|
|
842
|
-
local id="
|
|
885
|
+
local id="" paste=0
|
|
886
|
+
while [ $# -gt 0 ]; do
|
|
887
|
+
case "$1" in
|
|
888
|
+
--paste) paste=1; shift ;;
|
|
889
|
+
--*) die "unknown option: $1" ;;
|
|
890
|
+
*) if [ -z "$id" ]; then id="$1"; shift
|
|
891
|
+
else die "unexpected argument: $1 (usage: claude-accounts mint <acct-NN> [--paste])"; fi ;;
|
|
892
|
+
esac
|
|
893
|
+
done
|
|
843
894
|
[ -n "$id" ] || die "usage: claude-accounts mint <acct-NN> [--paste]"
|
|
844
895
|
valid_acct_id "$id" || die "not a valid account id: $id"
|
|
896
|
+
# A bare directory is NOT enough: removed accounts and killed `add` runs leave acct-NN
|
|
897
|
+
# dirs behind (this Mac carries nine), and minting into one binds a live token to a slot
|
|
898
|
+
# the manifest cannot name — which is exactly the unattributable state this guards.
|
|
899
|
+
account_ids | grep -qx "$id" \
|
|
900
|
+
|| die "unknown account: $id — mint binds a token to a REGISTERED account ('claude-accounts list' shows them)"
|
|
845
901
|
local d="$ACC_ROOT/$id"
|
|
846
|
-
|
|
847
|
-
local tok=""
|
|
848
|
-
|
|
849
|
-
|
|
902
|
+
seed_account_dir "$d"
|
|
903
|
+
local email tok="" got
|
|
904
|
+
email="$(account_email "$id")"
|
|
905
|
+
[ -n "$email" ] || die "$id has no email in the manifest — refusing to mint a token nobody could attribute"
|
|
906
|
+
if [ "$paste" = "1" ]; then
|
|
907
|
+
printf 'Paste the sk-ant-oat... token for %s (%s): ' "$id" "${email:-unknown email}"
|
|
850
908
|
read -r tok
|
|
851
909
|
tok="$(printf '%s' "$tok" | tr -d '[:space:]')"
|
|
852
910
|
else
|
|
853
|
-
echo "Running 'claude setup-token' for $id — approve in a browser signed
|
|
911
|
+
echo "Running 'claude setup-token' for $id — approve in a browser signed in as ${email:-THIS account}."
|
|
854
912
|
run_token_ceremony "$d" || die "no token captured — mint failed"
|
|
855
913
|
tok="$CEREMONY_TOKEN"
|
|
856
914
|
fi
|
|
857
915
|
[ -n "$tok" ] || die "no token captured — mint failed"
|
|
858
916
|
valid_subscription_token "$tok" \
|
|
859
917
|
|| die "that is not a subscription setup-token (sk-ant-oat...). API keys are not supported."
|
|
918
|
+
got="$(token_email "$tok")"
|
|
919
|
+
if [ -n "$got" ] && [ -n "$email" ] && [ "$got" != "$email" ]; then
|
|
920
|
+
die "that token authenticates as $got but $id is $email — nothing saved"
|
|
921
|
+
fi
|
|
860
922
|
( umask 077; printf '%s' "$tok" > "$d/server.token" )
|
|
861
923
|
chmod 600 "$d/server.token"
|
|
862
924
|
clear_auth_markers "$d"
|
|
863
925
|
log_to ops.log "mint $id"
|
|
864
926
|
echo "Token saved to $d/server.token"
|
|
927
|
+
[ -n "$got" ] || warn "a setup token carries no identity — $id now runs whichever account approved that grant${email:+, trusted to be $email}"
|
|
865
928
|
auto_sync
|
|
866
929
|
}
|
|
867
930
|
|
|
@@ -884,14 +947,7 @@ cmd_login() {
|
|
|
884
947
|
account_ids | grep -qx "$id" || die "unknown account: $id"
|
|
885
948
|
local d="$ACC_ROOT/$id" email got
|
|
886
949
|
seed_account_dir "$d"
|
|
887
|
-
email="$(
|
|
888
|
-
import json, sys
|
|
889
|
-
for a in json.load(open(sys.argv[1])).get('accounts', []):
|
|
890
|
-
if a['id'] == sys.argv[2]:
|
|
891
|
-
print(a.get('email', ''))
|
|
892
|
-
break
|
|
893
|
-
PYEOF
|
|
894
|
-
)"
|
|
950
|
+
email="$(account_email "$id")"
|
|
895
951
|
echo "Sign in as $email for $id."
|
|
896
952
|
if [ "$token" = "1" ]; then
|
|
897
953
|
run_token_ceremony "$d" || die "sign-in failed or aborted — nothing changed"
|
|
@@ -902,6 +958,7 @@ PYEOF
|
|
|
902
958
|
( umask 077; printf '%s' "$CEREMONY_TOKEN" > "$d/server.token" )
|
|
903
959
|
chmod 600 "$d/server.token"
|
|
904
960
|
clear_auth_markers "$d"
|
|
961
|
+
[ -n "$got" ] || warn "a setup token carries no identity — $id is trusted to hold $email because that is who you approved as"
|
|
905
962
|
echo "$id token saved (portable — works on Mac and server)."
|
|
906
963
|
else
|
|
907
964
|
run_login_ceremony "$d" "$email" \
|
|
@@ -2126,6 +2183,20 @@ cmd_health() {
|
|
|
2126
2183
|
return $rc
|
|
2127
2184
|
}
|
|
2128
2185
|
|
|
2186
|
+
# Every KNOWN subcommand answers `--help`/`-h` with the usage text and exit 0. This is not
|
|
2187
|
+
# a nicety: app-robot's runner probes for a verb with `<verb> --help` and reads a non-zero
|
|
2188
|
+
# exit as "this build predates the verb", which silently parked panel-to-Mac credential
|
|
2189
|
+
# distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
|
|
2190
|
+
# FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
|
|
2191
|
+
# the dispatcher below implements, and a test pins that both ways.
|
|
2192
|
+
_KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove mint login expired relogin re-login sync verify limits post-sync health self-update"
|
|
2193
|
+
case " $_KNOWN_VERBS " in
|
|
2194
|
+
*" ${1:-help} "*)
|
|
2195
|
+
for _arg in "$@"; do
|
|
2196
|
+
case "$_arg" in --help|-h) usage; exit 0 ;; esac
|
|
2197
|
+
done ;;
|
|
2198
|
+
esac
|
|
2199
|
+
|
|
2129
2200
|
case "${1:-help}" in
|
|
2130
2201
|
list) shift; cmd_list "$@" ;;
|
|
2131
2202
|
status) shift; cmd_status "$@" ;;
|
package/bin/codex-accounts
CHANGED
|
@@ -1935,6 +1935,20 @@ PYEOF
|
|
|
1935
1935
|
echo "codex account pool: $ACC_ROOT (manifest ready)"
|
|
1936
1936
|
}
|
|
1937
1937
|
|
|
1938
|
+
# Every KNOWN subcommand answers `--help`/`-h` with the usage text and exit 0. This is not
|
|
1939
|
+
# a nicety: app-robot's runner probes for a verb with `<verb> --help` and reads a non-zero
|
|
1940
|
+
# exit as "this build predates the verb", which silently parked panel-to-Mac credential
|
|
1941
|
+
# distribution for as long as the arg loops rejected the flag. An UNKNOWN verb must still
|
|
1942
|
+
# FAIL, or the probe stops meaning what it says — so this list must hold exactly the verbs
|
|
1943
|
+
# the dispatcher below implements, and a test pins that both ways.
|
|
1944
|
+
_KNOWN_VERBS="list status add import export-credential export-cred import-credential import-cred adopt dedupe remove login expired relogin re-login sync verify limits post-sync health self-update init-pool"
|
|
1945
|
+
case " $_KNOWN_VERBS " in
|
|
1946
|
+
*" ${1:-help} "*)
|
|
1947
|
+
for _arg in "$@"; do
|
|
1948
|
+
case "$_arg" in --help|-h) usage; exit 0 ;; esac
|
|
1949
|
+
done ;;
|
|
1950
|
+
esac
|
|
1951
|
+
|
|
1938
1952
|
case "${1:-help}" in
|
|
1939
1953
|
list) shift; cmd_list "$@" ;;
|
|
1940
1954
|
status) shift; cmd_status "$@" ;;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-multiacc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Multi-account addon for Claude Code and OpenAI Codex CLI: every claude / claude -p and every codex / codex exec runs under a randomly-picked subscription account with the most usage headroom. Mirrors to a deploy server. No API keys.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/tests/run-tests.sh
CHANGED
|
@@ -43,6 +43,14 @@ cat > "$FAKEBIN/claude" <<'EOF'
|
|
|
43
43
|
# fake real claude for tests (not a multiacc shim)
|
|
44
44
|
if [ "${1:-}" = "auth" ] && [ "${2:-}" = "status" ]; then
|
|
45
45
|
if [ -n "${FAKE_AUTH_FAIL:-}" ]; then echo '{"loggedIn": false}'; exit 0; fi
|
|
46
|
+
# A setup token is minted with scope user:inference ALONE, so the real CLI answers
|
|
47
|
+
# {loggedIn, authMethod, apiProvider} and NO email — only an OAuth login (config dir)
|
|
48
|
+
# reports one. The fake used to hand an email to both, which is precisely why every
|
|
49
|
+
# --token identity path passed here and died in the field.
|
|
50
|
+
if [ -n "${CLAUDE_CODE_OAUTH_TOKEN:-}" ] && [ -z "${CLAUDE_CONFIG_DIR:-}" ]; then
|
|
51
|
+
echo '{"loggedIn": true, "authMethod": "oauth_token", "apiProvider": "firstParty"}'
|
|
52
|
+
exit 0
|
|
53
|
+
fi
|
|
46
54
|
printf '{"loggedIn": true, "email": "%s"}\n' "${FAKE_EMAIL:-fake@test}"
|
|
47
55
|
exit 0
|
|
48
56
|
fi
|
|
@@ -1002,6 +1010,16 @@ claude-accounts remove acct-04 --yes >/dev/null 2>&1
|
|
|
1002
1010
|
# ---- 15c1. add --token: portable setup-token instead of creds ------------------------
|
|
1003
1011
|
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 FAKE_EMAIL=tok@test claude-accounts add tok@test --token 2>&1)"
|
|
1004
1012
|
check "add --token registers via portable token" "Registered acct-04 for tok@test" "$out"
|
|
1013
|
+
check "add --token says the identity cannot be verified" "a setup token carries no identity" "$out"
|
|
1014
|
+
case "$out" in
|
|
1015
|
+
*"sign-in verified"*) t_fail "add --token must not claim a verified sign-in" "it says 'sign-in verified' for an identity nothing can read" ;;
|
|
1016
|
+
*) t_ok "add --token does not claim a verified sign-in" ;;
|
|
1017
|
+
esac
|
|
1018
|
+
case "$(grep 'add acct-04 tok@test' "$ACC/ops.log" 2>/dev/null)" in
|
|
1019
|
+
*auth-verified*) t_fail "ops.log must not record --token as auth-verified" "the audit trail would relaunder the assumption" ;;
|
|
1020
|
+
*"identity unverifiable"*) t_ok "ops.log records the --token add as unverifiable" ;;
|
|
1021
|
+
*) t_fail "ops.log line for the --token add" "not found" ;;
|
|
1022
|
+
esac
|
|
1005
1023
|
[ -s "$ACC/acct-04/server.token" ] && t_ok "add --token writes server.token" || t_fail "add --token" "missing"
|
|
1006
1024
|
out="$(CLAUDE_ACCOUNT=acct-04 claude 2>&1)"
|
|
1007
1025
|
check "token account exports CLAUDE_CODE_OAUTH_TOKEN" "TOK=sk-ant-oat01-FAKE" "$out"
|
|
@@ -3872,6 +3890,74 @@ assert any('manifest' in w for w in d['warnings']), d['warnings']
|
|
|
3872
3890
|
&& t_ok "limits --json still emits a document (with a warning) when the pool is broken" \
|
|
3873
3891
|
|| t_fail "limits --json on a broken pool" "no usable document"
|
|
3874
3892
|
|
|
3893
|
+
# ---- 46. a setup token has no identity: the --token paths must not pretend --------
|
|
3894
|
+
# Regression for two field failures on 2026-08-24: `add <email> --token` died every time
|
|
3895
|
+
# with "identity could not be read back" (it demanded --force for a ceremony that cannot
|
|
3896
|
+
# exist), and `mint` named no account at all — so approving in the wrong browser session
|
|
3897
|
+
# silently pinned another account's subscription to the slot, undetectably.
|
|
3898
|
+
out="$(CLAUDE_MULTIACC_FORCE_TTY=1 claude-accounts add --token 2>&1)"
|
|
3899
|
+
rc=$?
|
|
3900
|
+
check "add --token with no email refuses precisely" "name it: claude-accounts add <email> --token" "$out"
|
|
3901
|
+
[ "$rc" != "0" ] && t_ok "add --token with no email exits nonzero" || t_fail "add --token no email" "exited 0"
|
|
3902
|
+
[ ! -d "$ACC/acct-04" ] && t_ok "refused --token add leaves no dir behind" || t_fail "add --token cleanup" "dir left"
|
|
3903
|
+
# ...and it must refuse BEFORE the ceremony: minting a real 1-year grant only to discard
|
|
3904
|
+
# it would leave a live credential issued for nothing.
|
|
3905
|
+
case "$out" in
|
|
3906
|
+
*"sign-in link"*) t_fail "add --token refuses before the ceremony" "setup-token already ran for an account it cannot name" ;;
|
|
3907
|
+
*) t_ok "add --token with no email never opens the ceremony" ;;
|
|
3908
|
+
esac
|
|
3909
|
+
|
|
3910
|
+
# mint must refuse a bare acct-NN directory the manifest does not know: removed accounts
|
|
3911
|
+
# and killed `add` runs leave those behind, and a token bound to one is unattributable.
|
|
3912
|
+
mkdir -p "$ACC/acct-77"
|
|
3913
|
+
out="$(printf 'sk-ant-oat01-ORPHANORPHANORPHANORPHANORPHANORPHAN\n' \
|
|
3914
|
+
| claude-accounts mint acct-77 --paste 2>&1)"
|
|
3915
|
+
check "mint refuses an unregistered account dir" "unknown account: acct-77" "$out"
|
|
3916
|
+
[ ! -s "$ACC/acct-77/server.token" ] && t_ok "no token is written into an orphan dir" \
|
|
3917
|
+
|| t_fail "mint orphan dir" "server.token was written to an unregistered slot"
|
|
3918
|
+
rm -rf "$ACC/acct-77"
|
|
3919
|
+
|
|
3920
|
+
# mint must NAME the account it is about to bind a token to (the only guard there is).
|
|
3921
|
+
printf 'sk-ant-oat01-MINTNAMEDMINTNAMEDMINTNAMEDMINTNAMEDMINTNAMED\n' \
|
|
3922
|
+
| claude-accounts mint acct-01 --paste > "$WORK/mint-named.out" 2>&1
|
|
3923
|
+
out="$(cat "$WORK/mint-named.out")"
|
|
3924
|
+
check "mint --paste names the account in its prompt" "for acct-01" "$out"
|
|
3925
|
+
check "mint warns that a setup token carries no identity" "carries no identity" "$out"
|
|
3926
|
+
|
|
3927
|
+
# ---- 47. every known verb answers --help with exit 0 (app-robot probes with it) ----
|
|
3928
|
+
# app-robot's runner asks `<verb> --help` to decide whether a Mac's build has the verb;
|
|
3929
|
+
# a non-zero exit reads as "too old" and parked panel-to-Mac credential distribution.
|
|
3930
|
+
# _KNOWN_VERBS must hold EXACTLY what the dispatcher implements. A verb missing from it
|
|
3931
|
+
# hides a real verb from the probe; a verb listed but unimplemented makes --help answer 0
|
|
3932
|
+
# for something that does not exist, which is how the probe stops meaning anything. The
|
|
3933
|
+
# first cut of this gate got both wrong (mint listed in codex, init-pool in claude), so
|
|
3934
|
+
# the parity is checked from the source, both directions, for both binaries.
|
|
3935
|
+
for _bin in claude-accounts codex-accounts; do
|
|
3936
|
+
_src="$REPO_DIR/bin/$_bin"
|
|
3937
|
+
_dispatch="$(grep -oE '^ [a-z0-9|_-]+\) shift; cmd_' "$_src" | sed -e 's/) shift; cmd_//' -e 's/^ //' | tr '|\n' ' ')"
|
|
3938
|
+
_gate="$(grep -m1 '^_KNOWN_VERBS=' "$_src" | sed -e 's/^_KNOWN_VERBS="//' -e 's/"$//')"
|
|
3939
|
+
_parity=1
|
|
3940
|
+
for _v in $_dispatch; do
|
|
3941
|
+
case " $_gate " in
|
|
3942
|
+
*" $_v "*) ;;
|
|
3943
|
+
*) _parity=0; t_fail "$_bin: dispatcher has '$_v', _KNOWN_VERBS does not" "app-robot's probe would read the verb as absent" ;;
|
|
3944
|
+
esac
|
|
3945
|
+
"$_bin" "$_v" --help >/dev/null 2>&1
|
|
3946
|
+
[ "$?" = "0" ] && t_ok "$_bin $_v --help exits 0" \
|
|
3947
|
+
|| t_fail "$_bin $_v --help" "non-zero exit — app-robot would read the verb as missing"
|
|
3948
|
+
done
|
|
3949
|
+
for _v in $_gate; do
|
|
3950
|
+
case " $_dispatch " in
|
|
3951
|
+
*" $_v "*) ;;
|
|
3952
|
+
*) _parity=0; t_fail "$_bin: _KNOWN_VERBS lists '$_v', the dispatcher does not implement it" "--help would answer 0 for a verb that does not exist" ;;
|
|
3953
|
+
esac
|
|
3954
|
+
done
|
|
3955
|
+
[ "$_parity" = "1" ] && t_ok "$_bin: the help gate and the dispatcher list the same verbs"
|
|
3956
|
+
"$_bin" frobnicate --help >/dev/null 2>&1
|
|
3957
|
+
[ "$?" != "0" ] && t_ok "$_bin: an UNKNOWN verb still fails --help (the probe keeps its meaning)" \
|
|
3958
|
+
|| t_fail "$_bin unknown verb --help" "exited 0 — the probe would accept a verb that does not exist"
|
|
3959
|
+
done
|
|
3960
|
+
|
|
3875
3961
|
# ---- summary ---------------------------------------------------------------------
|
|
3876
3962
|
echo
|
|
3877
3963
|
echo "passed: $PASS failed: $FAIL"
|