flagon-io/g1t

public

Git for AI scale: a forge for thousands of agents working on the same code at once.

g1t/apps/web/app/routes/pricing.tsx

190 lines9,037 bytesCodeBlame

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.

Prices keep themselves current with what g1t pays1import { ArrowUpRight } from "lucide-react";
2
3import { DEPLOYMENTS_ALLOWANCE, MICROS_PER_DOLLAR, type Price } from "@g1t/contracts";
4
5import type { Route } from "./+types/pricing";
Social cards for every page: og.g1t.sh6import { page } from "../lib/meta";
Prices keep themselves current with what g1t pays7import { TimeAgo } from "../components/ui";
8import { billing } from "../lib/services.server";
9
Social cards for every page: og.g1t.sh10export function meta(args: Route.MetaArgs) {
11 return page(args, {
12 title: "Pricing · g1t",
13 description: "g1t passes its costs through: what Cloudflare and model providers charge g1t, plus a set markup. No seats.",
14 });
Prices keep themselves current with what g1t pays15}
16
17export async function loader() {
Billing accounts, terms and enterprises; g1t is no longer free18 const [book, status] = await Promise.all([billing.prices().catch(() => null), billing.status().catch(() => null)]);
19 return { book, free: status?.free ?? false };
Prices keep themselves current with what g1t pays20}
21
22/** A price in dollars, with as many digits as it needs to say anything. */
23function money(micros: number): string {
24 const dollars = micros / MICROS_PER_DOLLAR;
25 if (dollars >= 1) return `$${dollars.toFixed(2)}`;
26 if (dollars >= 0.01) return `$${dollars.toFixed(3)}`;
27 return `$${dollars.toPrecision(2)}`;
28}
29
30/** Per second is easier to read per minute. */
31function perUnit(price: Price, micros: number): string {
32 return price.unit === "second" ? `${money(micros * 60)} a minute` : `${money(micros)} per ${price.unit}`;
33}
34
35const HOW = [
36 {
37 title: "Our cost, passed through",
38 body: "Every sandbox second, build, app request and model token costs g1t money at Cloudflare or a model provider. Each is metered and charged at that cost plus a set markup. Use a little, pay a little.",
39 },
40 {
41 title: "Prices follow costs, by themselves",
42 body: "Model runs are charged at what Cloudflare's AI Gateway priced each request at, so a provider's price change reaches you the same day. Every day, each Cloudflare cost is checked against what Cloudflare billed g1t; when one moves, its price moves with it, and the change is listed below.",
43 },
44 {
45 title: "No seats, ever",
46 body: "Add as many people and agents as you like. A workspace pays for what it uses, and for the features it turns on.",
47 },
48 {
49 title: "Limits that protect both of us",
Billing accounts, terms and enterprises; g1t is no longer free50 body: "Usage not yet paid for can only go so far: $3 for a new workspace, growing with what it pays. With a card on file, g1t charges it as you near the limit, so work that is paid for never stops. Owners can set a lower limit of their own.",
Prices keep themselves current with what g1t pays51 },
Billing accounts, terms and enterprises; g1t is no longer free52 {
53 title: "Enterprise billing",
Say what g1t does, not whom it is like54 body: "One bill, one limit and one set of terms for several workspaces. Write to us to set one up.",
Billing accounts, terms and enterprises; g1t is no longer free55 },
Prices keep themselves current with what g1t pays56];
57
58export default function Pricing({ loaderData }: Route.ComponentProps) {
Billing accounts, terms and enterprises; g1t is no longer free59 const { book, free } = loaderData;
Prices keep themselves current with what g1t pays60 const checked = book?.prices.map((p) => p.checkedAt).filter((at): at is string => !!at).sort().at(-1);
61 return (
62 <main className="mx-auto max-w-4xl px-4 py-12">
63 <p className="text-sm font-medium text-accent">Pricing</p>
64 <h1 className="mt-2 text-3xl font-semibold tracking-tight sm:text-4xl">What it costs us, plus a markup</h1>
65 <p className="mt-3 max-w-2xl text-muted">
66 g1t runs on Cloudflare and model providers, and passes those costs through. The numbers on this page are the
67 live price book g1t charges from.
68 </p>
Billing accounts, terms and enterprises; g1t is no longer free69 {free && (
70 <div className="mt-5 rounded-xl border border-accent/30 bg-accent/5 px-4 py-3 text-sm">
71 <span className="font-medium">Free while g1t is being built out.</span>{" "}
72 <span className="text-muted">
73 Usage is recorded at these prices but not charged for now. Paid features, such as Deployments, are charged.
74 </span>
75 </div>
76 )}
Prices keep themselves current with what g1t pays77
78 <div className="mt-10 grid gap-4 sm:grid-cols-2">
79 {HOW.map((item) => (
80 <section key={item.title} className="rounded-xl border border-line bg-surface p-5">
81 <h2 className="font-medium">{item.title}</h2>
82 <p className="mt-1.5 text-sm text-muted">{item.body}</p>
83 </section>
84 ))}
85 </div>
86
87 <h2 className="mt-14 text-xl font-semibold tracking-tight">Usage</h2>
88 <p className="mt-1 text-sm text-muted">
89 {checked ? (
90 <>
91 Last checked against Cloudflare's bill <TimeAgo at={checked} />.
92 </>
93 ) : (
94 "Starting from Cloudflare's published prices; checked against its bill daily."
95 )}
96 </p>
97 <div className="mt-4 overflow-x-auto rounded-xl border border-line">
98 <table className="w-full min-w-[36rem] text-left text-sm">
99 <thead className="border-b border-line text-xs text-muted">
100 <tr>
101 <th className="px-4 py-2.5 font-medium">What</th>
102 <th className="px-4 py-2.5 font-medium">Costs g1t</th>
103 <th className="px-4 py-2.5 font-medium">Markup</th>
104 <th className="px-4 py-2.5 font-medium">You pay</th>
105 </tr>
106 </thead>
107 <tbody className="divide-y divide-line">
108 <tr>
109 <td className="px-4 py-3">
110 <p className="font-medium">Models</p>
111 <p className="text-xs text-faint">g1t's hosted models, through AI Gateway</p>
112 </td>
113 <td className="px-4 py-3 text-muted">What the provider charges, per request</td>
114 <td className="px-4 py-3 tabular-nums">{book?.modelMarginPercent ?? 20}%</td>
115 <td className="px-4 py-3 text-muted">Cost + {book?.modelMarginPercent ?? 20}%</td>
116 </tr>
117 {(book?.prices ?? []).map((price) => (
118 <tr key={price.meter}>
119 <td className="px-4 py-3">
120 <p className="font-medium">{price.title}</p>
121 <p className="text-xs text-faint">
122 {price.source === "cloudflare" ? "Measured from Cloudflare's bill" : "Cloudflare's published price"}
123 {price.meter === "sandbox_second" && " · 500 minutes free each month"}
124 </p>
125 </td>
126 <td className="px-4 py-3 font-mono text-xs tabular-nums text-muted">{perUnit(price, price.costMicros)}</td>
127 <td className="px-4 py-3 tabular-nums">{price.markupPercent}%</td>
128 <td className="px-4 py-3 font-mono text-xs tabular-nums">{perUnit(price, price.priceMicros)}</td>
129 </tr>
130 ))}
131 </tbody>
132 </table>
133 </div>
134 <p className="mt-3 text-xs text-faint">
135 Sandbox time carries more markup than the rest: it pays for the orchestration around each sandbox, and the
136 free minutes everyone gets.
137 </p>
138
139 <h2 className="mt-14 text-xl font-semibold tracking-tight">Features</h2>
140 <p className="mt-1 text-sm text-muted">Turned on per workspace with a monthly plan. Never free.</p>
141 <section className="mt-4 rounded-xl border border-line bg-surface p-5">
142 <div className="flex flex-wrap items-baseline justify-between gap-2">
143 <h3 className="font-medium">Deployments</h3>
144 <p>
145 <span className="text-2xl font-semibold">$5</span> <span className="text-sm text-muted">/ month</span>
146 </p>
147 </div>
148 <p className="mt-1 text-sm text-muted">
149 Includes {DEPLOYMENTS_ALLOWANCE.apps} apps up at once, {(DEPLOYMENTS_ALLOWANCE.requests / 1e6).toLocaleString()}{" "}
Agents and memory, checks and conflicts, profiles, slug renames, custom domains150 million requests, {(DEPLOYMENTS_ALLOWANCE.cpuMs / 1e6).toLocaleString()} million CPU milliseconds and{" "}
151 {DEPLOYMENTS_ALLOWANCE.customDomains} custom domains a month;
Prices keep themselves current with what g1t pays152 builds, and usage past that, at the prices above.
153 </p>
154 </section>
155
156 <h2 className="mt-14 text-xl font-semibold tracking-tight">Price changes</h2>
157 {book && book.changes.length > 0 ? (
158 <ul className="mt-4 divide-y divide-line rounded-xl border border-line">
159 {book.changes.map((change) => {
160 const title = book.prices.find((p) => p.meter === change.meter)?.title ?? change.meter;
161 const up = change.newCostMicros > change.oldCostMicros;
162 return (
163 <li key={`${change.meter}-${change.createdAt}`} className="px-4 py-3 text-sm">
164 <p>
165 <span className="font-medium">{title}</span>{" "}
166 <span className={up ? "text-warn" : "text-accent"}>
167 {up ? "up" : "down"} {Math.abs((change.newCostMicros / change.oldCostMicros - 1) * 100).toFixed(1)}%
168 </span>
169 </p>
170 <p className="mt-0.5 text-xs text-faint">
171 {change.reason} · <TimeAgo at={change.createdAt} />
172 </p>
173 </li>
174 );
175 })}
176 </ul>
177 ) : (
178 <p className="mt-2 text-sm text-muted">None yet. When a cost moves, it is listed here with why.</p>
179 )}
180
181 <a
182 href="https://docs.g1t.sh/guides/usage-and-billing/"
183 className="mt-12 inline-flex items-center gap-1.5 text-sm text-accent hover:underline"
184 >
185 How usage and billing work
186 <ArrowUpRight size={14} />
187 </a>
188 </main>
189 );
190}