Skip to content
462 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";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look48import { assertSameOrigin, getViewer, 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,
Usage, Billing settings and prepaid AI credit; fixes from the UX audit143 staff: isStaff(viewer),
Agents as a team: lifecycle, merge queue, billing and a new shell144 account: unwrap(account),
The statement is a month at a time, a line per kind of charge145 statement: unwrap(statement),
146 group,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look147 plan: featureStates.find((state) => state.plan.feature === "plan") ?? null,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar148 securityPlan: featureStates.find((state) => state.plan.feature === "security") ?? null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look149 limit: limit?.ok ? limit.value : null,
Two limits, real invoices, trust that grows by itself, sales signals150 invoices: invoices?.ok ? invoices.value : [],
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put151 entitlements,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look152 requests: requests?.ok ? requests.value : [],
Usage, Billing settings and prepaid AI credit; fixes from the UX audit153 credits: general.length > 0 && credits?.ok ? { ...credits.value, grants: general } : null,
154 ai: ai?.ok ? ai.value : null,
155 report: report?.ok ? report.value : null,
156 details,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look157 trialMicros,
158 notice:
159 url.searchParams.has("checked") && entitlements
160 ? cardCheckResult(entitlements, trialMicros)
161 : done && DONE[done]
162 ? DONE[done]
163 : null,
164 problem: url.searchParams.get("problem"),
Agents as a team: lifecycle, merge queue, billing and a new shell165 };
166}
167
168export async function action({ request, params, context }: Route.ActionArgs) {
169 assertSameOrigin(request);
170 const user = requireUser(context, request);
171 const form = await request.formData();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look172 const slug = params.owner.toLowerCase();
173 // Spelled out: a form post arrives at a data address, not the page's.
174 const here = `${new URL(request.url).origin}/${slug}/-/billing`;
175 const intent = String(form.get("intent") ?? "");
176 const fail = (section: string, error: string) => data<SectionError>({ section, error }, { status: 422 });
177 const done = (key: string, anchor = "") => redirect(`/${slug}/-/billing?done=${key}${anchor}`);
178
179 switch (intent) {
180 case "portal": {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit181 // 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 look182 const started = await billing.billingPortal(user, slug, here);
Usage, Billing settings and prepaid AI credit; fixes from the UX audit183 if (!started.ok) return fail("payment", started.error.message);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look184 throw redirect(started.value.url);
185 }
186 case "subscribe": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar187 // The plan, or the Security and quality activation.
188 const feature = form.get("feature") === "security" ? "security" : "plan";
189 const started = await billing.subscribe(user, slug, feature, `${here}?plan=${feature}`);
190 if (!started.ok) return fail(feature, started.error.message);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look191 throw redirect(started.value.url);
192 }
193 case "cancel":
194 case "resume": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar195 const feature = form.get("feature") === "security" ? "security" : "plan";
196 const changed = await billing.cancelSubscription(user, slug, feature, intent === "resume");
197 if (!changed.ok) return fail(feature, changed.error.message);
198 const prefix = feature === "security" ? "security_" : "";
199 throw done(`${prefix}${intent === "resume" ? "resumed" : "canceled"}`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look200 }
201 case "card-check": {
202 const started = await billing.cardCheck(user, slug, here);
203 if (!started.ok) return fail("trial", started.error.message);
204 throw redirect(started.value.url);
205 }
Usage, Billing settings and prepaid AI credit; fixes from the UX audit206 case "buy-ai-credit": {
207 const parsed = parseAiPurchase({ amount: form.get("amount"), custom: form.get("custom") });
208 if (!parsed.ok) return fail("ai", parsed.error);
209 const started = await billing.buyAiCredit(user, slug, parsed.value.amountCents, here);
210 if (!started.ok) return fail("ai", started.error.message);
211 throw redirect(started.value.url);
212 }
213 case "ai-reload": {
214 const parsed = parseAiReload({ enabled: form.get("enabled"), threshold: form.get("threshold"), target: form.get("target"), monthly: form.get("monthly") });
215 if (!parsed.ok) return fail("ai", parsed.error);
216 const set = await billing.setAiReload(user, slug, parsed.value);
217 if (!set.ok) return fail("ai", set.error.message);
218 throw done("ai_reload", "#ai-credit");
219 }
220 case "budget": {
221 const parsed = parseBudgetAlerts({ alerts: form.getAll("alert"), pause: form.get("pause"), webhook: form.get("webhook") });
222 if (!parsed.ok) return fail("budget", parsed.error);
223 const set = await billing.setBudget(user, slug, { amountMicros: null, keepLimit: true, ...parsed.value });
224 if (!set.ok) return fail("budget", set.error.message);
225 throw done("budget", "#budget");
226 }
227 case "details": {
228 const parsed = parseInvoiceDetails(form);
229 if (!parsed.ok) return fail("details", parsed.error);
230 const saved = await billing.setBillingDetails(user, slug, parsed.value);
231 if (!saved.ok) return fail("details", saved.error.message);
232 throw done("details", "#details");
233 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look234 case "spend-limit": {
235 const parsed = parseSpendLimit({ mode: form.get("mode"), limit: form.get("limit") });
236 if (!parsed.ok) return fail("limit", parsed.error);
237 let micros = parsed.value.micros;
238 if (parsed.value.raiseOnce && micros == null) {
239 // The one-time raise with no amount: as high as it goes.
240 const limit = await billing.limit(slug, user);
241 micros = limit.ok ? (limit.value.raiseOnceMicros ?? null) : null;
242 if (micros == null) return fail("limit", "The one-time raise is not available. Prepay, or use Raise my limit.");
243 }
244 const set = await billing.setSpendLimit(user, slug, micros, parsed.value.useFull, parsed.value.raiseOnce);
245 if (!set.ok) return fail("limit", set.error.message);
246 throw done("limit", "#limit");
247 }
248 case "request": {
249 const parsed = parseLimitRequest({
250 kind: form.get("kind"),
251 amount: form.get("amount"),
252 reason: form.get("reason"),
253 expected: form.get("expected"),
254 });
255 const section = String(form.get("kind")) === "overage" ? "overage" : "raise";
256 if (!parsed.ok) return fail(section, parsed.error);
257 const sent = await billing.requestLimit(user, slug, parsed.value);
258 if (!sent.ok) return fail(section, sent.error.message);
259 throw done(section === "overage" ? "told" : "requested", `#${section}`);
260 }
261 case "caps": {
262 const parsed = parseCaps({ run: form.get("run"), issue: form.get("issue") });
263 if (!parsed.ok) return fail("caps", parsed.error);
264 const set = await billing.setCaps(user, slug, parsed.value);
265 if (!set.ok) return fail("caps", set.error.message);
266 throw done("caps", "#caps");
267 }
268 case "spike": {
269 const keep = String(form.get("decision")) === "keep";
270 const answered = await billing.confirmSpike(user, slug, keep);
271 if (!answered.ok) return fail("spike", answered.error.message);
272 throw done(keep ? "keep" : "stop");
Usage limits: unpaid usage can only go so far273 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look274 case "prepay": {
275 const parsed = parsePrepay({ amount: form.get("amount"), custom: form.get("custom"), method: form.get("method") });
276 if (!parsed.ok) return fail("prepay", parsed.error);
277 const started = await billing.checkout(user, slug, parsed.value.amountCents, here, parsed.value.method);
278 if (!started.ok) return fail("prepay", started.error.message);
279 throw redirect(started.value.url);
Paid features: a workspace turns on Deployments with a monthly plan280 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look281 default:
282 return fail("plan", "Unknown action.");
Paid features: a workspace turns on Deployments with a monthly plan283 }
Agents as a team: lifecycle, merge queue, billing and a new shell284}
285
286export default function WorkspaceBilling({ loaderData, actionData }: Route.ComponentProps) {
Usage, Billing settings and prepaid AI credit; fixes from the UX audit287 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 put288 loaderData;
Agents as a team: lifecycle, merge queue, billing and a new shell289 const { status } = account;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look290 const owner = role === "owner";
291 const error = actionData as SectionError;
292 const err = (section: string) => (error && error.section === section ? error.error : undefined);
293 const standing = planStatus(plan, entitlements);
294 const paying = standing.kind === "paid" || standing.kind === "canceling" || standing.kind === "past_due";
295 const free = standing.kind === "free" || standing.kind === "trial";
296 const prepaid = limit?.prepaidMicros ?? entitlements?.prepaidMicros ?? 0;
Usage, Billing settings and prepaid AI credit; fixes from the UX audit297 // The spend limit and its budget are the owners' to set only where
298 // billing lets them: on the plan, never a free workspace's ceiling.
299 const selfServe = !!limit && (limit.trust === "paid" || limit.trust === "established");
300 const summary = (loaded: Awaited<typeof details>) => (
301 <PlanSummary slug={slug} state={plan} status={standing} details={loaded} report={report} owner={owner} enabled={status.enabled} staff={staff} error={err("plan")} />
302 );
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look303
Agents as a team: lifecycle, merge queue, billing and a new shell304 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look305 <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_20rem]">
Agents as a team: lifecycle, merge queue, billing and a new shell306 <div className="min-w-0">
Free while g1t is being built out; agents can check out their own forks307 {status.free && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look308 <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 forks309 <h2 className="font-medium">Free while g1t is being built out</h2>
310 <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 look311 Runs are recorded with what they cost, so Usage shows what you use, but nothing is charged for now. We will say so well
312 before anything is.
Free while g1t is being built out; agents can check out their own forks313 </p>
314 </div>
315 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look316 {notice && <p className="mb-6 rounded-lg border border-accent/30 bg-accent/5 px-4 py-2.5 text-sm">{notice}</p>}
317 {problem && <p className="mb-6 rounded-lg border border-danger/40 bg-danger/5 px-4 py-2.5 text-sm">{problem}</p>}
318
319 {entitlements && <SpikeBanner slug={slug} entitlements={entitlements} owner={owner} />}
320 {err("spike") && <p className="mb-6 text-sm text-danger">{err("spike")}</p>}
321 <Alerts alerts={entitlements?.alerts ?? []} />
322
Usage, Billing settings and prepaid AI credit; fixes from the UX audit323 <Suspense fallback={summary(null)}>
324 <Await resolve={details}>{(loaded) => summary(loaded)}</Await>
325 </Suspense>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look326
Usage, Billing settings and prepaid AI credit; fixes from the UX audit327 {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 bar328
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look329 {free && status.enabled && (
Merge branch 'worktree-agent-a985c01f43538af01'330 <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 owner331 )}
332
Usage, Billing settings and prepaid AI credit; fixes from the UX audit333 {paying && limit && selfServe && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look334 <>
Merge branch 'worktree-agent-a985c01f43538af01'335 <SpendLimitCard limit={limit} owner={owner} error={err("limit")} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit336 <BudgetAlerts limit={limit} owner={owner} error={err("budget")} />
Merge branch 'worktree-agent-a985c01f43538af01'337 <RaiseCard requests={requests} owner={owner} error={err("raise")} />
Usage, Billing settings and prepaid AI credit; fixes from the UX audit338 <PrepayCard prepaidMicros={prepaid} owner={owner} live={status.live || !staff} error={err("prepay")} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look339 </>
340 )}
341
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging342 {credits && <CreditsCard credits={credits} />}
343
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look344 {entitlements && (paying || standing.kind === "trial" || standing.kind === "comped" || standing.kind === "enterprise") && (
Merge branch 'worktree-agent-a985c01f43538af01'345 <CapsCard entitlements={entitlements} owner={owner} error={err("caps")} />
Usage limits: unpaid usage can only go so far346 )}
347
Merge branch 'worktree-agent-a985c01f43538af01'348 {paying && <OverageCard requests={requests} owner={owner} error={err("overage")} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look349
Usage, Billing settings and prepaid AI credit; fixes from the UX audit350 <AddOns plan={plan} security={securityPlan} owner={owner} enabled={status.enabled} error={err("security")} />
351
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look352 {status.enabled && (
Usage, Billing settings and prepaid AI credit; fixes from the UX audit353 <Suspense fallback={<StripeSkeleton />}>
354 <Await resolve={details}>
355 {(loaded) => (
356 <>
357 <PaymentMethodCard details={loaded} owner={owner} enabled={status.enabled} staff={staff} error={err("payment")} />
358 <InvoiceDetailsCard details={loaded} owner={owner} enabled={status.enabled} error={err("details")} />
359 <InvoicesCard details={loaded} />
360 </>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace361 )}
Usage, Billing settings and prepaid AI credit; fixes from the UX audit362 </Await>
363 </Suspense>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace364 )}
365
Two limits, real invoices, trust that grows by itself, sales signals366 {invoices.length > 0 && <InvoiceList invoices={invoices} />}
Agents as a team: lifecycle, merge queue, billing and a new shell367
The statement is a month at a time, a line per kind of charge368 <StatementView slug={slug} statement={statement} group={group} />
Agents as a team: lifecycle, merge queue, billing and a new shell369 </div>
370
371 <aside className="space-y-5 text-sm">
372 <section className="rounded-xl border border-line bg-surface p-5">
373 <h3 className="font-medium">How it is charged</h3>
374 <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted">
375 <li>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit376 The plan is {wholeDollars((plan?.plan.monthlyCents ?? 2000) * 10_000)} a month for the workspace, with $10 of usage
377 included. Usage is charged at what it costs g1t plus {account.marginPercent}%, from the first second, after what is
378 included.
379 </li>
380 <li>
381 Agent runs: the model at the provider's price{ai && ai.modelMarkupPercent ? ` plus ${ai.modelMarkupPercent}%` : ", with no markup"}, plus
382 g1t's agent rate per million tokens{ai && ai.agentRateMicros > 0 ? ` (${dollars(ai.agentRateMicros)})` : ""}. AI Gateway: the
383 provider's price, free of markup while in beta.
Agents as a team: lifecycle, merge queue, billing and a new shell384 </li>
Usage, Billing settings and prepaid AI credit; fixes from the UX audit385 <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>
386 <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 shell387 <li>
Integrations: your own model provider, alerts that open issues, tickets agents read388 With your own model provider, connected under{" "}
389 <Link to={`/${slug}/-/integrations`} className="text-fg hover:underline">
390 Integrations
391 </Link>
Prices are what g1t pays plus 20%, from the first second392 , 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 shell393 </li>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put394 <li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look395 Each month closes with an itemised invoice. No card is charged less than{" "}
396 {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 put397 </li>
A flat project menu with Code first, views as tabs, and Stripe for every owner398 <li>No seats: add as many people and agents as you like.</li>
Agents as a team: lifecycle, merge queue, billing and a new shell399 </ul>
A flat project menu with Code first, views as tabs, and Stripe for every owner400 <div className="mt-4 space-y-1.5 border-t border-line pt-4 text-sm">
401 <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 audit402 <FileText size={14} /> Usage, by product and project
A flat project menu with Code first, views as tabs, and Stripe for every owner403 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look404 <Link to="/pricing" className="flex items-center gap-2 text-muted hover:text-fg">
405 <CreditCard size={14} /> Pricing and today's prices
406 </Link>
407 <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 owner408 <ArrowUpRight size={14} /> How usage and billing work
409 </a>
410 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell411 </section>
412 </aside>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put413 </div>
414 );
415}
416
Usage, Billing settings and prepaid AI credit; fixes from the UX audit417/** 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 signals418function InvoiceList({ invoices }: { invoices: WorkspaceInvoice[] }) {
419 return (
420 <section className="mb-10">
Usage, Billing settings and prepaid AI credit; fixes from the UX audit421 <h2 className="font-medium">Usage invoices</h2>
422 <p className="mt-1 text-sm text-muted">One when each month closes, and one each time g1t charges the card near your limit.</p>
Two limits, real invoices, trust that grows by itself, sales signals423 <ul className="mt-4 divide-y divide-line overflow-hidden rounded-xl border border-line">
424 {invoices.map((invoice) => (
425 <li key={invoice.invoiceId} className="px-4 py-3 text-sm">
426 <div className="flex flex-wrap items-center gap-3">
427 <span className="font-medium">
428 {invoice.reason === "month" ? `Usage for ${invoice.period}` : `Charged near the limit, ${invoice.period}`}
429 </span>
430 <span
431 className={`rounded-full border px-2 py-0.5 text-xs ${
Merge branch 'worktree-agent-ad7c6d88d93adc817'432 invoice.status === "paid" ? "border-success/40 text-success" : "border-danger/40 text-danger"
Two limits, real invoices, trust that grows by itself, sales signals433 }`}
434 >
435 {invoice.status === "paid" ? "Paid" : invoice.status === "failed" ? "Payment failed" : invoice.status}
436 </span>
437 <span className="ml-auto font-mono tabular-nums">{dollars(invoice.amountMicros)}</span>
438 {invoice.hostedUrl && (
439 <a href={invoice.hostedUrl} className="text-xs text-muted hover:text-fg">
440 View
441 </a>
442 )}
443 {invoice.pdfUrl && (
444 <a href={invoice.pdfUrl} className="text-xs text-muted hover:text-fg">
445 PDF
446 </a>
447 )}
448 </div>
449 <ul className="mt-1.5 space-y-0.5 text-xs text-faint">
450 {invoice.lines.map((line) => (
451 <li key={line.description} className="flex justify-between gap-4">
452 <span>{line.description}</span>
453 <span className="font-mono tabular-nums">{dollars(line.amountMicros)}</span>
454 </li>
455 ))}
456 </ul>
457 </li>
458 ))}
459 </ul>
460 </section>
461 );
462}

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