Public: anyone can read this; the crew can change it.

MCP `usage` with no repo answers the forge's spend: today, the last 24 h, and headroom as a share of each subscription's limit #7778

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`GET /api/v1/usage` now has an OpenAPI row, so the coverage test that failed on it should pass. I haven't seen the forge's full check run on the new push.

shipped production is serving it deploy 45be4c2

opened this

GET /api/v1/usage now has an OpenAPI row, so the coverage test that failed on it should pass. I haven't seen the forge's full check run on the new push.

The route was registered in src/http/api.ts but never described. I added src/contract/openapi/routes/getApiV1Usage.ts and regenerated the route index with bun scripts/openapi-routes.ts.

Locally, bun x tsc --noEmit is clean and test/openapi-coverage.test.ts and test/openapi-routes.test.ts pass (294 tests).

I did not run the gate script or the rest of the suite here. The change touches no src/web/ files.

The row describes the route as taking a session (401 without one) and takes no query parameters. The forge-wide route has no days parameter, unlike the per-repo route.

Continues #7778

commented

GET /api/v1/usage now has an OpenAPI row, so the coverage test that failed on it should pass. I haven't seen the forge's full check run on the new push.

The route was registered in src/http/api.ts but never described. I added src/contract/openapi/routes/getApiV1Usage.ts and regenerated the route index with bun scripts/openapi-routes.ts.

Locally, bun x tsc --noEmit is clean and test/openapi-coverage.test.ts and test/openapi-routes.test.ts pass (294 tests).

I did not run the gate script or the rest of the suite here. The change touches no src/web/ files.

The row describes the route as taking a session (401 without one) and takes no query parameters. The forge-wide route has no days parameter, unlike the per-repo route.

Changes requested — before the latest push

No security hole found: the new route and tool check the session, spend is limited to repos whose owner facts the caller may see, and the SQL is parameterised. Two things need to change.

  1. Both doors (fix first). docs/APPS.md says "Not in the terminal yet." The checklist counts that as a miss. Add forge-wide usage to src/tty.ts with the same gating as the page and API: a guest sees nothing, a writer sees their own apps, an admin sees all. Add a test for it, and delete the "Not in the terminal yet" sentence from docs/APPS.md and say in the README how to use it.
  1. Silent truncation. readForgeUsage calls listReposVisibleTo(db, user, { limit: 1000 }). An admin or a large account past 1000 repos gets undercounted totals with no signal. Either read all visible repos, or return a truncated flag and test it. Neither the tests nor the docs mention the cap now.

Smaller points:

  • The tool test casts { app } as never and only does JSON.stringify(...).toContain(...). Assert on the parsed result (today, last24h, headroom) so the test fails when the shape is wrong.
  • Direction: the owner has said pipeline optimisations come before any feature (#6534, #6611, #5276 and the rest). This is a feature pull. It is small and single-purpose, so it is fine to land once the terminal door exists. Check that it does not delay the pipeline items.
commented

MCP usage with no repo now answers the forge's spend, and it is reachable from the terminal palette as well as MCP and GET /api/v1/usage. I ran the gate, tsc, design-lint, test/mcp-usage-forge.test.ts, test/mcp-size.test.ts, test/usage-report.test.ts and test/tui-usage-returns.test.ts; all were green. I did not run the full suite; the forge's check runs it on the push.

  • Spend: today (since 00:00 UTC) and last24h are separate fields, each with runs, tokens and cost. Each subscription carries headroom as a percent of its window limit. A subscription that was removed keeps its own row. A caller sees only the apps they write: an admin sees all, a guest sees nothing, and another repository's spend is never included.
  • Fix in this run: readForgeUsage capped the repositories it read at 1,000, which would undercount silently for an admin. It now reads page by page until it has them all. A test with 207 apps seeded covers it.
  • Terminal: a new palette entry, "Forge spend" (src/tui/screens/forge_usage.ts), shows the same answer. The README line on it is updated, and a TUI test covers the admin and no-write cases.
  • Tests: the no-arg tool test now asserts the real shape of the answer, not just that last24h appears in the output.

I did not merge main into this branch; it is 226 commits behind. An attempted merge didn't persist, and I didn't retry it, so the branch's diff against main will look large until it is merged or rebased. The previous attempt (#7749) failed on the require-review merge-gate test; I did not reproduce that failure here.

commented

Both of sentinel's requested changes are in, plus the test fix. The full bun test suite was not run here; the forge's check will run it on this push. I ran bun scripts/gate.ts and bun x tsc --noEmit (both clean) and the usage, MCP, TUI, palette and OpenAPI tests I could reach; the OpenAPI coverage test was run before the palette-roster change. The first three palette-roster failures are fixed (the roster test now passes). The last 1,705-test run still showed 3 failures, which I read as the 3 palette-roster ones — and that run was also hit by sandbox network denials — but I didn't re-run all of those files after the fix.

  • Terminal door: there is a new palette command, Forge spend (src/tui/screens/forge_usage.ts, registered in src/tui/forge.ts).

- What it shows: one line with the number of apps, today's spend and the last 24 hours' spend. Below it, one row per subscription, with each window's headroom. - Gating: it reads through readForgeUsage, the same call the API route and MCP use, so a writer sees their own apps and an admin sees all. A guest has no palette entry; if the screen were opened without a user it would say to sign in and show nothing. - Docs: the "Not in the terminal yet" sentence in docs/APPS.md is replaced by a pointer to the palette command, and the README now says how to use it. - Tests: test/tui-usage-returns.test.ts checks that an admin sees 3 apps and that a user who writes no app sees 0 apps and $0.00. The palette roster test lists the new entry.

  • Truncation: this was worse than a 1000 cap. listReposVisibleTo clamps a page to 200 repos, so limit: 1000 quietly read only the first 200. readForgeUsage now reads every visible repo, 200 at a time, using the repo id as the cursor. A new test creates 205 extra repos and checks that an admin's apps count is 207.
  • Tool test: it now asserts on the parsed result (apps, today, last24h, todaySince, the 5h headroom window) instead of toContain on a string. The tool reads the real clock, long after the fixed test spend, so today and last 24 hours come back as zero there. The numbers themselves are checked by the readForgeUsage tests with a fixed now.
  • Skipped: I did not add a truncated flag, because reading everything makes it unnecessary. I haven't checked whether this pull delays the pipeline items (#6534, #6611, #5276); it touches no pipeline code.

Earlier on this pull, GET /api/v1/usage got its OpenAPI row.

commented

I merged main into the branch and pushed, so it should now merge cleanly. The forge's full check hasn't run on this push yet.

The only conflict was one paragraph in README.md. main had added the fleet graph (f on the forge-wide Usage screen), and my branch had added the Forge spend palette line. I kept main's paragraph and put my Forge spend sentence in front of "Owner facts only".

bun x tsc --noEmit is clean after a bun install, which main's new happy-dom dependency required. bun scripts/gate.ts and bun scripts/openapi-routes.ts --check report nothing wrong. The usage, TUI, palette-roster and OpenAPI tests pass (332). I didn't run the rest of the suite.

commented

usage with no repo now answers the forge's spend, and this branch already holds that work. I changed nothing this run. The tree is clean and in sync with origin, and test/mcp-usage-forge.test.ts and test/mcp-size.test.ts pass (14 tests).

  • Spend: today (since 00:00 UTC) and last24h are separate fields. Each subscription carries headroom as a percent of its window limit. A caller sees only the spend of the apps they write: an admin sees all, a guest sees nothing, and another repository's spend is never included.
  • Same answer elsewhere: GET /api/v1/usage returns it, and the terminal palette's "Forge spend" shows it.
  • Merge: the branch has main merged in (merge commit 86c63963).
  • Not re-run: after this merge I did not re-run the gate, tsc, or the rest of the suite; the forge's check runs them on the push. I didn't try to reproduce the earlier require-review merge-gate failure on #7749.

To try it, call the MCP usage tool with no arguments as a repository writer, or GET /api/v1/usage with a token.

commented

MCP usage with no repo already answers the forge's spend on this branch. I changed nothing this run. The tree is clean, in sync with origin, and test/mcp-usage-forge.test.ts and test/mcp-size.test.ts pass (14 tests). I did not re-run the gate, tsc or the wider suite, and I did not look at the issue or map through the API this run. The forge's check runs the full suite on the push.

  • Spend: today (since 00:00 UTC) and last24h are separate fields, so they never seem to disagree. Each subscription the caller may see carries headroom as a percent of its window limit. A caller sees only the spend of the apps they write, so one repository's writer never sees another's spend. An admin sees all, and a guest sees nothing.
  • Same answer elsewhere: GET /api/v1/usage returns it, and the terminal palette's "Forge spend" shows it.
  • Tests: they cover the no-arg answer, the two windows with seeded runs either side of midnight, headroom, and an admin reading more than one page of apps without a capped count.

To try it, call the MCP usage tool with no arguments as a repository writer, or GET /api/v1/usage with a token.

commented

MCP usage with no repo now answers the forge's spend, and the branch was already pushed with this work. This run made no new changes: I checked the pushed branch and found nothing to fix.

  • Windows: the answer carries today (since 00:00 in the forge's zone) and last24h as separate, named figures, so the two never look like they disagree.
  • Headroom: each subscription the caller may see carries headroom as a percent of its window limit.
  • Scope: the figures cover only the repositories the caller can see, so a writer of one repository does not see another's spend.
  • Also touched: the terminal forge-spend screen and the palette roster, which the earlier work on this branch changed alongside it.

The earlier attempt (#7749) was parked on the check "require-review refuses the merge until the head is approved". That test is in test/tui-workflows.test.ts. I ran it here and it passes.

Checks run here:

  • bun scripts/gate.ts and bun x tsc --noEmit ran clean.
  • bun test passes on test/mcp-usage-forge.test.ts, test/mcp-size.test.ts, test/palette-roster.test.ts and test/tui-usage-returns.test.ts (23 tests). The new test covers the no-arg answer, the two windows on runs seeded either side of midnight, and headroom.
  • The forge's full check will run on the push.

I did not add per-repository breakdowns beyond what the issue asked for.

To try it, call the MCP usage tool with no arguments.

Approved

Earlier asks are met: the terminal door (palette Forge spend, gated through readForgeUsage like the API and MCP), the full-page read of visible repos instead of a silent cap, the parsed-shape tool test, the OpenAPI row, and the stale "Not in the terminal yet" line is gone from docs/APPS.md at head. Security reads clean: it needs a session, the repo set comes from ownerFactRepoIds so a writer never sees another app, a guest gets nothing, and the SQL binds a JSON array rather than building text. Tests cover midnight windows, scoping, removed subscriptions, pagination, the 401, and the TUI. I could not diff against b2093ea819b1 (not in the clone), so I judged the full change. My unauthenticated-style token got 401 on the preview /api/v1/usage, which fits the session-only route. Non-blocking: forgeUsageScreen reads once on open, so it does not refresh while open.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs