Skip to content

Latest commit

ย 

History

14 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Codex Meter

V2 (recommended)

V2 is the recommended architecture: one persistent per-user Agent on each monitored computer and one Node.js 24.15+ Server providing both the Dashboard and versioned API. Devices operate concurrently; there is no lease and no invented token quota. The Agent counts newly observed token_count.lastUsage events from installation onward and delivers a privacy-allowlisted numeric event through a crash-safe SQLite outbox.

Status: the V2 MVP implementation, migrations, Dashboard, installers, Docker deployment, and release automation are present. Before a production rollout, run the two-real-machine validation in your own Codex environment.

Fast server start (Docker)

cp compose.v2.example.yml compose.yml
mkdir -p releases
# Download manifest.json, SHA256SUMS, and all three native Agent artifacts
# from one v2-agent-* GitHub Release into ./releases/, then verify them:
(cd releases && sha256sum --check SHA256SUMS)
export CODEX_METER_ADMIN_PASSWORD='replace-with-a-long-random-password'
export CODEX_METER_SERVER_URL='https://meter.example.com'
export CODEX_METER_TRUSTED_PROXIES='127.0.0.1,::1' # exact reverse-proxy backend source IP(s)
docker compose up -d --build
curl -fsS http://127.0.0.1:3000/api/v1/health
curl -fsS http://127.0.0.1:3000/api/v1/agent/releases/manifest.json

The Compose example sets CODEX_METER_RELEASE_DIR=/releases and mounts host ./releases at /releases:ro; Dashboard one-line installers depend on those release assets. The complete first-release tagging, asset download, checksum, and endpoint-verification procedure is in V2 Server deployment.

Put an HTTPS reverse proxy in front of the loopback-bound port. Preserve Host, set exactly X-Forwarded-Proto: https, and list the proxy's exact backend source IP in CODEX_METER_TRUSTED_PROXIES (Docker bridge/NAT deployments may not appear as loopback). Plaintext enrollment and Agent sync are rejected with HTTP 426. The single service stores its WAL-mode SQLite database at /data/meter.db; back up that file using a SQLite-safe backup or a stopped-container copy. Open the Dashboard, create Groups, choose Add Device, and run the displayed one-line installer. Released Agents for Linux x64, Windows x64, and macOS arm64 are self-contained and require no global Node.js or npm on monitored computers.

V2 semantics and caveats

  • Account quota reporting is read-only, optional, and may be stale or unavailable. It is never estimated from token counts.
  • Group percentage is the share of locally measured token usage, not exact OpenAI quota attribution or billing.
  • Never upload Codex rollout JSONL or auth.json, including in support requests.
  • Recognized inherited fork/subagent/revert history is skipped. Ambiguous inherited files are baselined, so they undercount safely rather than risk double-counting.
  • SQLite is a single-service MVP. Do not run multiple Server replicas against /data/meter.db; no Redis, PostgreSQL, or queue is required.

V2 documentation: architecture, installation, deployment, validation, privacy, and troubleshooting.


V1 legacy wrapper documentation

Everything below describes V1. V1 remains tested for compatibility but is legacy: it requires launching Codex through a wrapper, assumes exactly three users, and uses leases/operator-defined quota behavior that V2 deliberately does not use.

Node.js 22 Tests License: MIT

Codex Meter is a small, dependency-free quota and usage meter for exactly three people who each run and authenticate their own local OpenAI Codex CLI.

It is not an OpenAI plugin, OAuth proxy, or official billing tool. It is a cooperative local wrapper plus a central Node.js server.

ํ•œ๊ตญ์–ด ์š”์•ฝ: ์„ธ ์‚ฌ๋žŒ์ด ๊ฐ์ž ๋ณธ์ธ์˜ Codex CLI์™€ ๊ณ„์ •์„ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๋ฉด์„œ, ์ค‘์•™ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ๋Ÿ‰์„ ์ง‘๊ณ„ํ•˜๊ณ  ํ•„์š”ํ•  ๋•Œ๋งŒ ๊ฐ™์€ ํ•œ๋„๋ฅผ ์ ์šฉํ•˜๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธยท์‘๋‹ตยท์†Œ์Šค ์ฝ”๋“œยทCodex ์ธ์ฆ์ •๋ณด๋Š” ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ง€ ์•Š๊ณ  ์ˆซ์ž ํ† ํฐ ์นด์šดํ„ฐ 5๊ฐœ๋งŒ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

ํ•œ๊ตญ์–ด ์•ˆ๋‚ด

์–ด๋–ค ๋„๊ตฌ์ธ๊ฐ€์š”?

Codex Meter๋Š” ์„ธ ๋ช…์ด ๊ฐ์ž ๊ฐœ์ธ ์ปดํ“จํ„ฐ์˜ ํ„ฐ๋ฏธ๋„์—์„œ Codex CLI๋ฅผ ์‹คํ–‰ํ•˜๋˜, ์‚ฌ์šฉ๋Ÿ‰์€ ์ค‘์•™์—์„œ ์‚ฌ์šฉ์ž๋ณ„๋กœ ์ง‘๊ณ„ํ•˜๊ณ  ์„ ํƒ์ ์œผ๋กœ ๊ฐ™์€ ์ฟผํ„ฐ๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

์‹ค์ œ ๊ตฌ์„ฑ์€ ๋‹ค์Œ ๋‘ ๋ถ€๋ถ„์œผ๋กœ ๋‚˜๋‰ฉ๋‹ˆ๋‹ค.

  1. ๊ฐœ์ธ ์ปดํ“จํ„ฐ์˜ ๋ž˜ํผ๊ฐ€ Codex CLI๋ฅผ ๋Œ€์‹  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ค‘์•™ Meter ์„œ๋ฒ„๊ฐ€ ์„ธ ์‚ฌ์šฉ์ž์˜ ์‚ฌ์šฉ๋Ÿ‰, ์ฟผํ„ฐ, ์‹คํ–‰ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
์‚ฌ์šฉ์ž A/B/C์˜ ๊ฐœ์ธ ํ„ฐ๋ฏธ๋„
  โ””โ”€ Codex Meter ๋ž˜ํผ
       โ”œโ”€ ์ค‘์•™ ์„œ๋ฒ„์— ์‹คํ–‰ ํ—ˆ๊ฐ€ ์š”์ฒญ
       โ”œโ”€ ๋ณธ์ธ ์ปดํ“จํ„ฐ์˜ Codex CLI ์‹คํ–‰
       โ”œโ”€ ๋กœ์ปฌ ์„ธ์…˜์—์„œ token_count๋งŒ ์ง‘๊ณ„
       โ””โ”€ ์„ธ์…˜์—์„œ ์ถ”์ถœํ•œ ์ˆซ์ž ์‚ฌ์šฉ๋Ÿ‰ 5๊ฐœ๋งŒ ์ค‘์•™ ์„œ๋ฒ„์— ๋ณด๊ณ 

์ค‘์•™ Meter ์„œ๋ฒ„
  โ”œโ”€ ์‚ฌ์šฉ์ž๋ณ„ ๋ˆ„์  ์‚ฌ์šฉ๋Ÿ‰
  โ”œโ”€ ๊ด€์ฐฐ ๋ชจ๋“œ ๋˜๋Š” ์„ธ ๋ช…์—๊ฒŒ ๋™์ผํ•œ ์ฟผํ„ฐ ์ ์šฉ
  โ”œโ”€ ์‚ฌ์šฉ์ž๋‹น ๋™์‹œ ์‹คํ–‰ 1๊ฐœ ์ œํ•œ
  โ”œโ”€ ๊ฐ•์ œ ๋ชจ๋“œ์˜ ์ฟผํ„ฐ ์ดˆ๊ณผยท๋ชจ๋“  ๋ชจ๋“œ์˜ ๋น„ํ™œ์„ฑ ์‚ฌ์šฉ์ž ์ฐจ๋‹จ
  โ””โ”€ ์‚ฌ์šฉ์ž์šฉ ์กฐํšŒ API์™€ ๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ

์ด ํ”„๋กœ์ ํŠธ๋Š” OpenAI ๊ณต์‹ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด๋‚˜ ๊ณต์‹ ์‚ฌ์šฉ๋Ÿ‰ยท๊ณผ๊ธˆ ๋„๊ตฌ๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค. Codex ์•ž์—์„œ ์‹คํ–‰๋˜๋Š” ํ˜‘๋ ฅํ˜• ๋กœ์ปฌ ๋ž˜ํผ์™€ ๋ณ„๋„์˜ ์ค‘์•™ ๊ด€๋ฆฌ ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค.

์‚ฌ์šฉ๋Ÿ‰์€ ์ž๋™์œผ๋กœ ์ˆ˜์ง‘๋ฉ๋‹ˆ๋‹ค

์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ ์ˆ˜์น˜๋‚˜ ์‚ฌ์šฉ ๋‚ด์—ญ์„ ์ง์ ‘ ์ž…๋ ฅํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์—๊ฒŒ ํ•„์š”ํ•œ ์ˆ˜๋™ ์„ค์ •์€ ์ตœ์ดˆ ํ•œ ๋ฒˆ client.json์— ์ค‘์•™ ์„œ๋ฒ„ ์ฃผ์†Œ์™€ ๋ณธ์ธ์˜ Meter ํ† ํฐ์„ ์ €์žฅํ•˜๋Š” ๊ฒƒ๋ฟ์ž…๋‹ˆ๋‹ค.

์ดํ›„ Codex Meter ๋ž˜ํผ๋ฅผ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ๋‹ค์Œ ๊ณผ์ •์ด ์ž๋™์œผ๋กœ ์ง„ํ–‰๋ฉ๋‹ˆ๋‹ค.

  1. ์ค‘์•™ ์„œ๋ฒ„์— ์‹คํ–‰ ๊ฐ€๋Šฅ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ  ์‚ฌ์šฉ์ž lease๋ฅผ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  2. ๋ž˜ํผ๊ฐ€ ๊ฐ™์€ ํ„ฐ๋ฏธ๋„์—์„œ ์‚ฌ์šฉ์ž์˜ ๋กœ์ปฌ Codex CLI๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
  3. ๊ธฐ๋ณธ 5์ดˆ ๊ฐ„๊ฒฉ์œผ๋กœ ๋กœ์ปฌ Codex ์„ธ์…˜ JSONL์„ ๋‹ค์‹œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  4. ์ƒˆ๋กœ ๋ฐœ์ƒํ•œ token_count์˜ ๋‹ค์„ฏ ์นด์šดํ„ฐ ์ฐจ์ด๋ฅผ ์ž๋™ ๊ณ„์‚ฐํ•ด ์ค‘์•™ ์„œ๋ฒ„์— ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  5. ๊ฐ•์ œ ๋ชจ๋“œ์—์„œ๋Š” ์ฟผํ„ฐ ์ดˆ๊ณผ ์‘๋‹ต์„ ๋ฐ›์œผ๋ฉด ์‹คํ–‰ ์ค‘์ธ Codex๋ฅผ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ด€์ฐฐ ๋ชจ๋“œ์—์„œ๋Š” ์‚ฌ์šฉ๋Ÿ‰๋งŒ ๊ธฐ๋กํ•ฉ๋‹ˆ๋‹ค.
  6. Codex๊ฐ€ ๋๋‚˜๋ฉด ์ตœ์ข… ์‚ฌ์šฉ๋Ÿ‰๊ณผ ์‹คํ–‰ ์ข…๋ฃŒ๋ฅผ ์ž๋™ ๋ณด๊ณ ํ•ฉ๋‹ˆ๋‹ค.
  7. ์ผ์‹œ์ ์ธ ์ „์†ก ์‹คํŒจ๋Š” ๋กœ์ปฌ spool์— ๋ณด๊ด€ํ–ˆ๋‹ค๊ฐ€ ๋‹ค์Œ ์‹คํ–‰ ๋•Œ ์ž๋™ ์žฌ์ „์†กํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ /v1/usage ํ˜ธ์ถœ์ด๋‚˜ ๊ด€๋ฆฌ์ž ๋Œ€์‹œ๋ณด๋“œ๋Š” ์‚ฌ์šฉ๋Ÿ‰์„ ์ž…๋ ฅํ•˜๋Š” ๊ธฐ๋Šฅ์ด ์•„๋‹ˆ๋ผ ์ด๋ฏธ ์ž๋™ ์ˆ˜์ง‘๋œ ๊ฒฐ๊ณผ๋ฅผ ์กฐํšŒํ•˜๋Š” ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค. ๋‹ค๋งŒ ๊ณ„๋Ÿ‰์ด ์ ์šฉ๋˜๋ ค๋ฉด ์‚ฌ์šฉ์ž๊ฐ€ ์›๋ณธ codex ๋Œ€์‹  Codex Meter ๋ž˜ํผ๋ฅผ ํ†ตํ•ด ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๊ฐœ์ธ์ •๋ณด์™€ ์ธ์ฆ์ •๋ณด

๊ฐ ์‚ฌ์šฉ์ž๋Š” ์ž์‹ ์˜ ์ปดํ“จํ„ฐ์— Codex CLI๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์ง์ ‘ ์ธ์ฆํ•ฉ๋‹ˆ๋‹ค. Codex Meter๋Š” Codex OAuth ํ† ํฐ๊ณผ auth.json์„ ์ฝ๊ฑฐ๋‚˜ ๋ณต์‚ฌํ•˜๊ฑฐ๋‚˜ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

๋ž˜ํผ๋Š” ๋กœ์ปฌ Codex ์„ธ์…˜ JSONL์„ ํ•œ ์ค„์”ฉ ์ฝ๊ณ  ๊ฐ ๋ ˆ์ฝ”๋“œ๋ฅผ ํŒŒ์‹ฑํ•ด token_count ์ด๋ฒคํŠธ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ํ”„๋กฌํ”„ํŠธยท์‘๋‹ตยท๋„๊ตฌ ์‹คํ–‰ยท์†Œ์Šค ๋‚ด์šฉ ๋“ฑ token_count๊ฐ€ ์•„๋‹Œ ๋ ˆ์ฝ”๋“œ๋Š” ๋กœ์ปฌ์—์„œ ์ฆ‰์‹œ ๋ฒ„๋ฆฌ๋ฉฐ, ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์ค‘์•™ ์„œ๋ฒ„๋กœ ์ „์†กํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์„ธ์…˜์—์„œ ์ถ”์ถœํ•ด ์ „์†กํ•˜๋Š” ์‚ฌ์šฉ๋Ÿ‰ ์ •๋ณด๋Š” ๋‹ค์Œ ์ˆซ์ž 5๊ฐœ๋ฟ์ž…๋‹ˆ๋‹ค.

  • input_tokens
  • cached_input_tokens
  • output_tokens
  • reasoning_output_tokens
  • total_tokens

