Skip to content
735 linesCodeBlameRaw

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.

The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.1/**
2 * Spend's parts: the tiles at the top, spend by day, where it went, the
3 * budgets from the workspace down to a task with what happens at each,
4 * the costliest tasks with their receipts, pricing, and the top bar's
5 * spend pill. Every figure is one a service returned (lib/spend.server.ts).
6 */
7import { ArrowRight, Building2, Coins, Cpu, KeyRound, MessagesSquare, Puzzle, ReceiptText, Sparkles, UserRound, Users } from "lucide-react";
8import { type ReactNode, useEffect, useId, useState } from "react";
9import { Link, useFetcher } from "react-router";
10
11import type { AgentPolicy, AgentSession, PersonBudget } from "@g1t/contracts";
12
13import { AgentAvatar } from "./agent-avatar";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.14import { DollarsInput, useDialogFetcher } from "./agents/dialogs";
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.15import { Meter, PrivateTitle, SliceList, stepsLine } from "./agents/parts";
16import { monthName, shortDay } from "./agents/format";
17import { Badge } from "./ui/badge";
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.18import { Button } from "./ui/button";
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.19import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from "./ui/dialog";
20import { Field, FieldDescription, FieldError, FieldLabel } from "./ui/field";
21import { Hint } from "./ui/hint";
22import { Input } from "./ui/input";
23import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
24import { Skeleton } from "./ui/skeleton";
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.25import { ButtonLink, TimeAgo } from "./ui";
26import { Card } from "./ui/card";
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.27import { dollarsField } from "../lib/agent-form";
28import { cn } from "../lib/cn";
29import type { AgentBudgetRow, Budgets, PillData } from "../lib/spend.server";
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.30import { AT_LIMIT, type Pricing, type SpendScope, agentRateLabel, markupLabel, percentLabel, shareOfBudget } from "../lib/spend";
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.31import { money } from "../lib/money";
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.32
33
34/** A figure at the top of the page: what it is, the amount, and a line under it. */
35export function Tile({ label, value, sub, children }: { label: ReactNode; value: ReactNode; sub?: ReactNode; children?: ReactNode }) {
36 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.37 <Card className="min-w-0 p-4">
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.38 <p className="flex items-center gap-1.5 text-xs text-muted">{label}</p>
39 <p className="mt-1.5 truncate text-2xl font-semibold tracking-tight tabular-nums">{value}</p>
40 {sub && <p className="mt-0.5 text-xs text-faint">{sub}</p>}
41 {children}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.42 </Card>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.43 );
44}
45
46export function TileSkeleton() {
47 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.48 <Card className="p-4">
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.49 <Skeleton className="h-3 w-24" />
50 <Skeleton className="mt-3 h-7 w-28" />
51 <Skeleton className="mt-2 h-3 w-36" />
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.52 </Card>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.53 );
54}
55
56/**
57 * Spend by day over any span, one bar a day with its amount on hover or
58 * focus, and a table for screen readers.
59 */
60export function SpendBars({ days, className }: { days: { day: string; micros: number }[]; className?: string }) {
61 const max = Math.max(0, ...days.map((d) => d.micros));
62 return (
63 <div className={className}>
64 <div className="flex h-28 items-end gap-0.5" aria-hidden="true">
65 {days.map((d) => (
66 <Hint key={d.day} label={`${shortDay(d.day)} · ${money(d.micros)}`}>
67 <span className="flex h-full min-w-0 flex-1 items-end rounded-t-[3px] hover:bg-raised/60">
68 <span
69 className={cn("block w-full rounded-t-[3px]", d.micros > 0 ? "bg-accent/80" : "bg-line/70")}
70 style={{ height: max > 0 && d.micros > 0 ? `${Math.max(4, (d.micros / max) * 100)}%` : "2px" }}
71 />
72 </span>
73 </Hint>
74 ))}
75 </div>
76 <div className="mt-1.5 flex justify-between text-[0.6875rem] text-faint" aria-hidden="true">
77 <span>{days[0] ? shortDay(days[0].day) : ""}</span>
78 <span>{days.length > 1 ? shortDay(days[days.length - 1]!.day) : ""}</span>
79 </div>
80 <table className="sr-only">
81 <caption>Spend by day</caption>
82 <tbody>
83 {days.map((d) => (
84 <tr key={d.day}>
85 <th scope="row">{shortDay(d.day)}</th>
86 <td>{money(d.micros)}</td>
87 </tr>
88 ))}
89 </tbody>
90 </table>
91 </div>
92 );
93}
94
95/** A section with a heading, an aside, and what it holds. */
96export function Section({ id, title, aside, children, className }: { id: string; title: ReactNode; aside?: ReactNode; children: ReactNode; className?: string }) {
97 return (
98 <section aria-labelledby={id} className={cn("space-y-3", className)}>
99 <div className="flex flex-wrap items-baseline justify-between gap-x-3 gap-y-1">
100 <h2 id={id} className="text-base font-semibold tracking-tight">
101 {title}
102 </h2>
103 {aside && <div className="text-xs text-faint">{aside}</div>}
104 </div>
105 {children}
106 </section>
107 );
108}
109
110/** The ways to slice spend, as the page's tabs. */
111export type SliceKey = "agent" | "person" | "channel" | "model" | "kind" | "product" | "extension";
112
113export const SLICES: { key: SliceKey; label: string; icon: ReactNode; workspaceOnly?: boolean }[] = [
114 { key: "agent", label: "Agents", icon: <Sparkles size={13} /> },
115 { key: "person", label: "People", icon: <Users size={13} />, workspaceOnly: true },
116 { key: "channel", label: "Channels", icon: <MessagesSquare size={13} /> },
117 { key: "model", label: "Models", icon: <Cpu size={13} /> },
118 { key: "kind", label: "Kind of work", icon: <ReceiptText size={13} /> },
119 { key: "product", label: "Products", icon: <Building2 size={13} />, workspaceOnly: true },
120 { key: "extension", label: "Extensions", icon: <Puzzle size={13} />, workspaceOnly: true },
121];
122
123/** The tabs over the breakdown: each a link, so a slice is an address. */
124export function SliceTabs({ current, scope, href }: { current: SliceKey; scope: SpendScope; href: (key: SliceKey) => string }) {
125 const shown = SLICES.filter((s) => scope === "workspace" || !s.workspaceOnly);
126 return (
127 <nav aria-label="Slice spend by" className="-mx-1 flex gap-1 overflow-x-auto px-1 pb-1 [scrollbar-width:none]">
128 {shown.map((s) => (
129 <Link
130 key={s.key}
131 to={href(s.key)}
132 preventScrollReset
133 replace
134 aria-current={s.key === current ? "page" : undefined}
135 className={cn(
136 "inline-flex h-8 shrink-0 items-center gap-1.5 rounded-md px-2.5 text-[0.8125rem] transition-colors",
137 s.key === current ? "bg-raised text-fg ring-1 ring-line-strong" : "text-muted hover:bg-raised/60 hover:text-fg",
138 )}
139 >
140 <span className="text-faint">{s.icon}</span>
141 {s.label}
142 {s.key === "extension" && <span className="ml-0.5 text-[0.625rem] tracking-wide text-faint uppercase">Coming</span>}
143 </Link>
144 ))}
145 </nav>
146 );
147}
148
149/** Spend in a panel, the slices ranked. */
150export function SlicePanel({ children, foot }: { children: ReactNode; foot?: ReactNode }) {
151 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.152 <Card className="overflow-hidden">
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.153 {children}
154 {foot && <p className="border-t border-line px-4 py-2.5 text-xs text-faint">{foot}</p>}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.155 </Card>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.156 );
157}
158
159export { SliceList };
160
161/** Something not built yet, said plainly where it will be. */
162export function ComingNote({ title, children }: { title: string; children: ReactNode }) {
163 return (
164 <div className="px-4 py-8 text-center">
165 <p className="flex items-center justify-center gap-2 text-sm font-medium">
166 {title}
167 <Badge>Coming</Badge>
168 </p>
169 <p className="mx-auto mt-1.5 max-w-md text-sm text-muted">{children}</p>
170 </div>
171 );
172}
173
174// ── Budgets ──────────────────────────────────────────────────────────────
175
176/** One level of budget: its name, its amount, what is spent against it, and what happens at 100%. */
177function Level({
178 icon,
179 title,
180 amount,
181 spent,
182 budget,
183 atLimit,
184 action,
185 children,
186}: {
187 icon: ReactNode;
188 title: string;
189 amount: ReactNode;
190 spent?: number | null;
191 budget?: number | null;
192 atLimit: string;
193 action?: ReactNode;
194 children?: ReactNode;
195}) {
196 const share = spent != null ? shareOfBudget(spent, budget) : null;
197 return (
198 <li className="px-4 py-4">
199 <div className="flex flex-wrap items-start gap-x-3 gap-y-2">
200 <span className="mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg bg-raised text-muted ring-1 ring-line">{icon}</span>
201 <div className="min-w-0 grow basis-56">
202 <p className="text-sm font-medium">{title}</p>
203 <p className="mt-0.5 text-xs text-muted">
204 <span className="font-medium text-fg-soft">At 100%:</span> {atLimit}
205 </p>
206 </div>
207 <div className="flex shrink-0 items-center gap-3 max-sm:w-full max-sm:justify-between max-sm:pl-10">
208 <div className="sm:text-right">
209 <p className="text-sm tabular-nums">{amount}</p>
210 {spent != null && (
211 <p className="text-xs text-faint tabular-nums">
212 {money(spent)} spent{share != null ? ` · ${percentLabel(spent, budget)}` : ""}
213 </p>
214 )}
215 </div>
216 {action}
217 </div>
218 </div>
219 {share != null && <Meter spent={spent!} cap={budget} label={`${title}: spent of its budget`} size="sm" className="mt-3 ml-10" />}
220 {children}
221 </li>
222 );
223}
224
225/**
226 * The budgets, widest first: the workspace's spend limit, every agent
227 * together, each person, each agent, and each task. Owners change the
228 * agent levels here; the spend limit is on Billing, an agent's own on its
229 * profile.
230 */
231export function BudgetLadder({ slug, budgets, owner, me, action, workspaceFigures }: { slug: string; budgets: Budgets; owner: boolean; me: string; action: string; workspaceFigures: boolean }) {
232 const { limit, caps, policy, people, agents } = budgets;
233 const limitMicros = limit ? (limit.spendLimitMicros ?? limit.ceilingMicros) : null;
234 const mine = people?.people.find((p) => p.username === me) ?? null;
235 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.236 <Card asChild divided className="overflow-hidden">
237 <ol>
238 <Level
239 icon={<Building2 size={14} />}
240 title="The workspace"
241 amount={limit ? (limitMicros != null ? `${money(limitMicros)} a month` : "No limit") : <span className="text-faint">Unavailable</span>}
242 spent={limit && workspaceFigures ? (limit.spentMicros ?? null) : null}
243 budget={limitMicros}
244 atLimit={AT_LIMIT.workspace(limit?.pauseAtLimit ?? true)}
245 action={
246 <ButtonLink to={`/${slug}/-/billing`} variant="outline" size="sm">
247 {owner ? "Change" : "Billing"}
248 </ButtonLink>
249 }
250 >
251 {limit?.defaultSpendLimit && <p className="mt-2 ml-10 text-xs text-faint">Automatic until owners set one: $200, or twice last month&apos;s spend, whichever is more.</p>}
252 </Level>
253 <Level
254 icon={<Sparkles size={14} />}
255 title="All agents together"
256 amount={policy ? (policy.monthly_micros != null ? `${money(policy.monthly_micros)} a month` : "No budget of its own") : <span className="text-faint">Unavailable</span>}
257 spent={workspaceFigures ? budgets.agentsMonthMicros : null}
258 budget={policy?.monthly_micros ?? null}
259 atLimit={AT_LIMIT.agents}
260 action={owner && policy ? <PolicyDialog policy={policy} action={action} trigger={<Button type="button" variant="outline" size="sm">Change</Button>} /> : null}
261 />
262 <Level
263 icon={<UserRound size={14} />}
264 title="Each person"
265 amount={policy ? (policy.person_monthly_micros != null ? `${money(policy.person_monthly_micros)} a month each` : "No budget per person") : <span className="text-faint">Unavailable</span>}
266 spent={!owner && mine ? mine.spent_micros : null}
267 budget={!owner && mine ? mine.monthly_micros : null}
268 atLimit={AT_LIMIT.person}
269 action={owner && policy ? <PolicyDialog policy={policy} action={action} trigger={<Button type="button" variant="outline" size="sm">Change</Button>} /> : null}
270 >
271 <p className="mt-2 ml-10 text-xs text-faint">What agents spend on work a person asks for: their chats with agents and the sessions they start.</p>
272 {owner && people && <PeopleBudgets people={people.people} defaultMicros={people.default_micros} action={action} />}
273 </Level>
274 <Level
275 icon={<Sparkles size={14} />}
276 title="Each agent"
277 amount={policy ? (policy.default_agent_monthly_micros != null ? `${money(policy.default_agent_monthly_micros)} a month for a new agent` : "Set on each agent") : <span className="text-faint">Unavailable</span>}
278 atLimit={AT_LIMIT.agent}
279 >
280 {agents && agents.length > 0 && <AgentBudgets slug={slug} agents={agents} />}
281 </Level>
282 <Level
283 icon={<ReceiptText size={14} />}
284 title="Each task"
285 amount={policy ? `${money(policy.default_session_micros)} a session` : <span className="text-faint">Unavailable</span>}
286 atLimit={AT_LIMIT.session}
287 action={owner && policy ? <PolicyDialog policy={policy} action={action} trigger={<Button type="button" variant="outline" size="sm">Change</Button>} /> : null}
288 >
289 <dl className="mt-3 ml-10 grid grid-cols-1 gap-x-6 gap-y-2 text-xs sm:grid-cols-3">
290 <Fact label="A session" value={policy ? money(policy.default_session_micros) : "—"} hint="Unless its agent's own per-task cap is lower." />
291 <Fact label="One run of the plan" value={caps ? money(caps.runMicros) : "—"} hint="Set under Caps on agents, on Billing." />
292 <Fact label="Agents on one issue" value={caps ? money(caps.issueMicros) : "—"} hint="Every run on the issue together." />
293 </dl>
294 </Level>
295 </ol>
296 </Card>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.297 );
298}
299
300function Fact({ label, value, hint }: { label: string; value: string; hint: string }) {
301 return (
302 <div>
303 <dt className="text-faint">{label}</dt>
304 <dd className="mt-0.5 text-sm tabular-nums">{value}</dd>
305 <dd className="text-faint">{hint}</dd>
306 </div>
307 );
308}
309
310/** Each agent against its own monthly budget, a link to its profile. */
311function AgentBudgets({ slug, agents }: { slug: string; agents: AgentBudgetRow[] }) {
312 const [all, setAll] = useState(false);
313 const shown = all ? agents : agents.slice(0, 5);
314 return (
315 <ul className="mt-3 ml-10 space-y-2.5">
316 {shown.map((agent) => (
317 <li key={agent.id} className="flex items-center gap-2.5 text-sm">
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.318 <AgentAvatar agent={{ handle: agent.handle, avatar_seed: agent.avatar_seed, look: agent.look }} size={20} />
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.319 <Link to={`/${slug}/-/agents/${agent.handle}/spend`} className="min-w-0 grow truncate hover:underline">
320 {agent.display_name}
321 </Link>
322 <span className="shrink-0 text-xs text-faint tabular-nums">
323 {money(agent.spent_month_micros)}
324 {agent.budget.monthly_micros != null ? ` of ${money(agent.budget.monthly_micros)}` : " · no cap"}
325 </span>
326 <span className="hidden w-24 shrink-0 sm:block">
327 {agent.budget.monthly_micros != null && <Meter spent={agent.spent_month_micros} cap={agent.budget.monthly_micros} label={`${agent.display_name}: spent of its budget`} size="sm" />}
328 </span>
329 </li>
330 ))}
331 {agents.length > 5 && (
332 <li>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.333 <Button type="button" onClick={() => setAll((v) => !v)} variant="link" size="inline" className="text-xs font-normal">
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.334 {all ? "Show fewer" : `Show all ${agents.length}`}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.335 </Button>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.336 </li>
337 )}
338 </ul>
339 );
340}
341
342/** Each person who has a budget of their own or spent this month, for owners, with the way to set one. */
343function PeopleBudgets({ people, defaultMicros, action }: { people: PersonBudget[]; defaultMicros: number | null; action: string }) {
344 return (
345 <div className="mt-3 ml-10">
346 {people.length > 0 && (
347 <ul className="space-y-2.5">
348 {people.slice(0, 12).map((person) => (
349 <li key={person.username} className="flex items-center gap-2.5 text-sm">
350 <span className="min-w-0 grow truncate">
351 @{person.username}
352 {person.own && <span className="ml-1.5 text-xs text-faint">own budget</span>}
353 </span>
354 <span className="shrink-0 text-xs text-faint tabular-nums">
355 {money(person.spent_micros)}
356 {person.monthly_micros != null ? ` of ${money(person.monthly_micros)}` : " · no budget"}
357 </span>
358 <span className="hidden w-24 shrink-0 sm:block">
359 {person.monthly_micros != null && <Meter spent={person.spent_micros} cap={person.monthly_micros} label={`@${person.username}: spent of their budget`} size="sm" />}
360 </span>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.361 <PersonDialog action={action} person={person} defaultMicros={defaultMicros} trigger={<Button type="button" variant="link" size="inline" className="text-xs">Set</Button>} />
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.362 </li>
363 ))}
364 </ul>
365 )}
366 <div className="mt-2.5">
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.367 <PersonDialog action={action} person={null} defaultMicros={defaultMicros} trigger={<Button type="button" variant="link" size="inline" className="text-xs">Give someone their own budget</Button>} />
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.368 </div>
369 </div>
370 );
371}
372
373/** The workspace's agent policy: every agent together, each person, a new agent, a session. Owners only. */
374function PolicyDialog({ policy, action, trigger }: { policy: AgentPolicy; action: string; trigger: ReactNode }) {
375 const { fetcher, open, setOpen, error, busy } = useDialogFetcher("spend-policy");
376 const id = useId();
377 return (
378 <Dialog open={open} onOpenChange={setOpen}>
379 <DialogTrigger asChild>{trigger}</DialogTrigger>
380 <DialogContent>
381 <DialogHeader>
382 <DialogTitle>Agent budgets</DialogTitle>
383 <DialogDescription>Monthly, reset on the 1st (UTC). The workspace&apos;s spend limit sits above all of them. Leave a budget empty for none.</DialogDescription>
384 </DialogHeader>
385 <fetcher.Form method="post" action={action} className="grid gap-5">
386 <input type="hidden" name="intent" value="policy" />
387 <Field>
388 <FieldLabel htmlFor={`${id}-monthly`}>All agents together</FieldLabel>
389 <DollarsInput id={`${id}-monthly`} name="monthly" defaultValue={dollarsField(policy.monthly_micros)} placeholder="No budget of its own" />
390 </Field>
391 <Field>
392 <FieldLabel htmlFor={`${id}-person`}>Each person</FieldLabel>
393 <DollarsInput id={`${id}-person`} name="person" defaultValue={dollarsField(policy.person_monthly_micros)} placeholder="No budget per person" />
394 <FieldDescription>What agents may spend on the work one person asks for. Give someone more or less from their row.</FieldDescription>
395 </Field>
396 <Field>
397 <FieldLabel htmlFor={`${id}-agent`}>A new agent</FieldLabel>
398 <DollarsInput id={`${id}-agent`} name="default_agent" defaultValue={dollarsField(policy.default_agent_monthly_micros)} placeholder="None" />
399 <FieldDescription>Filled in when an agent is hired; change each one on its profile.</FieldDescription>
400 </Field>
401 <Field>
402 <FieldLabel htmlFor={`${id}-session`}>A session</FieldLabel>
403 <DollarsInput id={`${id}-session`} name="default_session" defaultValue={dollarsField(policy.default_session_micros)} required />
404 <FieldDescription>Between $0.10 and $500. A session stops here and asks before spending more.</FieldDescription>
405 </Field>
406 <FieldError>{error}</FieldError>
407 <DialogFooter>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.408 <Button type="button" variant="outline" onClick={() => setOpen(false)}>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.409 Cancel
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.410 </Button>
411 <Button type="submit" variant="accent" disabled={busy}>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.412 {busy ? "Saving…" : "Save budgets"}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.413 </Button>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.414 </DialogFooter>
415 </fetcher.Form>
416 </DialogContent>
417 </Dialog>
418 );
419}
420
421/** One person's own budget: an amount, none at all, or back to the default. Owners only. */
422function PersonDialog({ action, person, defaultMicros, trigger }: { action: string; person: PersonBudget | null; defaultMicros: number | null; trigger: ReactNode }) {
423 const { fetcher, open, setOpen, error, busy } = useDialogFetcher(`spend-person-${person?.username ?? "new"}`);
424 const id = useId();
425 return (
426 <Dialog open={open} onOpenChange={setOpen}>
427 <DialogTrigger asChild>{trigger}</DialogTrigger>
428 <DialogContent>
429 <DialogHeader>
430 <DialogTitle>{person ? `@${person.username}'s budget` : "A person's own budget"}</DialogTitle>
431 <DialogDescription>
432 What agents may spend on work this person asks for each month, in place of the default
433 {defaultMicros != null ? ` of ${money(defaultMicros)}` : " (none now)"}. 0 means no budget at all.
434 </DialogDescription>
435 </DialogHeader>
436 <fetcher.Form method="post" action={action} className="grid gap-5">
437 <input type="hidden" name="intent" value="person" />
438 {person ? (
439 <input type="hidden" name="username" value={person.username} />
440 ) : (
441 <Field>
442 <FieldLabel htmlFor={`${id}-username`}>Username</FieldLabel>
443 <Input id={`${id}-username`} name="username" required autoComplete="off" placeholder="ana" />
444 </Field>
445 )}
446 <Field>
447 <FieldLabel htmlFor={`${id}-amount`}>Monthly budget</FieldLabel>
448 <DollarsInput id={`${id}-amount`} name="amount" defaultValue={person?.own ? dollarsField(person.monthly_micros ?? 0) : ""} placeholder="The default" />
449 <FieldDescription>Empty puts them back on the default.</FieldDescription>
450 </Field>
451 <FieldError>{error}</FieldError>
452 <DialogFooter>
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.453 <Button type="button" variant="outline" onClick={() => setOpen(false)}>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.454 Cancel
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.455 </Button>
456 <Button type="submit" variant="accent" disabled={busy}>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.457 {busy ? "Saving…" : "Save"}
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.458 </Button>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.459 </DialogFooter>
460 </fetcher.Form>
461 </DialogContent>
462 </Dialog>
463 );
464}
465
466// ── Receipts ─────────────────────────────────────────────────────────────
467
468/** Where a task's receipt is. */
469export function receiptHref(slug: string, session: Pick<AgentSession, "id">): string {
470 return `/${slug}/-/spend/receipts/${session.id}`;
471}
472
473/** The costliest tasks, each a link to its receipt. */
474export function TaskList({ slug, sessions }: { slug: string; sessions: AgentSession[] }) {
475 if (sessions.length === 0) return <p className="px-4 py-6 text-center text-sm text-faint">No tasks in this period.</p>;
476 return (
477 <ul className="divide-y divide-line/60">
478 {sessions.map((session) => (
479 <li key={session.id} className="group relative flex items-center gap-3 px-4 py-3 transition-colors hover:bg-raised/40">
Agents have faces, and are never mistaken for people. Every agent wears a little bot face drawn from a look it owns, shape, colour, eyes, mouth, antenna, accessory and pattern, chosen in its builder and on its Profile tab with a live preview, Shuffle and a way back to the face its seed gives it; the face blinks on its own time, breathes, narrows its eyes while the agent works, shuts them asleep and bounces when it finishes, all of it still for anyone who asked for less motion. Wherever an agent shows, in chat, in a list, on a mention, on a review or a commit, its avatar carries an agent marker, and the people reading it are told so. In Chat, direct messages are two lists: People, and Agents, which also holds the agents you haven't talked to yet; a conversation with both a person and an agent in it is marked in the list, named in the conversation's header, spelled out by the composer and explained once the first time it opens. Agents keep their look in the agents service, which every service passes along. The chat and agents guides say so, and CONTRIBUTING makes the shared avatar the only way to draw an agent.480 <AgentAvatar agent={{ handle: session.agent_handle, avatar_seed: session.agent_avatar_seed, look: session.agent_look }} size={24} />
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.481 <div className="min-w-0 grow">
482 <Link to={receiptHref(slug, session)} className="block min-w-0 truncate text-sm font-medium after:absolute after:inset-0 hover:underline">
483 {session.visible ? session.title || "Untitled session" : <PrivateTitle />}
484 </Link>
485 <p className="mt-0.5 truncate text-xs text-faint">
486 {session.agent_name}
487 {session.visible && session.asked_by_username ? ` · for @${session.asked_by_username}` : ""} · {stepsLine(session)} · <TimeAgo at={session.created_at} />
488 </p>
489 </div>
490 <span className="shrink-0 text-sm tabular-nums">{money(session.charged_micros)}</span>
491 <ArrowRight size={14} className="shrink-0 text-faint group-hover:text-muted" aria-hidden="true" />
492 </li>
493 ))}
494 </ul>
495 );
496}
497
498// ── Pricing ──────────────────────────────────────────────────────────────
499
500/** How g1t prices what it runs, from the price book. */
501export function PricingCard({ pricing }: { pricing: Pricing | null }) {
502 if (!pricing) return <p className="rounded-xl border border-line bg-surface px-4 py-6 text-center text-sm text-muted">Prices couldn&apos;t be read right now.</p>;
503 const rows: { icon: ReactNode; title: string; value: string; about: string }[] = [
504 {
505 icon: <Cpu size={15} />,
506 title: "Models",
507 value: pricing.modelMarkupPercent > 0 ? `Provider price + ${pricing.modelMarkupPercent}%` : "Provider price",
508 about: pricing.modelMarkupPercent > 0 ? "What the model provider charges, with the markup shown." : "What the model provider charges, with no markup.",
509 },
510 {
511 icon: <Sparkles size={15} />,
512 title: "Agent rate",
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.513 value: agentRateLabel(pricing, (micros) => money(micros, { precise: true })),
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.514 about: "For what g1t runs around every model call: the model gateway, secrets, routing, context and pass-through to your own provider.",
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.515 },
516 {
517 icon: <Coins size={15} />,
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.518 title: "Everything else g1t runs",
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.519 value: pricing.markup ? `At cost + ${markupLabel(pricing.markup)}` : "At cost",
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.520 about: "Sandboxes, builds, hosting, storage, search and security scans, at what they cost g1t.",
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.521 },
522 {
523 icon: <KeyRound size={15} />,
524 title: "Your own model keys",
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.525 value: "Agent rate only",
526 about: "Your provider bills the model; g1t charges the agent rate for passing it through. Local models the same.",
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.527 },
528 { icon: <Building2 size={15} />, title: "Your own runners", value: "$0", about: "Time on your own hardware is never charged." },
Pricing says it plainly: models at the provider's price, the agent rate for what g1t runs around every model call (the gateway, secrets, routing, context and pass-through to your own provider, so your own keys too), everything else at cost plus 20%, your own runners free, no seats; Security and quality comes with the plan with no separate fee, and live activations end. Each agent has an effort setting, Auto to Max, with what a typical task has cost at each level, and Spend's Spend less, keep quality suggests a lower level only when the agent's own past work shows quality held, to apply or dismiss. The pricing, spend and agents guides say how.529 { icon: <Users size={15} />, title: "People", value: "No seats", about: "Invite everyone; people chatting costs nothing. Effort and weekly suggestions help agents spend less." },
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.530 ];
531 return (
The site is built from its shared components: one Button with its variants and sizes, one Badge, Card for every panel, Alert for notices, Kbd for keys, Table for the security tables, and Avatar, Command, Progress, ScrollArea, ToggleGroup, Collapsible and Accordion in the kit; raw buttons, hand-drawn cards and pills and per-file button class lists are gone, outline buttons stand the same height as filled ones, and the phone's Ask g1t loses its tile. Every page was compared pixel by pixel before and after.532 <Card asChild tone="plain" className="grid gap-px overflow-hidden bg-line sm:grid-cols-2 lg:grid-cols-3">
533 <ul>
534 {rows.map((row) => (
535 <li key={row.title} className="bg-surface p-4">
536 <p className="flex items-center gap-2 text-xs text-muted">
537 <span className="text-faint">{row.icon}</span>
538 {row.title}
539 </p>
540 <p className="mt-1 text-sm font-medium">{row.value}</p>
541 <p className="mt-0.5 text-xs text-faint">{row.about}</p>
542 </li>
543 ))}
544 </ul>
545 </Card>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.546 );
547}
548
549// ── The top bar's pill ───────────────────────────────────────────────────
550
551const SCOPE_KEY = "g1t_spend_pill";
552
553function storedScope(): SpendScope {
554 try {
555 return window.localStorage.getItem(SCOPE_KEY) === "workspace" ? "workspace" : "me";
556 } catch {
557 return "me";
558 }
559}
560
561/**
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.562 * Your spend this month in the top bar: what agents did for you, at price,
563 * against your budget, or, for owners and billing managers who switch it,
564 * everything the workspace used at price, with what it was charged against
565 * its spend limit underneath. Both tabs count the same way, at price, so
566 * the numbers add up across them. A click opens where it went. Read after
567 * the page draws, so it adds nothing to the page's time.
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.568 */
569export function SpendPill({ slug, mayWorkspace }: { slug: string; mayWorkspace: boolean }) {
570 const pill = useFetcher<PillData>({ key: `spend-pill:${slug}` });
571 const [scope, setScope] = useState<SpendScope>("me");
572 const [open, setOpen] = useState(false);
573 useEffect(() => {
574 setScope(mayWorkspace ? storedScope() : "me");
575 pill.load(`/${slug}/-/spend/pill`);
576 // eslint-disable-next-line react-hooks/exhaustive-deps
577 }, [slug, mayWorkspace]);
578 useEffect(() => {
579 if (open) pill.load(`/${slug}/-/spend/pill`);
580 // eslint-disable-next-line react-hooks/exhaustive-deps
581 }, [open]);
582 const choose = (next: SpendScope) => {
583 setScope(next);
584 try {
585 window.localStorage.setItem(SCOPE_KEY, next);
586 } catch {
587 // Kept for this page only.
588 }
589 };
590 const data = pill.data;
591 const ws = scope === "workspace" ? data?.workspace : null;
592 const spent = ws ? ws.spentMicros : (data?.me?.spentMicros ?? null);
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.593 // The limit governs what is charged, so the meter measures that.
594 const against = ws ? ws.chargedMicros : (data?.me?.spentMicros ?? null);
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.595 const budget = ws ? ws.limitMicros : (data?.me?.budgetMicros ?? null);
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.596 const share = against != null ? shareOfBudget(against, budget) : null;
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.597 const label = spent == null ? (data ? "Spend" : "") : money(spent);
598 return (
599 <Popover open={open} onOpenChange={setOpen}>
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.600 <PopoverTrigger asChild>
601 <Button
602 variant="ghost"
603 size="sm"
604 aria-label={spent == null ? "Spend this month" : `${ws ? "The workspace's" : "Your"} spend this month: ${money(spent)}${budget != null ? ` of ${money(budget)}` : ""}`}
605 className="gap-2 text-fg-soft tabular-nums"
606 >
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.607 {ws ? <Building2 size={14} className="shrink-0 text-faint" /> : <Coins size={14} className="shrink-0 text-faint" />}
608 {data ? <span>{label}</span> : <Skeleton className="h-3 w-10" />}
609 {share != null && (
610 <span className="hidden w-9 lg:block">
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.611 <Meter spent={against!} cap={budget} label={ws ? "Charged of the spend limit" : "Spent of your budget"} size="sm" />
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.612 </span>
613 )}
The top of every page is one surface: the breadcrumb, search, spend, Ask g1t, bell and + share one height, corners and spacing, Ask g1t is a plain tinted button with the g1t mark drawn on whole pixels, a page's own header sits directly under the bar at the same left edge, and every section, conversations and open artifacts included, draws the same rounded panel. Tables written in chat render as tables, and the site's file, checkbox, radio, number and date controls are the design system's. Button, Separator, Breadcrumb, Table and FileDrop join the shared components; the chat guide says how tables work.614 </Button>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.615 </PopoverTrigger>
616 <PopoverContent align="end" className="w-80 p-0">
617 <SpendPopover slug={slug} data={data ?? null} scope={scope} mayWorkspace={mayWorkspace} onScope={choose} onClose={() => setOpen(false)} />
618 </PopoverContent>
619 </Popover>
620 );
621}
622
623function SpendPopover({
624 slug,
625 data,
626 scope,
627 mayWorkspace,
628 onScope,
629 onClose,
630}: {
631 slug: string;
632 data: PillData | null;
633 scope: SpendScope;
634 mayWorkspace: boolean;
635 onScope: (scope: SpendScope) => void;
636 onClose: () => void;
637}) {
638 const month = data ? monthName(data.month).split(" ")[0] : "";
639 const ws = scope === "workspace" ? data?.workspace : null;
640 return (
641 <div>
642 <div className="flex items-center gap-1 border-b border-line p-1.5">
643 {(["me", "workspace"] as const).map((key) =>
644 key === "workspace" && !mayWorkspace ? (
645 <Hint key={key} label="The workspace's spend is for owners and billing managers.">
646 <span tabIndex={0} className="flex h-7 grow cursor-default items-center justify-center rounded-md text-xs text-faint">
647 Workspace
648 </span>
649 </Hint>
650 ) : (
651 <button
652 key={key}
653 type="button"
654 aria-pressed={scope === key}
655 onClick={() => onScope(key)}
656 className={cn("h-7 grow rounded-md text-xs transition-colors", scope === key ? "bg-surface text-fg ring-1 ring-line-strong" : "text-muted hover:text-fg")}
657 >
658 {key === "me" ? "You" : "Workspace"}
659 </button>
660 ),
661 )}
662 </div>
663 {!data ? (
664 <div className="space-y-2 p-4">
665 <Skeleton className="h-3 w-32" />
666 <Skeleton className="h-6 w-24" />
667 <Skeleton className="h-3 w-full" />
668 </div>
669 ) : ws ? (
670 <div className="p-4">
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.671 <p className="text-xs text-muted">The workspace · {month}, everything it used, at price</p>
672 <p className="mt-1 text-xl font-semibold tabular-nums">{ws.spentMicros != null ? money(ws.spentMicros) : "—"}</p>
673 {ws.chargedMicros != null && (
674 <p className="mt-1 text-xs text-muted">
675 Charged after its plan and credit: <span className="text-fg tabular-nums">{money(ws.chargedMicros)}</span>
676 {ws.limitMicros != null ? ` of a ${money(ws.limitMicros)} spend limit` : " · no spend limit set"}
677 </p>
678 )}
679 {ws.chargedMicros != null && ws.limitMicros != null && <Meter spent={ws.chargedMicros} cap={ws.limitMicros} label="Charged of the spend limit" size="sm" className="mt-2" />}
680 <p className="mt-3 text-xs text-muted">Of that, agents{ws.agentsMicros != null ? `: ${money(ws.agentsMicros)}` : ""}</p>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.681 <PopoverSlices slices={ws.byAgent} />
682 </div>
683 ) : data.me ? (
684 <div className="p-4">
The top bar's spend pill counts both tabs the same way, at price: You is what agents did for you, Workspace is everything the workspace used this month, the figure Home and Spend show, with what it was charged after its plan and credit underneath, against its spend limit; so a comped workspace reads as what it used and nothing charged, not the other way round. The spend guide says so.685 <p className="text-xs text-muted">You · {month}, what agents did for you, at price</p>
The Spend page says what a workspace spent, where it went and the budgets that hold it, in Workspace under Money: spent at price, agents' spend and the spend limit; by day; by agent, person, channel, model, kind of work and product; budgets from the workspace down to each person, agent and task, with what happens at 100%; the costliest tasks, each with a receipt of every session at the provider's price and what was charged; and how it's priced. Everyone sees what agents spent for them, and owners and billing managers the whole workspace; each person can have a monthly budget agents keep to, and the spend pill in the top bar shows your month or the workspace's against its limit. The spend guide says how.686 <p className="mt-1 text-xl font-semibold tabular-nums">
687 {money(data.me.spentMicros)}
688 {data.me.budgetMicros != null && <span className="ml-1.5 text-sm font-normal text-muted">of {money(data.me.budgetMicros)}</span>}
689 </p>
690 {data.me.budgetMicros != null ? (
691 <Meter spent={data.me.spentMicros} cap={data.me.budgetMicros} label="Spent of your budget" size="sm" className="mt-2" />
692 ) : (
693 <p className="mt-0.5 text-xs text-faint">No budget of your own; the workspace&apos;s limits apply.</p>
694 )}
695 <PopoverSlices slices={data.me.byKind} />
696 {data.me.byAgent.length > 0 && (
697 <p className="mt-2 truncate text-xs text-faint">
698 {data.me.byAgent
699 .slice(0, 3)
700 .map((s) => `${s.label.replace(/ \(@[^)]+\)$/, "")} ${money(s.micros)}`)
701 .join(" · ")}
702 </p>
703 )}
704 </div>
705 ) : (
706 <p className="p-4 text-sm text-muted">Your spend couldn&apos;t be read right now.</p>
707 )}
708 <div className="border-t border-line p-1.5">
709 <Link
710 to={`/${slug}/-/spend${scope === "workspace" ? "" : "?scope=me"}`}
711 onClick={onClose}
712 className="flex h-8 items-center justify-between rounded-md px-2.5 text-sm text-fg/90 hover:bg-surface hover:text-fg"
713 >
714 {scope === "workspace" ? "Open workspace spend" : "Your spend and budget"}
715 <ArrowRight size={14} className="text-faint" />
716 </Link>
717 </div>
718 </div>
719 );
720}
721
722function PopoverSlices({ slices }: { slices: { key: string; label: string; micros: number }[] }) {
723 const shown = slices.filter((s) => s.micros > 0).slice(0, 4);
724 if (shown.length === 0) return <p className="mt-2 text-xs text-faint">Nothing spent yet this month.</p>;
725 return (
726 <ul className="mt-2 space-y-1">
727 {shown.map((s) => (
728 <li key={s.key || "none"} className="flex items-center justify-between gap-3 text-sm">
729 <span className="min-w-0 truncate text-fg-soft">{s.label}</span>
730 <span className="shrink-0 tabular-nums">{money(s.micros)}</span>
731 </li>
732 ))}
733 </ul>
734 );
735}