Skip to content
509 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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1import { ArrowUpRight, CreditCard, FileText } from "lucide-react";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit2import { Suspense } from "react";
3import { Await, Link, data, redirect } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell4
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look5import { MICROS_PER_DOLLAR, type WorkspaceInvoice } from "@g1t/contracts";
Agents as a team: lifecycle, merge queue, billing and a new shell6
7import type { Route } from "./+types/billing";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look8import {
9 Alerts,
10 CapsCard,
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging11 CreditsCard,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look12 OverageCard,
13 PrepayCard,
14 RaiseCard,
15 type SectionError,
16 SpendLimitCard,
17 SpikeBanner,
18 TrialCard,
19} from "../../components/billing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit20import {
21 AddOns,
22 AiCreditCard,
23 BudgetAlerts,
24 InvoiceDetailsCard,
25 InvoicesCard,
26 PaymentMethodCard,
27 PlanSummary,
28 StripeSkeleton,
29} from "../../components/billing-settings";
The statement is a month at a time, a line per kind of charge30import { StatementView } from "../../components/statement";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look31import {
32 cardCheckResult,
33 dollars,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit34 parseAiPurchase,
35 parseAiReload,
36 parseBudgetAlerts,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look37 parseCaps,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit38 parseInvoiceDetails,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look39 parseLimitRequest,
40 parsePrepay,
41 parseSpendLimit,
42 planStatus,
43 wholeDollars,
44} from "../../lib/billing";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit45import { isStaff } from "../../lib/usage";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look46import { page } from "../../lib/meta";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas47import { billing } from "../../lib/services.server";
Membership as GitHub has it: owners, roles, member privileges, 2FA48import { assertSameOrigin, getViewer, managesBilling, requireUser, roleIn, unwrap } from "../../lib/session.server";
Agents as a team: lifecycle, merge queue, billing and a new shell49
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look50/** The trial and pools as published, when the price book cannot be read. */
51const DEFAULT_TRIAL_MICROS = 5_000_000;
Agents as a team: lifecycle, merge queue, billing and a new shell52
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look53/** What a change says once it is done (`?done=`). */
54const DONE: Record<string, string> = {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit55 subscribed: "The g1t plan is on, with $5 of AI credit to start.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look56 prepaid: "Payment received. Your prepaid balance and what you can use went up by as much.",
Usage, Billing settings and prepaid AI credit; fixes from the UX audit57 ai_credit: "AI credit bought. It is ready for Agent and AI Gateway usage.",
58 ai_reload: "Auto-reload saved.",
59 budget: "Budget alerts saved.",
60 details: "Invoice details saved on Stripe.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look61 limit: "Spend limit saved.",
62 caps: "Caps saved. They apply to runs that start from now on.",
63 requested: "Sent. g1t answers within one business day, here and by email.",
64 told: "Thanks. g1t looks at it and answers within one business day, here and by email.",
65 keep: "Compute is running again for 24 hours, unless spending doubles again first.",
66 stop: "New compute stays paused until an owner chooses Keep going.",
67 canceled: "The plan ends at the end of the period. Nothing more is charged for it.",
68 resumed: "The plan continues.",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar69 security_on: "Security and quality is on for every private repository in the workspace.",
70 security_canceled: "Security and quality ends at the end of the period. Nothing more is charged for it.",
71 security_resumed: "Security and quality continues.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look72};
73
Social cards for every page: og.g1t.sh74export function meta({ params, ...args }: Route.MetaArgs) {
75 return page(args, { title: `Billing · ${params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell76}
77
78export async function loader({ params, context, request }: Route.LoaderArgs) {
79 const viewer = getViewer(context);
80 const role = roleIn(viewer, params.owner);
81 // Members only; to anyone else the page does not exist.
82 if (!role) throw data(null, { status: 404 });
83 const slug = params.owner.toLowerCase();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look84 const url = new URL(request.url);
85 const here = `/${slug}/-/billing`;
Agents as a team: lifecycle, merge queue, billing and a new shell86
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look87 // Back from Stripe: record what happened, then drop the ids from the address.
88 const cardCheck = url.searchParams.get("card_check");
89 if (cardCheck) {
90 const checked = await billing.confirmCardCheck(slug, viewer, cardCheck);
91 throw redirect(checked.ok ? `${here}?checked=1#trial` : `${here}?problem=${encodeURIComponent(checked.error.message)}#trial`);
92 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit93 const aiCredit = url.searchParams.get("ai_credit");
94 if (aiCredit) {
95 const bought = await billing.confirmAiCredit(slug, viewer, aiCredit);
96 throw redirect(bought.ok ? `${here}?done=ai_credit#ai-credit` : `${here}?problem=${encodeURIComponent(bought.error.message)}#ai-credit`);
97 }
Agents as a team: lifecycle, merge queue, billing and a new shell98 const session = url.searchParams.get("session");
99 if (session) {
Paid features: a workspace turns on Deployments with a monthly plan100 if (url.searchParams.get("plan")) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look101 const started = await billing.confirmSubscription(slug, viewer, session);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar102 const doneKey = url.searchParams.get("plan") === "security" ? "security_on" : "subscribed";
103 throw redirect(started.ok ? `${here}?done=${doneKey}` : `${here}?problem=${encodeURIComponent(started.error.message)}`);
Paid features: a workspace turns on Deployments with a monthly plan104 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look105 const paid = await billing.confirm(slug, viewer, session);
106 throw redirect(paid.ok ? `${here}?done=prepaid#prepay` : `${here}?problem=${encodeURIComponent(paid.error.message)}#prepay`);
Agents as a team: lifecycle, merge queue, billing and a new shell107 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit108 // A plan started on the card already saved comes straight back.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar109 const plans = url.searchParams.getAll("plan");
110 if (plans.includes("started")) throw redirect(`${here}?done=${plans.includes("security") ? "security_on" : "subscribed"}`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look111
Usage, Billing settings and prepaid AI credit; fixes from the UX audit112 const now = new Date();
113 const monthStart = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), 1)).toISOString().slice(0, 10);
114 const today = now.toISOString().slice(0, 10);
The statement is a month at a time, a line per kind of charge115 const group: "day" | "project" = url.searchParams.get("group") === "project" ? "project" : "day";
Usage, Billing settings and prepaid AI credit; fixes from the UX audit116 // Stripe's own (payment method, invoice details, invoices) is streamed:
117 // the page does not wait on Stripe, and its sections hold their place.
118 const details = billing
119 .billingDetails(slug, viewer)
120 .then((result) => (result.ok ? result.value : null))
121 .catch(() => null);
122 const [account, statement, features, limit, invoices, entitlements, requests, book, credits, ai, report] = await Promise.all([
Agents as a team: lifecycle, merge queue, billing and a new shell123 billing.account(slug, viewer),
The statement is a month at a time, a line per kind of charge124 billing.statement(slug, viewer, url.searchParams.get("month"), group),
Paid features: a workspace turns on Deployments with a monthly plan125 billing.features(slug, viewer),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look126 billing.limit(slug, viewer).catch(() => null),
Two limits, real invoices, trust that grows by itself, sales signals127 billing.invoices(slug, viewer).catch(() => null),
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put128 billing.entitlements(slug).catch(() => null),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look129 billing.limitRequests(slug, viewer).catch(() => null),
130 billing.prices().catch(() => null),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging131 billing.credits(slug, viewer).catch(() => null),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit132 billing.aiCredit(slug, viewer).catch(() => null),
133 billing.usageReport(slug, viewer, { from: monthStart, until: today }).catch(() => null),
Agents as a team: lifecycle, merge queue, billing and a new shell134 ]);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look135 const featureStates = unwrap(features);
136 const trialMicros = book?.free?.trialWorkspaceMicros ?? DEFAULT_TRIAL_MICROS;
137 const done = url.searchParams.get("done");
Usage, Billing settings and prepaid AI credit; fixes from the UX audit138 // Credit from g1t for everything; AI credit has its own card.
139 const general = credits?.ok ? credits.value.grants.filter((grant) => grant.scope !== "models") : [];
Agents as a team: lifecycle, merge queue, billing and a new shell140 return {
141 slug,
142 role,
Membership as GitHub has it: owners, roles, member privileges, 2FA143 // Billing managers manage billing as owners do.
144 managesBilling: managesBilling(viewer, params.owner),
Usage, Billing settings and prepaid AI credit; fixes from the UX audit145 staff: isStaff(viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell146 account: unwrap(account),
The statement is a month at a time, a line per kind of charge147 statement: unwrap(statement),
148 group,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look149 plan: featureStates.find((state) => state.plan.feature === "plan") ?? null,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar150 securityPlan: featureStates.find((state) => state.plan.feature === "security") ?? null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look151 limit: limit?.ok ? limit.value : null,
Two limits, real invoices, trust that grows by itself, sales signals152 invoices: invoices?.ok ? invoices.value : [],
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put153 entitlements,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look154 requests: requests?.ok ? requests.value : [],
Usage, Billing settings and prepaid AI credit; fixes from the UX audit155 credits: general.length > 0 && credits?.ok ? { ...credits.value, grants: general } : null,
156 ai: ai?.ok ? ai.value : null,
157 report: report?.ok ? report.value : null,
158 details,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look159 trialMicros,
160 notice:
161 url.searchParams.has("checked") && entitlements
162 ? cardCheckResult(entitlements, trialMicros)
163 : done && DONE[done]
164 ? DONE[done]
165 : null,
166 problem: url.searchParams.get("problem"),
Agents as a team: lifecycle, merge queue, billing and a new shell167 };
168}
169
170export async function action({ request, params, context }: Route.ActionArgs) {
171 assertSameOrigin(request);
172 const user = requireUser(context, request);
173 const form = await request.formData();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look174 const slug = params.owner.toLowerCase();
175 // Spelled out: a form post arrives at a data address, not the page's.
176 const here = `${new URL(request.url).origin}/${slug}/-/billing`;
177 const intent = String(form.get("intent") ?? "");
178 const fail = (section: string, error: string) => data<SectionError>({ section, error }, { status: 422 });
179 const done = (key: string, anchor = "") => redirect(`/${slug}/-/billing?done=${key}${anchor}`);
180
181 switch (intent) {
182 case "portal": {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit183 // Cards, payment methods and receipts live on Stripe's own page.
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look184 const started = await billing.billingPortal(user, slug, here);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit185 if (!started.ok) return fail("payment", started.error.message);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look186 throw redirect(started.value.url);
187 }
188 case "subscribe": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar189 // The plan, or the Security and quality activation.
190 const feature = form.get("feature") === "security" ? "security" : "plan";
191 const started = await billing.subscribe(user, slug, feature, `${here}?plan=${feature}`);
192 if (!started.ok) return fail(feature, started.error.message);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look193 throw redirect(started.value.url);
194 }
195 case "cancel":
196 case "resume": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar197 const feature = form.get("feature") === "security" ? "security" : "plan";
198 const changed = await billing.cancelSubscription(user, slug, feature, intent === "resume");
199 if (!changed.ok) return fail(feature, changed.error.message);
200 const prefix = feature === "security" ? "security_" : "";
201 throw done(`${prefix}${intent === "resume" ? "resumed" : "canceled"}`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look202 }
203 case "card-check": {
204 const started = await billing.cardCheck(user, slug, here);
205 if (!started.ok) return fail("trial", started.error.message);
206 throw redirect(started.value.url);
207 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit208 case "buy-ai-credit": {
209 const parsed = parseAiPurchase({ amount: form.get("amount"), custom: form.get("custom") });
210 if (!parsed.ok) return fail("ai", parsed.error);
211 const started = await billing.buyAiCredit(user, slug, parsed.value.amountCents, here);
212 if (!started.ok) return fail("ai", started.error.message);
213 throw redirect(started.value.url);
214 }
215 case "ai-reload": {
216 const parsed = parseAiReload({ enabled: form.get("enabled"), threshold: form.get("threshold"), target: form.get("target"), monthly: form.get("monthly") });
217 if (!parsed.ok) return fail("ai", parsed.error);
218 const set = await billing.setAiReload(user, slug, parsed.value);
219 if (!set.ok) return fail("ai", set.error.message);
220 throw done("ai_reload", "#ai-credit");
221 }
222 case "budget": {
223 const parsed = parseBudgetAlerts({ alerts: form.getAll("alert"), pause: form.get("pause"), webhook: form.get("webhook") });
224 if (!parsed.ok) return fail("budget", parsed.error);
225 const set = await billing.setBudget(user, slug, { amountMicros: null, keepLimit: true, ...parsed.value });
226 if (!set.ok) return fail("budget", set.error.message);
227 throw done("budget", "#budget");
228 }
229 case "details": {
230 const parsed = parseInvoiceDetails(form);
231 if (!parsed.ok) return fail("details", parsed.error);
232 const saved = await billing.setBillingDetails(user, slug, parsed.value);
233 if (!saved.ok) return fail("details", saved.error.message);
234 throw done("details", "#details");
235 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look236 case "spend-limit": {
237 const parsed = parseSpendLimit({ mode: form.get("mode"), limit: form.get("limit") });
238 if (!parsed.ok) return fail("limit", parsed.error);
239 let micros = parsed.value.micros;
240 if (parsed.value.raiseOnce && micros == null) {
241 // The one-time raise with no amount: as high as it goes.
242 const limit = await billing.limit(slug, user);
243 micros = limit.ok ? (limit.value.raiseOnceMicros ?? null) : null;
244 if (micros == null) return fail("limit", "The one-time raise is not available. Prepay, or use Raise my limit.");
245 }
246 const set = await billing.setSpendLimit(user, slug, micros, parsed.value.useFull, parsed.value.raiseOnce);
247 if (!set.ok) return fail("limit", set.error.message);
248 throw done("limit", "#limit");
249 }
250 case "request": {
251 const parsed = parseLimitRequest({
252 kind: form.get("kind"),
253 amount: form.get("amount"),
254 reason: form.get("reason"),
255 expected: form.get("expected"),
256 });
257 const section = String(form.get("kind")) === "overage" ? "overage" : "raise";
258 if (!parsed.ok) return fail(section, parsed.error);
259 const sent = await billing.requestLimit(user, slug, parsed.value);
260 if (!sent.ok) return fail(section, sent.error.message);
261 throw done(section === "overage" ? "told" : "requested", `#${section}`);
262 }
263 case "caps": {
264 const parsed = parseCaps({ run: form.get("run"), issue: form.get("issue") });
265 if (!parsed.ok) return fail("caps", parsed.error);
266 const set = await billing.setCaps(user, slug, parsed.value);
267 if (!set.ok) return fail("caps", set.error.message);
268 throw done("caps", "#caps");
269 }
270 case "spike": {
271 const keep = String(form.get("decision")) === "keep";
272 const answered = await billing.confirmSpike(user, slug, keep);
273 if (!answered.ok) return fail("spike", answered.error.message);
274 throw done(keep ? "keep" : "stop");
Usage limits: unpaid usage can only go so far275 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look276 case "prepay": {
277 const parsed = parsePrepay({ amount: form.get("amount"), custom: form.get("custom"), method: form.get("method") });
278 if (!parsed.ok) return fail("prepay", parsed.error);
279 const started = await billing.checkout(user, slug, parsed.value.amountCents, here, parsed.value.method);
280 if (!started.ok) return fail("prepay", started.error.message);
281 throw redirect(started.value.url);
Paid features: a workspace turns on Deployments with a monthly plan282 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look283 default:
284 return fail("plan", "Unknown action.");
Paid features: a workspace turns on Deployments with a monthly plan285 }
Agents as a team: lifecycle, merge queue, billing and a new shell286}
287
288export default function WorkspaceBilling({ loaderData, actionData }: Route.ComponentProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit289 const { slug, role, staff, account, statement, group, plan, securityPlan, limit, invoices, entitlements, requests, credits, ai, report, details, trialMicros, notice, problem } =
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put290 loaderData;
Agents as a team: lifecycle, merge queue, billing and a new shell291 const { status } = account;
Membership as GitHub has it: owners, roles, member privileges, 2FA292 const owner = role === "owner" || loaderData.managesBilling;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look293 const error = actionData as SectionError;
294 const err = (section: string) => (error && error.section === section ? error.error : undefined);
295 const standing = planStatus(plan, entitlements);
296 const paying = standing.kind === "paid" || standing.kind === "canceling" || standing.kind === "past_due";
297 const free = standing.kind === "free" || standing.kind === "trial";
298 const prepaid = limit?.prepaidMicros ?? entitlements?.prepaidMicros ?? 0;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit299 // The spend limit and its budget are the owners' to set only where
300 // billing lets them: on the plan, never a free workspace's ceiling.
301 const selfServe = !!limit && (limit.trust === "paid" || limit.trust === "established");
302 const summary = (loaded: Awaited<typeof details>) => (
303 <PlanSummary slug={slug} state={plan} status={standing} details={loaded} report={report} owner={owner} enabled={status.enabled} staff={staff} error={err("plan")} />
304 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look305
Agents as a team: lifecycle, merge queue, billing and a new shell306 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look307 <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_20rem]">
Agents as a team: lifecycle, merge queue, billing and a new shell308 <div className="min-w-0">
Free while g1t is being built out; agents can check out their own forks309 {status.free && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look310 <div className="mb-6 rounded-xl border border-accent/30 bg-accent/5 p-5">
Free while g1t is being built out; agents can check out their own forks311 <h2 className="font-medium">Free while g1t is being built out</h2>
312 <p className="mt-1.5 max-w-2xl text-sm text-muted">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look313 Runs are recorded with what they cost, so Usage shows what you use, but nothing is charged for now. We will say so well
314 before anything is.
Free while g1t is being built out; agents can check out their own forks315 </p>
316 </div>
317 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look318 {notice && <p className="mb-6 rounded-lg border border-accent/30 bg-accent/5 px-4 py-2.5 text-sm">{notice}</p>}
319 {problem && <p className="mb-6 rounded-lg border border-danger/40 bg-danger/5 px-4 py-2.5 text-sm">{problem}</p>}
320
Merge Stripe Tax, the card fee on card payments, and one free workspace per person321 <Suspense fallback={null}>
322 <Await resolve={details}>
323 {(loaded) =>
324 loaded?.taxAddressNeededAt ? (
325 <div role="alert" className="mb-6 rounded-xl border border-warn/40 bg-warn/5 p-4 text-sm">
326 <p className="font-medium">Add a billing address</p>
327 <p className="mt-1 text-muted">
328 Stripe needs it to work out tax, so g1t did not charge the card. Nothing is lost: the charge goes through once the
329 address is saved.{" "}
330 {owner ? (
331 <a href="#details" className="text-fg underline underline-offset-2">
332 Add it under Invoice details
333 </a>
334 ) : (
335 "An owner adds it under Invoice details."
336 )}
337 </p>
338 </div>
339 ) : null
340 }
341 </Await>
342 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look343 {entitlements && <SpikeBanner slug={slug} entitlements={entitlements} owner={owner} />}
344 {err("spike") && <p className="mb-6 text-sm text-danger">{err("spike")}</p>}
345 <Alerts alerts={entitlements?.alerts ?? []} />
346
Usage, Billing settings and prepaid AI credit; fixes from the UX audit347 <Suspense fallback={summary(null)}>
348 <Await resolve={details}>{(loaded) => summary(loaded)}</Await>
349 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look350
Usage, Billing settings and prepaid AI credit; fixes from the UX audit351 {ai && status.enabled && <AiCreditCard credit={ai} owner={owner} enabled={status.enabled} staff={staff} error={err("ai")} />}
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar352
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look353 {free && status.enabled && (
Merge branch 'worktree-agent-a985c01f43538af01'354 <TrialCard entitlements={entitlements} trialMicros={trialMicros} owner={owner} enabled={status.enabled} error={err("trial")} />
A flat project menu with Code first, views as tabs, and Stripe for every owner355 )}
356
Usage, Billing settings and prepaid AI credit; fixes from the UX audit357 {paying && limit && selfServe && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look358 <>
Merge branch 'worktree-agent-a985c01f43538af01'359 <SpendLimitCard limit={limit} owner={owner} error={err("limit")} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit360 <BudgetAlerts limit={limit} owner={owner} error={err("budget")} />
Merge branch 'worktree-agent-a985c01f43538af01'361 <RaiseCard requests={requests} owner={owner} error={err("raise")} />
Merge Stripe Tax, the card fee on card payments, and one free workspace per person362 <PrepayCard prepaidMicros={prepaid} owner={owner} live={status.live || !staff} cardFee={ai?.cardFee ?? null} error={err("prepay")} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look363 </>
364 )}
365
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging366 {credits && <CreditsCard credits={credits} />}
367
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look368 {entitlements && (paying || standing.kind === "trial" || standing.kind === "comped" || standing.kind === "enterprise") && (
Merge branch 'worktree-agent-a985c01f43538af01'369 <CapsCard entitlements={entitlements} owner={owner} error={err("caps")} />
Usage limits: unpaid usage can only go so far370 )}
371
Merge branch 'worktree-agent-a985c01f43538af01'372 {paying && <OverageCard requests={requests} owner={owner} error={err("overage")} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look373
Usage, Billing settings and prepaid AI credit; fixes from the UX audit374 <AddOns plan={plan} security={securityPlan} owner={owner} enabled={status.enabled} error={err("security")} />
375
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look376 {status.enabled && (
Usage, Billing settings and prepaid AI credit; fixes from the UX audit377 <Suspense fallback={<StripeSkeleton />}>
378 <Await resolve={details}>
379 {(loaded) => (
380 <>
381 <PaymentMethodCard details={loaded} owner={owner} enabled={status.enabled} staff={staff} error={err("payment")} />
382 <InvoiceDetailsCard details={loaded} owner={owner} enabled={status.enabled} error={err("details")} />
383 <InvoicesCard details={loaded} />
384 </>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace385 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit386 </Await>
387 </Suspense>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace388 )}
389
Two limits, real invoices, trust that grows by itself, sales signals390 {invoices.length > 0 && <InvoiceList invoices={invoices} />}
Agents as a team: lifecycle, merge queue, billing and a new shell391
The statement is a month at a time, a line per kind of charge392 <StatementView slug={slug} statement={statement} group={group} />
Agents as a team: lifecycle, merge queue, billing and a new shell393 </div>
394
395 <aside className="space-y-5 text-sm">
396 <section className="rounded-xl border border-line bg-surface p-5">
397 <h3 className="font-medium">How it is charged</h3>
398 <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted">
399 <li>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit400 The plan is {wholeDollars((plan?.plan.monthlyCents ?? 2000) * 10_000)} a month for the workspace, with $10 of usage
401 included. Usage is charged at what it costs g1t plus {account.marginPercent}%, from the first second, after what is
402 included.
403 </li>
404 <li>
405 Agent runs: the model at the provider's price{ai && ai.modelMarkupPercent ? ` plus ${ai.modelMarkupPercent}%` : ", with no markup"}, plus
406 g1t's agent rate per million tokens{ai && ai.agentRateMicros > 0 ? ` (${dollars(ai.agentRateMicros)})` : ""}. AI Gateway: the
407 provider's price, free of markup while in beta.
Agents as a team: lifecycle, merge queue, billing and a new shell408 </li>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit409 <li>AI usage draws on prepaid AI credit first; at $0, new runs on g1t's models wait for more credit or auto-reload.</li>
410 <li>Credit from g1t comes off what you owe, the soonest-expiring first.</li>
Agents as a team: lifecycle, merge queue, billing and a new shell411 <li>
Integrations: your own model provider, alerts that open issues, tickets agents read412 With your own model provider, connected under{" "}
413 <Link to={`/${slug}/-/integrations`} className="text-fg hover:underline">
414 Integrations
415 </Link>
Prices are what g1t pays plus 20%, from the first second416 , the provider bills you for the model, and a run here is charged only its sandbox time.
Agents as a team: lifecycle, merge queue, billing and a new shell417 </li>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put418 <li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look419 Each month closes with an itemised invoice. No card is charged less than{" "}
420 {dollars(entitlements?.minChargeMicros ?? 5 * MICROS_PER_DOLLAR, 0)}; less carries over.
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put421 </li>
Merge Stripe Tax, the card fee on card payments, and one free workspace per person422 <li>
423 Prices exclude tax. Stripe adds tax where it applies, worked out from the billing address under Invoice details, and it
424 is its own line on every receipt and invoice.
425 </li>
426 <li>
427 Card payments carry Stripe's card processing fee as their own line, shown before you pay. Bank transfers and invoiced
428 billing have none.
429 </li>
430 <li>No seats: add as many people and agents as you like, once the workspace is on the plan.</li>
Agents as a team: lifecycle, merge queue, billing and a new shell431 </ul>
A flat project menu with Code first, views as tabs, and Stripe for every owner432 <div className="mt-4 space-y-1.5 border-t border-line pt-4 text-sm">
433 <Link to={`/${slug}/-/usage`} className="flex items-center gap-2 text-muted hover:text-fg">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit434 <FileText size={14} /> Usage, by product and project
A flat project menu with Code first, views as tabs, and Stripe for every owner435 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look436 <Link to="/pricing" className="flex items-center gap-2 text-muted hover:text-fg">
437 <CreditCard size={14} /> Pricing and today's prices
438 </Link>
439 <a href="https://docs.g1t.sh/guides/usage-and-billing/" className="flex items-center gap-2 text-muted hover:text-fg">
A flat project menu with Code first, views as tabs, and Stripe for every owner440 <ArrowUpRight size={14} /> How usage and billing work
441 </a>
442 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell443 </section>
444 </aside>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put445 </div>
446 );
447}
448
Usage, Billing settings and prepaid AI credit; fixes from the UX audit449/** The workspace's month-end invoices from g1t, each kept on Stripe with its PDF. */
Two limits, real invoices, trust that grows by itself, sales signals450function InvoiceList({ invoices }: { invoices: WorkspaceInvoice[] }) {
451 return (
452 <section className="mb-10">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit453 <h2 className="font-medium">Usage invoices</h2>
Merge Stripe Tax, the card fee on card payments, and one free workspace per person454 <p className="mt-1 text-sm text-muted">
455 One when each month closes, and one each time g1t charges the card near your limit. Amounts are for usage; the card fee
456 and tax are their own lines.
457 </p>
Two limits, real invoices, trust that grows by itself, sales signals458 <ul className="mt-4 divide-y divide-line overflow-hidden rounded-xl border border-line">
459 {invoices.map((invoice) => (
460 <li key={invoice.invoiceId} className="px-4 py-3 text-sm">
461 <div className="flex flex-wrap items-center gap-3">
462 <span className="font-medium">
463 {invoice.reason === "month" ? `Usage for ${invoice.period}` : `Charged near the limit, ${invoice.period}`}
464 </span>
465 <span
466 className={`rounded-full border px-2 py-0.5 text-xs ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'467 invoice.status === "paid" ? "border-success/40 text-success" : "border-danger/40 text-danger"
Two limits, real invoices, trust that grows by itself, sales signals468 }`}
469 >
470 {invoice.status === "paid" ? "Paid" : invoice.status === "failed" ? "Payment failed" : invoice.status}
471 </span>
472 <span className="ml-auto font-mono tabular-nums">{dollars(invoice.amountMicros)}</span>
473 {invoice.hostedUrl && (
474 <a href={invoice.hostedUrl} className="text-xs text-muted hover:text-fg">
475 View
476 </a>
477 )}
478 {invoice.pdfUrl && (
479 <a href={invoice.pdfUrl} className="text-xs text-muted hover:text-fg">
480 PDF
481 </a>
482 )}
483 </div>
484 <ul className="mt-1.5 space-y-0.5 text-xs text-faint">
485 {invoice.lines.map((line) => (
486 <li key={line.description} className="flex justify-between gap-4">
487 <span>{line.description}</span>
488 <span className="font-mono tabular-nums">{dollars(line.amountMicros)}</span>
489 </li>
490 ))}
Merge Stripe Tax, the card fee on card payments, and one free workspace per person491 {(invoice.feeMicros ?? 0) > 0 && (
492 <li className="flex justify-between gap-4">
493 <span>Card processing fee</span>
494 <span className="font-mono tabular-nums">{dollars(invoice.feeMicros ?? 0)}</span>
495 </li>
496 )}
497 {(invoice.taxMicros ?? 0) > 0 && (
498 <li className="flex justify-between gap-4">
499 <span>Tax</span>
500 <span className="font-mono tabular-nums">{dollars(invoice.taxMicros ?? 0)}</span>
501 </li>
502 )}
Two limits, real invoices, trust that grows by itself, sales signals503 </ul>
504 </li>
505 ))}
506 </ul>
507 </section>
508 );
509}

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