Burnmydays Claude Opus 4.8 (1M context) commited on
Commit
7054692
·
1 Parent(s): 3a0e867

fix Codex parser + sharpen instructions + add ./sigrank --all

Browse files

- Codex parser: unify Alpha/Beta into one _codex_input_estimate helper
(removes the two divergent hardcoded /9.0 copies). Beta now uses the
operator's REAL Claude input/output ratio; Alpha uses the AA 2:1 baseline.
- ./sigrank --all: run every provider in turn (claude, then codex); Codex
reuses Claude's measured ratio for the Beta pathway; one failing provider
doesn't stop the others.
- Instructions (app.py Clock Your Signal tab + README How-to-measure):
measure each provider separately, never bare `ccusage --json`; what data /
where to get it / where to input it; accurate two-pathway Codex description.
- Drop duplicate claudetodolist.md (content lives in SUPABASE_MIGRATION.md).

Supabase migration (submitted_at, hf_user, sigrank_sessions + RLS) applied
live via MCP and verified end-to-end. Canonical MO§ES Υ 18436.98 unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files changed (5) hide show
  1. README.md +45 -6
  2. app.py +20 -11
  3. claudetodolist.md +0 -147
  4. ingest.py +33 -27
  5. sigrank.py +33 -3
README.md CHANGED
@@ -47,12 +47,45 @@ is the token-domain expression of the same conservation law.
47
  - *[Word-based operator ranking — link placeholder]*
48
 
49
  ## What it does
50
- Paste `npx ccusage@latest --json` (Claude Code), `ccusage codex --json` (Codex),
51
  or four numbers →
52
  - **operator profile** — a 0.5B MiniCPM model narrates your architecture, plus
53
  raw ledger, composition, full metrics, cascade breakdown
54
  - **leaderboard placement** vs real operators, ranked by Υ, with blended **$/1M cost**
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  ## The model (Tiny Titan / MiniCPM)
57
  `openbmb/MiniCPM4-0.5B` (0.5B params, well under the 4B cap) runs on ZeroGPU and
58
  narrates the operator read. It is **non-blocking**: if unavailable, a deterministic
@@ -152,11 +185,17 @@ benchmarks on one side, real user token ledgers on the other — landing on the
152
  same architecture is the validation.
153
 
154
  ## Codex support
155
- Codex reports a *combined* input figure (fresh + cached) and never itemizes cache
156
- writes. SigRank splits it with the measured field anchor **input:output ≈ 2:1**
157
- (estimated fresh input = 2×output; remainder → cache_create, clamped ≥0). Every
158
- Codex-derived row is flagged with its directional caveat (↑ input-heavy /
159
- ↓ output-rich). The anchor is provisional and being refined (see CODEX.md).
 
 
 
 
 
 
160
 
161
  ## Cost
162
  For Claude Code, ccusage supplies real cost → exact $/1M. For manual/wild rows
 
47
  - *[Word-based operator ranking — link placeholder]*
48
 
49
  ## What it does
50
+ Paste `ccusage claude --json` (Claude Code), `ccusage codex --json` (Codex),
51
  or four numbers →
52
  - **operator profile** — a 0.5B MiniCPM model narrates your architecture, plus
53
  raw ledger, composition, full metrics, cascade breakdown
54
  - **leaderboard placement** vs real operators, ranked by Υ, with blended **$/1M cost**
55
 
56
+ ## How to measure yourself
57
+ Three things: **what data, where to get it, where to put it.**
58
+
59
+ **1 — What / where to get it.** SigRank needs four integers — `input`, `output`,
60
+ `cache_create`, `cache_read`. [`ccusage`](https://github.com/ryoppippi/ccusage) reads
61
+ them from your local logs. Run **one command per provider** — Claude and Codex are
62
+ different operators, so measure them **separately**:
63
+
64
+ ```
65
+ ccusage claude --json # Claude Code
66
+ ccusage codex --json # Codex
67
+ ```
68
+
69
+ > ⚠️ Don't use bare `ccusage --json` (no subcommand): it merges every agent into one
70
+ > total, which inflates input and distorts the architecture read.
71
+
72
+ **2 — Where to put it.** Either:
73
+
74
+ - **Local importer (no paste):** `./sigrank` (Claude), `./sigrank --codex` (Codex),
75
+ or `./sigrank --all` (each provider in turn). Reads your usage on your machine and
76
+ prints your profile + board rank. Nothing leaves your computer.
77
+ - **Hosted Space:** paste one provider's JSON into the **Clock Your Signal** box, or type
78
+ the four numbers `input output cache_create cache_read`.
79
+
80
+ **3 — Saving (optional).** On the Space, sign in with HuggingFace to earn one persistent
81
+ board entry + session history (Greatest Hits). Without login, your read is a live
82
+ snapshot only — scored against the field but not saved.
83
+
84
+ **Codex note.** Codex doesn't report a fresh-vs-cache input split, so its input is
85
+ *estimated*: on its own it uses the AA-backed **2:1** baseline; if you also have a Claude
86
+ profile (e.g. `./sigrank --codex`, which reads your Claude ratio first) it calibrates with
87
+ **your own Claude input:output ratio**. Estimated rows are flagged with `*`.
88
+
89
  ## The model (Tiny Titan / MiniCPM)
90
  `openbmb/MiniCPM4-0.5B` (0.5B params, well under the 4B cap) runs on ZeroGPU and
91
  narrates the operator read. It is **non-blocking**: if unavailable, a deterministic
 
185
  same architecture is the validation.
186
 
187
  ## Codex support
188
+ Codex never itemizes cache writes, so SigRank estimates the high-signal user input
189
+ from output via two pathways (`_codex_input_estimate` in `ingest.py`):
190
+
191
+ - **Alpha Codex alone:** the AA-backed **2:1** baseline `est_input = 2 × output`.
192
+ - **Beta Codex + a Claude profile:** the operator's **own** measured Claude
193
+ `input:output` ratio — `est_input = output × (claude_input / claude_output)`. The
194
+ CLI builds this automatically (`./sigrank --codex` reads your Claude usage first).
195
+
196
+ `cache_create` is the remainder (`raw_input − est_input`, clamped ≥0); `cache_read` is
197
+ measured directly. Every Codex-derived row is **flagged with `*`** and names the exact
198
+ pathway used in its caveat.
199
 
200
  ## Cost
201
  For Claude Code, ccusage supplies real cost → exact $/1M. For manual/wild rows
app.py CHANGED
@@ -327,25 +327,34 @@ def _build_demo():
327
  gr.HTML(board_html())
328
 
329
  with gr.Tab("Clock Your Signal"):
330
- gr.Markdown("""**Get your operator profile \u2014 every provider, one command.**
331
 
332
- **\u2460 Run ccusage** (reads your local usage \u2014 one command per provider):
333
  ```
334
- ccusage claude --json # Claude Code stats
335
- ccusage codex --json # Codex stats (estimated *)
336
- ccusage --json # ALL providers combined
337
  ```
338
- Or use the local importer:
 
 
 
 
339
  ```
340
- ./sigrank # Claude Code (measured)
341
- ./sigrank --codex # Codex (applies field anchor *)
 
342
  ```
343
 
344
- **\u2461 Paste the JSON below.** Drop your `ccusage` output \u2014 Claude, Codex, or combined \u2014 and we'll route it automatically. Or paste four numbers: `input output cache_create cache_read`.
 
 
345
 
346
- *Codex / non-Claude providers: input is calibrated via the 3:2:1 field anchor (or 1:9 if you have a Claude profile). Estimated rows are flagged with \\*.*
 
 
347
 
348
- **\u2462 Sign in to save.** HuggingFace users get one persistent board entry + session history. Paste without login = snapshot only.""")
 
349
  if _ON_SPACE:
350
  gr.LoginButton(elem_id="hf-login-btn")
351
  else:
 
327
  gr.HTML(board_html())
328
 
329
  with gr.Tab("Clock Your Signal"):
330
+ gr.Markdown("""**Get your operator profile \u2014 measure each provider separately.**
331
 
332
+ **\u2460 Get your numbers.** Run one command per provider in your terminal:
333
  ```
334
+ ccusage claude --json # Claude Code
335
+ ccusage codex --json # Codex (estimated *)
 
336
  ```
337
+ \u26a0\ufe0f **Run each provider on its own \u2014 never bare `ccusage --json`.** With no
338
+ subcommand it merges every agent into one total, inflating input and distorting your
339
+ architecture. Claude and Codex are different operators \u2014 measure them separately.
340
+
341
+ Prefer zero copy-paste? Use the local importer (reads your usage on your machine):
342
  ```
343
+ ./sigrank # Claude Code (measured)
344
+ ./sigrank --codex # Codex (calibrated *)
345
+ ./sigrank --all # each provider, one after another
346
  ```
347
 
348
+ **\u2461 Paste it below.** Drop one provider's `ccusage` JSON in the box \u2014 we route it
349
+ automatically. No JSON handy? Type four numbers in order:
350
+ `input output cache_create cache_read`.
351
 
352
+ *Codex reports no fresh-vs-cache input split, so its input is **estimated**: on its own it
353
+ uses the AA-backed **2:1** baseline; if you also have a Claude profile it uses **your own
354
+ Claude input:output ratio**. Estimated rows are flagged with \\*.*
355
 
356
+ **\u2462 Sign in to save.** A HuggingFace login earns one persistent board entry + session
357
+ history (Greatest Hits). Pasting without login is a live snapshot only.""")
358
  if _ON_SPACE:
359
  gr.LoginButton(elem_id="hf-login-btn")
360
  else:
claudetodolist.md DELETED
@@ -1,147 +0,0 @@
1
- 2 items... i had devin build the instructions for the following...
2
-
3
-
4
- 1) i dont feel like we covered enough on instructions and detailing what information we need, where to get it... and where to imput it
5
-
6
-
7
-
8
- 2) # Supabase Migration — SigRank Importer Overhaul
9
-
10
- Run these in the Supabase SQL Editor (Dashboard → SQL Editor → New Query).
11
-
12
- ---
13
-
14
- ## 1. Add columns to `sigrank_operators`
15
-
16
- ```sql
17
- -- Timestamp for when the entry was last submitted/updated
18
- ALTER TABLE sigrank_operators
19
- ADD COLUMN IF NOT EXISTS submitted_at TIMESTAMPTZ DEFAULT now();
20
-
21
- -- HuggingFace username — only authenticated users can persist
22
- ALTER TABLE sigrank_operators
23
- ADD COLUMN IF NOT EXISTS hf_user TEXT;
24
-
25
- -- Index for fast lookups by HF user
26
- CREATE INDEX IF NOT EXISTS idx_sigrank_operators_hf_user
27
- ON sigrank_operators (hf_user);
28
- ```
29
-
30
- ---
31
-
32
- ## 2. Create `sigrank_sessions` table (session history / Greatest Hits)
33
-
34
- ```sql
35
- CREATE TABLE IF NOT EXISTS sigrank_sessions (
36
- id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
37
- name TEXT NOT NULL,
38
- input BIGINT NOT NULL DEFAULT 0,
39
- output BIGINT NOT NULL DEFAULT 0,
40
- cache_create BIGINT NOT NULL DEFAULT 0,
41
- cache_read BIGINT NOT NULL DEFAULT 0,
42
- cost_usd DOUBLE PRECISION,
43
- source TEXT DEFAULT 'manual',
44
- estimated BOOLEAN DEFAULT FALSE,
45
- caveat TEXT,
46
- hf_user TEXT,
47
- submitted_at TIMESTAMPTZ DEFAULT now()
48
- );
49
-
50
- -- Index for loading a user's session history
51
- CREATE INDEX IF NOT EXISTS idx_sigrank_sessions_name
52
- ON sigrank_sessions (name, submitted_at DESC);
53
- ```
54
-
55
- ---
56
-
57
- ## 3. RLS policies (keep anon read-only, service key for writes)
58
-
59
- ```sql
60
- -- Enable RLS on the new table
61
- ALTER TABLE sigrank_sessions ENABLE ROW LEVEL SECURITY;
62
-
63
- -- Anon can read session history
64
- CREATE POLICY "anon_read_sessions" ON sigrank_sessions
65
- FOR SELECT USING (true);
66
-
67
- -- Service role can insert (writes come from the app backend)
68
- CREATE POLICY "service_insert_sessions" ON sigrank_sessions
69
- FOR INSERT WITH CHECK (true);
70
-
71
- -- Same pattern for the new columns on sigrank_operators
72
- -- (existing policies should already cover SELECT/INSERT;
73
- -- verify the existing INSERT policy allows the new columns)
74
- ```
75
-
76
- ---
77
-
78
- ## 4. Verify
79
-
80
- After running the above, check:
81
-
82
- ```sql
83
- -- Should show submitted_at and hf_user columns
84
- SELECT column_name, data_type
85
- FROM information_schema.columns
86
- WHERE table_name = 'sigrank_operators'
87
- ORDER BY ordinal_position;
88
-
89
- -- Should exist with all columns
90
- SELECT column_name, data_type
91
- FROM information_schema.columns
92
- WHERE table_name = 'sigrank_sessions'
93
- ORDER BY ordinal_position;
94
- ```
95
-
96
- ---
97
-
98
- ## Notes
99
-
100
- - `sigrank_operators` still upserts on `name` (one board entry per operator)
101
- - `sigrank_sessions` is append-only — every submission creates a new row
102
- - The app reads sessions via `load_session_history(name, limit=5)` for the Greatest Hits display
103
- - `hf_user` is populated only when the user is authenticated via HuggingFace OAuth on the Space
104
- - Without the `SUPABASE_SERVICE_KEY` env var, all writes are no-ops (safe for public demo)
105
-
106
-
107
-
108
- 3)Here's the spec for ./sigrank --all that Claude Code can implement:
109
- Goal: ./sigrank --all runs each ccusage provider sequentially and loads results into the user's profile one at a time.
110
- In sigrank.py:
111
-
112
- # Add to argparser:
113
- p.add_argument("--all", action="store_true",
114
- help="run all providers (claude + codex) sequentially")
115
-
116
- # In main(), before the existing try block:
117
- if args.all:
118
- for provider, is_codex in [("claude", False), ("codex", True)]:
119
- sub_args = type("a", (), {
120
- "file": None, "stdin": False, "codex": is_codex,
121
- "name": args.name, "no_color": args.no_color,
122
- "stdin_dash": None
123
- })()
124
- try:
125
- raw, how = _grab_usage(sub_args)
126
- # Build operator_profile from Claude data if running Codex
127
- op_profile = None
128
- if is_codex:
129
- # Try to get Claude's I/O ratio for Beta pathway
130
- try:
131
- claude_args = type("a", (), {"file": None, "stdin": False, "codex": False})()
132
- c_raw, _ = _grab_usage(claude_args)
133
- ci, co, _, _, _ = parse_ccusage(c_raw)
134
- if co > 0:
135
- op_profile = {"model_type": "claude", "io_ratio": ci / co}
136
- except Exception:
137
- pass
138
- i, o, cw, cr, meta = ingest_meta(raw, operator_profile=op_profile)
139
- m = compute(i, o, cw, cr, cost_usd=meta.get("cost"))
140
- if meta.get("estimated"):
141
- m["_caveat"] = meta.get("caveat")
142
- print(render(args.name, m, how, color=not args.no_color))
143
- except Exception as e:
144
- print(f" [{provider}] skipped: {e}")
145
- sys.exit(0)
146
- Flow: ./sigrank --all → runs ccusage claude --json, prints profile → runs ccusage codex --json, prints profile with Alpha or Beta pathway applied. Each provider is independent — if one fails, the other still runs. find the users model via ccusage --help
147
- That should be everything! PRs #5 and #6 cover the full importer overhaul. Let me know if you need anything else.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ingest.py CHANGED
@@ -182,18 +182,37 @@ def _extract_codex_totals(d):
182
  return tot
183
 
184
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
185
  def parse_codex_submission(payload, operator_profile=None):
186
  """
187
  Parses Codex token payloads to estimate true high-signal user input.
188
 
189
- Two pathways depending on operator telemetry:
190
-
191
- Pathway Alpha (Standard): No Claude footprint -> 3:2:1 baseline.
192
- estimated_user_input = outputTokens * 2.0
193
 
194
- Pathway Beta (Claude Engine): Operator has verified Claude profile ->
195
- dynamic 1:9 transmission velocity extraction.
196
- estimated_user_input = outputTokens / 9.0
197
 
198
  Returns (i, o, cw, cr, meta) mapped to the four pillars:
199
  i = calibrated_user_input (high-signal core)
@@ -209,16 +228,8 @@ def parse_codex_submission(payload, operator_profile=None):
209
  raw_cache = tot["cached"]
210
  cost = tot["cost"] if tot["cost"] > 0 else None
211
 
212
- # Pathway Beta: Dynamic User Profile Match (Claude Engine)
213
- if operator_profile and operator_profile.get("model_type") == "claude":
214
- estimated_user_input = raw_out / 9.0
215
- parsing_mode = "Claude Closed-Loop Calibration (1:9)"
216
- # Pathway Alpha: Fallback Standard (The Top 10 Wild Field Baseline)
217
- else:
218
- estimated_user_input = raw_out * 2.0
219
- parsing_mode = "Standard Open-Loop Baseline (3:2:1)"
220
-
221
- context_debt = max(0, raw_in - int(estimated_user_input))
222
 
223
  meta = {
224
  "source": "codex",
@@ -240,10 +251,10 @@ def ingest_meta(text, operator_profile=None):
240
  - Text with named fields (extracts by field name, not position)
241
  - Four bare numbers: input output cache_create cache_read
242
 
243
- operator_profile: optional dict with at least {"model_type": "claude"}
244
  when the submitting user has a verified Claude session profile. This
245
- switches the Codex parser from the 3:2:1 baseline to the 1:9 closed-loop
246
- calibration pathway.
247
  """
248
  text=text.strip()
249
  if not text: raise ValueError("empty")
@@ -284,13 +295,8 @@ def ingest_meta(text, operator_profile=None):
284
  raw_in = int(float(m.group(1).replace(",", "")))
285
  break
286
  raw_out = o # already includes reasoning from _extract_by_name
287
- if operator_profile and operator_profile.get("model_type") == "claude":
288
- est_input = raw_out / 9.0
289
- parsing_mode = "Claude Closed-Loop Calibration (1:9)"
290
- else:
291
- est_input = raw_out * 2.0
292
- parsing_mode = "Standard Open-Loop Baseline (3:2:1)"
293
- context_debt = max(0, raw_in - int(est_input))
294
  meta = {
295
  "source": "codex", "estimated": True,
296
  "parsing_mode": parsing_mode,
 
182
  return tot
183
 
184
 
185
+ def _codex_input_estimate(raw_out, operator_profile):
186
+ """Estimate Codex high-signal user input from output. Single source of truth
187
+ for both the JSON and named-field Codex paths (no duplicated magic numbers).
188
+
189
+ Codex never reports a fresh-vs-cache input split, so we estimate the
190
+ high-signal user input from the output (the real work product) via a ratio:
191
+
192
+ Beta (Codex + Claude present): use the operator's REAL Claude operating
193
+ ratio io_ratio = claude_input/claude_output -> est_input = output * io_ratio
194
+ (dynamic, never a constant — it's the operator's own measured behaviour).
195
+
196
+ Alpha (Codex alone, no Claude): AA-backed 2:1 baseline (input:output = 2),
197
+ supported by the wild corpus -> est_input = output * 2.0
198
+
199
+ Returns (est_input:int, parsing_mode:str).
200
+ """
201
+ if (operator_profile and operator_profile.get("model_type") == "claude"
202
+ and operator_profile.get("io_ratio")):
203
+ ratio = operator_profile["io_ratio"] # claude input / output
204
+ return int(raw_out * ratio), f"Claude operating-ratio {ratio:.3f}:1 (input:output)"
205
+ return int(raw_out * 2.0), "AA 2:1 baseline (wild-corpus backed)"
206
+
207
+
208
  def parse_codex_submission(payload, operator_profile=None):
209
  """
210
  Parses Codex token payloads to estimate true high-signal user input.
211
 
212
+ Two pathways depending on operator telemetry (see _codex_input_estimate):
 
 
 
213
 
214
+ Pathway Beta (Codex + Claude): est_input = output * (claude_input/claude_output)
215
+ Pathway Alpha (Codex alone): est_input = output * 2.0 (AA 2:1 baseline)
 
216
 
217
  Returns (i, o, cw, cr, meta) mapped to the four pillars:
218
  i = calibrated_user_input (high-signal core)
 
228
  raw_cache = tot["cached"]
229
  cost = tot["cost"] if tot["cost"] > 0 else None
230
 
231
+ estimated_user_input, parsing_mode = _codex_input_estimate(raw_out, operator_profile)
232
+ context_debt = max(0, raw_in - estimated_user_input)
 
 
 
 
 
 
 
 
233
 
234
  meta = {
235
  "source": "codex",
 
251
  - Text with named fields (extracts by field name, not position)
252
  - Four bare numbers: input output cache_create cache_read
253
 
254
+ operator_profile: optional dict {"model_type": "claude", "io_ratio": float}
255
  when the submitting user has a verified Claude session profile. This
256
+ switches the Codex parser from the Alpha 2:1 baseline to the Beta pathway
257
+ that uses the operator's own Claude input/output ratio.
258
  """
259
  text=text.strip()
260
  if not text: raise ValueError("empty")
 
295
  raw_in = int(float(m.group(1).replace(",", "")))
296
  break
297
  raw_out = o # already includes reasoning from _extract_by_name
298
+ est_input, parsing_mode = _codex_input_estimate(raw_out, operator_profile)
299
+ context_debt = max(0, raw_in - est_input)
 
 
 
 
 
300
  meta = {
301
  "source": "codex", "estimated": True,
302
  "parsing_mode": parsing_mode,
sigrank.py CHANGED
@@ -6,8 +6,9 @@ Her-style: reads YOUR usage on YOUR machine and prints your operator read. No
6
  paste, no token, no upload. The hosted Space's paste box is the backup; this is
7
  the real-app path.
8
 
9
- python sigrank.py # auto-run `ccusage --json` (Claude Code)
10
  python sigrank.py --codex # auto-run `ccusage codex --json`
 
11
  python sigrank.py --file u.json # read a saved ccusage/codex json
12
  python sigrank.py --name "you" # label your row
13
  cat u.json | python sigrank.py - # read from stdin (backup)
@@ -137,6 +138,8 @@ def main(argv=None):
137
  p.add_argument("stdin_dash", nargs="?", default=None, help="pass '-' to read JSON from stdin")
138
  p.add_argument("--file", help="read a saved ccusage/codex json file")
139
  p.add_argument("--codex", action="store_true", help="run `ccusage codex --json`")
 
 
140
  p.add_argument("--name", default="you", help="label for your row")
141
  p.add_argument("--no-color", action="store_true", help="plain output")
142
  args = p.parse_args(argv)
@@ -144,7 +147,34 @@ def main(argv=None):
144
 
145
  color = sys.stdout.isatty() and not args.no_color
146
 
147
- # For Codex: detect Claude profile so the parser uses 1:9 pathway.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  operator_profile = None
149
  if args.codex:
150
  try:
@@ -154,7 +184,7 @@ def main(argv=None):
154
  if _co > 0:
155
  operator_profile = {"model_type": "claude", "io_ratio": _ci / _co}
156
  except Exception:
157
- pass # no Claude data — Alpha pathway (3:2:1)
158
 
159
  try:
160
  raw, how = _grab_usage(args)
 
6
  paste, no token, no upload. The hosted Space's paste box is the backup; this is
7
  the real-app path.
8
 
9
+ python sigrank.py # auto-run `ccusage claude --json` (Claude Code)
10
  python sigrank.py --codex # auto-run `ccusage codex --json`
11
+ python sigrank.py --all # every provider in turn (claude, then codex)
12
  python sigrank.py --file u.json # read a saved ccusage/codex json
13
  python sigrank.py --name "you" # label your row
14
  cat u.json | python sigrank.py - # read from stdin (backup)
 
138
  p.add_argument("stdin_dash", nargs="?", default=None, help="pass '-' to read JSON from stdin")
139
  p.add_argument("--file", help="read a saved ccusage/codex json file")
140
  p.add_argument("--codex", action="store_true", help="run `ccusage codex --json`")
141
+ p.add_argument("--all", action="store_true",
142
+ help="run every provider in turn (claude, then codex)")
143
  p.add_argument("--name", default="you", help="label for your row")
144
  p.add_argument("--no-color", action="store_true", help="plain output")
145
  args = p.parse_args(argv)
 
147
 
148
  color = sys.stdout.isatty() and not args.no_color
149
 
150
+ # --all: run each provider sequentially and independently. Claude runs first;
151
+ # its measured input/output ratio is captured and handed to the Codex pass so
152
+ # Codex uses the Beta (operator-ratio) pathway. One provider failing (e.g. no
153
+ # Codex usage) never stops the others.
154
+ if args.all:
155
+ name = (args.name or "you").strip()[:24] or "you"
156
+ claude_profile = None
157
+ for provider, is_codex in (("claude", False), ("codex", True)):
158
+ try:
159
+ prov_args = type("a", (), {"file": None, "stdin": False, "codex": is_codex})()
160
+ raw, how = _grab_usage(prov_args)
161
+ prof = claude_profile if is_codex else None
162
+ i, o, cw, cr, meta = ingest_meta(raw, operator_profile=prof)
163
+ if i + o + cw + cr == 0:
164
+ print(f" [{provider}] no usage data — skipped")
165
+ continue
166
+ m = compute(i, o, cw, cr, cost_usd=meta.get("cost"))
167
+ if meta.get("estimated"):
168
+ m["_caveat"] = meta.get("caveat")
169
+ print(render(name, m, how, color))
170
+ if not is_codex and o > 0: # capture Claude ratio for the Codex pass
171
+ claude_profile = {"model_type": "claude", "io_ratio": i / o}
172
+ except Exception as e:
173
+ print(f" [{provider}] skipped: {e}")
174
+ return 0
175
+
176
+ # For Codex: detect the operator's real Claude I/O ratio so the parser uses
177
+ # the Beta pathway (output * claude_input/claude_output) instead of Alpha 2:1.
178
  operator_profile = None
179
  if args.codex:
180
  try:
 
184
  if _co > 0:
185
  operator_profile = {"model_type": "claude", "io_ratio": _ci / _co}
186
  except Exception:
187
+ pass # no Claude data — Alpha pathway (AA 2:1 baseline)
188
 
189
  try:
190
  raw, how = _grab_usage(args)