Skip to content

Architecture

eddy.wijaya edited this page Apr 3, 2026 · 1 revision

πŸ—οΈ Architecture

Component-Service architecture with clean data flow from auth to display.


πŸ“ Project Structure

opencode-codex-quota/
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.json
β”œβ”€β”€ biome.json
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ index.ts              # Plugin entry point, tool definition
β”‚   β”œβ”€β”€ types.ts              # All TypeScript interfaces + DisplayMode
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ auth-reader.ts    # Read auth.json, parse JWT, extract account_id + email
β”‚   β”‚   └── api-client.ts     # Call wham/usage endpoint, return typed QuotaResponse
β”‚   └── formatter/
β”‚       β”œβ”€β”€ markdown.ts       # Transform QuotaResponse β†’ raw Markdown string
β”‚       └── errors.ts         # Error codes β†’ Markdown error messages
└── tests/
    β”œβ”€β”€ auth-reader.test.ts
    β”œβ”€β”€ api-client.test.ts
    β”œβ”€β”€ markdown.test.ts
    β”œβ”€β”€ errors.test.ts
    β”œβ”€β”€ index.test.ts
    └── integration.test.ts

πŸ”„ Data Flow

User/Agent β†’ /codex_quota [mode?]
  β”‚
  β”œβ”€ Slash command path:
  β”‚   └─ command.execute.before hook
  β”‚       └─ Injects wrapper instruction with mode argument
  β”‚           └─ LLM calls codex_quota tool
  β”‚
  └─ Direct tool path:
      └─ Plugin tool execute()
          β”‚
          β”œβ”€ 1. AuthReader.read()
          β”‚     └─ Read auth.json
          β”‚         └─ Find OAuth entry (codex β†’ openai β†’ chatgpt β†’ opencode)
          β”‚             └─ Parse JWT β†’ { token, accountId, email }
          β”‚
          β”œβ”€ 2. ApiClient.query(token, accountId)
          β”‚     └─ GET chatgpt.com/backend-api/wham/usage
          β”‚         └─ Validate response β†’ QuotaResponse
          β”‚
          └─ 3. Formatter.format(response, mode)
                └─ Build Markdown sections
                    └─ Conditional includes
                        └─ Raw Markdown string β†’ OpenCode TUI (Glamour)

🧩 Components

AuthReader (src/services/auth-reader.ts)

Reads ~/.local/share/opencode/auth.json, finds OAuth credentials, parses JWT.

Function Purpose
readAuth(path?) Read auth.json, find valid provider, return AuthResult
parseJwt(token) Decode JWT payload, extract chatgpt_account_id + email

Provider key priority: codex β†’ openai β†’ chatgpt β†’ opencode (first-match-wins)

ApiClient (src/services/api-client.ts)

Calls the ChatGPT quota API with OAuth token.

Function Purpose
queryQuota(token, accountId) GET wham/usage, validate schema, return ApiResult

Timeout: 10 seconds (AbortController)

Formatter (src/formatter/markdown.ts)

Transforms typed API response into display Markdown.

Function Purpose
formatQuota(response, mode) Main entry β€” routes to compact or full formatter
buildProgressBar(percent) 12-char progress bar with clamping
formatResetClock(timestamp) Local clock string (same-day or with date)

Error Formatter (src/formatter/errors.ts)

Maps error codes to user-friendly Markdown messages.

Function Purpose
formatError(code, partialData?) E1–E11 β†’ formatted Markdown error

πŸ”Œ Plugin API Contract

Entry Point (src/index.ts)

export default {
  id: "opencode-codex-quota",
  server: codexQuotaServer,
} satisfies PluginModule

Hooks

Hook Purpose
config Registers /codex_quota command with template: "" and subtask: true
command.execute.before Reads input.arguments, injects wrapper instruction with mode
tool.codex_quota The actual quota tool β€” accepts optional mode arg

Slash Command Flow

User types: /codex_quota compact
  ↓
command.execute.before hook fires
  ↓
Reads arguments β†’ "compact"
  ↓
Resolves mode β†’ "compact"
  ↓
Injects instruction: "Call the codex_quota tool now with mode=compact..."
  ↓
LLM receives instruction, calls tool
  ↓
Tool returns Markdown β†’ TUI renders

πŸ”§ Tech Stack

Aspect Detail
Language TypeScript (strict)
Runtime Node.js >= 18
Plugin SDK @opencode-ai/plugin (Zod-based tool.schema)
Test Framework Vitest
Linter Biome
Build tsc β†’ dist/
Dependencies Zero runtime β€” only @opencode-ai/plugin (peer)

πŸ“– Next Steps