Skip to content
412 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";
Merge branch 'worktree-agent-a985c01f43538af01'2import { Form, Link, data, redirect } from "react-router";
Agents as a team: lifecycle, merge queue, billing and a new shell3
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look4import { MICROS_PER_DOLLAR, type WorkspaceInvoice } from "@g1t/contracts";
Agents as a team: lifecycle, merge queue, billing and a new shell5
6import type { Route } from "./+types/billing";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look7import {
8 Alerts,
9 CapsCard,
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging10 CreditsCard,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look11 OverageCard,
12 PlanCard,
13 PrepayCard,
14 RaiseCard,
15 type SectionError,
16 SpendLimitCard,
17 SpikeBanner,
18 TrialCard,
19} from "../../components/billing";
The statement is a month at a time, a line per kind of charge20import { StatementView } from "../../components/statement";
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar21import { SecurityActivationCard } from "../../components/security-activation";
Merge branch 'worktree-agent-a985c01f43538af01'22import { SubmitButton } from "../../components/ui";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look23import {
24 cardCheckResult,
25 dollars,
26 parseCaps,
27 parseLimitRequest,
28 parsePrepay,
29 parseSpendLimit,
30 planStatus,
31 wholeDollars,
32} from "../../lib/billing";
33import { page } from "../../lib/meta";
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas34import { billing } from "../../lib/services.server";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look35import { assertSameOrigin, getViewer, requireUser, roleIn, unwrap } from "../../lib/session.server";
Agents as a team: lifecycle, merge queue, billing and a new shell36
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look37/** The trial and pools as published, when the price book cannot be read. */
38const DEFAULT_TRIAL_MICROS = 5_000_000;
Agents as a team: lifecycle, merge queue, billing and a new shell39
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look40/** What a change says once it is done (`?done=`). */
41const DONE: Record<string, string> = {
42 subscribed: "The g1t plan is on.",
43 prepaid: "Payment received. Your prepaid balance and what you can use went up by as much.",
44 limit: "Spend limit saved.",
45 caps: "Caps saved. They apply to runs that start from now on.",
46 requested: "Sent. g1t answers within one business day, here and by email.",
47 told: "Thanks. g1t looks at it and answers within one business day, here and by email.",
48 keep: "Compute is running again for 24 hours, unless spending doubles again first.",
49 stop: "New compute stays paused until an owner chooses Keep going.",
50 canceled: "The plan ends at the end of the period. Nothing more is charged for it.",
51 resumed: "The plan continues.",
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar52 security_on: "Security and quality is on for every private repository in the workspace.",
53 security_canceled: "Security and quality ends at the end of the period. Nothing more is charged for it.",
54 security_resumed: "Security and quality continues.",
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look55};
56
Social cards for every page: og.g1t.sh57export function meta({ params, ...args }: Route.MetaArgs) {
58 return page(args, { title: `Billing · ${params.owner} · g1t` });
Agents as a team: lifecycle, merge queue, billing and a new shell59}
60
61export async function loader({ params, context, request }: Route.LoaderArgs) {
62 const viewer = getViewer(context);
63 const role = roleIn(viewer, params.owner);
64 // Members only; to anyone else the page does not exist.
65 if (!role) throw data(null, { status: 404 });
66 const slug = params.owner.toLowerCase();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look67 const url = new URL(request.url);
68 const here = `/${slug}/-/billing`;
Agents as a team: lifecycle, merge queue, billing and a new shell69
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look70 // Back from Stripe: record what happened, then drop the ids from the address.
71 const cardCheck = url.searchParams.get("card_check");
72 if (cardCheck) {
73 const checked = await billing.confirmCardCheck(slug, viewer, cardCheck);
74 throw redirect(checked.ok ? `${here}?checked=1#trial` : `${here}?problem=${encodeURIComponent(checked.error.message)}#trial`);
75 }
Agents as a team: lifecycle, merge queue, billing and a new shell76 const session = url.searchParams.get("session");
77 if (session) {
Paid features: a workspace turns on Deployments with a monthly plan78 if (url.searchParams.get("plan")) {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look79 const started = await billing.confirmSubscription(slug, viewer, session);
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar80 const doneKey = url.searchParams.get("plan") === "security" ? "security_on" : "subscribed";
81 throw redirect(started.ok ? `${here}?done=${doneKey}` : `${here}?problem=${encodeURIComponent(started.error.message)}`);
Paid features: a workspace turns on Deployments with a monthly plan82 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look83 const paid = await billing.confirm(slug, viewer, session);
84 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 shell85 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look86 // A plan started on the card already checked comes straight back.
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar87 const plans = url.searchParams.getAll("plan");
88 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 look89
The statement is a month at a time, a line per kind of charge90 const group: "day" | "project" = url.searchParams.get("group") === "project" ? "project" : "day";
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging91 const [account, statement, features, meters, limit, invoices, entitlements, requests, book, credits] = await Promise.all([
Agents as a team: lifecycle, merge queue, billing and a new shell92 billing.account(slug, viewer),
The statement is a month at a time, a line per kind of charge93 billing.statement(slug, viewer, url.searchParams.get("month"), group),
Paid features: a workspace turns on Deployments with a monthly plan94 billing.features(slug, viewer),
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas95 billing.usageMeters(slug, viewer).catch(() => null),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look96 billing.limit(slug, viewer).catch(() => null),
Two limits, real invoices, trust that grows by itself, sales signals97 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 put98 billing.entitlements(slug).catch(() => null),
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look99 billing.limitRequests(slug, viewer).catch(() => null),
100 billing.prices().catch(() => null),
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging101 billing.credits(slug, viewer).catch(() => null),
Agents as a team: lifecycle, merge queue, billing and a new shell102 ]);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look103 const featureStates = unwrap(features);
104 const trialMicros = book?.free?.trialWorkspaceMicros ?? DEFAULT_TRIAL_MICROS;
105 const done = url.searchParams.get("done");
Agents as a team: lifecycle, merge queue, billing and a new shell106 return {
107 slug,
108 role,
109 account: unwrap(account),
The statement is a month at a time, a line per kind of charge110 statement: unwrap(statement),
111 group,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look112 plan: featureStates.find((state) => state.plan.feature === "plan") ?? null,
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar113 securityPlan: featureStates.find((state) => state.plan.feature === "security") ?? null,
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas114 meters: meters?.ok ? meters.value : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look115 limit: limit?.ok ? limit.value : null,
Two limits, real invoices, trust that grows by itself, sales signals116 invoices: invoices?.ok ? invoices.value : [],
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put117 entitlements,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look118 requests: requests?.ok ? requests.value : [],
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging119 credits: credits?.ok && credits.value.grants.length > 0 ? credits.value : null,
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look120 trialMicros,
121 notice:
122 url.searchParams.has("checked") && entitlements
123 ? cardCheckResult(entitlements, trialMicros)
124 : done && DONE[done]
125 ? DONE[done]
126 : null,
127 problem: url.searchParams.get("problem"),
Agents as a team: lifecycle, merge queue, billing and a new shell128 };
129}
130
131export async function action({ request, params, context }: Route.ActionArgs) {
132 assertSameOrigin(request);
133 const user = requireUser(context, request);
134 const form = await request.formData();
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look135 const slug = params.owner.toLowerCase();
136 // Spelled out: a form post arrives at a data address, not the page's.
137 const here = `${new URL(request.url).origin}/${slug}/-/billing`;
138 const intent = String(form.get("intent") ?? "");
139 const fail = (section: string, error: string) => data<SectionError>({ section, error }, { status: 422 });
140 const done = (key: string, anchor = "") => redirect(`/${slug}/-/billing?done=${key}${anchor}`);
141
142 switch (intent) {
143 case "portal": {
144 // Card, invoices and billing details live on Stripe's own page.
145 const started = await billing.billingPortal(user, slug, here);
146 if (!started.ok) return fail("plan", started.error.message);
147 throw redirect(started.value.url);
148 }
149 case "subscribe": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar150 // The plan, or the Security and quality activation.
151 const feature = form.get("feature") === "security" ? "security" : "plan";
152 const started = await billing.subscribe(user, slug, feature, `${here}?plan=${feature}`);
153 if (!started.ok) return fail(feature, started.error.message);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look154 throw redirect(started.value.url);
155 }
156 case "cancel":
157 case "resume": {
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar158 const feature = form.get("feature") === "security" ? "security" : "plan";
159 const changed = await billing.cancelSubscription(user, slug, feature, intent === "resume");
160 if (!changed.ok) return fail(feature, changed.error.message);
161 const prefix = feature === "security" ? "security_" : "";
162 throw done(`${prefix}${intent === "resume" ? "resumed" : "canceled"}`);
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look163 }
164 case "card-check": {
165 const started = await billing.cardCheck(user, slug, here);
166 if (!started.ok) return fail("trial", started.error.message);
167 throw redirect(started.value.url);
168 }
169 case "spend-limit": {
170 const parsed = parseSpendLimit({ mode: form.get("mode"), limit: form.get("limit") });
171 if (!parsed.ok) return fail("limit", parsed.error);
172 let micros = parsed.value.micros;
173 if (parsed.value.raiseOnce && micros == null) {
174 // The one-time raise with no amount: as high as it goes.
175 const limit = await billing.limit(slug, user);
176 micros = limit.ok ? (limit.value.raiseOnceMicros ?? null) : null;
177 if (micros == null) return fail("limit", "The one-time raise is not available. Prepay, or use Raise my limit.");
178 }
179 const set = await billing.setSpendLimit(user, slug, micros, parsed.value.useFull, parsed.value.raiseOnce);
180 if (!set.ok) return fail("limit", set.error.message);
181 throw done("limit", "#limit");
182 }
183 case "request": {
184 const parsed = parseLimitRequest({
185 kind: form.get("kind"),
186 amount: form.get("amount"),
187 reason: form.get("reason"),
188 expected: form.get("expected"),
189 });
190 const section = String(form.get("kind")) === "overage" ? "overage" : "raise";
191 if (!parsed.ok) return fail(section, parsed.error);
192 const sent = await billing.requestLimit(user, slug, parsed.value);
193 if (!sent.ok) return fail(section, sent.error.message);
194 throw done(section === "overage" ? "told" : "requested", `#${section}`);
195 }
196 case "caps": {
197 const parsed = parseCaps({ run: form.get("run"), issue: form.get("issue") });
198 if (!parsed.ok) return fail("caps", parsed.error);
199 const set = await billing.setCaps(user, slug, parsed.value);
200 if (!set.ok) return fail("caps", set.error.message);
201 throw done("caps", "#caps");
202 }
203 case "spike": {
204 const keep = String(form.get("decision")) === "keep";
205 const answered = await billing.confirmSpike(user, slug, keep);
206 if (!answered.ok) return fail("spike", answered.error.message);
207 throw done(keep ? "keep" : "stop");
Usage limits: unpaid usage can only go so far208 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look209 case "prepay": {
210 const parsed = parsePrepay({ amount: form.get("amount"), custom: form.get("custom"), method: form.get("method") });
211 if (!parsed.ok) return fail("prepay", parsed.error);
212 const started = await billing.checkout(user, slug, parsed.value.amountCents, here, parsed.value.method);
213 if (!started.ok) return fail("prepay", started.error.message);
214 throw redirect(started.value.url);
Paid features: a workspace turns on Deployments with a monthly plan215 }
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look216 default:
217 return fail("plan", "Unknown action.");
Paid features: a workspace turns on Deployments with a monthly plan218 }
Agents as a team: lifecycle, merge queue, billing and a new shell219}
220
221export default function WorkspaceBilling({ loaderData, actionData }: Route.ComponentProps) {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging222 const { slug, role, account, statement, group, plan, securityPlan, meters, limit, invoices, entitlements, requests, credits, trialMicros, notice, problem } =
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put223 loaderData;
Agents as a team: lifecycle, merge queue, billing and a new shell224 const { status } = account;
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look225 const owner = role === "owner";
226 const error = actionData as SectionError;
227 const err = (section: string) => (error && error.section === section ? error.error : undefined);
228 const standing = planStatus(plan, entitlements);
229 const paying = standing.kind === "paid" || standing.kind === "canceling" || standing.kind === "past_due";
230 const free = standing.kind === "free" || standing.kind === "trial";
231 const prepaid = limit?.prepaidMicros ?? entitlements?.prepaidMicros ?? 0;
232
Agents as a team: lifecycle, merge queue, billing and a new shell233 return (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look234 <div className="grid gap-10 lg:grid-cols-[minmax(0,1fr)_20rem]">
Agents as a team: lifecycle, merge queue, billing and a new shell235 <div className="min-w-0">
Free while g1t is being built out; agents can check out their own forks236 {status.free && (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look237 <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 forks238 <h2 className="font-medium">Free while g1t is being built out</h2>
239 <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 look240 Runs are recorded with what they cost, so Usage shows what you use, but nothing is charged for now. We will say so well
241 before anything is.
Free while g1t is being built out; agents can check out their own forks242 </p>
243 </div>
244 )}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look245 {notice && <p className="mb-6 rounded-lg border border-accent/30 bg-accent/5 px-4 py-2.5 text-sm">{notice}</p>}
246 {problem && <p className="mb-6 rounded-lg border border-danger/40 bg-danger/5 px-4 py-2.5 text-sm">{problem}</p>}
247
248 {entitlements && <SpikeBanner slug={slug} entitlements={entitlements} owner={owner} />}
249 {err("spike") && <p className="mb-6 text-sm text-danger">{err("spike")}</p>}
250 <Alerts alerts={entitlements?.alerts ?? []} />
251
252 <PlanCard
253 state={plan}
254 status={standing}
255 entitlements={entitlements}
256 owner={owner}
257 enabled={status.enabled}
258 live={status.live}
status.g1t.sh with incident management, invites that land you in the workspace, settings as pages, usage without quotas259 meters={meters}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look260 error={err("plan")}
261 />
262
Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar263 <SecurityActivationCard state={securityPlan} owner={owner} enabled={status.enabled} error={err("security")} />
264
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look265 {free && status.enabled && (
Merge branch 'worktree-agent-a985c01f43538af01'266 <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 owner267 )}
268
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look269 {paying && limit && limit.trust !== "internal" && (
270 <>
Merge branch 'worktree-agent-a985c01f43538af01'271 <SpendLimitCard limit={limit} owner={owner} error={err("limit")} />
272 <RaiseCard requests={requests} owner={owner} error={err("raise")} />
273 <PrepayCard prepaidMicros={prepaid} owner={owner} live={status.live} error={err("prepay")} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look274 </>
275 )}
276
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging277 {credits && <CreditsCard credits={credits} />}
278
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look279 {entitlements && (paying || standing.kind === "trial" || standing.kind === "comped" || standing.kind === "enterprise") && (
Merge branch 'worktree-agent-a985c01f43538af01'280 <CapsCard entitlements={entitlements} owner={owner} error={err("caps")} />
Usage limits: unpaid usage can only go so far281 )}
282
Merge branch 'worktree-agent-a985c01f43538af01'283 {paying && <OverageCard requests={requests} owner={owner} error={err("overage")} />}
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look284
285 {status.enabled && (
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace286 <section className="mb-10 rounded-xl border border-line bg-surface p-5">
287 <div className="flex flex-wrap items-center justify-between gap-3">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look288 <div className="min-w-0">
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace289 <h2 className="font-medium">Card and invoices</h2>
290 {account.card ? (
291 <p className="mt-1 text-sm">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look292 <span className="capitalize">{account.card.brand}</span> ending <span className="font-mono">{account.card.last4}</span>
293 <span className="text-muted"> · expires {String(account.card.expMonth).padStart(2, "0")}/{account.card.expYear}</span>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace294 </p>
295 ) : (
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look296 <p className="mt-1 max-w-xl text-sm text-muted">No card yet. The plan, the trial and prepaying each ask for one on Stripe.</p>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace297 )}
298 <p className="mt-2 max-w-xl text-xs text-faint">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look299 Cards, invoices, receipts and the billing email, address and tax ID are on Stripe's billing page. g1t never sees card
300 numbers.
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace301 </p>
302 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look303 {owner && (
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace304 <Form method="post">
Merge branch 'worktree-agent-a985c01f43538af01'305 <SubmitButton variant="quiet" name="intent" value="portal" pending="Opening Stripe…">
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace306 <CreditCard size={14} />
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look307 Open Stripe billing
Merge branch 'worktree-agent-a985c01f43538af01'308 </SubmitButton>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace309 </Form>
310 )}
311 </div>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look312 {!status.live && (
313 <p className="mt-3 text-xs text-faint">Test mode: use card 4242 4242 4242 4242, any future date and code.</p>
Billing on Stripe's pages, month-end charges, warnings; sudo by workspace314 )}
315 </section>
316 )}
317
Two limits, real invoices, trust that grows by itself, sales signals318 {invoices.length > 0 && <InvoiceList invoices={invoices} />}
Agents as a team: lifecycle, merge queue, billing and a new shell319
The statement is a month at a time, a line per kind of charge320 <StatementView slug={slug} statement={statement} group={group} />
Agents as a team: lifecycle, merge queue, billing and a new shell321 </div>
322
323 <aside className="space-y-5 text-sm">
324 <section className="rounded-xl border border-line bg-surface p-5">
325 <h3 className="font-medium">How it is charged</h3>
326 <ul className="mt-2 list-disc space-y-1.5 pl-4 text-muted">
327 <li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look328 The plan is {wholeDollars((plan?.plan.monthlyCents ?? 2000) * 10_000)} a month for the workspace. Usage is charged at what it
329 costs g1t plus {account.marginPercent}%, from the first second, after what is included.
Agents as a team: lifecycle, merge queue, billing and a new shell330 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look331 <li>What paid first is on each statement line: the plan's included usage, the trial, the open-source pool, or g1t.</li>
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging332 <li>Credit from g1t comes off what you owe, before anything prepaid, the soonest-expiring first.</li>
Agents as a team: lifecycle, merge queue, billing and a new shell333 <li>
Integrations: your own model provider, alerts that open issues, tickets agents read334 With your own model provider, connected under{" "}
335 <Link to={`/${slug}/-/integrations`} className="text-fg hover:underline">
336 Integrations
337 </Link>
Prices are what g1t pays plus 20%, from the first second338 , 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 shell339 </li>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put340 <li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look341 Each month closes with an itemised invoice. No card is charged less than{" "}
342 {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 put343 </li>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look344 <li>Alerts at 50, 75, 90 and 100% of the included usage and your limits, here and by email.</li>
A flat project menu with Code first, views as tabs, and Stripe for every owner345 <li>No seats: add as many people and agents as you like.</li>
Agents as a team: lifecycle, merge queue, billing and a new shell346 </ul>
A flat project menu with Code first, views as tabs, and Stripe for every owner347 <div className="mt-4 space-y-1.5 border-t border-line pt-4 text-sm">
348 <Link to={`/${slug}/-/usage`} className="flex items-center gap-2 text-muted hover:text-fg">
349 <FileText size={14} /> Usage, run by run
350 </Link>
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look351 <Link to="/pricing" className="flex items-center gap-2 text-muted hover:text-fg">
352 <CreditCard size={14} /> Pricing and today's prices
353 </Link>
354 <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 owner355 <ArrowUpRight size={14} /> How usage and billing work
356 </a>
357 </div>
Agents as a team: lifecycle, merge queue, billing and a new shell358 </section>
359 </aside>
Team plan, an open-source pool, monthly trials and honest metering; the sidebar for everyone; a workspace that stays put360 </div>
361 );
362}
363
Two limits, real invoices, trust that grows by itself, sales signals364/** The workspace's invoices from g1t, each kept on Stripe with its PDF. */
365function InvoiceList({ invoices }: { invoices: WorkspaceInvoice[] }) {
366 return (
367 <section className="mb-10">
368 <h2 className="font-medium">Invoices</h2>
369 <p className="mt-1 text-sm text-muted">
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look370 One when each month closes, and one each time g1t charges the card near your limit. Receipts and PDFs are also on Stripe's
371 billing page.
Two limits, real invoices, trust that grows by itself, sales signals372 </p>
373 <ul className="mt-4 divide-y divide-line overflow-hidden rounded-xl border border-line">
374 {invoices.map((invoice) => (
375 <li key={invoice.invoiceId} className="px-4 py-3 text-sm">
376 <div className="flex flex-wrap items-center gap-3">
377 <span className="font-medium">
378 {invoice.reason === "month" ? `Usage for ${invoice.period}` : `Charged near the limit, ${invoice.period}`}
379 </span>
380 <span
381 className={`rounded-full border px-2 py-0.5 text-xs ${
382 invoice.status === "paid" ? "border-accent/40 text-accent" : "border-danger/40 text-danger"
383 }`}
384 >
385 {invoice.status === "paid" ? "Paid" : invoice.status === "failed" ? "Payment failed" : invoice.status}
386 </span>
387 <span className="ml-auto font-mono tabular-nums">{dollars(invoice.amountMicros)}</span>
388 {invoice.hostedUrl && (
389 <a href={invoice.hostedUrl} className="text-xs text-muted hover:text-fg">
390 View
391 </a>
392 )}
393 {invoice.pdfUrl && (
394 <a href={invoice.pdfUrl} className="text-xs text-muted hover:text-fg">
395 PDF
396 </a>
397 )}
398 </div>
399 <ul className="mt-1.5 space-y-0.5 text-xs text-faint">
400 {invoice.lines.map((line) => (
401 <li key={line.description} className="flex justify-between gap-4">
402 <span>{line.description}</span>
403 <span className="font-mono tabular-nums">{dollars(line.amountMicros)}</span>
404 </li>
405 ))}
406 </ul>
407 </li>
408 ))}
409 </ul>
410 </section>
411 );
412}

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