Skip to content

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

g1t.sh
784 linesCodeBlameRaw
1import { ArrowUpRight } from "lucide-react";
2
3import type { FeaturePlan, FreeTier, Price } from "@g1t/contracts";
4import { type InstanceType, machineSize, machines } from "@g1t/contracts/instances";
5
6import type { Route } from "./+types/pricing";
7import { money as sum, wholeDollars } from "../lib/money";
8import { page } from "../lib/meta";
9import { application, faqPage } from "../lib/structured-data";
10import { TimeAgo } from "../components/ui";
11import { Card } from "../components/ui/card";
12import { billing } from "../lib/services.server";
13
14export function meta(args: Route.MetaArgs) {
15 const plan = args.loaderData?.book?.plans?.find((p) => p.feature === "plan") ?? DEFAULT_PLAN;
16 return [
17 ...page(args, {
18 title: `g1t pricing - $${plan.monthlyCents / 100} per workspace, never per seat, usage at cost plus 20%`,
19 description:
20 "People chat free on every plan, and the forge is free. One plan, $20 a month per workspace with $10 of usage included. Everything g1t runs is billed at cost plus 20%. Your own runners are free. No seats, ever.",
21 }),
22 application(plan.monthlyCents / 100),
23 faqPage(FAQ),
24 ];
25}
26
27export async function loader() {
28 const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]);
29 return { book, free: status?.free ?? false };
30}
31
32/** A price, with the fraction of a cent it is set to: `$0.024`, `$0.25`. */
33function money(micros: number): string {
34 return sum(micros, { precise: true });
35}
36
37/** Per second is easier to read per minute. */
38function perUnit(price: Price, micros: number): string {
39 return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`;
40}
41
42function gigabytes(bytes: number): string {
43 return `${Math.round((bytes / 1e9) * 10) / 10} GB`;
44}
45
46function count(n: number): string {
47 return n.toLocaleString("en-US");
48}
49
50/**
51 * Price-book rows the table shows in rows of their own, or not at all. The
52 * agent and model meters are retired: a price book that still lists them
53 * leaves them off the page.
54 */
55const SHOWN_APART = new Set([
56 "app_month",
57 "agent_models",
58 "agent_tokens",
59 "agent_tokens_own",
60 "agent_token_weight_input",
61 "agent_token_weight_output",
62 "agent_token_weight_cache_read",
63 "agent_token_weight_cache_write",
64 "gateway_models",
65 "card_fee_percent",
66 "card_fee_fixed",
67 "security_activation",
68 // What a sandbox second costs g1t: runner minutes are charged at each
69 // machine's rate, in rows of their own.
70 "sandbox_second",
71 "sandbox_base_second",
72 "sandbox_cpu_second",
73]);
74
75/**
76 * What a minute of a machine costs g1t at Cloudflare's list prices with
77 * every vCPU busy: memory, disk and the Durable Object behind it (128 MB)
78 * for every second, and its vCPUs.
79 */
80function busyMinuteMicros(instance: InstanceType): number {
81 return 60 * (instance.memoryGib * 2.5 + instance.diskGb * 0.07 + 0.125 * 12.5 + instance.vcpu * 20);
82}
83
84/** A retired meter: agents' models and tokens, and the AI Gateway. */
85function retired(meter: string): boolean {
86 return meter.startsWith("agent_") || meter === "gateway_models";
87}
88
89/** What the page says when billing cannot be reached: the published defaults. */
90const DEFAULT_FREE: Required<FreeTier> = {
91 trialWorkspaceMicros: 5_000_000,
92 trialMonthlyPoolMicros: 100_000_000,
93 ossPoolMicros: 25_000_000,
94 ossRepoMicros: 2_000_000,
95 freePrivateStorageBytes: 1_000_000_000,
96 auditRetentionDays: 7,
97 planAuditRetentionDays: 90,
98 minChargeMicros: 5_000_000,
99 gitOperationsIncluded: 50_000,
100 paidStartCeilingMicros: 100_000_000,
101 overageForgiveCostMicros: 50_000_000,
102};
103
104const DEFAULT_PLAN: FeaturePlan = {
105 feature: "plan",
106 title: "g1t",
107 monthlyCents: 2000,
108 includes: [
109 "$10 of usage each month, used first: everything g1t runs at cost plus 20%",
110 "Everyone in the workspace at one price, never per person",
111 "Unlimited projects, previews and repositories",
112 "Checks, workflows, the merge queue, deployments, semantic search, and Security and quality on private repositories",
113 "Usage past $10 is charged the same way, up to your spend limit",
114 ],
115 overage:
116 "Everything g1t runs is metered from the first unit at what it costs g1t plus 20%. Your own runners cost nothing. Unused included usage does not roll over.",
117};
118
119/** How each part of a workspace is charged. */
120const WORKSPACE_CHARGES: { what: string; how: string; soon?: boolean }[] = [
121 {
122 what: "People chatting",
123 how: "Included on every plan, the free one too: channels, direct messages, threads, mentions and live delivery. No seats, and history is never cut off.",
124 },
125 { what: "Docs", how: "Included on every plan, like chat: pages, live editing and history.", soon: true },
126 {
127 what: "Your coding agents",
128 how: "Nothing from g1t. Coding agents you already use connect through the MCP server and the API with your access tokens; their models are billed by whoever runs them. What they start on g1t, such as checks and workflows, is charged like anyone else's.",
129 },
130 {
131 what: "Files in chat and Docs",
132 how: "The storage meter, at cost plus 20%, past what is free.",
133 soon: true,
134 },
135];
136
137const FAQ: { q: string; a: string }[] = [
138 {
139 q: "Do people pay to use Chat?",
140 a: "No. Messages, channels, threads and live delivery are included on every plan, with no per-person price and no limit on history. A workspace on the plan can add as many people as it likes at the same price.",
141 },
142 {
143 q: "Does using my coding agent with g1t cost extra?",
144 a: "No. Claude Code, Codex, Cursor and other coding agents connect through g1t's MCP server and API with your access tokens, at no charge. Their models are billed by whoever runs them. What they start on g1t, such as checks and workflows, is charged like anything else.",
145 },
146 {
147 q: "Does adding the whole company cost more?",
148 a: "No. There are no seats. People who only chat cost nothing.",
149 },
150 {
151 q: "What does moving from GitHub cost, and can we leave again?",
152 a: "Moving in is an import: bring repositories across from GitHub with every branch, tag and their issues. Your code is plain git and g1t is MIT licensed, so leaving is a git clone, and the core forge runs on your own machine with Docker Compose.",
153 },
154 {
155 q: "Is Docs included?",
156 a: "Docs is coming soon. When it ships, pages, editing and history are included on every plan, like chat. Files are on the storage meter.",
157 },
158];
159
160/** The ways to reach g1t about an enterprise account. */
161const ENTERPRISE_MAIL = "mailto:hey@flagon.io?subject=Enterprise%20billing%20for%20g1t";
162
163const HOW = [
164 {
165 title: "Our cost, passed through",
166 body: "Every runner minute, build and app request costs g1t money at Cloudflare. Builds and app traffic are charged at that cost plus 20%, from the first second and the first request; runner minutes at a fixed rate for each machine, set from its cost plus 20% and never under its cost with every vCPU busy. Use a little, pay a little.",
167 },
168 {
169 title: "Prices follow costs, by themselves",
170 body: "Every day, sandbox, build and app costs are checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below. The rest are Cloudflare's published prices, marked as such.",
171 },
172 {
173 title: "The 20% is the overhead",
174 body: "It pays for running g1t and for building and keeping up the features you use. The same 20% on everything, and nothing bundled in.",
175 },
176 {
177 title: "Your own runners are free",
178 body: "Checks, workflows and the merge queue on your own hardware cost nothing here.",
179 },
180 {
181 title: "No seats, ever",
182 body: "Once a workspace is on the plan, add as many people as you like. It pays one flat price for the plan, and for what it uses past what the plan includes.",
183 },
184 {
185 title: "Prices exclude tax",
186 body: "Every price here is before tax. Stripe adds sales tax, VAT or GST where it applies, worked out from the billing address, and shows it as its own line before you pay and on every receipt and invoice. A valid business tax ID is applied where the law says so.",
187 },
188 {
189 title: "Card fees are passed on, nothing more",
190 body: "Paying by card adds Stripe's card processing fee as its own line, shown before you pay, so the 20% is never spent on fees. A bank transfer or invoiced billing has no card fee.",
191 },
192];
193
194/** One row of the free-and-plan table. */
195type Row = { what: string; free: string; plan: string; note?: string };
196
197function rows(tier: Required<FreeTier>): Row[] {
198 return [
199 {
200 what: "Chat",
201 free: "Included, for the people already in the workspace",
202 plan: "Included, for unlimited members",
203 note: "Channels, direct messages, threads, mentions and live delivery. History is never cut off.",
204 },
205 {
206 what: "Docs (coming soon)",
207 free: "Included",
208 plan: "Included",
209 note: "Pages, live editing and history, once Docs ships.",
210 },
211 {
212 what: "The forge",
213 free: "Free",
214 plan: "Included",
215 note: "Public and private repositories, issues, pull requests, reviews, protected branches, code owners, search and Explore.",
216 },
217 {
218 what: "Checks, workflows, merge queue",
219 free: "After a card check: the trial, and the open-source pool for checks, workflows and the merge queue on public repositories",
220 plan: "From the plan's included usage, then at cost plus 20%",
221 },
222 {
223 what: "Projects and previews",
224 free: "Not available: deployments run on g1t's machines",
225 plan: "Unlimited, and never charged for. An app no one visits costs nothing.",
226 },
227 {
228 what: "Deployments",
229 free: "Not available",
230 plan: "Builds by the second, app requests, CPU time and custom domains, each from the first at cost plus 20%",
231 },
232 {
233 what: "Semantic search",
234 free: "After a card check, from the trial",
235 plan: "From the plan's included usage, then at cost plus 20%",
236 },
237 {
238 what: "Private storage",
239 free: `${gigabytes(tier.freePrivateStorageBytes)}, never charged. Past it, pushes to private repositories stop until you make room or start the plan.`,
240 plan: `${gigabytes(tier.freePrivateStorageBytes)} free, then at cost plus 20%. Pushes never stop.`,
241 note: "Public repositories are never charged for storage.",
242 },
243 {
244 what: "Git operations",
245 free: `${count(tier.gitOperationsIncluded)} a month, never charged. Past it, slowed to 60 an hour until the month turns.`,
246 plan: `${count(tier.gitOperationsIncluded)} a month free, then at cost plus 20%. Never slowed.`,
247 note: "Clones, fetches and pushes through g1t.",
248 },
249 {
250 what: "Actions cache",
251 free: "2 GiB an entry and 10 GiB a repository, never charged",
252 plan: "The same limits, with what it stores at cost plus 20%",
253 },
254 {
255 what: "Audit log",
256 free: `${tier.auditRetentionDays} days, with export`,
257 plan: `${tier.planAuditRetentionDays} days, with export`,
258 note: "Longer by arrangement. Older entries are deleted each day.",
259 },
260 {
261 what: "Secret scanning, push protection, vulnerability alerts, security updates",
262 free: "Included",
263 plan: "Included",
264 note: "Custom patterns, validity checks, code scanning, dependency review and the security overview on private repositories come with the plan, with no price of their own: their scans are charged at cost plus 20%. On public repositories they are free.",
265 },
266 { what: "Single sign-on", free: "On every plan, once it is built", plan: "On every plan, once it is built" },
267 {
268 what: "Members",
269 free: "Only the people already in it: a free workspace cannot add members, invite people or invite outside collaborators",
270 plan: "Unlimited, never per seat",
271 note: "Each person can own one free workspace. More workspaces need the plan.",
272 },
273 {
274 what: "Usage past what is included",
275 free: "Not possible: a free workspace never runs up a bill",
276 plan: "Charged at cost plus 20%, up to a spend limit you set. No quotas: only your limit stops anything.",
277 },
278 ];
279}
280
281export default function Pricing({ loaderData }: Route.ComponentProps) {
282 const { book, free } = loaderData;
283 const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1);
284 const given = Object.fromEntries(Object.entries(book?.free ?? {}).filter(([, value]) => value != null));
285 const tier: Required<FreeTier> = { ...DEFAULT_FREE, ...given };
286 const plan = book?.plans?.find((p) => p.feature === "plan") ?? DEFAULT_PLAN;
287 const price = plan.monthlyCents / 100;
288 const dollars = wholeDollars;
289 // What g1t's own metered work is marked up, from the price book: one figure today.
290 const runMarkups = (book?.prices ?? [])
291 .filter((p) => !SHOWN_APART.has(p.meter) && p.meter !== "security_activation" && p.costMicros > 0)
292 .map((p) => p.markupPercent);
293 const markup = runMarkups.length && Math.min(...runMarkups) === Math.max(...runMarkups) ? `${runMarkups[0]}%` : "20%";
294 // Price changes, without the retired agent and model meters.
295 const changes = (book?.changes ?? []).filter((change) => !retired(change.meter));
296 return (
297 <main className="mx-auto max-w-4xl px-4 py-12">
298 <p className="text-sm font-medium text-accent">Pricing</p>
299 <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What g1t runs, at cost plus {markup}.</h1>
300 <p className="mt-3 max-w-2xl text-muted">
301 People chatting and the forge are free for everyone. Everything g1t runs is billed at what it costs g1t plus{" "}
302 {markup}. Your own runners are free, connecting your coding agents costs nothing, and there are no seats. The numbers on this page are the live price book g1t charges from. Prices exclude tax, which is added
303 where it applies.
304 </p>
305 <Card asChild tone="plain" className="mt-6 grid gap-px overflow-hidden bg-line text-sm sm:grid-cols-2 lg:grid-cols-4">
306 <ul>
307 {[
308 ["What g1t runs", `At cost + ${markup}`, "Sandboxes, builds, hosting, storage, search and scans"],
309 ["Your own runners", "Free", "Checks and workflows on your hardware"],
310 ["Your coding agents", "Free to connect", "Through the MCP server and the API"],
311 ["People", "No seats", "Everyone in the workspace at one price"],
312 ].map(([what, price, about]) => (
313 <li key={what} className="bg-surface px-4 py-3">
314 <p className="text-xs text-muted">{what}</p>
315 <p className="mt-0.5 font-medium">{price}</p>
316 <p className="mt-0.5 text-xs text-faint">{about}</p>
317 </li>
318 ))}
319 </ul>
320 </Card>
321 {free && (
322 <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm">
323 <span className="font-medium">Free while g1t is being built out.</span>{" "}
324 <span className="text-muted">Usage is recorded at these prices but not charged for now. The plan is charged.</span>
325 </div>
326 )}
327
328 <section className="mt-10 rounded-xl border border-accent/40 bg-surface p-6">
329 <div className="flex flex-wrap items-baseline justify-between gap-3">
330 <h2 className="flex flex-wrap items-center gap-2 text-xl font-semibold tracking-tight">
331 {plan.title}
332 <span className="rounded-full bg-accent/15 px-2 py-0.5 text-xs font-medium tracking-normal text-accent">Recommended</span>
333 </h2>
334 <p>
335 <span className="text-3xl font-semibold tabular-nums">${price}</span>{" "}
336 <span className="text-sm text-muted">a month per workspace</span>
337 <span className="block text-right text-xs text-faint">
338 {plan.cardFeeCents ? `+ $${(plan.cardFeeCents / 100).toFixed(2)} card processing fee, plus tax where it applies` : "Plus tax where it applies"}
339 </span>
340 </p>
341 </div>
342 <p className="mt-2 text-sm text-muted">
343 The plan for a team that ships: unlimited members, checks, the merge queue and deployments.
344 </p>
345 <p className="mt-3 text-sm text-fg-soft">
346 One price for the whole team: a 10-person team pays ${price} a month, not ${price} a person.
347 </p>
348 <ul className="mt-4 space-y-1.5 text-sm text-muted">
349 {plan.includes.map((line) => (
350 <li key={line} className="flex gap-2">
351 <span aria-hidden className="text-accent">
352 ✓
353 </span>
354 {line}
355 </li>
356 ))}
357 </ul>
358 <p className="mt-3 text-xs text-faint">{plan.overage}</p>
359 <div className="mt-5 grid gap-5 border-t border-line pt-5 text-sm sm:grid-cols-2">
360 <div>
361 <p className="font-medium">Where the ${price} goes</p>
362 <p className="mt-1 text-muted">
363 $10 comes back to you as usage: sandboxes, builds and deployments at cost plus 20%, used before
364 anything else. The other $10 pays for running g1t, the free forge for everyone, and the people building it.
365 </p>
366 </div>
367 <div>
368 <p className="font-medium">No quotas</p>
369 <p className="mt-1 text-muted">
370 No count of projects, previews, builds, requests or git operations ever stops a workspace on the plan. Past
371 the $10, usage is charged at cost plus 20%, and only your spend limit stops it. Unused included usage does
372 not roll over.
373 </p>
374 </div>
375 </div>
376 <p className="mt-5 text-sm text-muted">An owner starts the plan from the workspace's Billing page.</p>
377 </section>
378
379 <section id="workspace" className="mt-14">
380 <h2 className="text-xl font-semibold tracking-tight">Chat, Docs and your coding agents</h2>
381 <p className="mt-1 max-w-3xl text-sm text-muted">
382 People talking costs g1t very little, so it is included. So is connecting the coding agents you already use.
383 </p>
384 <Card tone="plain" className="mt-4 overflow-hidden">
385 <dl className="divide-y divide-line text-sm">
386 {WORKSPACE_CHARGES.map((row) => (
387 <div key={row.what} className="grid gap-1 px-4 py-3 sm:grid-cols-[14rem_1fr] sm:gap-4">
388 <dt className="font-medium">
389 {row.what}
390 {row.soon && (
391 <span className="ml-2 inline-block rounded bg-raised px-1.5 py-0.5 text-xs font-normal whitespace-nowrap text-muted">Coming soon</span>
392 )}
393 </dt>
394 <dd className="text-muted">{row.how}</dd>
395 </div>
396 ))}
397 </dl>
398 </Card>
399 </section>
400
401 <h2 className="mt-14 text-xl font-semibold tracking-tight">Free and the plan, side by side</h2>
402 <p className="mt-1 text-sm text-muted">
403 Secret scanning, push protection, vulnerability alerts and security updates are free everywhere, on every plan.
404 </p>
405 {/* On a phone, one row at a time with both answers under it; a table that wide would scroll. */}
406 <Card asChild tone="plain" divided className="mt-4 text-sm sm:hidden">
407 <ul>
408 {rows(tier).map((row) => (
409 <li key={row.what} className="px-4 py-3">
410 <p className="font-medium">{row.what}</p>
411 {row.note && <p className="text-xs text-faint">{row.note}</p>}
412 <dl className="mt-2 grid grid-cols-[4.5rem_1fr] gap-x-3 gap-y-1.5">
413 <dt className="text-xs leading-5 text-faint">Free</dt>
414 <dd className="text-muted">{row.free}</dd>
415 <dt className="text-xs leading-5 text-faint">{plan.title}</dt>
416 <dd className="text-muted">{row.plan}</dd>
417 </dl>
418 </li>
419 ))}
420 </ul>
421 </Card>
422 <Card tone="plain" className="mt-4 hidden overflow-x-auto sm:block">
423 <table className="w-full min-w-xl text-left text-sm">
424 <thead className="border-b border-line text-xs text-muted">
425 <tr>
426 <th className="px-4 py-2.5 font-medium">
427 <span className="sr-only">What</span>
428 </th>
429 <th className="px-4 py-2.5 font-medium">Free</th>
430 <th className="px-4 py-2.5 font-medium">
431 {plan.title}, ${price} a month
432 </th>
433 </tr>
434 </thead>
435 <tbody className="divide-y divide-line">
436 {rows(tier).map((row) => (
437 <tr key={row.what} className="align-top">
438 <td className="px-4 py-3">
439 <p className="font-medium">{row.what}</p>
440 {row.note && <p className="text-xs text-faint">{row.note}</p>}
441 </td>
442 <td className="px-4 py-3 text-muted">{row.free}</td>
443 <td className="px-4 py-3 text-muted">{row.plan}</td>
444 </tr>
445 ))}
446 </tbody>
447 </table>
448 </Card>
449 <p className="mt-3 text-sm text-muted">
450 Git operations cost g1t money too: Cloudflare charges g1t $0.15 per 1,000. {count(tier.gitOperationsIncluded)} a
451 month is far more than an active workspace uses.
452 </p>
453
454 <h2 className="mt-14 text-xl font-semibold tracking-tight">No card, no compute</h2>
455 <div className="mt-3 max-w-3xl space-y-3 text-sm text-muted">
456 <p>
457 Checks, workflows, the merge queue, deployments and semantic search run on g1t's machines. They need
458 the plan, or a card check.
459 </p>
460 <p>
461 The reason: compute costs g1t real money from the first second, and free compute draws people who use it to
462 mine cryptocurrency. A card check is the smallest gate that stops that, with one trial per card.
463 </p>
464 <p>
465 A card check is never charged. Stripe saves and verifies the card, with 3-D Secure where your bank asks for it.
466 Nothing is charged until an owner starts the plan, and then the same card is used, without asking for it again.
467 The trial needs a credit or debit card; prepaid cards can still pay for the plan.
468 </p>
469 </div>
470
471 <div className="mt-5 grid gap-4 sm:grid-cols-2">
472 <Card asChild className="p-5">
473 <section>
474 <h3 className="font-medium">The trial</h3>
475 <p className="mt-1.5 text-sm text-muted">
476 {dollars(tier.trialWorkspaceMicros)} of usage, once per workspace and once per card, after a card check with a credit or
477 debit card. It comes from a{" "}
478 {dollars(tier.trialMonthlyPoolMicros)} pool each month for everyone; when a month's pool is given out, new
479 trials wait until the 1st. The trial does not cover deployments and never turns into a charge. If the last
480 run on a trial goes past it, g1t covers the difference.
481 </p>
482 </section>
483 </Card>
484 <Card asChild className="p-5">
485 <section>
486 <h3 className="font-medium">The open-source pool</h3>
487 <p className="mt-1.5 text-sm text-muted">
488 Checks, workflows and the merge queue on public repositories, after a card check:{" "}
489 {dollars(tier.ossPoolMicros)} a month in all, up to {dollars(tier.ossRepoMicros)} a month per repository.
490 When the pool or a repository's share is used up, the work waits for the
491 month to turn, or the workspace pays as usual.
492 </p>
493 </section>
494 </Card>
495 </div>
496
497 <h2 className="mt-14 text-xl font-semibold tracking-tight">Limits, and how to raise them</h2>
498 <p className="mt-1 max-w-3xl text-sm text-muted">
499 g1t lets usage run ahead of payment only so far. Limits keep a mistake small, for you and for g1t.
500 </p>
501 <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2">
502 <Card asChild className="p-4">
503 <li>
504 <p className="font-medium">{dollars(tier.paidStartCeilingMicros)} in the first month</p>
505 <p className="mt-1 text-muted">
506 A workspace on the plan can use up to {dollars(tier.paidStartCeilingMicros)} in its first month. After that,
507 the limit grows with payments that clear.
508 </p>
509 </li>
510 </Card>
511 <Card asChild className="p-4">
512 <li>
513 <p className="font-medium">Your own spend limit</p>
514 <p className="mt-1 text-muted">
515 Owners set a monthly spend limit anywhere up to the highest limit the workspace has had, with no approval.
516 Once, you can raise it yourself to twice that.
517 </p>
518 </li>
519 </Card>
520 <Card asChild className="p-4">
521 <li>
522 <p className="font-medium">Prepay to raise it now</p>
523 <p className="mt-1 text-muted">
524 Prepaying from $25 raises what you can use by the same amount, at once. From $1,000 you can also pay by bank
525 transfer.
526 </p>
527 </li>
528 </Card>
529 <Card asChild className="p-4">
530 <li>
531 <p className="font-medium">Raise my limit</p>
532 <p className="mt-1 text-muted">
533 Need more? Ask from Billing with the amount, why, and what you expect to spend. You get an answer within one
534 business day, in the app and by email.
535 </p>
536 </li>
537 </Card>
538 </ul>
539
540 <h2 className="mt-14 text-xl font-semibold tracking-tight">When usage runs high</h2>
541 <ul className="mt-4 grid gap-3 text-sm sm:grid-cols-2">
542 <Card asChild className="p-4">
543 <li>
544 <p className="font-medium">A spike pauses new work</p>
545 <p className="mt-1 text-muted">
546 When an hour's spend is five times your usual hour, and at least $5, new compute waits for an owner to choose
547 Keep going or Stop. Runs already going finish.
548 </p>
549 </li>
550 </Card>
551 <Card asChild className="p-4">
552 <li>
553 <p className="font-medium">Alerts at 50, 75, 90 and 100%</p>
554 <p className="mt-1 text-muted">
555 Of the plan's included usage, your spend limit and g1t's limit: in the app and by email.
556 </p>
557 </li>
558 </Card>
559 <Card asChild className="p-4">
560 <li>
561 <p className="font-medium">Spent more than you meant to? Tell us</p>
562 <p className="mt-1 text-muted">
563 Tell us from Billing what happened. Once in 12 months, g1t can credit back usage you did not mean, always
564 including its 20% on it. Larger credits, or a second one within 12 months, are reviewed by a person.
565 </p>
566 </li>
567 </Card>
568 </ul>
569 <p className="mt-3 text-sm text-muted">
570 No card is charged less than {dollars(tier.minChargeMicros)} when a month closes, so a payment's fee is never most
571 of it. Smaller amounts carry over to the next invoice. Charges at a limit always go through.
572 </p>
573
574 <div className="mt-14 grid gap-4 sm:grid-cols-2">
575 {HOW.map((item) => (
576 <Card asChild key={item.title} className="p-5">
577 <section>
578 <h2 className="font-medium">{item.title}</h2>
579 <p className="mt-1.5 text-sm text-muted">{item.body}</p>
580 </section>
581 </Card>
582 ))}
583 </div>
584
585 <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2>
586 <p className="mt-1 text-sm text-muted">
587 {checked ? (
588 <>
589 Last checked against Cloudflare's bill <TimeAgo at={checked} />.
590 </>
591 ) : (
592 "Starting from Cloudflare's published prices; checked against its bill daily."
593 )}
594 </p>
595 <Card tone="plain" className="mt-4 overflow-x-auto">
596 <table className="w-full text-left text-sm sm:min-w-xl">
597 <thead className="border-b border-line text-xs text-muted">
598 <tr>
599 <th className="px-4 py-2.5 font-medium">What</th>
600 <th className="px-4 py-2.5 font-medium">Costs g1t</th>
601 {/* The markup is in what you pay; on a phone the column gives way. */}
602 <th className="hidden px-4 py-2.5 font-medium sm:table-cell">Markup</th>
603 <th className="px-4 py-2.5 font-medium">You pay</th>
604 </tr>
605 </thead>
606 <tbody className="divide-y divide-line">
607 {(() => {
608 const feePercent = book?.prices.find((p) => p.meter === "card_fee_percent");
609 const feeFixed = book?.prices.find((p) => p.meter === "card_fee_fixed");
610 return (
611 <>
612 {feePercent && feeFixed && (
613 <tr>
614 <td className="px-4 py-3">
615 <p className="font-medium">Card processing fee</p>
616 <p className="text-xs text-faint">
617 On every card payment, as its own line shown before you pay; never on bank transfers or invoiced billing
618 </p>
619 </td>
620 <td className="px-4 py-3 text-muted">Stripe's fee</td>
621 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
622 <td className="px-4 py-3 font-mono text-xs tabular-nums">
623 {(feePercent.priceMicros / 10_000).toFixed(1)}% + {money(feeFixed.priceMicros)}
624 </td>
625 </tr>
626 )}
627 </>
628 );
629 })()}
630 {machines().map((instance) => (
631 <tr key={instance.machine}>
632 <td className="px-4 py-3">
633 <p className="font-medium">
634 Runner minutes, {instance.label === "standard" ? "standard machine" : instance.label}
635 </p>
636 <p className="text-xs text-faint">
637 {machineSize(instance)}. Each job's time rounded up to the minute; checks, workflows and the merge queue
638 </p>
639 </td>
640 <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">Up to {money(busyMinuteMicros(instance))} a minute</td>
641 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">Fixed rate</td>
642 <td className="px-4 py-3 font-mono text-xs tabular-nums">{money(instance.microsPerMinute)} a minute</td>
643 </tr>
644 ))}
645 <tr>
646 <td className="px-4 py-3">
647 <p className="font-medium">Your own runners</p>
648 <p className="text-xs text-faint">Checks, workflows and the merge queue on your own hardware</p>
649 </td>
650 <td className="px-4 py-3 text-muted">Nothing</td>
651 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
652 <td className="px-4 py-3 text-muted">Free</td>
653 </tr>
654 <tr>
655 <td className="px-4 py-3">
656 <p className="font-medium">Projects, previews and apps</p>
657 <p className="text-xs text-faint">Having them. An app's requests and CPU time are charged below.</p>
658 </td>
659 <td className="px-4 py-3 text-muted">Next to nothing</td>
660 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">—</td>
661 <td className="px-4 py-3 text-muted">Not charged</td>
662 </tr>
663 {(book?.prices ?? []).filter((price) => !SHOWN_APART.has(price.meter)).map((price) => (
664 <tr key={price.meter}>
665 <td className="px-4 py-3">
666 <p className="font-medium">{price.title}</p>
667 <p className="text-xs text-faint">
668 {price.meter === "actions_cache" ? "On the plan only. A free workspace is never charged for it. " : ""}
669 {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"}
670 </p>
671 </td>
672 <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td>
673 <td className="hidden px-4 py-3 tabular-nums sm:table-cell">{price.markupPercent}%</td>
674 <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td>
675 </tr>
676 ))}
677 </tbody>
678 </table>
679 </Card>
680
681 <h2 className="mt-14 text-xl font-semibold tracking-tight">Enterprise</h2>
682 <p className="mt-1 text-sm text-muted">
683 For organizations with several workspaces: one invoice and one limit. The prices are the ones on this page.
684 </p>
685 <Card asChild className="mt-4 p-5">
686 <section>
687 <ul className="grid gap-3 text-sm sm:grid-cols-2">
688 <li>
689 <p className="font-medium">Consolidated invoicing</p>
690 <p className="text-muted">One monthly invoice for every workspace, paid by card or bank transfer.</p>
691 </li>
692 <li>
693 <p className="font-medium">Custom terms</p>
694 <p className="text-muted">One limit across every workspace, invoice terms and a billing contact, set with you.</p>
695 </li>
696 <li>
697 <p className="font-medium">Security on every plan</p>
698 <p className="text-muted">
699 The audit log with export, {tier.planAuditRetentionDays} days or longer by arrangement, and secret push
700 protection, for every workspace.
701 </p>
702 </li>
703 <li>
704 <p className="font-medium">
705 Single sign-on <span className="ml-1 rounded bg-raised px-1.5 py-0.5 text-xs text-muted">Coming</span>
706 </p>
707 <p className="text-muted">Through your identity provider, on every plan once it is built. Not available yet.</p>
708 </li>
709 </ul>
710 <a
711 href={ENTERPRISE_MAIL}
712 className="mt-5 inline-flex items-center gap-1.5 rounded-md border border-line px-3 py-1.5 text-sm hover:border-line-strong"
713 >
714 Write to us about enterprise
715 <ArrowUpRight size={14} />
716 </a>
717 </section>
718 </Card>
719
720 <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2>
721 {book && changes.length > 0 ? (
722 <Card asChild tone="plain" divided className="mt-4">
723 <ul>
724 {changes.map((change) => {
725 const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter;
726 // What you pay moved: the cost, the markup, or both.
727 const before = change.oldCostMicros * (100 + (change.oldMarkupPercent ?? change.markupPercent));
728 const after = change.newCostMicros * (100 + change.markupPercent);
729 const up = after > before;
730 const fresh = after === before || before === 0;
731 return (
732 <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm">
733 <p>
734 <span className="font-medium">{title}</span>{" "}
735 {fresh ? (
736 <span className="text-muted">new</span>
737 ) : (
738 <span className={up ? "text-warn" : "text-accent"}>
739 {up ? "up" : "down"} {Math.abs((after / before - 1) * 100).toFixed(1)}%
740 </span>
741 )}
742 </p>
743 <p className="mt-0.5 text-xs text-faint">
744 {change.effectiveAt ? (
745 <>
746 {change.reason} · <span className="text-fg-soft">takes effect {change.effectiveAt.slice(0, 10)}</span>
747 </>
748 ) : (
749 <>
750 {change.reason} · <TimeAgo at={change.createdAt} />
751 </>
752 )}
753 </p>
754 </li>
755 );
756 })}
757 </ul>
758 </Card>
759 ) : (
760 <p className="mt-2 text-sm text-muted">None yet. When a price moves, it is listed here with why.</p>
761 )}
762
763 <h2 id="faq" className="mt-14 text-xl font-semibold tracking-tight">Questions</h2>
764 <Card asChild tone="plain" divided className="mt-4">
765 <dl>
766 {FAQ.map((item) => (
767 <div key={item.q} className="px-4 py-4 text-sm">
768 <dt className="font-medium">{item.q}</dt>
769 <dd className="mt-1.5 text-muted">{item.a}</dd>
770 </div>
771 ))}
772 </dl>
773 </Card>
774
775 <a
776 href="https://docs.g1t.sh/guides/usage-and-billing/"
777 className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline"
778 >
779 How usage and billing work
780 <ArrowUpRight size={14} />
781 </a>
782 </main>
783 );
784}