# shusub2 - 文档层级:客户端工具 - 应用性质:客户端 TUI - 源码来源:repo 原生 - 运行关系:客户端工具 - 部署模型:无 - 对应服务:无 - 对应 stack:无 - Secret 边界:本地用户配置 - 分发方式:`uvx` / `uv tool install`。 - 包名:`shusub2` - 命令名:`shusub2` - 本地状态路径:`~/.config/shusub2/` - 目标 API:`cliproxy-codex-quota` 的 `/api/tui/accounts`、可选 `sub2api-status` `/api/status`、可选 Pricing Monitor `/api/ui-data?view=accounts`,以及可选 Sub2API `/api/v1/admin/usage` 和 `/api/v1/admin/ops/errors`。 - 备注:账号数据无 secret,不需要 SSH、数据库访问、API key、OAuth credentials 或 plaintext env;请求与错误日志可选持有 Sub2API admin API key,仅存本机 `~/.config/shusub2/logs-token`(0600)或 `SHUSUB2_LOGS_TOKEN`。 Single-page terminal dashboard for the token-safe Sub2API account feed, request logs, and merged cn/us error logs. Dedicated full-table views remain available for each data set. ## Quick Start Run once from a public Git repo with `uvx`: ```bash uvx --from git+https://gitea.shujk.top/shujakuin/shusub2.git shusub2 \ --api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts ``` Bootstrap a new machine from `uvx`: save the API URL, install `shusub2` as a user command, then run it later as `shusub2`: ```bash uvx --from git+https://gitea.shujk.top/shujakuin/shusub2.git shusub2 \ --api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts \ --install shusub2 ``` Install as a user command: ```bash uv tool install git+https://gitea.shujk.top/shujakuin/shusub2.git shusub2 --api-url https://codex.server2.shujk.top/1232131231313123/api/tui/accounts --save-config ``` Configure the default public API URL for `shusub2`: ```bash mkdir -p ~/.config/shusub2 chmod 700 ~/.config/shusub2 printf '%s\n' 'https://codex.server2.shujk.top/1232131231313123/api/tui/accounts' > ~/.config/shusub2/api-url chmod 600 ~/.config/shusub2/api-url shusub2 ``` Environment variables override the config file: - `SHUSUB2_API_URL` - `SUB2API_QUOTA_TUI_API_URL` - `SHUSUB2_API_URL_FILE` - `SHUSUB2_STATUS_URL` - `SHUSUB2_STATUS_URL_FILE` - `SHUSUB2_PRICING_URL` - `SHUSUB2_PRICING_URL_FILE` - `SHUSUB2_LOGS_URL` - `SHUSUB2_LOGS_URL_FILE` - `SHUSUB2_LOGS_TOKEN` - `SHUSUB2_LOGS_TOKEN_FILE` - `SHUSUB2_LOGS_REFRESH_SECONDS` - `SHUSUB2_LOGS_LIMIT` - `SHUSUB2_ERRORS_CN_URL` - `SHUSUB2_ERRORS_CN_URL_FILE` - `SHUSUB2_ERRORS_US_URL` - `SHUSUB2_ERRORS_US_URL_FILE` - `SHUSUB2_ERRORS_REFRESH_SECONDS` - `SHUSUB2_ERRORS_LIMIT` - `SHUSUB2_ERRORS_TIME_RANGE` - `SHUSUB2_VERSION_CHECK_URL` - `SHUSUB2_VERSION_CHECK_TIMEOUT` - `SHUSUB2_NO_VERSION_CHECK` ## Unified Dashboard `shusub2` opens a compact dashboard for Accounts, Keys, Logs, and Errors. Its summary also reports Pricing Monitor upstream health and CNY total. The Accounts table attaches a source balance only when an account has a canonical quota-source name and the sanitized Pricing Monitor projection has one unambiguous matching source. Press `a`, `k`, `l`, or `e` to focus a dashboard table; press `p` to open the full Upstreams source-balance page. `/` filters the active table and `r` refreshes all data immediately. Automatic Accounts and Upstreams, Keys/Logs, and Errors refresh defaults to every five minutes. The client requests gzip-compressed JSON and transparently decodes it when the upstream supports it. The two-line detail area follows the selected row. Accounts, Keys, Logs, and Errors use green, yellow, magenta, and red section styling respectively. The dashboard sizes every column from the fetched content so wide terminals show complete keys, accounts, and models. On a narrow terminal, a table scrolls horizontally instead of truncating a field: ```text ACCOUNT | Group | Source | Src CNY | Src state | Today | Daily | 5h | 7d | Avail UPSTREAMS (p) | Source | Kind | Balance | CNY | Unit | Status | Updated | Age KEY | Today | Tokens | Req LOG KEY | Account | Model | First | Duration | Tok/s | Input | Output | Cache | Tokens | Cost | Time | Age ERR | Status | Key | Account | Model | Time | Age ``` The Keys table shows today's usage sorted by actual cost, including key name, tokens, and request count. It uses the same admin API key as Logs and Errors; without that token the table is hidden and the summary remains compact. Use `--accounts`, `--pricing`, `--logs`, or `--errors` to start a dedicated full-table view. The corresponding `--once` form still prints only that data set. ## Request Logs Page The middle dashboard table shows request logs, and `l` focuses it. `shusub2 --logs` starts the dedicated logs page, while `shusub2 --once --logs` prints one logs snapshot to stdout. The logs page reads the latest requests (default 100, `--logs-limit`) from the Sub2API admin usage API and refreshes every five minutes by default (`--logs-refresh-seconds` / `SHUSUB2_LOGS_REFRESH_SECONDS`). Columns: ```text Key | Account | Model | Effort | Type | Input | Output | Cache | Tokens | Cost | First | Duration | Tok/s | Time | Age ``` `Type` is the Sub2API `request_type` (`sync` / `stream` / `ws_v2` / `cyber`). `Tokens` is input + output + cache write + cache read; the detail line below the table shows the per-bucket breakdown, actual cost, first-token latency, decode speed, upstream model mapping, user, and request id. `Effort` is the request's `reasoning_effort` (`-` when absent). `First` is the first-token latency and `Duration` the total request time, both shown in seconds. `Tok/s` is the decode throughput computed as `output_tokens / (duration - first_token)`; it shows `-` when there is no output or no positive decode window. `Input`, `Output`, and `Cache` are token buckets; `Cache` combines cache write and cache read. `Tokens` is their total. `Age` is relative to local current time (`now`, `5m ago`, `2h ago`, etc.). In the TUI each API key name is rendered in a stable per-key color so rows from the same key are easy to group visually (`--once --logs` output stays plain text). The logs URL defaults to `https://sub2apicn.shujk.top/api/v1/admin/usage` and can be overridden with `--logs-url` / `SHUSUB2_LOGS_URL` / `~/.config/shusub2/logs-url`. The page needs a Sub2API **admin API key** (generated in the Sub2API web UI under Settings). Configure it once: ```bash mkdir -p ~/.config/shusub2 && chmod 700 ~/.config/shusub2 printf '%s\n' '' > ~/.config/shusub2/logs-token chmod 600 ~/.config/shusub2/logs-token ``` or via `SHUSUB2_LOGS_TOKEN`. The key is sent as the `x-api-key` header and is never written anywhere else by the client. Without a token the dashboard Logs and Errors tables stay empty and show a configuration hint; the Accounts table keeps working without any secret. ## Errors Page The bottom dashboard table shows merged errors, and `e` focuses it. `shusub2 --errors` starts the dedicated errors page, while `shusub2 --once --errors` prints one merged snapshot to stdout. The page pulls the latest admin ops error logs from **both** cn and us in parallel, then merges them by `created_at`: - cn default: `https://sub2apicn.shujk.top/api/v1/admin/ops/errors` - us default: `https://us.sub2.shujk.top/api/v1/admin/ops/errors` `us.sub2.shujk.top` is the DNS-only standard HTTPS direct entry for the same server4 backend. It avoids the Cloudflare 1010 behavior sometimes returned by `sub2apius.shujk.top` to non-browser clients while avoiding the fixed `:19857` port in the default client configuration. Use `--errors-us-url` to select a different direct origin when required. The dashboard and dedicated errors page refresh every five minutes by default (`--errors-refresh-seconds` / `SHUSUB2_ERRORS_REFRESH_SECONDS`). Query window defaults to `24h` (`--errors-time-range` / `SHUSUB2_ERRORS_TIME_RANGE`); each source uses `page=1` and `page_size` from `--errors-limit` (default 100, max 500). Columns: ```text Node | Status | Key | Account | Model | Phase | Type | Owner | Time | Age ``` `Age` is relative to local current time (`now`, `5m ago`, `2h ago`, etc.). Auth reuses the same admin API key as the logs page. ## Local Development ```bash cd apps/sub2api-quota-tui uv run shusub2 ``` Run inside zellij: ```bash zellij action new-pane --name sub2api-quota -- \ bash -lc 'cd /home/shujakuin/infra/apps/sub2api-quota-tui && uv run shusub2' ``` Configuration: - `--api-url` / `SUB2API_QUOTA_TUI_API_URL` - `--status-url` / `SHUSUB2_STATUS_URL` - `--pricing-url` / `SHUSUB2_PRICING_URL` - `--logs-url` / `SHUSUB2_LOGS_URL` - `--logs-token` / `SHUSUB2_LOGS_TOKEN` - `--logs-refresh-seconds` / `SHUSUB2_LOGS_REFRESH_SECONDS` - `--logs-limit` / `SHUSUB2_LOGS_LIMIT` - `--errors-refresh-seconds` / `SHUSUB2_ERRORS_REFRESH_SECONDS` - `--errors-limit` / `SHUSUB2_ERRORS_LIMIT` - `--errors-time-range` / `SHUSUB2_ERRORS_TIME_RANGE` - `--accounts` / `--pricing` / `--logs` / `--errors` - `--save-config` - `--install` - `--refresh-seconds` / `SUB2API_QUOTA_TUI_REFRESH_SECONDS` - `--timeout` / `SUB2API_QUOTA_TUI_TIMEOUT` The dashboard reads `/api/tui/accounts`, can optionally read `sub2api-status` `/api/status` for channel monitor health, and reads Pricing Monitor's `/api/ui-data?view=accounts` for upstream source balances. The default pricing URL is the Tailscale HTTPS service; `--pricing-url`, `SHUSUB2_PRICING_URL`, or `~/.config/shusub2/pricing-url` can override it. This is a cached, read-only projection collected by Pricing Monitor on its own cadence: `shusub2` neither contacts upstream providers directly nor asks Pricing Monitor to refresh. The Accounts table does not need SSH, database access, API keys, OAuth credentials, or plaintext env files. The Keys, Logs, and Errors tables authenticate to the Sub2API admin APIs with an admin API key stored only in `~/.config/shusub2/logs-token` (0600) or `SHUSUB2_LOGS_TOKEN`. When `--status-url` is omitted, `shusub2` infers a sibling `/api/status` URL from account URLs ending in `/api/tui/accounts`, so the public Codex endpoint automatically enables monitor availability. On startup it also checks the public Gitea repo for a newer package version and prints a short upgrade hint when one is available. On the dedicated `--accounts` page, a per-key panel shows today's usage for each API key (`Key | Today | Tokens | Req`, sorted by cost, key names in the same per-key colors as the logs page). It combines the Sub2API admin `dashboard/api-keys-trend` and `dashboard/api-keys-usage` endpoints and needs the same admin API key as the logs page; without a token the panel stays empty and the status line shows a hint. `--once` prints the same panel after the accounts table when a token is configured. The dedicated Accounts page keeps the full columns for scanning inside zellij: ```text Name | Provider | Group | Source | Src CNY | Src state | Daily | Today | Tokens | Req | Kind | 5h | 7d | Reset | Status | Availability ``` `Provider` distinguishes `openai` and `anthropic` accounts from the public `platform` field returned by the API. `Daily` is shown as `used/limit` when Sub2API has `quota_daily_*` fields in `accounts.extra`; otherwise it is `-`. `5h` / `7d` are only the account feed's used/remaining quota percentages. They are not upstream provider balances and therefore never claim to be a cash or source-account balance. `Source`, `Src CNY`, and `Src state` use Pricing Monitor's cached, sanitized `view=accounts` projection. A balance is attached only for exact canonical account names of the form `{family}-quota-{source}` or `{family}-quotaonly-{source}`, where `{source}` is one or more lowercase alphanumeric segments joined by single hyphens, and a single normalized source name. Provider, URL, display-name, and fuzzy matching are deliberately not used. Accounts without a unique mapping show `-`; an `error` or `stale` source retains its source/state label but does not show a CNY amount. The dedicated `--pricing` page shows Pricing Monitor source rows such as `code-plan`, `codexapis`, `testvideo`, `kedaya`, `ycy`, and `mdkj`: raw available balance with its source unit, derived CNY when supplied, health state, and last successful collection time. It reads only the existing sanitized source projection. `Group` is the derived Sub2API tier alias group. Higher tiers win when multiple aliases exist: `id < slow < fast < sfast`. The table shows `sfast` first, then `fast`, `slow`, `id`, and ungrouped accounts. When `--status-url` is configured, the status line shows channel monitor health, and the selected account detail shows the matching monitor status when one exists. Monitor binding first uses the shared `base_url_hash` emitted by the account API and `sub2api-status`; name-token matching remains only as a fallback for older status payloads. `Availability` is derived from the bound channel monitor. Accounts without a matching monitor show `-`.