sigrank 0.0.187 → 0.0.189

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -342,6 +342,7 @@ All adapters are token-only (no message content, no cost fields, no credentials)
342
342
  | Amp | ✅ `~/.local/share/amp/threads` | Native 4-pillar; per-message |
343
343
  | Kimi | ✅ `~/.kimi/sessions` | Native 4-pillar; `StatusUpdate` lines only |
344
344
  | pi-agent | ✅ `~/.pi/agent/sessions` | Native 4-pillar; per-message JSONL |
345
+ | oh-my-pi (`omp`) | ✅ `~/.omp/agent/sessions` (recursive, incl. nested subagent transcripts) | Native 4-pillar from `.message.usage` on `type:"message"` entries (`cacheWrite`→`cacheCreate`); **`reasoningTokens` is already inside `output`** — never added, or it double-counts; `usage.cost` reuses the same four key names for USD floats and is dropped; dedup by (session header `id`, entry `id`). Separate harness from pi-agent |
345
346
  | OpenClaw | ✅ `~/.openclaw` | Native 4-pillar; per-message JSONL |
346
347
  | Droid | ✅ `~/.factory/sessions/*.settings.json` | Native 4-pillar; `thinking_tokens`→output |
347
348
  | Codebuff | ✅ `~/.config/manicode` | Native 4-pillar; `chat-messages.json` |
@@ -382,19 +383,19 @@ All adapters are token-only (no message content, no cost fields, no credentials)
382
383
  ## Dev / test
383
384
 
384
385
  ```bash
385
- node test.mjs # 13 test groups, 200 assertions (no network, no fs writes)
386
+ node test.mjs # 14 test groups, 313 assertions (no network; fs writes confined to OS-tmpdir fixtures)
386
387
  node sign.test.mjs # ed25519 signing + canon parity
387
388
  node index.mjs # stdio MCP server directly (pipe to MCP client)
388
389
  ```
389
390
 
390
- Tests verify (13 groups, 200 assertions):
391
+ Tests verify (14 groups, 313 assertions):
391
392
 
392
393
  - `rank_paste` canon: MO§ES `(1251211, 11296121, 128196310, 2555179769)` → Υ 18436.98 · TRANSMITTER
393
394
  - `submit_paste` preview (no codename) + POST shape (injected fetch, no live writes)
394
395
  - `tokenpull` dedup, window slicing, 4-window pillars (mock adapter)
395
396
  - `tokenpull_submit` all 4 windows POST, sha256 hash, ddmmyy stamp
396
397
  - `tokenpullCodex` io_ratio conversion per-window
397
- - Adapter registry (15 platforms) + per-adapter shape contracts
398
+ - Adapter registry (16 platforms) + per-adapter shape contracts
398
399
  - `rank_windows` 4-window paste scoring, partial input, no-network
399
400
  - `watch_tokenpull` cascade snapshot, interval_s, submit path
400
401
  - `enroll` posts identity (public key only), maps 201 enrolled + 410 code_invalid
@@ -414,7 +415,7 @@ Tests verify (13 groups, 200 assertions):
414
415
  | `tui.mjs` | Full tabbed TUI: Dashboard / Trends / Compare / Board / Watch / Connect |
415
416
  | `cascade.mjs` | Pure cascade math (Υ, SNR, leverage, velocity, 10xDEV, class) |
416
417
  | `tokenpull.mjs` | On-device log scanner — Claude, Codex, multi-platform |
417
- | `adapters.mjs` | Platform adapter registry (15+ platforms) |
418
+ | `adapters.mjs` | Platform adapter registry (16+ platforms) |
418
419
  | `tools.mjs` | MCP tool table + dispatcher |
419
420
  | `connect.mjs` | Connect-code enrollment + device identity |
420
421
  | `keystore.mjs` | Local key management (paste-keys, not API keys) |
@@ -24,6 +24,7 @@
24
24
  * tokenpullCodex(); adapters that yield native 4-pillar via messages() go through
25
25
  * tokenpull() directly. The split happens at the tokenpullAny() routing layer.)
26
26
  * - Reasoning / thinking tokens → folded into `output` (they are output-side spend)
27
+ * EXCEPTION: omp already reports reasoningTokens INSIDE output — see adapter #16
27
28
  * - No cache-creation data available → cacheCreate: 0 + adapter sets `estimated: true`
28
29
  * - Cost fields (USD) → NEVER used or forwarded (SigRank scores cost efficiency from
29
30
  * token ratios, not from dollar amounts — cost efficiency is derived, not ingested)
@@ -875,6 +876,68 @@ export const otherAdapter = {
875
876
  },
876
877
  };
877
878
 
879
+ // ── 16. oh-my-pi (omp) ───────────────────────────────────────────────────────
880
+ // ~/.omp/agent/sessions/<bucket>/<timestamp>_<sessionId>/<name>.jsonl (recursive)
881
+ // NOT pi-agent (#4): oh-my-pi forked from pi long ago and is a separate harness
882
+ // with its own on-disk format. Line 1 is a fixed-width 256-byte {"type":"title"}
883
+ // slot; line 2 is {"type":"session"} and carries the session `.id`. Neither line
884
+ // holds usage. Other observed `.type` values (all usage-free): custom,
885
+ // custom_message, model_change, thinking_level_change, session_init, credential_pin.
886
+ // Usage lives at `.message.usage` on `.type === "message"` entries:
887
+ // { input, output, cacheRead, cacheWrite, totalTokens, reasoningTokens, cost:{…} }
888
+ // Native 4-pillar (no estimation): input→input, output→output,
889
+ // cacheWrite→cacheCreate, cacheRead→cacheRead.
890
+ // TRAP 1 — reasoningTokens is ALREADY INSIDE output. Do NOT fold it in. Every other
891
+ // adapter here folds reasoning into output, so the reflex is wrong for omp; adding
892
+ // it double-counts. Proof: totalTokens === input+output+cacheRead+cacheWrite
893
+ // exactly, and reasoningTokens is always ≤ output.
894
+ // TRAP 2 — usage.cost reuses the SAME four key names (input/output/cacheRead/
895
+ // cacheWrite, plus total) for USD floats. Read the four counts straight off
896
+ // `usage`; never descend into `cost`. Cost fields are never forwarded (file header).
897
+ // Nested subagent transcripts (critic.jsonl beside critic/__advisor.jsonl) are real
898
+ // operator work and ARE counted — same policy as Claude's `subagents/`.
899
+ // Dedup key is (session header `.id`, entry top-level `.id`) — `.message` has no id.
900
+ // File cap: a real omp tree runs to 20k+ transcripts, past walkFiles' 10_000 default,
901
+ // which would silently drop half the operator's tokens — pass an explicit cap.
902
+ const OMP_MAX_FILES = 500_000;
903
+ export const ompAdapter = {
904
+ platform: "omp",
905
+ defaultRoot: () => join(homedir(), ".omp", "agent", "sessions"),
906
+ async *messages(root) {
907
+ for (const r of roots("OMP_DATA_DIR", root)) {
908
+ for await (const path of walkFiles(r, isJsonl, { n: 0 }, OMP_MAX_FILES)) {
909
+ const text = await readUtf8(path);
910
+ let sid = null;
911
+ for (const [ev] of parseJsonl(text, path)) {
912
+ if (!ev) continue;
913
+ if (ev.type === "session") {
914
+ sid = ev.id || null; // line-2 header: session half of the dedup key
915
+ continue;
916
+ }
917
+ if (ev.type !== "message") continue;
918
+ const u = ev.message && ev.message.usage;
919
+ if (!u) continue;
920
+ const input = Number(u.input || 0);
921
+ const output = Number(u.output || 0); // reasoningTokens ALREADY inside — TRAP 1
922
+ const cacheCreate = Number(u.cacheWrite || 0);
923
+ const cacheRead = Number(u.cacheRead || 0);
924
+ if (input + output + cacheCreate + cacheRead === 0) continue;
925
+ yield {
926
+ id: ev.id || null,
927
+ sid,
928
+ ts: ev.timestamp || null,
929
+ input,
930
+ output,
931
+ cacheCreate,
932
+ cacheRead,
933
+ file: path,
934
+ };
935
+ }
936
+ }
937
+ }
938
+ },
939
+ };
940
+
878
941
  // ── Registry ──────────────────────────────────────────────────────────────────
879
942
  /** All non-Claude, non-Codex adapters keyed by platform ID. */
880
943
  export const ADAPTERS = {
@@ -893,6 +956,7 @@ export const ADAPTERS = {
893
956
  hermes: hermesAdapter,
894
957
  devin: devinAdapter,
895
958
  other: otherAdapter,
959
+ omp: ompAdapter,
896
960
  };
897
961
 
898
962
  export const ALL_PLATFORMS = Object.keys(ADAPTERS).concat(["claude", "codex"]);
package/lib/constants.mjs CHANGED
@@ -62,6 +62,8 @@ export const TOKSCALE_CLIENT_MAP = {
62
62
  opencode: "opencode",
63
63
  openclaw: "openclaw",
64
64
  pi: "pi",
65
+ omp: "omp",
66
+ "oh-my-pi": "omp",
65
67
  cursor: "other",
66
68
  roocode: "other",
67
69
  mux: "other",
@@ -73,6 +75,7 @@ export const TOKSCALE_CLIENT_MAP = {
73
75
  trae: "other",
74
76
  warp: "other",
75
77
  cline: "other",
78
+ "9router": "other",
76
79
  gjc: "other",
77
80
  grok: "other",
78
81
  jcode: "other",
@@ -86,6 +89,23 @@ export const TOKSCALE_CLIENT_MAP = {
86
89
  synthetic: null,
87
90
  };
88
91
 
92
+ /**
93
+ * Platforms whose local data tokscale CANNOT see, so `tokscale models --json`
94
+ * never lists them as an active client. pullActivePlatforms() auto-detect must
95
+ * probe these directly or their cascade row can never appear, even with GBs of
96
+ * native local telemetry (that was exactly the omp bug: tokscale reports
97
+ * claude/codex/copilot/gemini/grok/kimi/kiro/opencode/pi and nothing else, so
98
+ * a tokscale-detected machine silently dropped oh-my-pi entirely).
99
+ *
100
+ * The adapter pull itself filters out platforms with no data, so probing a
101
+ * platform the operator doesn't use costs one cheap missing-directory walk.
102
+ *
103
+ * As of 2026-08-08: oh-my-pi. Delete an entry once tokscale reports that client
104
+ * (TOKSCALE_CLIENT_MAP already maps the `omp` / `oh-my-pi` client names for
105
+ * when that happens).
106
+ */
107
+ export const TOKSCALE_BLIND_PLATFORMS = ["omp"];
108
+
89
109
  /**
90
110
  * The "actually on the board" predicate. A submission is ranked only when the
91
111
  * server returns HTTP ok AND verification_tier === "verified" AND persisted
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sigrank",
3
- "version": "0.0.187",
3
+ "version": "0.0.189",
4
4
  "author": "SunrisesIllNeverSee (https://github.com/SunrisesIllNeverSee)",
5
5
  "description": "SigRank MCP server — the yield cascade + live leaderboard as MCP tools any agent can call",
6
6
  "mcpName": "io.github.SunrisesIllNeverSee/sigrank-mcp",
@@ -782,10 +782,17 @@ async function runCompare({ platform = "claude" } = {}) {
782
782
  const WATCH_WINDOWS = ["7d", "30d", "90d", "all"];
783
783
 
784
784
  // Detect which platforms have real local data (input+output > 0 in any window) via tokenpull.
785
- // Returns an array of pull results (the same shape tokenpullAny yields), one per active platform.
786
- async function detectActivePulls() {
787
- // Unified: one loader for me / watch / the TUI Dashboard (was a separate tokenpullAny loop).
788
- return pullActivePlatforms();
785
+ // Returns an array of pull results (the same shape tokenpullAny yields), one per active
786
+ // platform, through the shared loader used by `me` / `watch` / the TUI Dashboard.
787
+ //
788
+ // `platform` scopes the PULL, not just the render: the watcher re-pulls every `refresh`
789
+ // seconds (default 30) and an unscoped auto-detect also probes the tokscale-blind ~10 GiB
790
+ // oh-my-pi tree (~46s), so `watch --platform claude` used to scan everything and then
791
+ // display one row — scan longer than refresh interval, permanently saturated.
792
+ // null / '' / 'all' still mean "everything", keeping withTokscaleBlind's omp row.
793
+ export async function detectActivePulls(platform, opts = {}) {
794
+ const scoped = platform && platform !== "all" ? [platform] : undefined;
795
+ return pullActivePlatforms({ platforms: scoped }, opts).catch(() => []);
789
796
  }
790
797
 
791
798
  async function runWatch({
@@ -815,9 +822,9 @@ async function runWatch({
815
822
  write(HIDE_CURSOR);
816
823
 
817
824
  const draw = async () => {
818
- // Re-detect each tick so a platform that just started writing logs appears automatically.
819
- let pulls = await detectActivePulls().catch(() => []);
820
- if (platFilter) pulls = pulls.filter((d) => d.platform === platFilter);
825
+ // Re-pull each tick so a platform that just started writing logs appears automatically.
826
+ // Scoped by platFilter, so a focused watch never scans platforms it cannot display.
827
+ const pulls = await detectActivePulls(platFilter);
821
828
 
822
829
  if (lines > 0) write(CURSOR_UP(lines));
823
830
 
@@ -961,8 +968,11 @@ async function runSigRank() {
961
968
  writeln(` ${gold("⊙ SigRank")} ${bold("Operator Dashboard")}`);
962
969
  writeln(` ${dim("reading local data…")}`);
963
970
 
971
+ // ../tokenpull.mjs — cli.mjs lives in presentation/, so "./tokenpull.mjs" resolved to
972
+ // presentation/tokenpull.mjs (nonexistent) and threw ERR_MODULE_NOT_FOUND, taking the
973
+ // whole dashboard read down. Matches the `../adapters.mjs` static import above.
964
974
  const { tokenpullAny, freshVerifierPillars } =
965
- await import("./tokenpull.mjs");
975
+ await import("../tokenpull.mjs");
966
976
 
967
977
  // Local sources first (fast) — board fetch with 5s timeout runs in parallel
968
978
  const boardPromise = Promise.race([
@@ -971,7 +981,7 @@ async function runSigRank() {
971
981
  ]);
972
982
 
973
983
  // FIX A1-CLI (2026-06-27): verifiers (ccusage/tokscale/tokendash) are fetched
974
- // ON-DEMAND for ACTIVE platforms only — not synchronously for all 15 platforms
984
+ // ON-DEMAND for ACTIVE platforms only — not synchronously for every ALL_PLATFORMS entry
975
985
  // upfront (which blocked Dashboard paint up to ~40s, same class of bug as TUI
976
986
  // FIX A1). The sync ccusagePillars()/tokscalePillars()/tokenDashPillars() loop
977
987
  // over ALL_PLATFORMS is replaced by parallel freshVerifierPillars() calls after
@@ -644,8 +644,10 @@ function renderDashboard(data, status = "", scrollOffset = 0) {
644
644
  emit(` ${dim("·".repeat(Math.max(0, Math.min(w - 4, narrow ? 96 : 114))))}`);
645
645
 
646
646
  if (active.length === 0) {
647
+ // Count is derived from the registry — a hardcoded number drifts every time an
648
+ // adapter is added (it read "14 platforms" while ALL_PLATFORMS held 17).
647
649
  emit(
648
- ` ${dim(" reading token logs… (14 platforms, ~5s · press [R] to refresh)")}`,
650
+ ` ${dim(` reading token logs… (${ALL_PLATFORMS.length} platforms, ~5s · press [R] to refresh)`)}`,
649
651
  );
650
652
  }
651
653
 
@@ -1586,7 +1588,10 @@ function renderConnect(id, codeBuf = "", msg = "") {
1586
1588
  // null/'all'/'all-windows' means "everything". Each row is a (platform, window) cascade cell:
1587
1589
  // Υ Yield + SNR/Lev/Vel/class. Iterates active platforms (input+output > 0 in any window).
1588
1590
  async function renderWatch(platform = "all", win = "all-windows") {
1589
- const { tokenpullAny } = await import("./tokenpull.mjs");
1591
+ // ../tokenpull.mjs — this file lives in presentation/, so "./tokenpull.mjs" resolved
1592
+ // to presentation/tokenpull.mjs (nonexistent) and threw ERR_MODULE_NOT_FOUND for
1593
+ // every row. Matches the static `../tokenpull.mjs` import at the top of this file.
1594
+ const { tokenpullAny } = await import("../tokenpull.mjs");
1590
1595
  const ALL_PLATFORMS = [
1591
1596
  "claude",
1592
1597
  "codex",
@@ -1602,6 +1607,7 @@ async function renderWatch(platform = "all", win = "all-windows") {
1602
1607
  "copilot",
1603
1608
  "openclaw",
1604
1609
  "pi",
1610
+ "omp",
1605
1611
  ];
1606
1612
  const platFilter = platform && platform !== "all" ? platform : null;
1607
1613
  const winFilter = win && win !== "all-windows" ? win : null;
@@ -2064,7 +2070,7 @@ export async function runTui({
2064
2070
  dashData = { active: [], _loading: true };
2065
2071
  await redraw();
2066
2072
  // FIX 0: progressive Dashboard load — claude first (fast), render it, THEN fill
2067
- // the other 13 platforms in the background + redraw. The user sees their cascade
2073
+ // the remaining ALL_PLATFORMS entries in the background + redraw. The user sees their cascade
2068
2074
  // within ~1 read instead of a 7s blank "loading dashboard…".
2069
2075
  dashData = await loadDashboardData().catch((e) => {
2070
2076
  status = `dashboard error: ${e.message}`;
@@ -2481,6 +2487,7 @@ export async function runTui({
2481
2487
  "copilot",
2482
2488
  "openclaw",
2483
2489
  "pi",
2490
+ "omp",
2484
2491
  ];
2485
2492
  if (k === "p" && (activeTab === 2 || activeTab === 4)) {
2486
2493
  if (activeTab === 2) {
package/submit/index.mjs CHANGED
@@ -25,6 +25,7 @@ const PLATFORM_ENUM = new Set([
25
25
  "chatgpt",
26
26
  "gemini",
27
27
  "pi",
28
+ "omp",
28
29
  "codex",
29
30
  "multi",
30
31
  "other",
package/tools/index.mjs CHANGED
@@ -9,7 +9,10 @@
9
9
  import { ALL_PLATFORMS } from "../adapters/index.mjs";
10
10
  import { execFileAsync } from "./_helpers.mjs";
11
11
  import { buildFetch, DEFAULT_API_BASE, DEFAULT_FETCH_TIMEOUT } from "./_helpers.mjs";
12
- import { TOKSCALE_CLIENT_MAP } from "../lib/constants.mjs";
12
+ import {
13
+ TOKSCALE_CLIENT_MAP,
14
+ TOKSCALE_BLIND_PLATFORMS,
15
+ } from "../lib/constants.mjs";
13
16
 
14
17
  import { TOOL_DEF as rankPasteTool, handleRankPaste } from "./rank-paste.mjs";
15
18
  import { TOOL_DEF as getLeaderboardTool, handleGetLeaderboard } from "./get-leaderboard.mjs";
@@ -98,10 +101,22 @@ async function _tokscaleDetectClients() {
98
101
  return [...clients];
99
102
  }
100
103
 
101
- /** Pull a specific set of platforms in parallel, filter to active ones. */
104
+ /** Detection targets = what tokscale saw ∪ the platforms tokscale is blind to.
105
+ * Without the union, an operator with GBs of oh-my-pi telemetry gets no omp row
106
+ * on any machine where tokscale succeeds, because tokscale never names that
107
+ * client. Pure + exported so the union is unit-testable without shelling out. */
108
+ export function withTokscaleBlind(detected) {
109
+ return [...new Set([...(detected || []), ...TOKSCALE_BLIND_PLATFORMS])];
110
+ }
111
+
112
+ /** Pull a specific set of platforms in parallel, filter to active ones.
113
+ * opts.callTool overrides the per-platform pull for tests (same opts-injection
114
+ * convention as opts.adapter in pullByPlatform) so the target-set logic is
115
+ * assertable without a real filesystem scan. */
102
116
  async function _pullExplicit(platforms, opts = {}) {
117
+ const pull = opts.callTool || callTool;
103
118
  const settled = await Promise.allSettled(
104
- platforms.map((p) => callTool("tokenpull", { platform: p }, opts)),
119
+ platforms.map((p) => pull("tokenpull", { platform: p }, opts)),
105
120
  );
106
121
  const active = settled
107
122
  .filter((r) => r.status === "fulfilled" && r.value)
@@ -129,15 +144,17 @@ export async function pullActivePlatforms({ platforms } = {}, opts = {}) {
129
144
  return _pullExplicit(platforms, opts);
130
145
  }
131
146
  // Auto-detect: run tokscale once to discover all active clients, then pull
132
- // only those. This is much faster than trying all 17 adapters (most fail
133
- // silently on a machine that doesn't have that tool installed).
147
+ // only those (plus the tokscale-blind platforms). This is much faster than trying
148
+ // all adapters (most fail silently on a machine that doesn't have that tool).
134
149
  //
135
150
  // Flow: tokscale surfaces ALL clients → map to our platform names →
136
151
  // ccusage is primary for Claude (more accurate) → other platforms use
137
152
  // their adapters. Anything in tokscale NOT in ccusage gets included.
138
- const detected = await _tokscaleDetectClients().catch(() => null);
153
+ // opts.detectClients overrides the tokscale probe for tests (no shelling out).
154
+ const detect = opts.detectClients || _tokscaleDetectClients;
155
+ const detected = await detect().catch(() => null);
139
156
  if (detected && detected.length > 0) {
140
- return _pullExplicit(detected, opts);
157
+ return _pullExplicit(withTokscaleBlind(detected), opts);
141
158
  }
142
159
  // Fallback: tokscale not installed or failed → try all adapters (old behavior)
143
160
  return _pullExplicit(ALL_PLATFORMS, opts);