Meter ์„œ๋ฒ„์šฉ ์‚ฌ์šฉ์ž ํ† ํฐ์€ Codex ์ธ์ฆ์ •๋ณด์™€ ์™„์ „ํžˆ ๋ณ„๊ฐœ์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ์ƒํƒœ์—๋Š” ํ‰๋ฌธ ํ† ํฐ ๋Œ€์‹  SHA-256 ํ•ด์‹œ๋งŒ ์ €์žฅ๋ฉ๋‹ˆ๋‹ค.

ํ•„์š”ํ•œ ํ™˜๊ฒฝ

  • ์ค‘์•™ ์„œ๋ฒ„์™€ ๊ฐ ์‚ฌ์šฉ์ž ์ปดํ“จํ„ฐ์— Node.js 22 ์ด์ƒ
  • ๊ฐ ์‚ฌ์šฉ์ž ์ปดํ“จํ„ฐ์— ๊ณต์‹ Codex CLI ์„ค์น˜ ๋ฐ ๋กœ์ปฌ ์ธ์ฆ
  • ์›๊ฒฉ ์—ฐ๊ฒฐ ์‹œ HTTPS ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ, VPN ๋˜๋Š” SSH ํ„ฐ๋„
  • ์ •ํ™•ํžˆ ์„ธ ๊ฐœ์˜ ๊ณ ์œ ํ•œ Meter ์‚ฌ์šฉ์ž ID

์™ธ๋ถ€ ๋Ÿฐํƒ€์ž„ ํŒจํ‚ค์ง€๊ฐ€ ์—†์œผ๋ฏ€๋กœ npm install์€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

1. ์ค‘์•™ ์„œ๋ฒ„ ์„ค์น˜

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
node --version
npm test

์„ธ ๋ช…์˜ ์‚ฌ์šฉ๋Ÿ‰์„ ๋Œ€๋žต ๋น„๊ตํ•˜๋Š” ๋ชฉ์ ์ด๋ผ๋ฉด ๊ด€์ฐฐ ์ „์šฉ ๋ชจ๋“œ๋กœ ์ตœ์ดˆ ํ•œ ๋ฒˆ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ์ด ๋ชจ๋“œ๋Š” ํ† ํฐ์„ ๊ธฐ๋กํ•˜์ง€๋งŒ ์ž„์˜์˜ ํ† ํฐ ํ•œ๋„๋กœ Codex๋ฅผ ์ค‘์ง€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

export CODEX_METER_STATE="$HOME/.codex-meter-server/state.json"
umask 077
node bin/admin.js init \
  --users=alice,bob,carol \
  --observe-only \
  --reset-ms=2592000000 \
  --max-leases=1 \
  --lease-ttl-ms=120000 \
  > meter-tokens-once.json

์˜ˆ์‹œ๋Š” 30์ผ๋งˆ๋‹ค ์ธก์ • ์นด์šดํ„ฐ๋งŒ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ•์ œ ํ† ํฐ ์ •์ฑ…์ด ํ•„์š”ํ•  ๋•Œ๋งŒ --observe-only ๋Œ€์‹  --quota=์›ํ•˜๋Š”_์–‘์˜_์ •์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”. ์ด ๊ฐ’์€ OpenAI ํ”Œ๋žœ ํ•œ๋„๊ฐ€ ์•„๋‹ˆ๋ผ ์šด์˜์ž๊ฐ€ ์ •ํ•˜๋Š” ๋กœ์ปฌ ์ •์ฑ…์ž…๋‹ˆ๋‹ค.

meter-tokens-once.json์—๋Š” ๊ด€๋ฆฌ์ž ํ† ํฐ 1๊ฐœ์™€ ์‚ฌ์šฉ์ž ํ† ํฐ 3๊ฐœ๊ฐ€ ์ตœ์ดˆ ํ•œ ๋ฒˆ๋งŒ ํ‰๋ฌธ์œผ๋กœ ์ถœ๋ ฅ๋ฉ๋‹ˆ๋‹ค. ๊ฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณธ์ธ์˜ ํ† ํฐ๋งŒ ์•ˆ์ „ํ•œ ๋ฐฉ๋ฒ•์œผ๋กœ ์ „๋‹ฌํ•œ ๋’ค ํŒŒ์ผ์„ ์•ˆ์ „ํ•˜๊ฒŒ ์‚ญ์ œํ•˜์„ธ์š”. ํ† ํฐ์„ ์žƒ์–ด๋ฒ„๋ฆฌ๋ฉด ํ•ด์‹œ์—์„œ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†์œผ๋ฏ€๋กœ ์ƒˆ ์ƒํƒœ๋ฅผ ์ดˆ๊ธฐํ™”ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์„œ๋ฒ„๋ฅผ ๋กœ์ปฌ ์ฃผ์†Œ์—์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

CODEX_METER_HOST=127.0.0.1 \
CODEX_METER_PORT=8787 \
node bin/server.js

์ƒํƒœ ํ™•์ธ:

curl http://127.0.0.1:8787/health

ํ‰๋ฌธ HTTP ์„œ๋ฒ„๋ฅผ ์‹ ๋ขฐํ•  ์ˆ˜ ์—†๋Š” ๋„คํŠธ์›Œํฌ์— ์ง์ ‘ ๊ณต๊ฐœํ•˜์ง€ ๋งˆ์„ธ์š”. ์›๊ฒฉ ์‚ฌ์šฉ์ž๋Š” HTTPS, VPN ๋˜๋Š” SSH ํ„ฐ๋„์„ ํ†ตํ•ด ์ ‘์†ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

2. macOSยทLinux ์‚ฌ์šฉ์ž ์„ค์ •

๊ฐ ์‚ฌ์šฉ์ž ์ปดํ“จํ„ฐ์—์„œ ์ €์žฅ์†Œ๋ฅผ ๋ฐ›๊ณ  ๊ฐœ์ธ ์„ค์ • ํŒŒ์ผ์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค.

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
mkdir -p "$HOME/.codex-meter"
chmod 700 "$HOME/.codex-meter"
cat > "$HOME/.codex-meter/client.json" <<'JSON'
{
  "serverUrl": "https://meter.example.internal/",
  "meterToken": "๋ณธ์ธ์—๊ฒŒ_๋ฐœ๊ธ‰๋œ_METER_TOKEN",
  "pollIntervalMs": 5000
}
JSON
chmod 600 "$HOME/.codex-meter/client.json"
chmod +x clients/unix/codex-meter
mkdir -p "$HOME/.local/bin"
ln -s "$(pwd)/clients/unix/codex-meter" "$HOME/.local/bin/codex-meter"

์ด์ œ ์›๋ž˜ codex๋ฅผ ์‹คํ–‰ํ•˜๋˜ ์ž๋ฆฌ์— ๋ž˜ํผ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

/path/to/codex-meter/clients/unix/codex-meter
/path/to/codex-meter/clients/unix/codex-meter --model MODEL_NAME "์ž‘์—… ๋‚ด์šฉ"
codex-meter

3. Windows PowerShell ์‚ฌ์šฉ์ž ์„ค์ •

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
New-Item -ItemType Directory -Force "$HOME\.codex-meter" | Out-Null
@'
{
  "serverUrl": "https://meter.example.internal/",
  "meterToken": "๋ณธ์ธ์—๊ฒŒ_๋ฐœ๊ธ‰๋œ_METER_TOKEN",
  "pollIntervalMs": 5000
}
'@ | Set-Content -Encoding utf8 "$HOME\.codex-meter\client.json"

PowerShell ๋ž˜ํผ๋กœ Codex๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

powershell -NoProfile -File .\clients\windows\codex-meter.ps1
powershell -NoProfile -File .\clients\windows\codex-meter.ps1 --model MODEL_NAME "์ž‘์—… ๋‚ด์šฉ"

PowerShell ๋ž˜ํผ์™€ Node.js ๋ž˜ํผ๋Š” ์ธ์ˆ˜๋ฅผ ๋ฐฐ์—ด๋กœ ์ „๋‹ฌํ•˜๋ฉฐ cmd.exe๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ ๋„ค์ดํ‹ฐ๋ธŒ codex.exe๋ฅผ ์šฐ์„  ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ‘œ์ค€ npm ์„ค์น˜์˜ codex.cmd๋งŒ ์žˆ์œผ๋ฉด ์ธ์ ‘ํ•œ ๊ณต์‹ @openai/codex/bin/codex.js๋ฅผ Node.js๋กœ ์ง์ ‘ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์„ค์น˜ ์œ„์น˜๊ฐ€ ํŠน์ˆ˜ํ•˜๋ฉด CODEX_METER_CODEX์— .cmd๊ฐ€ ์•„๋‹Œ ๋„ค์ดํ‹ฐ๋ธŒ codex.exe ์ „์ฒด ๊ฒฝ๋กœ๋ฅผ ์ง€์ •ํ•˜์„ธ์š”.

4. ์‚ฌ์šฉ๋Ÿ‰ ํ™•์ธ๊ณผ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ

๋ธŒ๋ผ์šฐ์ €์—์„œ ์„œ๋ฒ„ ๋ฃจํŠธ ์ฃผ์†Œ๋ฅผ ์—ด๊ณ  ์ž์‹ ์˜ Meter ํ† ํฐ์„ ์ž…๋ ฅํ•˜๋ฉด ๋ณธ์ธ ์‚ฌ์šฉ๋Ÿ‰๋งŒ ์กฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ํ† ํฐ์€ OpenAI API ํ‚ค๊ฐ€ ์•„๋‹ˆ๋ผ ์„œ๋ฒ„ ์ดˆ๊ธฐํ™” ๋•Œ ๋ณ„๋„๋กœ ๋ฐœ๊ธ‰๋˜๋Š” Meter ์ „์šฉ ์ž๊ฒฉ ์ฆ๋ช…์ด๋ฉฐ, ์›น ํ™”๋ฉด์€ ํ† ํฐ์„ URLยท์ฟ ํ‚คยท๋ธŒ๋ผ์šฐ์ € ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

curl --oauth2-bearer USER_METER_TOKEN \
  https://meter.example.internal/v1/usage

๊ด€๋ฆฌ์ž๋Š” ๊ฐ™์€ ๋ฃจํŠธ ํ™”๋ฉด์˜ ๊ด€๋ฆฌ์ž ๋กœ๊ทธ์ธ์— Admin Meter ํ† ํฐ์„ ์ž…๋ ฅํ•ด ์ „์ฒด ๋Œ€์‹œ๋ณด๋“œ๋ฅผ ์กฐํšŒํ•˜๊ฑฐ๋‚˜, ์ธ์ฆ๋œ JSON API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

curl --oauth2-bearer ADMIN_METER_TOKEN \
  https://meter.example.internal/admin.json

์‚ฌ์šฉ์ž๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ•˜๊ฑฐ๋‚˜ ๋‹ค์‹œ ํ™œ์„ฑํ™”ํ•  ๋•Œ๋Š” ๊ฒฝ์Ÿ ์ƒํƒœ๋ฅผ ํ”ผํ•˜๊ธฐ ์œ„ํ•ด ๋จผ์ € ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ•œ ๋’ค ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

export CODEX_METER_STATE="$HOME/.codex-meter-server/state.json"
node bin/admin.js set-enabled alice false
node bin/admin.js set-enabled alice true

๋„คํŠธ์›Œํฌ ์žฅ์• ์™€ ์ œํ•œ ์‚ฌํ•ญ

  • ์‹คํ–‰ ์ „ ์ค‘์•™ ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•  ์ˆ˜ ์—†์œผ๋ฉด Codex ์‹คํ–‰์„ ์‹œ์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ •์ƒ์ ์œผ๋กœ ์‹œ์ž‘ํ•œ ๋’ค ์ผ์‹œ์ ์ธ ๋„คํŠธ์›Œํฌ ์žฅ์• ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋กœ์ปฌ Codex๋Š” ๊ณ„์† ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
  • ์ „์†กํ•˜์ง€ ๋ชปํ•œ ์ˆซ์ž ์‚ฌ์šฉ๋Ÿ‰์€ ๊ฐœ์ธ ์ปดํ“จํ„ฐ์˜ ๋น„๊ณต๊ฐœ spool ํŒŒ์ผ์— ์ €์žฅํ–ˆ๋‹ค๊ฐ€ ๋‹ค์Œ ์‹คํ–‰ ๋•Œ ๋‹ค์‹œ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
  • ์ธ์ฆ ์‹คํŒจ๋‚˜ ์ž˜๋ชป๋œ ์š”์ฒญ ๊ฐ™์€ ์˜๊ตฌ์ ์ธ HTTP 4xx ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ž˜ํผ๊ฐ€ Codex ์‹คํ–‰์„ ์ค‘์ง€ํ•ฉ๋‹ˆ๋‹ค.
  • ์˜จ๋ผ์ธ ์ƒํƒœ์—์„œ๋„ ํŒŒ์ผ ํ™•์ธ ์ฃผ๊ธฐ๋งŒํผ ์ฟผํ„ฐ๋ฅผ ์กฐ๊ธˆ ์ดˆ๊ณผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์„œ๋ฒ„๋‚˜ ๋„คํŠธ์›Œํฌ๊ฐ€ ๋Š๊ธด ๋™์•ˆ์—๋Š” ์ค‘์•™ ์ฐจ๋‹จ์„ ์ ์šฉํ•  ์ˆ˜ ์—†์–ด ์ดˆ๊ณผ๋Ÿ‰์ด ์ปค์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์›๋ณธ codex๋ฅผ ์ง์ ‘ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ ๋กœ์ปฌ ํ”„๋กœ๊ทธ๋žจ์„ ์ˆ˜์ •ํ•˜๋ฉด ๊ณ„๋Ÿ‰์„ ์šฐํšŒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด ๋„๊ตฌ๋Š” ์„ธ ์‚ฌ์šฉ์ž๊ฐ€ ๋ž˜ํผ ์‚ฌ์šฉ์— ๋™์˜ํ•˜๋Š” ํ˜‘๋ ฅํ˜• ๊ณ„๋Ÿ‰ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค.
  • Codex ์„ธ์…˜ ์‚ญ์ œยท์†์ƒ, ๊ฐ‘์ž‘์Šค๋Ÿฌ์šด ์ „์› ์ฐจ๋‹จ, ํ–ฅํ›„ ์„ธ์…˜ ํ˜•์‹ ๋ณ€๊ฒฝ์€ ์ธก์ • ์ •ํ™•๋„๋ฅผ ๋‚ฎ์ถœ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ‘œ์‹œ๋˜๋Š” ์‚ฌ์šฉ๋Ÿ‰์€ OpenAI์˜ ๊ณต์‹ Pro/Codex ์ž”์—ฌ๋Ÿ‰์ด๋‚˜ ์ฒญ๊ตฌ ์‚ฌ์šฉ๋Ÿ‰์ด ์•„๋‹™๋‹ˆ๋‹ค.

English documentation

What it does

Each user's computer                         Central meter server
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”              โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ own Codex CLI + own OAuth   โ”‚              โ”‚ three meter identities   โ”‚
โ”‚             โ”‚               โ”‚              โ”‚ equal shared policy      โ”‚
โ”‚ Codex Meter wrapper         โ”‚โ”€โ”€ 5 countersโ†’โ”‚ quota + active lease     โ”‚
โ”‚ reads local token_count onlyโ”‚โ† allow/stop โ”€โ”‚ usage API + admin view   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜              โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  • Requires exactly three unique meter users.
  • Supports observe-only measurement or one configurable quota applied equally to all three users; both modes use the same reset period.
  • Allows one active wrapper per user.
  • Denies a new run when the user is disabled or already active; enforcement mode also denies users who are out of quota.
  • In enforcement mode, stops a connected run after its measured usage crosses the quota.
  • Expires stale leases after a crashed or disconnected client.
  • Spools numeric updates during transient network failures and replays them idempotently.
  • Gives each user an authenticated own-usage endpoint and gives the administrator aggregate JSON/HTML views.
  • Uses only Node.js 22 built-ins; no npm install is required.

Privacy boundary

Codex Meter locally streams Codex session JSONL line by line only to find token_count records. It discards every other record and never retains or transmits prompts, responses, tool calls, source content, or other session content.

The only usage values accepted by the server are these five nonnegative safe-integer counters:

  • input_tokens
  • cached_input_tokens
  • output_tokens
  • reasoning_output_tokens
  • total_tokens

Codex Meter does not read, copy, proxy, distribute, or store Codex OAuth credentials or auth.json. Each person installs and authenticates Codex locally. Separate random meter tokens authorize only this meter, and the server stores their SHA-256 hashes rather than plaintext tokens.

Requirements

Server

  • Linux, macOS, or Windows with Node.js 22+
  • A trusted HTTPS reverse proxy, VPN, or SSH tunnel if clients connect remotely

Each client

  • Node.js 22+
  • Official Codex CLI installed and authenticated locally
  • A distinct meter token issued by the server administrator

Quick start: server

Clone the repository on the server:

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
node --version
npm test

For rough relative measurement, initialize the state once in observe-only mode. This records tokens but never stops Codex at an arbitrary token threshold. Exactly three unique user IDs are mandatory:

export CODEX_METER_STATE="$HOME/.codex-meter-server/state.json"
umask 077
node bin/admin.js init \
  --users=alice,bob,carol \
  --observe-only \
  --reset-ms=2592000000 \
  --max-leases=1 \
  --lease-ttl-ms=120000 \
  > meter-tokens-once.json

The example resets only the measurement counters every 30 days. If you intentionally want local enforcement, replace --observe-only with --quota=YOUR_POSITIVE_INTEGER. That value is an operator-defined local policy, not an OpenAI plan limit.

meter-tokens-once.json contains the admin token and three user tokens in plaintext only once. Give each person only their own token through a secure channel, then securely remove the file. If you lose the tokens, initialize a new state instead of trying to recover them from the hash-only state file.

Start the server on localhost:

CODEX_METER_HOST=127.0.0.1 \
CODEX_METER_PORT=8787 \
node bin/server.js

Health check:

curl http://127.0.0.1:8787/health

Do not expose this plain HTTP listener directly to an untrusted network. Use HTTPS through a trusted reverse proxy or access it only through a VPN/SSH tunnel. Bearer tokens are credentials and plain HTTP exposes them in transit.

Client setup

Clone or copy this repository to each user's computer. Never place Codex auth.json inside this project.

Each user creates a private client.json containing the reachable server URL and only that user's meter token.

macOS / Linux

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
mkdir -p "$HOME/.codex-meter"
chmod 700 "$HOME/.codex-meter"
cat > "$HOME/.codex-meter/client.json" <<'JSON'
{
  "serverUrl": "https://meter.example.internal/",
  "meterToken": "PASTE_ONLY_THIS_USERS_METER_TOKEN",
  "pollIntervalMs": 5000
}
JSON
chmod 600 "$HOME/.codex-meter/client.json"
chmod +x clients/unix/codex-meter
mkdir -p "$HOME/.local/bin"
ln -s "$(pwd)/clients/unix/codex-meter" "$HOME/.local/bin/codex-meter"

Use the wrapper anywhere you would normally use codex:

/path/to/codex-meter/clients/unix/codex-meter
/path/to/codex-meter/clients/unix/codex-meter --model MODEL_NAME "your prompt"
codex-meter

Optional shell alias:

alias codex-meter="/path/to/codex-meter/clients/unix/codex-meter"

Windows 10/11 PowerShell

git clone https://github.com/SANGDNOG/codex-meter.git
cd codex-meter
New-Item -ItemType Directory -Force "$HOME\.codex-meter" | Out-Null
@'
{
  "serverUrl": "https://meter.example.internal/",
  "meterToken": "PASTE_ONLY_THIS_USERS_METER_TOKEN",
  "pollIntervalMs": 5000
}
'@ | Set-Content -Encoding utf8 "$HOME\.codex-meter\client.json"

Run Codex through the PowerShell wrapper:

powershell -NoProfile -File .\clients\windows\codex-meter.ps1
powershell -NoProfile -File .\clients\windows\codex-meter.ps1 --model MODEL_NAME "your prompt"

The wrapper forwards arguments as an array and never invokes cmd.exe. It prefers a native codex.exe; for the standard npm codex.cmd shim, it executes the adjacent official @openai/codex/bin/codex.js with Node. For a nonstandard installation, set CODEX_METER_CODEX to the full native codex.exe path, not a .cmd file.

Configuration

Server environment variables

Variable Default Purpose
CODEX_METER_STATE ~/.codex-meter-server/state.json Server state file
CODEX_METER_HOST 127.0.0.1 Listen address
CODEX_METER_PORT 8787 Listen port

Client settings

Setting / variable Default Purpose
serverUrl required Reachable meter server base URL
meterToken required This user's separate meter credential
pollIntervalMs 5000 Polling interval, from 1000 to 60000 ms
CODEX_METER_HOME ~/.codex-meter Client config, lock, and spool directory
CODEX_HOME ~/.codex Local Codex home; sessions are read below sessions/
CODEX_METER_CODEX auto-detected Explicit Codex executable path

Usage and administration

A user can open the server root URL and enter their Meter token to see only their own usage. This is a meter-specific credential issued during server initialization, not an OpenAI API key. The page does not store it in a URL, cookie, local storage, or session storage. The authenticated JSON API remains available:

curl --oauth2-bearer USER_METER_TOKEN \
  https://meter.example.internal/v1/usage

The administrator enters the Admin Meter token in the root page to open the aggregate dashboard, or reads the authenticated JSON API:

curl --oauth2-bearer ADMIN_METER_TOKEN \
  https://meter.example.internal/admin.json

To disable or re-enable a user, stop the server first so there are no competing state writers:

export CODEX_METER_STATE="$HOME/.codex-meter-server/state.json"
node bin/admin.js set-enabled alice false
node bin/admin.js set-enabled alice true

Exit codes

Code Meaning
0 or Codex code Normal Codex exit
69 Meter unavailable at startup
73 Another local wrapper holds the client lock
74 Local Codex sessions could not be scanned
75 Quota/disable stop or permanent HTTP 4xx meter failure
77 Start denied because of quota, disable, or active lease
78 Missing or invalid local configuration
127 Codex executable could not be started

Failure behavior

  • After a successful start, transient network/server failures fail open so local Codex can continue.
  • Numeric absolute updates are written to a private local spool and replayed on the next run.
  • Duplicate replay does not double-count because updates use absolute per-lease high-water values.
  • HTTP 4xx authentication/protocol failures stop the wrapped Codex process.
  • Connected quota enforcement may overshoot by roughly one polling interval.
  • During an outage, central disable/quota enforcement is unavailable and overshoot can be unbounded until connectivity and replay return.

Limitations

  • This is cooperative metering. A user can bypass it by launching codex directly or altering local software.
  • It is not official OpenAI/Codex quota or billing accounting and may differ from provider totals.
  • One meter user may run only one wrapper at a time; overlapping scans could double-count one user's session directory.
  • Local session deletion/truncation, abrupt power loss, filesystem failure, multiple independent client homes, or future Codex session-format changes can reduce accuracy.
  • SIGKILL or sudden power loss can happen before the final local scan. Stale leases prevent permanent lockout but cannot recover events that were never observed.

Tests

npm test

The deterministic node:test suite covers local parser filtering, strict request schemas, delta calculation, idempotent updates/replay, authentication failures, exhausted starts, quota crossing, stale leases, hash-only token storage, local locking, literal shell-free arguments, and the real wrapper/server stop path with a fake Codex process.

Project layout

bin/                  server, admin CLI, and Codex wrapper entry points
clients/unix/         macOS/Linux launcher
clients/windows/      PowerShell launcher
lib/                  server, store, client, command, and usage modules
test/                 deterministic Node.js tests and fixtures

License and disclaimer

MIT License. See LICENSE.

This is an independent community project and is not affiliated with, endorsed by, or supported by OpenAI. โ€œOpenAIโ€ and โ€œCodexโ€ are trademarks of their respective owners.

About

Privacy-preserving quota and usage meter for three local Codex CLI users

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages