@bobfrankston/rmfmail 1.2.250 → 1.2.252

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.
@@ -29,7 +29,15 @@ const bridge = read("client/lib/mailxapi.js");
29
29
  const rpc = read("packages/mailx-service/jsonrpc.ts");
30
30
 
31
31
  const called = new Set();
32
- for (const m of api.matchAll(/ipc\(\)\.(\w+)\s*\??\.?\s*\(/g)) called.add(m[1]);
32
+ // Match every shape api-client uses to reach the bridge:
33
+ // ipc().foo?.(…) plain
34
+ // (ipc() as any).foo?.(…) cast — the old pattern required a literal
35
+ // "ipc()." and skipped ALL of these
36
+ // const fn = (ipc() as any).foo; captured first, called later
37
+ // Hence: no trailing "(" requirement — a property read off the bridge is
38
+ // a call site as far as this contract is concerned. fetchRemoteImage was
39
+ // invisible to the old regex on both counts (Bob 2026-08-11).
40
+ for (const m of api.matchAll(/ipc\(\)(?:\s+as\s+[\w.<>[\]]+)?\s*\)?\.(\w+)/g)) called.add(m[1]);
33
41
 
34
42
  const inBridge = new Set();
35
43
  for (const m of bridge.matchAll(/^\s+(\w+)\s*:\s*(?:async\s+)?function/gm)) inBridge.add(m[1]);
package/bin/mailx.js CHANGED
@@ -448,6 +448,26 @@ if (hasFlag("register-share") || hasFlag("unregister-share")) {
448
448
  console.log(r.message);
449
449
  process.exit(r.ok ? 0 : 1);
450
450
  }
451
+ // AI spend report. Reads the per-call log written by aiTransform — no daemon
452
+ // needed, no IPC, so it works whether or not rmfmail is running. Bob asked to
453
+ // monitor AI costs (2026-08-11); this is the surface that answers it.
454
+ if (hasFlag("aiusage") || hasFlag("ai-usage")) {
455
+ const { summarizeAiUsage } = await import("@bobfrankston/mailx-service/ai-usage.js");
456
+ const all = summarizeAiUsage(0);
457
+ const mtd = summarizeAiUsage();
458
+ const fmt = (s, label) => {
459
+ console.log(`\n${label}: ${s.calls} call(s), ${s.inputTokens} input + ${s.outputTokens} output tokens`);
460
+ console.log(` cost: $${s.costUsd.toFixed(4)}${s.unpricedCalls ? ` (+ ${s.unpricedCalls} call(s) with no published price — tokens counted, cost not)` : ""}`);
461
+ for (const [model, m] of Object.entries(s.byModel)) {
462
+ console.log(` ${model}: ${m.calls} call(s), ${m.inputTokens} in / ${m.outputTokens} out, $${m.costUsd.toFixed(4)}`);
463
+ }
464
+ };
465
+ fmt(mtd, "This month");
466
+ fmt(all, "All time");
467
+ console.log("\nLocal models (ollama) run on this machine and cost $0 — they are counted so the total is measured, not assumed.");
468
+ console.log(`Per-call log: ${path.join(process.env.USERPROFILE || process.env.HOME || ".", ".rmfmail", "logs", "ai-usage.jsonl")}`);
469
+ process.exit(0);
470
+ }
451
471
  // Read our own version once — used for the instance file + upgrade check below.
452
472
  const __selfRoot = path.join(import.meta.dirname, "..");
453
473
  const __selfVersion = (() => {