codex-usage-analyzer
Advanced tools
| # Codex Usage Lookup Benchmark | ||
| This document records a reproducible snapshot of the time required for three CLI commands to return Codex usage data. It compares this narrow lookup workflow only. It is not a whole-product comparison, an accuracy ranking, or a general performance guarantee. | ||
| The snapshot was captured on 2026-07-13 during issue scoping with the same warm-up, repetition, sequential execution, and output-suppression policy now encoded by [`measure-command.mjs`](../scripts/benchmarks/measure-command.mjs). A new run will vary with the machine, retained history, filesystem cache, network, and upstream service state. | ||
| ## Results | ||
| Lower is faster. | ||
| | Tool | Usage source for this command | Median | Min-max | Relative time | | ||
| |---|---|---:|---:|---:| | ||
| | `codex-usage-analyzer@0.2.0` | Codex app-server account usage | 1.145s | 0.800-1.222s | 1.0x | | ||
| | `ccusage@20.0.17` | Retained local Codex sessions | 3.306s | 3.252-3.329s | 2.9x | | ||
| | `tokscale@4.4.1` | Retained local Codex sessions | 19.723s | 19.440-20.328s | 17.2x | | ||
| Relative time is each median divided by the `codex-usage-analyzer` median and rounded to one decimal place. The table does not compare provider coverage, feature sets, security, presentation, or the correctness of differently sourced values. | ||
| ## Method | ||
| - Install exact package versions in an isolated temporary directory. Installation time is excluded. | ||
| - Verify npm registry signatures, then run the installed package binaries without package-runner startup or download time. | ||
| - Run commands sequentially on the same machine. | ||
| - Run one warm-up invocation, followed by five measured invocations. | ||
| - Measure wall-clock time from child process start through exit with a monotonic clock. | ||
| - Discard child stdout. Do not retain stderr content; count bytes only. | ||
| - Do not record usage values, session content, filenames, account identity, credentials, or private filesystem paths. | ||
| - Keep pricing network access out of the local scanners: use ccusage offline mode and Tokscale's pricing-cache-only environment setting. | ||
| The original snapshot used an equivalent temporary timing script. The repository harness codifies the method for later runs and reports `median`, `mean`, `min`, and `max`; it does not make the recorded snapshot immutable or automatically install comparison packages. | ||
| ## Environment | ||
| | Item | Recorded value | | ||
| |---|---| | ||
| | Date | 2026-07-13 | | ||
| | Architecture | arm64 | | ||
| | Operating system | macOS 26.5.2 | | ||
| | Node.js | 24.15.0 | | ||
| | Codex CLI | 0.144.0-alpha.4 | | ||
| | Retained history | 1,000-4,999 JSONL files; 2 GiB or more | | ||
| | Warm-up | 1 run per command | | ||
| | Measured runs | 5 runs per command | | ||
| Retained history is deliberately reported as a coarse bucket. The benchmark record contains no user home, temporary directory, session filename, account identifier, or usage metric. | ||
| ## Command arrays | ||
| The executable and argument arrays below describe the measured lookup work. The benchmark runner invokes them with `shell: false`; metacharacters are not interpreted by a child shell. | ||
| ### codex-usage-analyzer 0.2.0 | ||
| ```json | ||
| { | ||
| "command": "codex-usage-analyzer", | ||
| "args": ["--json"] | ||
| } | ||
| ``` | ||
| This command starts the installed Codex CLI and calls OpenAI Codex's documented [`account/usage/read`](https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md) app-server method. | ||
| ### ccusage 20.0.17 | ||
| ```json | ||
| { | ||
| "command": "ccusage", | ||
| "args": ["codex", "daily", "--offline", "--json"] | ||
| } | ||
| ``` | ||
| The command shape and offline option are documented in the official [`ccusage` v20.0.17 source](https://github.com/ccusage/ccusage/tree/v20.0.17). | ||
| ### Tokscale 4.4.1 | ||
| ```json | ||
| { | ||
| "env": { | ||
| "TOKSCALE_PRICING_CACHE_ONLY": "1" | ||
| }, | ||
| "command": "tokscale", | ||
| "args": ["--client", "codex", "--json", "--no-spinner"] | ||
| } | ||
| ``` | ||
| The client filter, JSON mode, and pricing-cache-only behavior are defined in the official [`tokscale` v4.4.1 source](https://github.com/junhoyeo/tokscale/tree/v4.4.1). | ||
| ## Reproduction | ||
| Install and signature-check the exact packages in an isolated directory according to your own dependency-review policy. Put that directory's `node_modules/.bin` on `PATH`, then run the repository harness once per command: | ||
| ```bash | ||
| node scripts/benchmarks/measure-command.mjs --warmup 1 --runs 5 -- codex-usage-analyzer --json | ||
| node scripts/benchmarks/measure-command.mjs --warmup 1 --runs 5 -- ccusage codex daily --offline --json | ||
| TOKSCALE_PRICING_CACHE_ONLY=1 node scripts/benchmarks/measure-command.mjs --warmup 1 --runs 5 -- tokscale --client codex --json --no-spinner | ||
| ``` | ||
| The harness output contains aggregate timing and an stderr byte count. It does not contain the child command, arguments, stdout, stderr content, environment, or local paths. | ||
| ## Interpretation limits | ||
| The commands do not read the same source: | ||
| - `codex-usage-analyzer` delegates authentication and remote communication to the installed Codex process and reads account-level data through the documented app-server method. | ||
| - The compared ccusage and Tokscale commands parse retained local Codex session history for their reports. | ||
| Remote lookup time can change with network latency, Codex startup, authentication state, and service health. Local scan time can change with retained history size, file layout, parser behavior, filesystem performance, and cache warmth. A local scan may also cover a different time range from account-level data. | ||
| For those reasons, use this result only as a dated observation of Codex usage lookup latency in the recorded environment. Re-run the benchmark before making a current performance claim, and do not use this table as a substitute for evaluating data semantics or product requirements. |
| import { constants } from "node:fs"; | ||
| import { access, stat } from "node:fs/promises"; | ||
| import { homedir } from "node:os"; | ||
| import { posix as path } from "node:path"; | ||
| export async function resolveCodexExecutable(options = {}) { | ||
| const platform = options.platform ?? process.platform; | ||
| if (platform !== "darwin") { | ||
| return "codex"; | ||
| } | ||
| const env = options.env ?? process.env; | ||
| const homeDir = options.homeDir ?? homedir(); | ||
| const isExecutable = options.isExecutable ?? isExecutableFile; | ||
| if (await hasExecutableOnPath(env.PATH, isExecutable)) { | ||
| return "codex"; | ||
| } | ||
| for (const candidate of macAppCandidates(homeDir)) { | ||
| if (await safelyCheckExecutable(candidate, isExecutable)) { | ||
| return candidate; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| async function hasExecutableOnPath(pathValue, isExecutable) { | ||
| if (typeof pathValue !== "string" || pathValue.length === 0) { | ||
| return false; | ||
| } | ||
| for (const directory of pathValue.split(":")) { | ||
| const candidate = path.join(directory || ".", "codex"); | ||
| if (await safelyCheckExecutable(candidate, isExecutable)) { | ||
| return true; | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function macAppCandidates(homeDir) { | ||
| const executableParts = ["Contents", "Resources", "codex"]; | ||
| return [ | ||
| path.join("/Applications", "ChatGPT.app", ...executableParts), | ||
| path.join("/Applications", "Codex.app", ...executableParts), | ||
| path.join(homeDir, "Applications", "ChatGPT.app", ...executableParts), | ||
| path.join(homeDir, "Applications", "Codex.app", ...executableParts) | ||
| ]; | ||
| } | ||
| async function safelyCheckExecutable(candidate, isExecutable) { | ||
| try { | ||
| return await isExecutable(candidate); | ||
| } catch { | ||
| return false; | ||
| } | ||
| } | ||
| async function isExecutableFile(candidate) { | ||
| const metadata = await stat(candidate); | ||
| if (!metadata.isFile()) { | ||
| return false; | ||
| } | ||
| await access(candidate, constants.X_OK); | ||
| return true; | ||
| } |
+2
-1
| { | ||
| "name": "codex-usage-analyzer", | ||
| "version": "0.2.0", | ||
| "version": "0.3.0", | ||
| "description": "Read Codex account usage through the official app-server protocol.", | ||
@@ -37,2 +37,3 @@ "keywords": [ | ||
| "src/app-server-client.js", | ||
| "src/codex-executable.js", | ||
| "src/cli.js", | ||
@@ -39,0 +40,0 @@ "src/errors.js", |
+36
-14
@@ -9,8 +9,16 @@ # codex-usage-analyzer | ||
| `codex-usage-analyzer` starts your installed Codex CLI, calls `account/usage/read`, and emits a stable, identity-free contract. It does not scan local sessions or directly read authentication files, tokens, keychains, prompts, or responses. | ||
| `codex-usage-analyzer` starts a compatible Codex process from your installed CLI or macOS app, calls `account/usage/read`, and emits a stable, identity-free contract. It does not scan local sessions or directly read authentication files, tokens, keychains, prompts, or responses. | ||
| > **Documented upstream:** This CLI uses OpenAI Codex's documented | ||
| > [`account/usage/read`](https://github.com/openai/codex/blob/main/codex-rs/app-server/README.md) | ||
| > app-server method. | ||
| ## Support | ||
| Maintained with support from **OpenAI’s [Codex for Open Source](https://developers.openai.com/community/codex-for-oss)** program. | ||
| > _Support is provided to the maintainer and does not imply endorsement._ | ||
| ## Quick start | ||
| ```bash | ||
| npx --yes codex-usage-analyzer@latest | ||
| npx codex-usage-analyzer@latest | ||
| ``` | ||
@@ -38,3 +46,3 @@ | ||
| ```bash | ||
| npx --yes codex-usage-analyzer@latest --json | ||
| npx codex-usage-analyzer@latest --json | ||
| ``` | ||
@@ -67,4 +75,16 @@ | ||
| - **Stable integration:** consume a versioned JSON contract with allowlisted fields and explicit `null` semantics. | ||
| - **Small runtime:** use Node.js built-ins and the Codex CLI already installed on your machine; there are no runtime package dependencies. | ||
| - **Small runtime:** use Node.js built-ins and a Codex CLI or compatible macOS app already on your machine; there are no runtime package dependencies. | ||
| ## Codex lookup benchmark | ||
| Lower is faster. This snapshot measured one warm environment on 2026-07-13 with one warm-up and five timed runs. | ||
| | Tool | Median | Relative time | | ||
| |---|---:|---:| | ||
| | `codex-usage-analyzer@0.2.0` | 1.145s | 1.0x | | ||
| | `ccusage@20.0.17` | 3.306s | 2.9x | | ||
| | `tokscale@4.4.1` | 19.723s | 17.2x | | ||
| This compares Codex usage lookup only, not whole products or every workflow they support. The data sources also differ: `codex-usage-analyzer` reads account-level usage through app-server, while the comparison commands scan retained local Codex session history. See the [benchmark methodology, environment, command arrays, and limitations](docs/codex-lookup-benchmark.md). | ||
| ## Supported metrics | ||
@@ -86,6 +106,6 @@ | ||
| - Node.js 20 or newer | ||
| - A recent Codex CLI available as `codex` on `PATH` | ||
| - A recent Codex CLI available as `codex` on `PATH`, or a compatible macOS ChatGPT app or Codex app installed in a standard Applications location | ||
| - A ChatGPT-backed Codex sign-in that supports `account/usage/read` | ||
| API-key-only and Bedrock authentication do not provide this account usage method. Sign in through the installed Codex CLI before running the analyzer. The package delegates authentication to Codex and never asks you to paste a token. | ||
| API-key-only and Bedrock authentication do not provide this account usage method. Sign in through Codex before running the analyzer. The package delegates authentication to the selected Codex CLI or app and never asks you to paste a token. | ||
@@ -137,9 +157,11 @@ ## CLI reference | ||
| 1. Spawn `codex app-server` without a shell. | ||
| 2. Complete the stable app-server initialization handshake. | ||
| 3. Call `account/usage/read`. | ||
| 4. Allowlist and validate the supported fields. | ||
| 5. Stop the child process and return the normalized document. | ||
| 1. Prefer the `codex` executable available on `PATH`. | ||
| 2. On macOS, fall back to a compatible ChatGPT or Codex app bundle in a standard Applications location. | ||
| 3. Spawn the selected Codex executable with `app-server`, without a shell. | ||
| 4. Complete the stable app-server initialization handshake. | ||
| 5. Call `account/usage/read`. | ||
| 6. Allowlist and validate the supported fields. | ||
| 7. Stop the child process and return the normalized document. | ||
| The package has no direct credential reader and no private profile endpoint fallback. Authentication and service communication remain inside the installed Codex process. | ||
| The package has no direct credential reader and no private profile endpoint fallback. Authentication and service communication remain inside the selected Codex process. | ||
@@ -169,4 +191,4 @@ ## Downstream integrations | ||
| |---|---| | ||
| | `CODEX_NOT_FOUND` | Install or update Codex and confirm `codex` is on `PATH`. | | ||
| | `APP_SERVER_START_FAILED` or `APP_SERVER_EXITED` | Confirm the installed Codex CLI can start and that your environment permits child processes. | | ||
| | `CODEX_NOT_FOUND` | Install or update a Codex CLI on `PATH`, or install a compatible macOS ChatGPT or Codex app in a standard Applications location. | | ||
| | `APP_SERVER_START_FAILED` or `APP_SERVER_EXITED` | Confirm the selected Codex CLI or app can start and that your environment permits child processes. | | ||
| | `APP_SERVER_TIMEOUT` | Retry after checking connectivity; SDK callers can set `timeoutMs` up to 120000. | | ||
@@ -173,0 +195,0 @@ | `APP_SERVER_RPC_ERROR` | Update Codex and confirm a compatible ChatGPT-backed sign-in. | |
| import { spawn } from "node:child_process"; | ||
| import { createInterface } from "node:readline"; | ||
| import { resolveCodexExecutable } from "./codex-executable.js"; | ||
| import { CODEX_USAGE_ERROR_CODES, CodexUsageError } from "./errors.js"; | ||
@@ -15,6 +16,22 @@ | ||
| const spawnProcess = options.spawnProcess ?? spawn; | ||
| const resolveExecutable = options.resolveExecutable ?? resolveCodexExecutable; | ||
| let command; | ||
| let child; | ||
| try { | ||
| child = spawnProcess("codex", ["app-server"], { | ||
| command = await resolveExecutable(); | ||
| } catch { | ||
| return Promise.reject(new CodexUsageError( | ||
| CODEX_USAGE_ERROR_CODES.APP_SERVER_START_FAILED | ||
| )); | ||
| } | ||
| if (command === null) { | ||
| return Promise.reject(new CodexUsageError( | ||
| CODEX_USAGE_ERROR_CODES.CODEX_NOT_FOUND | ||
| )); | ||
| } | ||
| try { | ||
| child = spawnProcess(command, ["app-server"], { | ||
| stdio: ["pipe", "pipe", "pipe"], | ||
@@ -180,3 +197,3 @@ windowsHide: true | ||
| title: "Codex Usage Analyzer", | ||
| version: "0.2.0" | ||
| version: "0.3.0" | ||
| } | ||
@@ -183,0 +200,0 @@ } |
+1
-1
@@ -6,3 +6,3 @@ import { readAccountUsage } from "./account-usage.js"; | ||
| export const PACKAGE_NAME = "codex-usage-analyzer"; | ||
| export const PACKAGE_VERSION = "0.2.0"; | ||
| export const PACKAGE_VERSION = "0.3.0"; | ||
@@ -9,0 +9,0 @@ const USAGE = [ |
+1
-1
@@ -16,3 +16,3 @@ export const CODEX_USAGE_ERROR_CODES = Object.freeze({ | ||
| [CODEX_USAGE_ERROR_CODES.CODEX_NOT_FOUND]: | ||
| "Codex CLI was not found. Install or update Codex, then sign in with ChatGPT.", | ||
| "Codex CLI or compatible app was not found. Install or update Codex, then sign in with ChatGPT.", | ||
| [CODEX_USAGE_ERROR_CODES.APP_SERVER_START_FAILED]: | ||
@@ -19,0 +19,0 @@ "Codex app-server could not be started.", |
+1
-1
| export declare const PACKAGE_NAME: "codex-usage-analyzer"; | ||
| export declare const PACKAGE_VERSION: "0.2.0"; | ||
| export declare const PACKAGE_VERSION: "0.3.0"; | ||
| export declare const ACCOUNT_USAGE_CONTRACT_VERSION: 1; | ||
@@ -4,0 +4,0 @@ export declare const ACCOUNT_USAGE_SUMMARY_FIELDS: readonly [ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 2 instances
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
57034
20.09%18
12.5%720
11.28%218
11.22%1
-50%3
Infinity%