Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; | |
| 3 | ||
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 4 | import { money } from "./money.ts"; |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 5 | import { byProject, columns, defaultGrain, isStaff, monthSpan, pendingSentence, productStyle, quantity, receipt, resolveRange, ticks, usageCsv } from "./usage.ts"; |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 6 | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 7 | const axisMoney = (micros: number) => money(micros, { compact: true }); |
| 8 | ||
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 9 | const NOW = new Date("2026-10-08T15:00:00Z"); |
| 10 | ||
| 11 | test("periods resolve to whole UTC days, both ends included", () => { | |
| 12 | assert.deepEqual(resolveRange(null, {}, NOW), { period: "cycle", from: "2026-10-01", until: "2026-10-08" }); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 13 | // The current cycle is the one "this month" Home asks billing for. |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 14 | assert.deepEqual(resolveRange("cycle", {}, NOW), { period: "cycle", ...monthSpan(NOW) }); |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 15 | assert.deepEqual(monthSpan(NOW), { from: "2026-10-01", until: "2026-10-08" }); |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 16 | assert.deepEqual(resolveRange("last_cycle", {}, NOW), { period: "last_cycle", from: "2026-09-01", until: "2026-09-30" }); |
| 17 | assert.deepEqual(resolveRange("7d", {}, NOW), { period: "7d", from: "2026-10-02", until: "2026-10-08" }); | |
| 18 | assert.deepEqual(resolveRange("custom", { from: "2026-10-05", until: "2026-09-20" }, NOW), { period: "custom", from: "2026-09-20", until: "2026-10-05" }); | |
| 19 | // A custom range that is not days falls back to the cycle. | |
| 20 | assert.equal(resolveRange("custom", { from: "yesterday", until: "2026-10-01" }, NOW).period, "cycle"); | |
| 21 | assert.equal(resolveRange("nonsense", {}, NOW).period, "cycle"); | |
| 22 | }); | |
| 23 | ||
| 24 | test("columns cover every day, stack products, and add up when cumulative", () => { | |
| 25 | const days = [ | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 26 | { day: "2026-10-01", product: "deployments", micros: 2_000_000 }, |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 27 | { day: "2026-10-01", product: "sandboxes", micros: 500_000 }, |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 28 | { day: "2026-10-03", product: "deployments", micros: 1_000_000 }, |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 29 | ]; |
| 30 | const daily = columns(days, "2026-10-01", "2026-10-03"); | |
| 31 | assert.deepEqual(daily.map((c) => c.total), [2_500_000, 0, 1_000_000]); | |
| 32 | assert.equal(daily[0]!.parts.sandboxes, 500_000); | |
| 33 | const running = columns(days, "2026-10-01", "2026-10-03", "day", true); | |
| 34 | assert.deepEqual(running.map((c) => c.total), [2_500_000, 2_500_000, 3_500_000]); | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 35 | assert.equal(running[2]!.parts.deployments, 3_000_000); |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 36 | // Weeks start on Monday: Oct 1 2026 is a Thursday. |
| 37 | const weekly = columns(days, "2026-10-01", "2026-10-08", "week"); | |
| 38 | assert.deepEqual(weekly.map((c) => c.key), ["2026-09-28", "2026-10-05"]); | |
| 39 | assert.equal(weekly[0]!.total, 3_500_000); | |
| 40 | assert.equal(columns(days, "2026-09-15", "2026-10-03", "month").length, 2); | |
| 41 | assert.equal(defaultGrain("2026-10-01", "2026-10-31"), "day"); | |
| 42 | assert.equal(defaultGrain("2026-07-01", "2026-10-01"), "week"); | |
| 43 | }); | |
| 44 | ||
| 45 | test("the money axis has clean ticks, never the same label twice", () => { | |
| 46 | assert.deepEqual(ticks(0), [0]); | |
| 47 | const t = ticks(3_320_000); | |
| 48 | assert.equal(t[0], 0); | |
| 49 | assert.ok(t[t.length - 1]! >= 3_320_000); | |
| 50 | const labels = t.map(axisMoney); | |
| 51 | assert.equal(new Set(labels).size, labels.length, labels.join(" ")); | |
| 52 | const small = ticks(4_000).map(axisMoney); | |
| 53 | assert.equal(new Set(small).size, small.length, small.join(" ")); | |
| 54 | assert.equal(axisMoney(2_000_000), "$2"); | |
| 55 | assert.equal(axisMoney(1_500_000_000), "$1.5K"); | |
| 56 | }); | |
| 57 | ||
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 58 | test("quantities read in their units", () => { |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 59 | assert.equal(quantity(1_234_567, "tokens"), "1.2M tokens"); |
| 60 | assert.equal(quantity(3_725, "seconds"), "1h 2m"); | |
| 61 | assert.equal(quantity(504_000_000, "bytes"), "504 MB"); | |
| 62 | assert.equal(quantity(1, "entries"), "1 entry"); | |
| 63 | }); | |
| 64 | ||
| 65 | test("the receipt shows usage at price, then what paid, then what is charged", () => { | |
| 66 | const lines = receipt( | |
| 67 | { priceMicros: 3_320_000, discountMicros: 3_320_000, includedMicros: 0, creditsMicros: 0, chargedMicros: 0, pendingMicros: 0, costMicros: 0 }, | |
| 68 | 100, | |
| 69 | ); | |
| 70 | assert.deepEqual(lines.map((l) => l.label), ["Usage at price", "Discount (100%)", "Charged"]); | |
| 71 | }); | |
| 72 | ||
| 73 | test("grouping by project adds a project's meters together", () => { | |
| 74 | const meter = (label: string, parts: [string, number][]) => ({ | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 75 | key: label, label, product: "deployments", unit: "entries", quantity: 0, micros: 0, daily: [], |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 76 | byProject: parts.map(([project, micros]) => ({ project, micros, quantity: 0 })), |
| 77 | }); | |
| 78 | const rows = byProject({ | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 79 | products: [{ key: "deployments", label: "Deployments", micros: 0, meters: [meter("Requests", [["acme/web", 300], ["acme/api", 500]]), meter("Domains", [["acme/web", 400]])] }], |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 80 | }); |
| 81 | assert.deepEqual(rows.map((r) => [r.project, r.micros]), [["acme/web", 700], ["acme/api", 500]]); | |
| 82 | }); | |
| 83 | ||
| 84 | test("the CSV has a row per day and meter with anything on it", () => { | |
| 85 | const csv = usageCsv({ | |
| 86 | from: "2026-10-01", | |
| 87 | until: "2026-10-02", | |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 88 | products: [{ key: "sandboxes", label: "Sandboxes", micros: 0, meters: [{ key: "sandbox_time", label: "Sandbox time", product: "sandboxes", unit: "seconds", quantity: 0, micros: 0, daily: [0, 1_500_000], byProject: [] }] }], |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 89 | }); |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 90 | assert.equal(csv, "day,product,meter,usd\n2026-10-02,Sandboxes,Sandbox time,1.500000\n"); |
| Usage, Billing settings and prepaid AI credit; fixes from the UX audit | 91 | }); |
| 92 | ||
| 93 | test("test-mode hints are for g1t's own people", () => { | |
| 94 | assert.ok(isStaff({ workspaces: [{ slug: "flagon-io" }] })); | |
| 95 | assert.ok(!isStaff({ workspaces: [{ slug: "acme" }] })); | |
| 96 | assert.ok(!isStaff(null)); | |
| 97 | }); | |
| Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero | 98 | |
| Money is written one way. A single formatter turns millionths of a dollar into dollars, rounding half up on whole micros rather than on a float, so the same sum reads the same on every page: under a cent reads <$0.01 on a total and exactly nothing is $0.00, while the statement's lines, a session's receipt, the price book and an agent's effort costs carry up to four places where the fraction of a cent is the point; the six formatters that each rounded their own way are gone. This month is the calendar month in UTC from its first day to today everywhere, and billing counts the month's not-yet-closed usage in any range that reaches into the current month, so the top bar's pill, Spend, Home and Usage ask for the same days and get the same figure; Home now reads the usage report the others read instead of adding up statements. Usage's pending sentence says what of that usage the close will charge after the discount and included usage, which the billing API returns as pending_charged_micros. A reconciliation test holds the pill, Spend, Home and Usage to one number for one month. The usage and billing guide says how amounts are written and what this month means. | 99 | test("the pending note says what the close will charge, with the fraction of a cent that explains a gap", () => { |
| 100 | // A comped workspace: $0.009 pending, nothing of it charged. | |
| 101 | assert.equal( | |
| 102 | pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 0 }), | |
| 103 | "$0.009 of it is metered this month and not yet closed; nothing will be charged when the month closes, after your discount and included usage.", | |
| 104 | ); | |
| 105 | assert.equal( | |
| 106 | pendingSentence({ pendingMicros: 9_000, pendingChargedMicros: 6_300 }), | |
| 107 | "$0.009 of it is metered this month and not yet closed; $0.0063 of it will be charged when the month closes, after your discount and included usage.", | |
| 108 | ); | |
| 109 | assert.equal( | |
| 110 | pendingSentence({ pendingMicros: 1_340_000, pendingChargedMicros: 1_340_000 }), | |
| 111 | "$1.34 of it is metered this month and not yet closed; all of it will be charged when the month closes, after your discount and included usage.", | |
| 112 | ); | |
| 113 | // An older billing, with no split yet. | |
| 114 | assert.equal(pendingSentence({ pendingMicros: 9_000 }), "$0.009 of it is metered this month and not yet closed; it is charged when the month closes."); | |
| Sidebar: soon rows and the project row keep their styles inside a hint; tiny amounts read as zero | 115 | }); |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 116 | |
| The contracts no longer describe g1t's own agents, and workspaces can keep their AI providers. Every consumer had already stopped using them, so the TypeScript and Rust contracts drop the workspace-agent, ability, look, skill-library, context, runner-agent and mention modules, the chat and docs RPCs agents posted and read through, the folio agent methods, the agent_waiting notification, agent.asked and memory.changed events, AI credit, token and gateway billing types, model routes and sessions, team agents, agent tokens, plan, review, message and confidence types, command rules, cost caps and confidence rules. The agents:run and models:* scopes are no longer offered by any preset, OAuth default or permission form, but a stored token or grant that names them still reads; and the removed operations are gone from the scope table, so the API's check that every operation has exactly one scope is strict again. What stored data needs in order to parse (old run and credential kinds, an agent team lead, retired rules and webhook events, old guardrail and integration settings) still parses, and a run's token is still PrincipalKind::Agent, which now means a run's token and the @g1t rows written before. The customize, marketplace, skills and claude-plugin contracts stay only until the mobile app drops its Customize screens. | 117 | test("products no longer offered still read in older usage", () => { |
| The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404. | 118 | assert.equal(productStyle("agent").label, "Agent"); |
| 119 | assert.equal(productStyle("sandboxes").label, "Sandboxes"); | |
| 120 | }); |