Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
536 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.

Usage, Billing settings and prepaid AI credit; fixes from the UX audit1/**
2 * Billing settings' newer sections: the plan at a glance with the upcoming
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.3 * invoice, the budget's alerts, the
Usage, Billing settings and prepaid AI credit; fixes from the UX audit4 * payment method (kept on Stripe), add-ons, invoice details and the
5 * invoices. Cards and payment methods are handled on Stripe's own page:
6 * g1t has no card form. Each form posts to the billing route's action with
7 * an `intent`.
8 */
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.9import { ArrowUpRight, Bell, CreditCard, FileText, Mail, ReceiptText, Sparkles } from "lucide-react";
10import type { ReactNode } from "react";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit11import { Form, Link } from "react-router";
12
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.13import type { BillingDetails, FeatureState, Limit, UsageReport } from "@g1t/contracts";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit14
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.15import { PLUS_TAX, type PlanStatus } from "../lib/billing";
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.16import { money, wholeDollars } from "../lib/money";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit17import { Card } from "./billing";
18import { ErrorText, SubmitButton } from "./ui";
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.19import { Checkbox } from "./ui/checkbox";
Chat controls, public profiles, shadcn selects, and no Docs tab in a project20import { SelectField } from "./ui/select";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit21import { Skeleton } from "./ui/skeleton";
22
23const FIELD =
24 "w-full rounded-md border border-line bg-bg px-2.5 py-1.5 text-sm outline-none transition-colors placeholder:text-faint hover:border-line-strong focus:border-accent-dim";
25
26// --- The plan ---------------------------------------------------------------------------------
27
28/**
29 * The plan at a glance: its name and standing, the billing period, the
30 * included usage, and the upcoming invoice, from g1t's own ledger.
31 */
32export function PlanSummary({
33 slug,
34 state,
35 status,
36 details,
37 report,
38 owner,
39 enabled,
40 staff,
41 error,
42}: {
43 slug: string;
44 state: FeatureState | null;
45 status: PlanStatus;
46 details: BillingDetails | null;
47 report: UsageReport | null;
48 owner: boolean;
49 enabled: boolean;
50 staff: boolean;
51 error?: string;
52}) {
53 const plan = state?.plan;
54 const subscription = state?.subscription ?? null;
55 const on = ["paid", "canceling", "comped", "enterprise", "past_due"].includes(status.kind);
56 const now = new Date();
57 const start = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1));
58 const end = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, 0));
59 const period = `${start.toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" })} – ${end.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" })}`;
60 const upcoming = details?.upcoming;
61 return (
62 <Card
63 id="plan"
64 tone={status.kind === "past_due" || status.kind === "canceling" ? "warn" : on ? "accent" : "plain"}
65 icon={<Sparkles size={16} />}
66 title={
67 <>
68 {plan?.title ?? "g1t"}
Merge branch 'worktree-agent-ad7c6d88d93adc817'69 <span className={`rounded-full px-2 py-0.5 text-xs ${on && status.kind !== "past_due" && status.kind !== "canceling" ? "bg-success/15 text-success" : status.kind === "free" || status.kind === "trial" ? "border border-line text-muted" : "bg-warn/15 text-warn"}`}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit70 {status.label}
71 {status.kind === "canceling" && subscription?.periodEnd ? `, ${new Date(subscription.periodEnd).toLocaleDateString()}` : ""}
72 </span>
73 </>
74 }
75 about={
76 on
77 ? `Billing period ${period}. One price for the workspace, never per seat.`
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.78 : "The forge is free: repositories, issues, pull requests, reviews, search and the audit log. Checks, workflows and deployments need the plan, or the trial."
Usage, Billing settings and prepaid AI credit; fixes from the UX audit79 }
80 aside={
81 <p className="shrink-0 sm:text-right">
82 <span className="text-2xl font-semibold tabular-nums tracking-tight">${((plan?.monthlyCents ?? 2000) / 100).toFixed(0)}</span>
83 <span className="text-sm text-muted"> / month</span>
84 <span className="block text-xs text-faint">with {wholeDollars(report?.included?.of ?? 10_000_000)} of usage included</span>
Merge Stripe Tax, the card fee on card payments, and one free workspace per person85 {status.kind !== "comped" && status.kind !== "enterprise" && (
86 <span className="block text-xs text-faint">
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.87 {plan?.cardFeeCents ? `+ ${money(plan.cardFeeCents * 10_000)} card processing fee, ` : ""}
Merge Stripe Tax, the card fee on card payments, and one free workspace per person88 {plan?.cardFeeCents ? PLUS_TAX : "Plus tax where it applies"}
89 </span>
90 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit91 </p>
92 }
93 >
94 <div className="mt-4 grid gap-4 sm:grid-cols-2">
95 {report?.included ? (
96 <div className="rounded-lg border border-line bg-bg/40 p-3">
97 <p className="flex justify-between text-sm">
98 <span className="text-muted">Included usage</span>
99 <span className="tabular-nums">
100 {money(report.included.used)} <span className="text-faint">/ {money(report.included.of)}</span>
101 </span>
102 </p>
103 <div className="mt-2 h-1.5 overflow-hidden rounded-full bg-line" role="presentation">
104 <div className="h-full rounded-full bg-accent" style={{ width: `${Math.min(100, (report.included.used / Math.max(1, report.included.of)) * 100)}%` }} />
105 </div>
106 <p className="mt-1.5 text-xs text-faint">Used first; starts again on the 1st. Unused usage does not carry over.</p>
107 </div>
108 ) : (
109 <div className="rounded-lg border border-line bg-bg/40 p-3 text-sm">
110 <p className="text-muted">Usage at price this month</p>
111 <p className="mt-1 text-lg font-semibold tabular-nums">{report ? money(report.totals.priceMicros) : "—"}</p>
112 {(report?.discountPercent ?? 0) > 0 && report && (
113 <p className="text-xs text-faint">
114 Discount ({report.discountPercent}%) −{money(report.totals.discountMicros)} · charged {money(report.totals.chargedMicros)}
115 </p>
116 )}
117 </div>
118 )}
119 <div className="rounded-lg border border-line bg-bg/40 p-3 text-sm">
120 <p className="flex justify-between">
121 <span className="text-muted">Upcoming invoice</span>
122 <span className="text-lg font-semibold tabular-nums">{upcoming ? money(upcoming.totalMicros) : "—"}</span>
123 </p>
124 <p className="text-xs text-faint">Including add-ons and usage, from g1t's ledger{upcoming ? `; the month closes ${new Date(upcoming.closesAt).toLocaleDateString("en-US", { month: "short", day: "numeric", timeZone: "UTC" })}` : ""}.</p>
125 {upcoming && (
126 <details className="mt-1.5">
127 <summary className="cursor-pointer text-xs text-muted hover:text-fg">View upcoming invoice</summary>
128 <dl className="mt-1.5 space-y-1 text-xs">
129 <div className="flex justify-between">
130 <dt className="text-muted">Plan and add-ons</dt>
131 <dd className="tabular-nums">{money(upcoming.subscriptionsMicros)}</dd>
132 </div>
133 <div className="flex justify-between">
134 <dt className="text-muted">Usage owed, after included usage, credit and discount</dt>
135 <dd className="tabular-nums">{money(upcoming.usageMicros)}</dd>
136 </div>
137 </dl>
138 </details>
139 )}
140 </div>
141 </div>
142 <div className="mt-4 flex flex-wrap items-center gap-2">
143 <Link to={`/${slug}/-/usage`} className="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm text-fg/80 hover:border-line-strong hover:text-fg">
144 <FileText size={14} /> Usage
145 </Link>
146 <a href="#invoices" className="inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm text-fg/80 hover:border-line-strong hover:text-fg">
147 <ReceiptText size={14} /> Invoices
148 </a>
149 {owner && enabled && (
150 <Form method="post" className="contents">
151 <input type="hidden" name="feature" value="plan" />
152 {status.kind === "free" || status.kind === "trial" ? (
153 <SubmitButton variant="accent" name="intent" value="subscribe" pending="Opening Stripe…">
154 <CreditCard size={14} />
155 Start the g1t plan
156 </SubmitButton>
157 ) : status.kind === "canceling" ? (
158 <SubmitButton variant="accent" name="intent" value="resume" pending="Saving…">
159 Keep the plan
160 </SubmitButton>
161 ) : status.kind === "paid" && subscription ? (
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.162 <SubmitButton variant="outline" name="intent" value="cancel" pending="Saving…">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit163 Downgrade to free at the period's end
164 </SubmitButton>
165 ) : null}
166 </Form>
167 )}
168 <a href="mailto:sales@g1t.sh?subject=Custom%20needs" className="ml-auto inline-flex items-center gap-1.5 text-sm text-muted hover:text-fg">
169 <Mail size={14} /> Custom needs? Contact us
170 </a>
171 </div>
172 {staff && enabled && (status.kind === "free" || status.kind === "trial") && (
173 <p className="mt-3 text-xs text-faint">Test mode: card 4242 4242 4242 4242, any future date and code.</p>
174 )}
175 {(status.kind === "free" || status.kind === "trial") && (
176 <p className="mt-3 text-xs text-faint">
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.177 Your first month starts at a {wholeDollars(100_000_000)} limit on usage past
Usage, Billing settings and prepaid AI credit; fixes from the UX audit178 what is included, so a stolen card cannot run up a bill.
179 </p>
180 )}
181 <ErrorText>{error}</ErrorText>
182 </Card>
183 );
184}
185
186// --- Budget alerts --------------------------------------------------------------------------------
187
188/** The budget's alerts, whether it pauses usage, and its webhook: beside the spend limit. */
189export function BudgetAlerts({ limit, owner, error }: { limit: Limit; owner: boolean; error?: string }) {
190 const levels = limit.alertLevels ?? [100, 90, 75, 50];
191 const pause = limit.pauseAtLimit ?? true;
192 const spent = limit.spentMicros ?? 0;
193 const of = limit.spendLimitMicros;
194 return (
195 <Card id="budget" icon={<Bell size={16} />} title="Budget alerts" about="On usage past what the plan includes, measured against the spend limit above. Each alert is emailed to the owners once a month.">
196 {of != null && (
197 <div className="mt-3">
198 <div className="flex justify-between text-sm">
199 <span className="text-muted">Spent this month</span>
200 <span className="tabular-nums">
201 {money(spent)} <span className="text-faint">of {money(of)}</span>
202 </span>
203 </div>
204 <div className="relative mt-1.5 h-1.5 rounded-full bg-line" role="presentation">
Merge branch 'worktree-agent-ad7c6d88d93adc817'205 <div className={`h-full rounded-full ${spent >= of ? "bg-danger" : spent * 4 >= of * 3 ? "bg-warn" : "bg-success"}`} style={{ width: `${Math.min(100, (spent / Math.max(1, of)) * 100)}%` }} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit206 {levels.filter((l) => l < 100).map((l) => (
207 <span key={l} className="absolute -top-0.5 h-2.5 w-px bg-fg/40" style={{ left: `${l}%` }} />
208 ))}
209 </div>
210 </div>
211 )}
212 {owner ? (
213 <Form method="post" className="mt-4 space-y-3 text-sm">
214 <input type="hidden" name="intent" value="budget" />
215 <fieldset className="flex flex-wrap items-center gap-3">
216 <legend className="mb-1.5 text-xs text-muted">Alert at</legend>
217 {[50, 75, 90, 100].map((level) => (
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.218 <label key={level} className="flex cursor-pointer items-center gap-1.5 tabular-nums">
219 <Checkbox name="alert" value={String(level)} defaultChecked={levels.includes(level)} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit220 {level}%
221 </label>
222 ))}
223 </fieldset>
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.224 <label className="flex cursor-pointer flex-wrap items-center gap-2">
225 <Checkbox name="pause" defaultChecked={pause} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit226 Pause usage at 100%
227 <span className="text-xs text-faint">Off, the budget only alerts; g1t's own limit still applies.</span>
228 </label>
229 <label className="block">
230 <span className="block text-xs text-muted">Webhook, told of each alert (optional)</span>
231 <input name="webhook" type="url" placeholder="https://hooks.example.com/g1t" defaultValue={limit.budgetWebhook ?? ""} className={`mt-1 ${FIELD} max-w-md`} />
232 </label>
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.233 <SubmitButton variant="outline" match={{ intent: "budget" }} pending="Saving…">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit234 Save alerts
235 </SubmitButton>
236 </Form>
237 ) : (
238 <p className="mt-3 text-sm text-muted">
239 Alerts at {levels.map((l) => `${l}%`).join(", ") || "none"}; usage {pause ? "pauses" : "does not pause"} at 100%.
240 </p>
241 )}
242 <ErrorText>{error}</ErrorText>
243 </Card>
244 );
245}
246
247// --- Payment method -------------------------------------------------------------------------------
248
249export function PaymentMethodCard({ details, owner, enabled, staff, error }: { details: BillingDetails | null; owner: boolean; enabled: boolean; staff: boolean; error?: string }) {
250 const method = details?.paymentMethod;
251 return (
252 <Card
253 id="payment"
254 icon={<CreditCard size={16} />}
255 title="Payment method"
256 about="Cards are added, removed and made the default on Stripe's billing page. g1t never sees card numbers."
257 aside={
258 owner && enabled ? (
259 <Form method="post">
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.260 <SubmitButton variant="outline" name="intent" value="portal" pending="Opening Stripe…">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit261 Manage in Stripe
262 <ArrowUpRight size={14} />
263 </SubmitButton>
264 </Form>
265 ) : null
266 }
267 >
268 <div className="mt-3 text-sm">
269 {method ? (
270 <p className="flex flex-wrap items-center gap-2">
271 <span className="rounded border border-line px-1.5 py-0.5 text-xs uppercase tracking-wide text-muted">{method.brand ?? method.kind}</span>
272 {method.last4 && (
273 <span>
274 ending <span className="font-mono">{method.last4}</span>
275 </span>
276 )}
277 {method.expMonth && method.expYear && (
278 <span className="text-muted">
279 · expires {String(method.expMonth).padStart(2, "0")}/{method.expYear}
280 </span>
281 )}
282 <span className="text-xs text-faint">default</span>
283 </p>
284 ) : (
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.285 <p className="text-muted">No payment method yet. Starting the plan saves the card you pay with.</p>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit286 )}
287 {details?.unavailable && <p className="mt-2 text-xs text-warn">{details.unavailable}</p>}
288 {staff && <p className="mt-2 text-xs text-faint">Test mode: card 4242 4242 4242 4242, any future date and code.</p>}
289 </div>
290 <ErrorText>{error}</ErrorText>
291 </Card>
292 );
293}
294
295// --- Add-ons ------------------------------------------------------------------------------------
296
Merge remote-tracking branch 'origin/claude/cool-newton-7nm8r9' into claude/cool-newton-7nm8r9297export function AddOns({ plan, security, error }: { plan: FeatureState | null; security: FeatureState | null; error?: string }) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit298 const planOn = !!plan?.on;
299 const rows: { key: string; name: string; about: string; price: string; on: boolean; label: string; control: ReactNode }[] = [];
300 if (security) {
301 rows.push({
302 key: "security",
303 name: security.plan.title,
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.304 about: "Custom patterns, validity checks, code scanning and dependency review on private repositories. No price of its own: scans are charged at cost plus 20%.",
305 price: "With the plan",
Usage, Billing settings and prepaid AI credit; fixes from the UX audit306 on: security.on,
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.307 label: security.on ? "On" : "Needs the plan",
308 control: null,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit309 });
310 }
311 rows.push({
312 key: "deployments",
313 name: "Production deployments",
314 about: "Previews per pull request and production on g1t.page, metered at cost plus 20%.",
315 price: "With the plan",
316 on: planOn,
317 label: planOn ? "On" : "Needs the plan",
318 control: null,
319 });
320 return (
321 <section id="add-ons" className="mb-6 scroll-mt-20 overflow-hidden rounded-xl border border-line bg-surface">
322 <h2 className="px-5 pt-5 font-medium">Add-ons</h2>
323 <ul className="mt-3 divide-y divide-line border-t border-line">
324 {rows.map((row) => (
325 <li key={row.key} className="flex flex-wrap items-center gap-x-4 gap-y-2 px-5 py-3 text-sm">
326 <div className="min-w-0 flex-1 basis-60">
327 <p className="font-medium">{row.name}</p>
328 <p className="text-xs text-muted">{row.about}</p>
329 </div>
330 <span className="tabular-nums text-muted">{row.price}</span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'331 <span className={`rounded-full px-2 py-0.5 text-xs ${row.on ? "bg-success/15 text-success" : "border border-line text-muted"}`}>{row.label}</span>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit332 {row.control}
333 </li>
334 ))}
335 </ul>
336 {error && (
337 <div className="px-5 pb-4">
338 <ErrorText>{error}</ErrorText>
339 </div>
340 )}
341 </section>
342 );
343}
344
345// --- Invoice details ------------------------------------------------------------------------------
346
347const TAX_IDS: [string, string][] = [
348 ["", "None"],
349 ["eu_vat", "EU VAT"],
350 ["gb_vat", "UK VAT"],
351 ["us_ein", "US EIN"],
352 ["ca_bn", "Canadian BN"],
353 ["au_abn", "Australian ABN"],
354 ["ch_vat", "Swiss VAT"],
355 ["no_vat", "Norwegian VAT"],
356 ["in_gst", "Indian GST"],
357 ["jp_cn", "Japanese corporate number"],
358 ["br_cnpj", "Brazilian CNPJ"],
359 ["mx_rfc", "Mexican RFC"],
360 ["sg_uen", "Singapore UEN"],
361 ["nz_gst", "New Zealand GST"],
362 ["za_vat", "South African VAT"],
363];
364
Merge Stripe Tax, the card fee on card payments, and one free workspace per person365/** What Stripe's check of a tax ID found. */
366const TAX_ID_STATUS: Record<string, string> = {
367 verified: "Verified by Stripe.",
368 pending: "Stripe is checking it.",
369 unverified: "Stripe could not verify it. Check the number.",
370 unavailable: "Stripe cannot check this kind of ID.",
371};
372
Usage, Billing settings and prepaid AI credit; fixes from the UX audit373const LANGUAGES: [string, string][] = [
374 ["", "Automatic"],
375 ["en", "English"],
376 ["en-GB", "English (UK)"],
377 ["de", "German"],
378 ["fr", "French"],
379 ["es", "Spanish"],
380 ["it", "Italian"],
381 ["nl", "Dutch"],
382 ["pt-BR", "Portuguese (Brazil)"],
383 ["ja", "Japanese"],
384 ["zh", "Chinese"],
385];
386
387export function InvoiceDetailsCard({ details, owner, enabled, error }: { details: BillingDetails | null; owner: boolean; enabled: boolean; error?: string }) {
388 const a = details?.address;
389 const disabled = !owner || !enabled;
390 return (
Merge Stripe Tax, the card fee on card payments, and one free workspace per person391 <Card
392 id="details"
393 icon={<ReceiptText size={16} />}
394 title="Invoice details"
395 about="Who invoices are for. Kept on the workspace's Stripe customer and printed on every invoice. Tax is worked out from the address."
396 >
397 {details?.customer && !details.taxLocation && (
398 <p className={`mt-3 rounded-lg border px-3 py-2 text-sm ${details.taxAddressNeededAt ? "border-warn/40 bg-warn/5" : "border-line bg-bg/40 text-muted"}`}>
399 {details.taxAddressNeededAt
400 ? "Add the billing address: Stripe needs at least the country (and in the US the ZIP code) to work out tax, so g1t is not charging the card until it is here."
401 : "No billing address yet. Stripe needs at least the country (and in the US the ZIP code) to work out tax before g1t charges the card."}
402 </p>
403 )}
404 {(details?.taxExempt === "exempt" || details?.taxExempt === "reverse") && (
405 <p className="mt-3 text-xs text-muted">
406 {details.taxExempt === "exempt" ? "Tax exempt: no tax is added." : "Reverse charge: you account for the tax yourself, and invoices say so."}
407 </p>
408 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit409 <Form method="post" className="mt-4 grid gap-3 text-sm sm:grid-cols-2">
410 <input type="hidden" name="intent" value="details" />
411 <fieldset disabled={disabled} className="contents">
412 <label className="block">
413 <span className="text-xs text-muted">Invoice email</span>
414 <input name="email" type="email" defaultValue={details?.email ?? ""} className={`mt-1 ${FIELD}`} />
415 </label>
416 <label className="block">
417 <span className="text-xs text-muted">Company name</span>
418 <input name="name" defaultValue={details?.name ?? ""} className={`mt-1 ${FIELD}`} />
419 </label>
420 <label className="block sm:col-span-2">
421 <span className="text-xs text-muted">Address</span>
422 <input name="line1" placeholder="Street" defaultValue={a?.line1 ?? ""} className={`mt-1 ${FIELD}`} />
423 <input name="line2" placeholder="Suite, floor (optional)" aria-label="Address line 2" defaultValue={a?.line2 ?? ""} className={`mt-2 ${FIELD}`} />
424 </label>
425 <div className="grid grid-cols-2 gap-3 sm:col-span-2 sm:grid-cols-4">
426 <input name="city" placeholder="City" aria-label="City" defaultValue={a?.city ?? ""} className={FIELD} />
427 <input name="state" placeholder="State or region" aria-label="State or region" defaultValue={a?.state ?? ""} className={FIELD} />
428 <input name="postalCode" placeholder="Postal code" aria-label="Postal code" defaultValue={a?.postalCode ?? ""} className={FIELD} />
429 <input name="country" placeholder="Country (US)" aria-label="Country, two letters" maxLength={2} defaultValue={a?.country ?? ""} className={`${FIELD} uppercase`} />
430 </div>
431 <label className="block">
432 <span className="text-xs text-muted">Tax ID</span>
433 <span className="mt-1 flex gap-2">
Chat controls, public profiles, shadcn selects, and no Docs tab in a project434 <SelectField
435 name="taxIdType"
436 defaultValue={details?.taxIdType ?? ""}
437 aria-label="Kind of tax ID"
438 className="h-auto w-auto shrink-0 px-2.5 py-1.5"
439 options={TAX_IDS.map(([value, label]) => ({ value, label }))}
440 />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit441 <input name="taxId" defaultValue={details?.taxId ?? ""} aria-label="Tax ID" className={FIELD} />
442 </span>
Merge Stripe Tax, the card fee on card payments, and one free workspace per person443 {details?.taxId && details.taxIdStatus && (
444 <span className="mt-1 block text-xs text-faint">{TAX_ID_STATUS[details.taxIdStatus] ?? `Stripe's check: ${details.taxIdStatus}`}</span>
445 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit446 </label>
447 <label className="block">
448 <span className="text-xs text-muted">Purchase order</span>
449 <input name="poNumber" defaultValue={details?.poNumber ?? ""} className={`mt-1 ${FIELD}`} />
450 </label>
451 <label className="block">
452 <span className="text-xs text-muted">Invoice language</span>
Chat controls, public profiles, shadcn selects, and no Docs tab in a project453 <SelectField
454 name="language"
455 defaultValue={details?.language ?? ""}
456 aria-label="Invoice language"
457 className="mt-1 h-auto px-2.5 py-1.5"
458 options={LANGUAGES.map(([value, label]) => ({ value, label }))}
459 />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit460 </label>
461 {!disabled && (
462 <div className="flex items-end">
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.463 <SubmitButton variant="outline" match={{ intent: "details" }} pending="Saving…">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit464 Save invoice details
465 </SubmitButton>
466 </div>
467 )}
468 </fieldset>
469 </Form>
470 {!owner && <p className="mt-3 text-xs text-faint">An owner changes these.</p>}
471 <ErrorText>{error}</ErrorText>
472 </Card>
473 );
474}
475
476// --- Invoices ---------------------------------------------------------------------------------------
477
478const STATUS: Record<string, string> = { paid: "Paid", open: "Open", void: "Void", uncollectible: "Uncollectible", draft: "Draft" };
479
480export function InvoicesCard({ details }: { details: BillingDetails | null }) {
481 const invoices = details?.invoices ?? [];
482 return (
483 <section id="invoices" className="mb-6 scroll-mt-20 overflow-hidden rounded-xl border border-line bg-surface">
484 <div className="px-5 pt-5">
485 <h2 className="font-medium">Invoices</h2>
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.486 <p className="mt-1 text-sm text-muted">The plan, add-ons and each month's usage, as Stripe sent them.</p>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit487 </div>
488 {invoices.length === 0 ? (
489 <p className="px-5 py-5 text-sm text-faint">No invoices yet.</p>
490 ) : (
491 <ul className="mt-3 divide-y divide-line border-t border-line">
492 {invoices.map((invoice) => (
493 <li key={invoice.id} className="flex flex-wrap items-center gap-x-4 gap-y-1 px-5 py-2.5 text-sm">
494 <span className="w-24 shrink-0 text-muted tabular-nums">{new Date(invoice.createdAt).toLocaleDateString("en-US", { timeZone: "UTC" })}</span>
495 <span className="min-w-0 flex-1 truncate">{invoice.description ?? invoice.number ?? invoice.id}</span>
Merge branch 'worktree-agent-ad7c6d88d93adc817'496 <span className={`rounded-full px-2 py-0.5 text-xs ${invoice.status === "paid" ? "bg-success/15 text-success" : invoice.status === "open" ? "bg-warn/15 text-warn" : "border border-line text-muted"}`}>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit497 {STATUS[invoice.status] ?? invoice.status}
498 </span>
499 <span className="w-20 text-right tabular-nums">{money(invoice.totalCents * 10_000)}</span>
500 <span className="flex gap-3 text-xs">
501 {invoice.hostedUrl && (
502 <a href={invoice.hostedUrl} className="text-muted hover:text-fg">
503 View
504 </a>
505 )}
506 {invoice.pdfUrl && (
507 <a href={invoice.pdfUrl} className="text-muted hover:text-fg">
508 PDF
509 </a>
510 )}
511 </span>
512 </li>
513 ))}
514 </ul>
515 )}
516 </section>
517 );
518}
519
520/** Stripe's sections while Stripe is asked: the same shapes. */
521export function StripeSkeleton() {
522 return (
523 <div aria-busy="true">
524 <span role="status" className="sr-only">
525 Loading from Stripe…
526 </span>
527 {[5.5, 14, 8].map((rem, i) => (
528 <div key={i} className="mb-6 rounded-xl border border-line bg-surface p-5" aria-hidden="true" style={{ minHeight: `${rem}rem` }}>
529 <Skeleton className="h-4 w-36" />
530 <Skeleton className="mt-3 h-3 w-72 max-w-full" />
531 <Skeleton className="mt-4 h-8 w-full" />
532 </div>
533 ))}
534 </div>
535 );
536}

This file's history is long; its oldest lines are credited to the oldest commit read.