@jeffreyjyz/mpc 0.1.4 → 0.1.6

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.
Files changed (48) hide show
  1. package/README.md +95 -93
  2. package/package.json +13 -5
  3. package/src/cli/engine/cost.ts +1 -4
  4. package/src/cli/engine/index.ts +3 -12
  5. package/src/cli/engine/project.ts +1 -4
  6. package/src/cli/engine/rows.ts +2 -12
  7. package/src/cli/engine/score.ts +6 -18
  8. package/src/cli/flow/check.ts +6 -17
  9. package/src/cli/flow/collect.ts +1 -3
  10. package/src/cli/flow/columns.ts +1 -3
  11. package/src/cli/flow/sort.ts +7 -24
  12. package/src/cli/options.ts +1 -3
  13. package/src/cli/parse/cac.ts +13 -52
  14. package/src/cli/parse/fields.ts +2 -6
  15. package/src/cli/parse/map.ts +7 -24
  16. package/src/cli/parse/plugins.ts +3 -11
  17. package/src/cli/parse/validate.ts +4 -12
  18. package/src/cli/run.ts +6 -27
  19. package/src/constants/cli.ts +1 -5
  20. package/src/constants/view.ts +3 -12
  21. package/src/data/bench/cc.ts +3 -12
  22. package/src/data/bench/index.ts +6 -22
  23. package/src/data/bench/resolve.ts +5 -18
  24. package/src/data/scrape/catalog/catalog.ts +6 -25
  25. package/src/data/scrape/catalog/numeric.ts +2 -10
  26. package/src/data/scrape/index.ts +2 -9
  27. package/src/data/scrape/tabs.ts +2 -5
  28. package/src/data/shape.ts +4 -18
  29. package/src/data/sources/aa/api.ts +2 -6
  30. package/src/data/sources/aa/parse.ts +3 -12
  31. package/src/data/sources/aa/web.ts +1 -3
  32. package/src/data/sources/cc/catalog.ts +4 -16
  33. package/src/data/sources/cc/plans.ts +1 -3
  34. package/src/data/sources/opencode.ts +2 -9
  35. package/src/data/usage/index.ts +5 -19
  36. package/src/data/usage/logs.ts +2 -6
  37. package/src/data/usage/opencodeDb.ts +4 -11
  38. package/src/data/usage/opencodeV2.ts +1 -4
  39. package/src/index.ts +1 -3
  40. package/src/view/footer.ts +8 -31
  41. package/src/view/layout/fit.ts +4 -16
  42. package/src/view/layout/segments.ts +5 -22
  43. package/src/view/layout/table.ts +4 -14
  44. package/src/view/layout/usage.ts +4 -12
  45. package/src/view/render.ts +1 -6
  46. package/src/view/text/export.ts +2 -7
  47. package/src/view/text/format.ts +2 -8
  48. package/src/view/text/styles.ts +3 -10
package/src/data/shape.ts CHANGED
@@ -88,11 +88,7 @@ export interface ShapeResolution {
88
88
  note: string;
89
89
  }
90
90
 
91
- async function payloadOf(
92
- source: string,
93
- args: string[],
94
- warn: boolean,
95
- ): Promise<string | null> {
91
+ async function payloadOf(source: string, args: string[], warn: boolean): Promise<string | null> {
96
92
  if (source !== SHAPE_MEASURED) {
97
93
  try {
98
94
  return await Bun.file(source).text();
@@ -128,10 +124,7 @@ async function payloadOf(
128
124
  * `note` names which path was taken, so the footer can show whether reqshape
129
125
  * was used and what the alternative flag is.
130
126
  */
131
- export async function loadShapes(
132
- spec: string,
133
- since?: string,
134
- ): Promise<ShapeResolution> {
127
+ export async function loadShapes(spec: string, since?: string): Promise<ShapeResolution> {
135
128
  const trimmed = spec.trim();
136
129
  const args = ["--format", "json"];
137
130
  if (since) args.push("--since", since);
@@ -139,10 +132,7 @@ export async function loadShapes(
139
132
  // A nested mpc (reqshape runs `mpc --json`) must not measure again — that is
140
133
  // the cycle. Callers that pass `--shape off` never reach here; this catches
141
134
  // the ones that do not (a sibling still pinned to the old default).
142
- if (
143
- process.env[SHAPE_GUARD_ENV] &&
144
- (trimmed === SHAPE_AUTO || trimmed === SHAPE_MEASURED)
145
- ) {
135
+ if (process.env[SHAPE_GUARD_ENV] && (trimmed === SHAPE_AUTO || trimmed === SHAPE_MEASURED)) {
146
136
  return {
147
137
  note: "fixed workload · nested mpc (reqshape is already resolving the shape)",
148
138
  };
@@ -176,11 +166,7 @@ export async function loadShapes(
176
166
  }
177
167
 
178
168
  const measured = trimmed === SHAPE_MEASURED;
179
- const payload = await payloadOf(
180
- measured ? SHAPE_MEASURED : trimmed,
181
- args,
182
- true,
183
- );
169
+ const payload = await payloadOf(measured ? SHAPE_MEASURED : trimmed, args, true);
184
170
  if (!payload) {
185
171
  return {
186
172
  note: "fixed workload · shape source unavailable · --shape auto to fall back automatically",
@@ -11,14 +11,10 @@ export async function loadAaApi(
11
11
  const maxPages = 20;
12
12
 
13
13
  for (let page = 1; page <= maxPages; page++) {
14
- const res = await fetchTextWithKey(
15
- `${API_URL}?page=${page}&page_size=${pageSize}`,
16
- key,
17
- );
14
+ const res = await fetchTextWithKey(`${API_URL}?page=${page}&page_size=${pageSize}`, key);
18
15
  const body = JSON.parse(res) as unknown;
19
16
  mergeAaPage(parseAaApi(body), intelligence, tps);
20
- const pagination = (body as { pagination?: { has_more?: boolean } })
21
- .pagination;
17
+ const pagination = (body as { pagination?: { has_more?: boolean } }).pagination;
22
18
  if (!pagination?.has_more) break;
23
19
  }
24
20
 
@@ -19,10 +19,7 @@ export function parseAaApi(body: unknown): {
19
19
  const record = row as Record<string, unknown>;
20
20
  const name = String(record.name ?? record.slug ?? record.id ?? "");
21
21
  const slug = typeof record.slug === "string" ? record.slug : undefined;
22
- const keys = [
23
- slug ? normalizeKey(slug) : "",
24
- normalizeKey(name),
25
- ].filter(Boolean);
22
+ const keys = [slug ? normalizeKey(slug) : "", normalizeKey(name)].filter(Boolean);
26
23
  if (keys.length === 0) continue;
27
24
 
28
25
  const iq = findNumber(record, /intelligence_index/i);
@@ -47,14 +44,8 @@ export function parseAaApi(body: unknown): {
47
44
  /** Depth-first search for the first finite number under a matching key. */
48
45
  function findNumber(value: unknown, pattern: RegExp): number | null {
49
46
  if (!value || typeof value !== "object") return null;
50
- for (const [key, child] of Object.entries(
51
- value as Record<string, unknown>,
52
- )) {
53
- if (
54
- typeof child === "number" &&
55
- Number.isFinite(child) &&
56
- pattern.test(key)
57
- ) {
47
+ for (const [key, child] of Object.entries(value as Record<string, unknown>)) {
48
+ if (typeof child === "number" && Number.isFinite(child) && pattern.test(key)) {
58
49
  return child;
59
50
  }
60
51
  }
@@ -64,9 +64,7 @@ function readArray(text: string, key: string): unknown[] | null {
64
64
 
65
65
  /** A finite, positive number; `null` for anything else (incl. 0 = unmeasured). */
66
66
  function positive(value: unknown): number | null {
67
- return typeof value === "number" && Number.isFinite(value) && value > 0
68
- ? value
69
- : null;
67
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
70
68
  }
71
69
 
72
70
  function labelOf(model: AaModel): string {
@@ -1,10 +1,5 @@
1
1
  import { CC_PLANS } from "#~/constants/sources.ts";
2
- import {
3
- extractCatalog,
4
- fetchText,
5
- parseRoleRows,
6
- parseTables,
7
- } from "#~/data/scrape/index.ts";
2
+ import { extractCatalog, fetchText, parseRoleRows, parseTables } from "#~/data/scrape/index.ts";
8
3
  import type { CatalogEntry } from "#~/types.ts";
9
4
 
10
5
  /** Per-model token rates + monthly credit allowance for a CommandCode plan. */
@@ -19,9 +14,7 @@ export async function loadCcCatalog(planId: string): Promise<CatalogEntry[]> {
19
14
  // <table> while still publishing no per-model credits, so "no credit header"
20
15
  // no longer means "grid" — inferring it sent Go down `parseRoleRows`, which
21
16
  // matches no row, and the load threw.
22
- const tables = def.grid
23
- ? await parseRoleRows(html)
24
- : await parseTables(html);
17
+ const tables = def.grid ? await parseRoleRows(html) : await parseTables(html);
25
18
  const entries = extractCatalog(tables, {
26
19
  provider: "cc",
27
20
  plan: def.label,
@@ -31,9 +24,7 @@ export async function loadCcCatalog(planId: string): Promise<CatalogEntry[]> {
31
24
  });
32
25
  if (entries.length === 0) {
33
26
  const shape = def.grid ? "row" : "table";
34
- throw new Error(
35
- `no model ${shape}s parsed from ${url} — docs layout may have changed`,
36
- );
27
+ throw new Error(`no model ${shape}s parsed from ${url} — docs layout may have changed`);
37
28
  }
38
29
  if (def.creditHeader && def.standardAllowance !== undefined) {
39
30
  fillDeals(
@@ -54,10 +45,7 @@ export async function loadCcCatalog(planId: string): Promise<CatalogEntry[]> {
54
45
  * entries that came from a credits table — those carry no badges, so without
55
46
  * this a paid model silently loses the deal published beside its name.
56
47
  */
57
- export function fillDeals(
58
- entries: CatalogEntry[],
59
- rateOnly: CatalogEntry[],
60
- ): void {
48
+ export function fillDeals(entries: CatalogEntry[], rateOnly: CatalogEntry[]): void {
61
49
  const byKey = new Map(entries.map((entry) => [entry.key, entry]));
62
50
  for (const entry of rateOnly) {
63
51
  const existing = byKey.get(entry.key);
@@ -13,9 +13,7 @@ export async function loadCcPlan(planId: string): Promise<PlanInfo> {
13
13
  const plans = await cmdusePlans();
14
14
  const match = plans.find((p) => p.name === def.cmduse);
15
15
  if (!match) {
16
- throw new Error(
17
- `cmduse plans --json has no plan named "${def.cmduse}"`,
18
- );
16
+ throw new Error(`cmduse plans --json has no plan named "${def.cmduse}"`);
19
17
  }
20
18
  return {
21
19
  provider: "cc",
@@ -1,9 +1,5 @@
1
1
  import { DOC_URL, OC_MODELS_URL, OC_PLANS } from "#~/constants/sources.ts";
2
- import {
3
- extractCatalog,
4
- fetchText,
5
- parseTabTables,
6
- } from "#~/data/scrape/index.ts";
2
+ import { extractCatalog, fetchText, parseTabTables } from "#~/data/scrape/index.ts";
7
3
  import type { CatalogEntry, PlanInfo } from "#~/types.ts";
8
4
 
9
5
  function planDef(planId: string) {
@@ -21,10 +17,7 @@ function planDef(planId: string) {
21
17
  * plans share one price list, so the tab label is what selects the right
22
18
  * allowance table: identical headers and rates, different monthly limits.
23
19
  */
24
- export async function loadOcGoCatalog(
25
- planId: string,
26
- peak = false,
27
- ): Promise<CatalogEntry[]> {
20
+ export async function loadOcGoCatalog(planId: string, peak = false): Promise<CatalogEntry[]> {
28
21
  const def = planDef(planId);
29
22
  const html = await fetchText(DOC_URL);
30
23
  const tabs = await parseTabTables(html);
@@ -2,12 +2,7 @@ import { runCmduse } from "#~/data/cmduse.ts";
2
2
  import { defaultUsageLog, mergeUsage, readUsageLog } from "./log.ts";
3
3
  import { scanLogs } from "./logs.ts";
4
4
  import { defaultOpencodeDb, readOpencodeDb } from "./opencodeDb.ts";
5
- import {
6
- type AccountSummary,
7
- accountSummary,
8
- parseUsage,
9
- type UsageEntry,
10
- } from "./parse.ts";
5
+ import { type AccountSummary, accountSummary, parseUsage, type UsageEntry } from "./parse.ts";
11
6
 
12
7
  export type { AccountSummary, UsageEntry } from "./parse.ts";
13
8
 
@@ -40,10 +35,7 @@ async function cmduseModel(since?: Date): Promise<UsageEntry[] | null> {
40
35
  }
41
36
  }
42
37
 
43
- function sinceFor(
44
- window: UsageWindow,
45
- account?: AccountSummary,
46
- ): Date | undefined {
38
+ function sinceFor(window: UsageWindow, account?: AccountSummary): Date | undefined {
47
39
  if (window === "all") return undefined;
48
40
  if (window.endsWith("d")) {
49
41
  const days = Number(window.slice(0, -1));
@@ -76,8 +68,7 @@ export async function loadUsage(
76
68
  ): Promise<UsageReportInput> {
77
69
  if (source) {
78
70
  const file = Bun.file(source);
79
- if (!(await file.exists()))
80
- throw new Error(`usage file not found: ${source}`);
71
+ if (!(await file.exists())) throw new Error(`usage file not found: ${source}`);
81
72
  return {
82
73
  entries: parseUsage(await file.text()),
83
74
  label: source,
@@ -96,19 +87,14 @@ export async function loadUsage(
96
87
 
97
88
  const sources: string[] = [];
98
89
  if (db) {
99
- sources.push(
100
- `opencode db (${db.providers.length} providers, ${db.records} records)`,
101
- );
90
+ sources.push(`opencode db (${db.providers.length} providers, ${db.records} records)`);
102
91
  }
103
92
  if (log) sources.push("provider usage log");
104
93
  if (sessions.length > 0) {
105
94
  sources.push(fromCmduse ? "cmduse model --json" : "local session logs");
106
95
  }
107
96
 
108
- const entries = mergeUsage(
109
- mergeUsage(db?.entries ?? [], log ?? []),
110
- sessions,
111
- );
97
+ const entries = mergeUsage(mergeUsage(db?.entries ?? [], log ?? []), sessions);
112
98
  return {
113
99
  entries,
114
100
  label: sources.join(" + ") || "no usage found",
@@ -43,10 +43,7 @@ function jsonlFiles(dir: string, out: string[]): string[] {
43
43
  continue;
44
44
  }
45
45
  if (stat.isDirectory()) jsonlFiles(path, out);
46
- else if (
47
- name.endsWith(".jsonl") &&
48
- !name.endsWith(".checkpoints.jsonl")
49
- ) {
46
+ else if (name.endsWith(".jsonl") && !name.endsWith(".checkpoints.jsonl")) {
50
47
  out.push(path);
51
48
  }
52
49
  }
@@ -91,8 +88,7 @@ export function scanLogs(
91
88
  continue;
92
89
  }
93
90
  if (!line.usage || !line.model) continue;
94
- if (since && line.timestamp && new Date(line.timestamp) < since)
95
- continue;
91
+ if (since && line.timestamp && new Date(line.timestamp) < since) continue;
96
92
  const key = normalizeKey(line.model);
97
93
  const bucket = byModel.get(key) ?? {
98
94
  name: line.model,
@@ -8,8 +8,7 @@ import type { UsageEntry } from "./parse.ts";
8
8
  /** Default location of opencode's message store. */
9
9
  export function defaultOpencodeDb(): string {
10
10
  if (process.env.OPENCODE_DB) return process.env.OPENCODE_DB;
11
- const base =
12
- process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
11
+ const base = process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
13
12
  return join(base, "opencode", "opencode.db");
14
13
  }
15
14
 
@@ -48,10 +47,7 @@ export interface OpencodeDbUsage {
48
47
  * table otherwise, so an upgraded opencode does not read as "no usage". Read-only
49
48
  * so a running opencode is unaffected; returns null when the DB is unusable.
50
49
  */
51
- export function readOpencodeDb(
52
- path: string,
53
- since?: Date,
54
- ): OpencodeDbUsage | null {
50
+ export function readOpencodeDb(path: string, since?: Date): OpencodeDbUsage | null {
55
51
  let db: Database;
56
52
  try {
57
53
  db = new Database(path, { readonly: true });
@@ -60,9 +56,7 @@ export function readOpencodeDb(
60
56
  }
61
57
  try {
62
58
  const sinceMs = since ? since.getTime() : 0;
63
- const turns = hasV2(db)
64
- ? readV2Turns(db, sinceMs)
65
- : readLegacyTurns(db, sinceMs);
59
+ const turns = hasV2(db) ? readV2Turns(db, sinceMs) : readLegacyTurns(db, sinceMs);
66
60
 
67
61
  const byKey = new Map<string, UsageEntry>();
68
62
  const seen = new Set<string>();
@@ -121,8 +115,7 @@ function readLegacyTurns(db: Database, sinceMs: number): Turn[] {
121
115
  } catch {
122
116
  continue;
123
117
  }
124
- if (data.role !== "assistant" || !data.modelID || !data.tokens)
125
- continue;
118
+ if (data.role !== "assistant" || !data.modelID || !data.tokens) continue;
126
119
  turns.push({
127
120
  id: `${data.modelID}@${(data.time?.created ?? 0) as number}`,
128
121
  modelID: data.modelID,
@@ -58,10 +58,7 @@ export function readV2Turns(db: Database, sinceMs: number): Turn[] {
58
58
  turns.push({
59
59
  id: row.id,
60
60
  modelID: model.id,
61
- providerID:
62
- typeof model.providerID === "string"
63
- ? model.providerID
64
- : "unknown",
61
+ providerID: typeof model.providerID === "string" ? model.providerID : "unknown",
65
62
  cost: typeof data.cost === "number" ? data.cost : 0,
66
63
  tokens,
67
64
  });
package/src/index.ts CHANGED
@@ -4,8 +4,6 @@ import { run } from "./cli/run.ts";
4
4
  try {
5
5
  process.exitCode = await run(process.argv.slice(2));
6
6
  } catch (error) {
7
- console.error(
8
- `mpc: ${error instanceof Error ? error.message : String(error)}`,
9
- );
7
+ console.error(`mpc: ${error instanceof Error ? error.message : String(error)}`);
10
8
  process.exitCode = 1;
11
9
  }
@@ -2,12 +2,7 @@ import { LEGEND, PROVIDER_COLOR, SGR } from "#~/constants/view.ts";
2
2
  import type { CompareRow, PlanInfo, Workload } from "#~/types.ts";
3
3
  import { tally } from "./layout/segments.ts";
4
4
  import type { ReportMeta } from "./schema.ts";
5
- import {
6
- paint,
7
- planTitle,
8
- providerName,
9
- shortProviderName,
10
- } from "./text/index.ts";
5
+ import { paint, planTitle, providerName, shortProviderName } from "./text/index.ts";
11
6
 
12
7
  /** One side's per-request token vector; fields with no measured signal stay out. */
13
8
  function shapeText(workload: Workload): string {
@@ -20,9 +15,7 @@ function shapeText(workload: Workload): string {
20
15
  parts.push(`${workload.reasoning.toLocaleString("en-US")} reasoning`);
21
16
  }
22
17
  if (workload.cacheWrite > 0) {
23
- parts.push(
24
- `${workload.cacheWrite.toLocaleString("en-US")} cache-write`,
25
- );
18
+ parts.push(`${workload.cacheWrite.toLocaleString("en-US")} cache-write`);
26
19
  }
27
20
  return parts.join(" · ");
28
21
  }
@@ -63,8 +56,7 @@ export function footer(rows: CompareRow[], meta: ReportMeta): void {
63
56
  // end, which made every label as faint as its value and read as one grey
64
57
  // block (the shape decision among it).
65
58
  const label = (text: string): string => paint(SGR.bold, text.padEnd(10));
66
- const line = (key: string, text: string): void =>
67
- console.log(`${label(key)}${text}`);
59
+ const line = (key: string, text: string): void => console.log(`${label(key)}${text}`);
68
60
  const plans = [meta.ocPlan, meta.ccPlan].map(planBlock);
69
61
  const planWidth = Math.max(...plans.map((p) => p.title.length));
70
62
  const [ocBlock, ccBlock] = plans;
@@ -79,10 +71,7 @@ export function footer(rows: CompareRow[], meta: ReportMeta): void {
79
71
  `${perSide ? `${shortProviderName("oc-go")} ` : ""}${shapeText(ocWorkload)} tokens per request`,
80
72
  );
81
73
  if (perSide) {
82
- line(
83
- "",
84
- `${shortProviderName("cc")} ${shapeText(ccWorkload)} tokens per request`,
85
- );
74
+ line("", `${shortProviderName("cc")} ${shapeText(ccWorkload)} tokens per request`);
86
75
  }
87
76
  if (meta.shapeNote) line("shape", meta.shapeNote);
88
77
 
@@ -97,28 +86,16 @@ export function footer(rows: CompareRow[], meta: ReportMeta): void {
97
86
  `rolling caps as % of monthly allowance: ${planTitle(meta.ocPlan)} ${windowPercents(meta.ocPlan, meta.window)} · ${planTitle(meta.ccPlan)} ${windowPercents(meta.ccPlan, meta.window)}`,
98
87
  );
99
88
  if (meta.abilityLabel) {
100
- line(
101
- "ability",
102
- `${meta.abilityLabel}${meta.abilityNote ? ` · ${meta.abilityNote}` : ""}`,
103
- );
89
+ line("ability", `${meta.abilityLabel}${meta.abilityNote ? ` · ${meta.abilityNote}` : ""}`);
104
90
  }
105
91
 
106
92
  const t = tally(rows);
107
- const parts = [
108
- `${providerName("oc-go")} ${t.ocWins}`,
109
- `${providerName("cc")} ${t.ccWins}`,
110
- ];
93
+ const parts = [`${providerName("oc-go")} ${t.ocWins}`, `${providerName("cc")} ${t.ccWins}`];
111
94
  if (t.ties > 0) parts.push(`tie ${t.ties}`);
112
95
  line("wins", `head-to-head ${t.headToHead} shared: ${parts.join(" · ")}`);
113
- line(
114
- "",
115
- `exclusive: ${providerName("oc-go")} ${t.ocOnly} · ${providerName("cc")} ${t.ccOnly}`,
116
- );
96
+ line("", `exclusive: ${providerName("oc-go")} ${t.ocOnly} · ${providerName("cc")} ${t.ccOnly}`);
117
97
 
118
98
  LEGEND.forEach(([key, text], i) => {
119
- line(
120
- i === 0 ? "legend" : "",
121
- `${paint(SGR.bold, (key ?? "").padEnd(9))}${text}`,
122
- );
99
+ line(i === 0 ? "legend" : "", `${paint(SGR.bold, (key ?? "").padEnd(9))}${text}`);
123
100
  });
124
101
  }
@@ -3,10 +3,7 @@ import type { Column, FitResult, Row } from "#~/view/schema.ts";
3
3
  import { columns } from "#~/view/schema.ts";
4
4
  import { buildSegments } from "./segments.ts";
5
5
 
6
- function measure(
7
- rows: Row[],
8
- ids: string[],
9
- ): { cols: Column[]; widths: number[] } {
6
+ function measure(rows: Row[], ids: string[]): { cols: Column[]; widths: number[] } {
10
7
  const cols = columns(ids);
11
8
  const widths = cols.map((c) => c.header.length);
12
9
  for (const row of rows) {
@@ -23,10 +20,7 @@ export function tableWidth(rows: Row[], ids: string[]): number {
23
20
  const { cols, widths } = measure(rows, ids);
24
21
  const segs = buildSegments(ids, cols, widths);
25
22
  if (segs.length === 0) return 0;
26
- return (
27
- segs.reduce((sum, seg) => sum + seg.width, 0) +
28
- BAR.length * (segs.length - 1)
29
- );
23
+ return segs.reduce((sum, seg) => sum + seg.width, 0) + BAR.length * (segs.length - 1);
30
24
  }
31
25
 
32
26
  /**
@@ -34,11 +28,7 @@ export function tableWidth(rows: Row[], ids: string[]): number {
34
28
  * with the same `drop` value are removed together, so both providers stay
35
29
  * symmetric.
36
30
  */
37
- export function fitColumns(
38
- rows: Row[],
39
- ids: string[],
40
- limit: number,
41
- ): FitResult {
31
+ export function fitColumns(rows: Row[], ids: string[], limit: number): FitResult {
42
32
  let current = [...ids];
43
33
  const dropped: string[] = [];
44
34
  while (limit > 0 && tableWidth(rows, current) > limit) {
@@ -47,9 +37,7 @@ export function fitColumns(
47
37
  .filter((drop): drop is number => drop !== undefined);
48
38
  if (levels.length === 0) break;
49
39
  const lowest = Math.min(...levels);
50
- const remove = new Set(
51
- current.filter((id) => columns([id])[0]?.drop === lowest),
52
- );
40
+ const remove = new Set(current.filter((id) => columns([id])[0]?.drop === lowest));
53
41
  if (remove.size === 0) break;
54
42
  current = current.filter((id) => !remove.has(id));
55
43
  dropped.push(...remove);
@@ -9,11 +9,7 @@ function groupOf(id: string): GroupKey {
9
9
  return "misc";
10
10
  }
11
11
 
12
- export function buildSegments(
13
- ids: string[],
14
- cols: Column[],
15
- widths: number[],
16
- ): Segment[] {
12
+ export function buildSegments(ids: string[], cols: Column[], widths: number[]): Segment[] {
17
13
  const segs: Segment[] = [];
18
14
  ids.forEach((id, i) => {
19
15
  const column = cols[i];
@@ -33,19 +29,12 @@ export function buildSegments(
33
29
  }
34
30
  });
35
31
  for (const seg of segs) {
36
- seg.width =
37
- seg.widths.reduce((a, b) => a + b, 0) +
38
- GAP.length * (seg.widths.length - 1);
32
+ seg.width = seg.widths.reduce((a, b) => a + b, 0) + GAP.length * (seg.widths.length - 1);
39
33
  }
40
34
  return segs;
41
35
  }
42
36
 
43
- function cellText(
44
- col: Column,
45
- text: string,
46
- width: number,
47
- alignRight: boolean,
48
- ): string {
37
+ function cellText(col: Column, text: string, width: number, alignRight: boolean): string {
49
38
  if (col.pad === false) return text;
50
39
  return alignRight && col.right ? text.padStart(width) : text.padEnd(width);
51
40
  }
@@ -58,12 +47,7 @@ export function renderSegment(
58
47
  ): string {
59
48
  return seg.columns
60
49
  .map((col, i) => {
61
- const text = cellText(
62
- col,
63
- cells[i] ?? "",
64
- seg.widths[i] ?? 0,
65
- alignRight,
66
- );
50
+ const text = cellText(col, cells[i] ?? "", seg.widths[i] ?? 0, alignRight);
67
51
  const code = styles?.[i];
68
52
  return code ? paint(code, text) : text;
69
53
  })
@@ -85,8 +69,7 @@ export function tally(rows: Row[]): Tally {
85
69
  if (row.oc && row.cc) {
86
70
  result.headToHead++;
87
71
  if (row.oc.payPerRequest === row.cc.payPerRequest) result.ties++;
88
- else if (row.oc.payPerRequest < row.cc.payPerRequest)
89
- result.ocWins++;
72
+ else if (row.oc.payPerRequest < row.cc.payPerRequest) result.ocWins++;
90
73
  else result.ccWins++;
91
74
  } else if (row.oc) {
92
75
  result.ocOnly++;
@@ -4,10 +4,7 @@ import { columns } from "#~/view/schema.ts";
4
4
  import { paint } from "#~/view/text/index.ts";
5
5
  import { buildSegments, renderSegment } from "./segments.ts";
6
6
 
7
- function measure(
8
- rows: Row[],
9
- ids: string[],
10
- ): { cols: Column[]; widths: number[] } {
7
+ function measure(rows: Row[], ids: string[]): { cols: Column[]; widths: number[] } {
11
8
  const cols = columns(ids);
12
9
  const widths = cols.map((c) => c.header.length);
13
10
  for (const row of rows) {
@@ -34,15 +31,10 @@ export function printTable(
34
31
  .map((seg) => {
35
32
  const label = labels[seg.key];
36
33
  if (!label) return " ".repeat(seg.width);
37
- const left = Math.max(
38
- 0,
39
- Math.floor((seg.width - label.title.length) / 2),
40
- );
34
+ const left = Math.max(0, Math.floor((seg.width - label.title.length) / 2));
41
35
  return paint(
42
36
  label.color,
43
- label.title
44
- .padStart(left + label.title.length)
45
- .padEnd(seg.width),
37
+ label.title.padStart(left + label.title.length).padEnd(seg.width),
46
38
  );
47
39
  })
48
40
  .join(bar),
@@ -65,9 +57,7 @@ export function printTable(
65
57
  )
66
58
  .join(bar),
67
59
  );
68
- console.log(
69
- segs.map((seg) => "─".repeat(seg.width)).join(paint(SGR.dim, BAR_RULE)),
70
- );
60
+ console.log(segs.map((seg) => "─".repeat(seg.width)).join(paint(SGR.dim, BAR_RULE)));
71
61
  for (const row of rows) {
72
62
  console.log(
73
63
  segs
@@ -85,9 +85,7 @@ function headToHeadLine(report: UsageProjection): string {
85
85
  }
86
86
  const excluded = head.total - head.shared;
87
87
  const note =
88
- excluded > 0
89
- ? ` (${excluded} one-sided row${excluded === 1 ? "" : "s"} excluded)`
90
- : "";
88
+ excluded > 0 ? ` (${excluded} one-sided row${excluded === 1 ? "" : "s"} excluded)` : "";
91
89
  const delta = Math.abs(head.cc - head.oc);
92
90
  const pct = head.cc > 0 ? (delta / head.cc) * 100 : 0;
93
91
  const verdict =
@@ -129,9 +127,7 @@ export function renderUsage(report: UsageProjection, meta: UsageMeta): void {
129
127
  const line = (values: string[], rightSet: Set<number>): string =>
130
128
  values
131
129
  .map((v, i) =>
132
- rightSet.has(i)
133
- ? v.padStart(widths[i] ?? 0)
134
- : v.padEnd(widths[i] ?? 0),
130
+ rightSet.has(i) ? v.padStart(widths[i] ?? 0) : v.padEnd(widths[i] ?? 0),
135
131
  )
136
132
  .join(" ");
137
133
  const right = new Set([1, 2, 3, 4, 5, 6]);
@@ -149,15 +145,11 @@ export function renderUsage(report: UsageProjection, meta: UsageMeta): void {
149
145
  console.log(
150
146
  `${paint(SGR.bold, "plans")} ${planTitle(meta.plans["oc-go"])} $${meta.plans["oc-go"].price}/mo · ${planTitle(meta.plans.cc)} $${meta.plans.cc.price}/mo`,
151
147
  );
152
- const over = report.rows
153
- .filter((r) => r.cc?.overCap || r.oc?.overCap)
154
- .map((r) => r.name);
148
+ const over = report.rows.filter((r) => r.cc?.overCap || r.oc?.overCap).map((r) => r.name);
155
149
  if (over.length > 0) {
156
150
  console.log(dim(`over cap for at least one plan: ${over.join(", ")}`));
157
151
  }
158
152
  if (report.unmatched.length > 0) {
159
- console.log(
160
- dim(`unmatched models (excluded): ${report.unmatched.join(", ")}`),
161
- );
153
+ console.log(dim(`unmatched models (excluded): ${report.unmatched.join(", ")}`));
162
154
  }
163
155
  }
@@ -11,12 +11,7 @@ export { fitColumns, tableWidth } from "./layout/fit.ts";
11
11
  export { tally } from "./layout/segments.ts";
12
12
  export { columns } from "./schema.ts";
13
13
  export { renderCsv, renderMarkdown } from "./text/export.ts";
14
- export {
15
- fmtUsd,
16
- planTitle,
17
- providerName,
18
- shortProviderName,
19
- } from "./text/index.ts";
14
+ export { fmtUsd, planTitle, providerName, shortProviderName } from "./text/index.ts";
20
15
  export { setAbilityRange, setColor, setColorMode, setThresholds };
21
16
 
22
17
  export function renderText(
@@ -41,10 +41,7 @@ export function renderCsv(rows: Row[], summary: Tally): string {
41
41
  }
42
42
 
43
43
  /** Markdown table with the default columns. */
44
- export function renderMarkdown(
45
- rows: Row[],
46
- plans: [PlanInfo, PlanInfo],
47
- ): string {
44
+ export function renderMarkdown(rows: Row[], plans: [PlanInfo, PlanInfo]): string {
48
45
  const lines = [
49
46
  `# ${planTitle(plans[0])} vs ${planTitle(plans[1])}`,
50
47
  "",
@@ -64,9 +61,7 @@ export function renderMarkdown(
64
61
  String(100 - Math.max(row.oc?.index ?? -1, row.cc?.index ?? -1, 0)),
65
62
  String(row.oc?.valueIndex ?? row.cc?.valueIndex ?? "—"),
66
63
  ];
67
- lines.push(
68
- `| ${cells.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`,
69
- );
64
+ lines.push(`| ${cells.map((c) => c.replace(/\|/g, "\\|")).join(" | ")} |`);
70
65
  }
71
66
  return `${lines.join("\n")}\n`;
72
67
  }
@@ -41,10 +41,7 @@ export function fmtPerDollar(m: EntryMetrics | undefined): string {
41
41
  return fmtCount(1 / m.payPerRequest);
42
42
  }
43
43
 
44
- export function fmtRequests(
45
- value: number | undefined,
46
- hasEntry: boolean,
47
- ): string {
44
+ export function fmtRequests(value: number | undefined, hasEntry: boolean): string {
48
45
  if (!hasEntry || value === undefined) return "—";
49
46
  return fmtCount(value);
50
47
  }
@@ -71,9 +68,6 @@ export function shortProviderName(provider: ProviderId): string {
71
68
  }
72
69
 
73
70
  /** e.g. "OpenCode Go", "CommandCode GOAT". */
74
- export function planTitle(plan: {
75
- provider: ProviderId;
76
- label: string;
77
- }): string {
71
+ export function planTitle(plan: { provider: ProviderId; label: string }): string {
78
72
  return `${providerName(plan.provider)} ${plan.label}`;
79
73
  }