# @tokensrc/codex

Standalone Codex account selection, OIDC authentication and official Codex launcher.

```sh
npx @tokensrc/codex list
npx @tokensrc/codex --server https://code-inbox.mcisaas.com --tenant-id numa-realm list
npx @tokensrc/codex --profile-url https://code-inbox.mcisaas.com/public/v1/tenants/numa-realm/client-configuration list
npx @tokensrc/codex use
npx @tokensrc/codex run
```

The standalone command tree includes `login`, `logout`, `request`, `list`, `upload`, `use`,
`run`, `app`, `auth-agent`, `enrollment`, `admin`, `shortcut`, `status`, and `reset`. The
`admin accounts` subtree lists tenant accounts, shows redacted credential-version
diagnostics, records explicit subscription renewals, and requests one server-side refresh; it never receives refresh
tokens, ciphertext, or credential fingerprints. `admin assignments` lists
bindings, binds an account to a verified Keycloak subject, and unbinds by
assignment ID:

```sh
numa codex admin accounts list
numa codex admin accounts credential-versions acct_xxx
numa codex admin accounts renew acct_xxx --expires-at 2026-10-01T00:00:00Z
numa codex admin accounts refresh acct_xxx
numa codex admin assignments list
numa codex admin assignments bind acct_xxx keycloak-subject --default
numa codex admin assignments unbind assignment_xxx
```

When discovery advertises `requestBoundDeviceCredentials: true`, v6 replaces
direct user/account assignments with a request and per-device binding model. A
request belongs to one Keycloak subject and may contain several bindings for
that same user. Every binding owns a distinct complete ChatGPT token set and
credential lineage; Numa never copies a refresh token between installations.

```sh
numa codex request list
numa codex request reconcile
numa codex request bindings request_xxx

numa codex admin requests list --expiring-within 30
numa codex admin requests review request_xxx --decision APPROVE --reason "审批通过" \
  --version 0 --device-limit 2 --valid-from 2026-09-01T00:00:00+08:00 \
  --valid-until 2026-10-01T00:00:00+08:00
numa codex admin requests bind-device request_xxx --installation install_xxx \
  --device-label "Work Mac" --platform darwin --version 1 --pool-entry pool_xxx
numa codex admin requests replacement-candidates --within 30
numa codex admin requests replace-device-token request_xxx binding_xxx \
  --version 2 --reason SUBSCRIPTION_EXPIRING \
  --replace-before 2026-09-25T00:00:00+08:00 --pool-entry pool_new
```

Business requests are created by tenant administrators in the web console. End users can
inspect the resulting request and reconcile their device, but cannot create or cancel a request.

The first v6 reconciliation reads only the safe identifiers of an already
distributed local credential. The Server atomically converts it into one
approved migration request and one `LEGACY_AUTO_MIGRATED` binding; the existing
token set changes ownership but is not duplicated. A different installation for
the same user receives `codex_device_binding_required`, its installation ID, and
administrator-contact guidance. An administrator must bind a new PoolEntry with
an independent token set before that device becomes available.

Administrator request lists are ordered by nearest expiry. `--expiring-within`
uses the Server's `expiresBefore` filter and excludes terminal requests. Under
`replacement-candidates` identifies bindings whose `accountExpiresAt` or health
requires a successor. The old binding stays active while the successor receives
an independent PoolEntry/lineage; only the device's successful install ACK makes
the successor active and retires the old token set. A temporary delivery failure
therefore keeps a healthy old token set usable and retryable. Under
v6, managed `numa codex login` can only add a complete token set to the unbound
pool; direct `--assign-to`/`--assign-to-subject` flags and legacy assignment
mutations are disabled. The full Server transaction and migration invariants are
documented in `docs/superpowers/specs/2026-08-31-request-bound-device-token-sets-design.md`.

An account whose `subscriptionStatus` is `EXPIRED`, or whose
`subscriptionExpiresAt` has passed, is unavailable even when a cached OpenAI
access token has a later JWT expiry. Renewal requires a strictly later,
future ISO-8601 timestamp. It restores the platform subscription window only;
an independently rejected provider credential still requires a fresh managed
login. The renewal mutation carries the previously observed expiry for an
atomic compare-and-set and an `Idempotency-Key`; an unknown response must be
retried with the same key and request body.

The account service implements renewal at
`POST /v1/admin/codex/accounts/{accountId}/renew`. In one transaction it must
verify `expectedSubscriptionExpiresAt`, require the requested expiry to extend
both the current time and stored expiry, set the subscription status to
`ACTIVE`, and preserve a separate provider-credential failure such as
`NEEDS_REAUTH`. User account and v2/v4 assignment responses propagate the
subscription status and expiry. Before renewal, an expired assignment is never
delivery-eligible and credential/device-session endpoints reject it with
`codex_account_expired`; after renewal it becomes eligible only when its
credential lineage is otherwise healthy.

Credential expiry is independent from subscription expiry. The legacy Server
error `client_credential_expired` is normalized to
`codex_account_needs_reauth`, never `codex_account_expired`. On an explicit 409
reauthentication decision, the client removes only the exact rejected delivery
recovery journal. Network failures, 5xx responses, and unknown mutation
outcomes retain that journal for idempotent recovery.

For an active subscription with `credentialStatus=NEEDS_REAUTH`, an
administrator replaces the credential with a fresh isolated official login,
targeted to the affected Numa username or exact directory email:

```sh
numa codex admin accounts credential-versions acct_xxx --json
numa codex admin assignments list --json
numa codex login --assign-to affected-user
```

The affected user then runs `numa codex list --json` and `numa codex run` to
verify a new lineage. Do not renew the subscription unless its own status or
timestamp has expired. Do not copy or share `auth.json`; official Codex login
credentials are collected only inside the isolated login flow.

If the official login succeeds but upload returns
`codex_token_pool_healthy_lineage_exists`, do not repeat the login. The Server
must first reconcile the stale lineage to `NEEDS_REAUTH`; a lineage that just
failed delivery with `client_credential_expired` cannot validly block its own
replacement as healthy.

When the discovered profile advertises `requestBoundDeviceCredentials: true`,
the legacy direct-assignment mutations (`upload`, administrator refresh,
assignment bind/unbind, and `auth-agent --restore`) fail with
`schema_v2_legacy_management_disabled`; they never silently fall back.

The client connects to
`X-Tenant-Id: numa-realm`. With `--server` and `--tenant-id` it resolves RFC 9728
protected-resource metadata, the tenant client-configuration, and OIDC discovery
before login. `--profile-url` and `TOKENSRC_CODEX_PROFILE_URL` select the public
configuration directly. Configuration priority is SDK/command options,
`TOKENSRC_CODEX_*` environment variables, an explicit profile, local settings,
automatic discovery, then legacy built-in defaults. Settings live under
`~/.config/tokensrc-codex` on Unix-like systems and
`%APPDATA%\tokensrc-codex` on Windows.

Embedding applications can register the same commands with a Commander program:

```ts
import { createCodexModule } from "@tokensrc/codex";

const codex = await createCodexModule({
  auth: {
    tokenProvider: async ({ forceRefresh }) => getAccessToken(forceRefresh)
  },
  connection: {
    serverUrl: "https://platform.example/api",
    tenantId: "numa-realm"
  },
  persistence: { configDirectory: applicationConfigDirectory },
  desktopApp: {
    inspect: inspectHostDesktopApp
  }
});

codex.registerCommands(program, { nested: true });
const local = await codex.status.loadLocal();
const remote = await codex.status.loadRemote();
```

The optional `desktopApp.inspect` hook adds host-provided desktop version details
to local and `status` output. The package never downloads, installs, replaces,
upgrades, or downgrades a desktop application; `codex app` only prepares the
selected official credentials and launches the existing official application.

Hosts may instead inject a static access token or standalone OIDC settings, and
can customize branding and launcher behavior. Injected access tokens and provider
results remain in memory and are never persisted or included in log messages. A
provider is forcibly refreshed once after HTTP 401; a second failure is returned
without another retry. Numa and standalone OIDC settings delegate to the shared
`@numa-tech/cli-auth` lifecycle, which serializes the Keycloak OIDC session's
refresh-token rotation across processes. This is separate from the Server-owned
OpenAI credential lineage. A rejected identity refresh grant becomes `login_required`; standalone users
are directed to `tokensrc-codex login`, while transient identity-provider errors
retain the cached session.

The main export includes `createCodexModule`, stable error types, status/runtime
helpers, `resolveCodexProfile`, and protocol types. Focused exports are also available at `./commands`,
`./command-directory`, `./console-status`, `./runtime`, `./errors`, `./protocol`,
`./config`, `./profile`, `./shortcuts`, and `./tui`.

### 释放前协助退出进程

运行 `numa codex release`，检查到应用进程时可选择“重新检查”“取消切换”或
“关闭以上应用进程”。不需要额外参数。`use`、`run -s` 的释放对话框也提供同样的选项。

选择关闭后，TUI 逐项显示名称、PID、可获取的窗口标题及退出状态，保留已退出的记录，
并显示已退出数量和剩余倒计时。先请求正常退出并等待 10 秒；仍存活的进程会列出，
只有确认后才强制结束，再复查退出结果。请先保存工作；退出未完成时停止后续释放。

强制退出后仍有进程时，会输出可复制到独立终端执行的手动脚本（macOS/Linux 为 shell，
Windows 为 PowerShell）。脚本只处理列出的进程，执行前重新核验当前用户、PID、名称与
启动时间；身份变化则跳过。手动退出后重新运行 `numa codex release`。新启动实例会单独
标记为“未自动终止”；Unix 上已退出但等待父进程回收的 `Z` 状态不会继续阻止释放。

当前命令的父级 Codex 会话不能由此命令关闭。标题只在系统允许时显示，终端会话或无窗口
进程可能没有标题；不会拿完整命令行代替标题。macOS 桌面应用使用正常退出请求，Unix
CLI 使用 SIGTERM；Windows 使用窗口关闭请求，没有主窗口的进程需手动退出或确认强制结束。
非交互输出保留逐项日志。

### 原设备绑定退役后的授权替换

`use` 或 `run -s` 发现本地原绑定已在服务端退役、所选新授权属于当前用户和设备时，
会提示“检测到本地已有登录授权，但原设备绑定已失效，无法确认当前授权是否仍属于原绑定”，
并提供“使用授权并替换”和“取消并保留”。账号相同不能证明授权来源，当前登录不会作为旧绑定的凭据上报。
若本机已完成释放，只留下绑定元数据和 `RELEASED` 记录、没有剩余登录文件，则不再提示替换，直接领取新授权。

取消会保留本地登录和待处理记录；确认后复用进程检查及关闭进度 TUI，再清理本地登录和旧操作记录、
安装服务器授权。清理记录支持中断恢复，不会向已退役绑定重复上报或登记释放。安装失败后可重试同一
`use` 命令；清理后出现其他登录时停止覆盖。非交互模式需要转到交互终端确认。
