Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- gitnexus:start -->
# GitNexus MCP

This project is indexed by GitNexus as **claude-session-intelligence** (698 symbols, 2165 relationships, 57 execution flows).
This project is indexed by GitNexus as **claude-session-intelligence** (704 symbols, 2183 relationships, 58 execution flows).

## Always Start Here

Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Disable with `toolArchive.enabled=false` or `CLAUDE_TOOL_ARCHIVE=0` if you don't
<!-- gitnexus:start -->
# GitNexus MCP

This project is indexed by GitNexus as **claude-session-intelligence** (698 symbols, 2165 relationships, 57 execution flows).
This project is indexed by GitNexus as **claude-session-intelligence** (704 symbols, 2183 relationships, 58 execution flows).

## Always Start Here

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ Set `fields` in `~/.claude/statusline-intel.json` to the list + order you want.
| `compactCost` | `c$12.34 / s$68.20` | Cost + cache-savings accrued **since last /compact**. Incrementally cached at `/tmp/claude-cost-sincecompact-<sid>`, auto-invalidated when compact mtime advances. Shows `c$` only when ≥$0.01, `s$` only when ≥$0.10; hidden when both trivial (dim) |
| `blockUsage` | `b:47% r:1hr 12m` | Claude's 5-hour block quota used + reset countdown; leads line 2 next to session duration + time until reset. Sourced from statusline stdin `rate_limits` (CC ≥2.1.90); cached `/api/oauth/usage` fallback for older builds, rendering last-good values with a `~` marker during API outages |
| `weekUsage` | `w:31% r:4d 12hr` | Weekly (7-day) quota used + time until reset. Same sourcing as `blockUsage`; % zone-coloured: green <60%, yellow 60-85%, orange 85-95%, red ≥95% |
| `modelSplit` | `fable-5:sc$80·sp99%·wr:~85%` | Per model: session cost (`sc$`), share of session spend (`sp%`), and estimated weekly window remaining (`wr:~%`). `wr` attributes the account weekly used% across models by their share of this week's tracked spend — an SI estimate (hence `~`), since Anthropic exposes no per-model weekly quota. Parent-session splits are recorded to `session_model_costs` on each redraw, so the weekly attribution gets richer as sessions run |
| `modelSplit` | `fable-5:$1.7k·61% opus-4-8:$938·33%` | Per model: session spend (`$`, abbreviated to `k` past $1000) and share of session spend (`%`). Merges parent-conversation turns with subagent runs from the events DB; top 4 models, ones rounding to 0% dropped. No per-model quota is shown — Anthropic exposes only the account-wide weekly window (the `w:` cell), so a per-model "weekly remaining" would be fabricated. Per-model session spend is still recorded to `session_model_costs` for `/si stats` |
| `tokenSpeed` | `tps:238` | Output-token generation speed (tokens/sec) — median of the last few streamed turns, measured from per-message chunk-timestamp spans (pure generation time). Falls back to session average (total output / total API time) when the tail has no streamed turn |
| `modelQuota` | `opus:12/88%` | Per-model QUOTA used/available — dormant until Anthropic populates per-model limits (stdin extra `rate_limits` keys or `seven_day_<model>` buckets in the usage payload; both are null on plans without per-model limits) |
| `cwd` | `~/DWS/claude-session-intelligence` | Full working directory, ccstatusline-style. `$HOME` collapses to `~`; middle-ellipsis when longer than 60 chars (keeps the leaf) (dim) |
Expand Down
60 changes: 27 additions & 33 deletions plugins/session-intelligence/statusline/statusline-intel.js
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,17 @@ function fmtUsd(n) {
return `$${n.toFixed(2)}`;
}

// Denser USD for the per-model split, where a long session stacks several
// figures on one line: roll to `k` past $1000 so `$1727` reads `$1.7k`,
// `$12480` reads `$12k`. Small values keep cents.
function fmtUsdShort(n) {
if (!Number.isFinite(n) || n <= 0) return '$0';
if (n >= 10000) return `$${Math.round(n / 1000)}k`;
if (n >= 1000) return `$${(n / 1000).toFixed(1)}k`;
if (n >= 10) return `$${Math.round(n)}`;
return `$${n.toFixed(2)}`;
}

// Single-letter duration formatter for tight cells (compactAge, session).
// `3hr 23m` -> `3h23m`, `11hr` -> `11h`, `45m` -> `45m`. Spans over 1d
// keep the day component and skip minutes — the precision isn't useful at
Expand Down Expand Up @@ -1519,7 +1530,7 @@ function buildRenderers(C) {
return `${C.dim}tps:${v}${C.reset}`;
},

modelSplit: (input, ctx) => {
modelSplit: (_input, ctx) => {
// Parent-conversation turns from the transcript accumulator...
const byModel = (ctx.tokenTotals && ctx.tokenTotals.byModel) || {};
const merged = { ...byModel };
Expand All @@ -1534,45 +1545,28 @@ function buildRenderers(C) {
if (!entries.length || total <= 0) return '';
entries.sort((a, b) => b[1] - a[1]);

const events = loadEventsLib();
// Persist this session's per-model split so weeklyModelSpend can see
// parent-session spend across sessions (agents are already in the DB).
// Persist this session's per-model split for cross-session analytics
// (/si stats) — the only surviving record of parent-session splits
// once a transcript ages out.
try {
const events = loadEventsLib();
if (events && typeof events.upsertSessionModelCosts === 'function' && ctx.sessionId) {
events.upsertSessionModelCosts(ctx.sessionId, byModel);
}
} catch { /* telemetry is best-effort */ }

// Per-model weekly-remaining ESTIMATE (`wr:~61%`): Anthropic exposes
// no per-model weekly quota (seven_day_<model> buckets are null on
// plans without per-model limits — verified 2026-07-17), so attribute
// the account weekly used% across models by their share of this
// week's tracked spend: wr(model) = 100 − weeklyUsed × spendShare.
// `~` marks it as SI's estimate, not an Anthropic number.
let weeklyUsed = null;
const sd = input && input.rate_limits && input.rate_limits.seven_day;
if (sd && typeof sd.used_percentage === 'number') weeklyUsed = sd.used_percentage;
let weekSpend = null;
if (weeklyUsed !== null && events && typeof events.weeklyModelSpend === 'function') {
try { weekSpend = events.weeklyModelSpend(); } catch { weekSpend = null; }
}
const weekTotal = weekSpend ? weekSpend.reduce((sum, r) => sum + (r.cost_usd || 0), 0) : 0;
const weekByModel = weekSpend
? Object.fromEntries(weekSpend.map((r) => [r.model, r.cost_usd || 0]))
: {};

const parts = entries.slice(0, 4).map(([m, c]) => {
// Self-labelling `$·%`: session spend + share of session spend. No
// per-model quota shown — Anthropic exposes only the account-wide
// weekly window (the separate `w:` cell), so a per-model "weekly
// remaining" would be fabricated. Models rounding to 0% are dropped.
const parts = [];
for (const [m, c] of entries.slice(0, 4)) {
const share = Math.round((c / total) * 100);
if (share < 1) continue;
const name = m.replace(/^claude-/, '').replace(/-20\d{6,}$/, '');
const usd = fmtUsd(c);
let cell = `${name}:sc${usd || '$0'}\u00b7sp${Math.round((c / total) * 100)}%`;
if (weeklyUsed !== null && weekTotal > 0) {
const share = (weekByModel[m] || 0) / weekTotal;
const wr = Math.max(0, Math.min(100, Math.round(100 - weeklyUsed * share)));
cell += `\u00b7wr:~${wr}%`;
}
return cell;
});
return `${C.dim}${parts.join(' ')}${C.reset}`;
parts.push(`${name}:${fmtUsdShort(c)}·${share}%`);
}
return parts.length ? `${C.dim}${parts.join(' ')}${C.reset}` : '';
},

/** Per-model QUOTA used/available — dormant until real data exists.
Expand Down
Loading