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
252 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.

Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running1/**
2 * The changes staff make to how a workspace or an enterprise pays: terms,
3 * moving workspaces on and off enterprises, credits, and Stripe billing
4 * links. What is acted on comes from the billing service and identity, not
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging5 * from the form; each change shows a confirmation first, and a credit over
6 * $100 needs the workspace's slug typed out.
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running7 */
8import type { Terms } from "@g1t/contracts";
9import { data, redirect } from "react-router";
10
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging11import { parseCreditForm } from "./credits";
12import { fields, parseAllowances, parseEmail, parseGoodwill, parseNote, parsePayment, parseSlug, parseTerms, text } from "./forms";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running13import { FORGIVE_COST_MICROS, goodwillWarning } from "./pricing";
14import type { ActionData } from "./review";
15import { admin, identity, priceBook } from "./services.server";
sudo: the billing reset no longer names sandbox_months (dropped in 0015), checked against the migrations by a test; a failed reset says why instead of an error page16import { settle } from "./settle";
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running17import type { Staff } from "./staff";
18import type { Enterprise } from "./workspaces";
19
20/** What a page acts on. `accountId` is billing's internal id, never shown. */
21export type Subject =
22 | { kind: "workspace"; slug: string; accountId: string; terms: Terms; billedTo: Enterprise | null }
23 | { kind: "enterprise"; accountId: string; name: string; terms: Terms; workspaces: string[]; billingEmail: string | null };
24
25function failed(section: string, error: string, values?: Record<string, string>) {
26 return data<ActionData>({ error, section, values }, { status: 422 });
27}
28
29export async function billingAction(request: Request, staff: Staff, subject: Subject, path: string) {
30 const form = await request.formData();
31 const intent = text(form, "intent");
32 const confirmed = text(form, "confirm") === "yes";
33 const back = (done: string) => redirect(`${path}?done=${done}#top`);
34 const isEnterprise = subject.kind === "enterprise";
35
36 if (intent === "terms") {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging37 const values = fields(form, "preset", "discount", "ceiling", "note", "until");
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running38 if (subject.kind === "workspace" && subject.billedTo) {
39 return failed("terms", `This workspace is charged on ${subject.billedTo.name}'s terms. Change them on the enterprise.`, values);
40 }
41 const terms = parseTerms(form, staff.email);
42 if (!terms.ok) return failed("terms", terms.error, values);
43 if (!confirmed) return { review: { intent, before: subject.terms, after: terms.value, fields: values } } satisfies ActionData;
44 const result = await admin.setTerms(subject.accountId, terms.value, staff.email);
45 if (!result.ok) return failed("terms", result.error.message, values);
46 return back("terms");
47 }
48
49 if (intent === "allowances") {
50 // The plan without its price, the account's share of g1t's pools, and staff's overrides.
The site no longer has g1t's own agents. g1t is where the coding agents you already use do their work, so the Agents mode is gone from the rail, with every page under /<workspace>/-/agents: personas, faces, skills, abilities, routines, computers, sessions and agent spend. Customize (skills, connectors, plugins, marketplaces), the context hub, model providers, the AI Gateway page, the Spend page and pill, Ask g1t, and the repository's Agents, Plans and agent settings pages go too, and their addresses now answer 404.51 const values = fields(form, "plan", "oss", "trial", "hold", "note");
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running52 if (subject.kind === "workspace" && subject.billedTo) {
53 return failed("allowances", `This workspace is on ${subject.billedTo.name}. Set its plan and pools on the enterprise.`, values);
54 }
55 const allowances = parseAllowances(form);
56 if (!allowances.ok) return failed("allowances", allowances.error, values);
57 const note = parseNote(values.note);
58 if (!note.ok) return failed("allowances", note.error, values);
59 const result = await admin.setAllowances(subject.accountId, allowances.value, note.value, staff.email);
60 if (!result.ok) return failed("allowances", result.error.message, values);
61 return back("allowances");
62 }
63
64 if (intent === "attach") {
65 const values = fields(form, "workspace", "target");
66 const section = isEnterprise ? "members" : "billed-to";
67 let slug: string;
68 let target: Enterprise;
69 if (subject.kind === "enterprise") {
70 const parsed = parseSlug(values.workspace);
71 if (!parsed.ok) return failed(section, parsed.error, values);
72 slug = parsed.value;
73 if (subject.workspaces.includes(slug)) return failed(section, `${slug} is already on this enterprise.`, values);
74 if (!(await identity.workspace(slug))) return failed(section, `There is no workspace called ${slug}.`, values);
75 target = { id: subject.accountId, name: subject.name };
76 } else {
77 slug = subject.slug;
78 const enterprise = (await admin.accounts()).find((row) => row.account.kind === "enterprise" && row.account.id === values.target);
79 if (!enterprise) return failed(section, "Choose an enterprise to move onto.", values);
80 target = { id: enterprise.account.id, name: enterprise.account.name };
81 }
82 if (!confirmed) return { review: { intent, workspace: slug, targetName: target.name, fields: values } } satisfies ActionData;
83 const result = await admin.attach(slug, target.id, staff.email);
84 if (!result.ok) return failed(section, result.error.message, values);
85 return back("attach");
86 }
87
88 if (intent === "detach") {
89 const values = fields(form, "workspace");
90 const section = isEnterprise ? "members" : "billed-to";
91 let slug: string;
92 let from: string;
93 if (subject.kind === "enterprise") {
94 slug = values.workspace;
95 if (!subject.workspaces.includes(slug)) return failed(section, `${slug} is not on this enterprise.`);
96 from = subject.name;
97 } else {
98 if (!subject.billedTo) return failed(section, `${subject.slug} already pays for itself.`);
99 slug = subject.slug;
100 from = subject.billedTo.name;
101 }
102 if (!confirmed) return { review: { intent, workspace: slug, from, fields: values } } satisfies ActionData;
103 const result = await admin.attach(slug, null, staff.email);
104 if (!result.ok) return failed(section, result.error.message);
105 return back("detach");
106 }
107
108 if (intent === "credit") {
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging109 const values = fields(form, "workspace", "preset", "amount", "kind", "expires", "expiresOn", "refundFor", "refundDay", "note");
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running110 const workspace = subject.kind === "enterprise" ? values.workspace : subject.slug;
111 if (subject.kind === "enterprise" && !subject.workspaces.includes(workspace)) {
112 return failed("credit", "Choose one of this enterprise's workspaces.", values);
113 }
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging114 const credit = parseCreditForm(form, workspace);
115 if (!credit.ok) return failed("credit", credit.error, values);
116 const { amountMicros, note, ...options } = credit.value;
117 const result = await admin.credit(workspace, amountMicros, note, staff.email, options);
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running118 if (!result.ok) return failed("credit", result.error.message, values);
119 return back("credit");
120 }
121
Billing: credits with a kind and expiry, discounts instead of comped, and safer charging122 if (intent === "revoke-credit") {
123 // What is left of a grant, taken back. The grant must be one of the page's.
124 const values = fields(form, "id", "reason");
125 const workspaces = subject.kind === "enterprise" ? subject.workspaces : [subject.slug];
126 const reason = parseNote(values.reason);
127 if (!reason.ok) return failed("credits", reason.error, values);
128 const { grants } = await admin.credits(subject.kind === "workspace" ? { workspace: subject.slug } : {});
129 const grant = grants.find((row) => row.id === values.id && workspaces.includes(row.workspace));
130 if (!grant) return failed("credits", "That credit is not this page's, or is gone.", values);
131 const result = await admin.revokeCredit(grant.id, reason.value, staff.email);
132 if (!result.ok) return failed("credits", result.error.message, values);
133 return back("revoked");
134 }
135
sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's136 if (intent === "reset") {
137 // A test workspace's billing wiped. Billing refuses it on a live Stripe
138 // key, for comped workspaces and for an enterprise's.
139 const values = fields(form, "note", "confirmation");
140 if (subject.kind !== "workspace") return failed("top", "Reset a workspace, not an enterprise.");
141 const note = parseNote(values.note);
142 if (!note.ok) return failed("reset", note.error, values);
143 if (values.confirmation !== subject.slug) {
144 return failed("reset", `Type the workspace's slug, ${subject.slug}, exactly, to reset it.`, { ...values, confirmation: "" });
145 }
sudo: the billing reset no longer names sandbox_months (dropped in 0015), checked against the migrations by a test; a failed reset says why instead of an error page146 const result = await settle(admin.resetBilling(subject.slug, values.confirmation, note.value, staff.email));
147 if (!result.ok) return failed("reset", `Billing did not answer: ${result.error}`, values);
148 if (!result.value.ok) return failed("reset", result.value.error.message, values);
sudo: a billing reset runs the costs analysis again so every figure is fresh; every submit button shows it is working (CSS only); no margin percentage on less than a cent sold149 return back(result.value.value.refreshed === false ? "reset-stale" : "reset");
sudo: reset a test workspace's billing so it starts again as a new customer; refused on a live Stripe key, for comped workspaces and for an enterprise's150 }
151
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running152 if (intent === "payment") {
153 // A bank transfer that reached g1t outside Stripe's page.
154 const values = fields(form, "amount", "reference", "note", "confirmation");
155 if (subject.kind !== "workspace") return failed("top", "Record a payment on the workspace it is for.");
156 const payment = parsePayment(form, subject.slug);
157 if (!payment.ok) return failed("payment", payment.error, { ...values, confirmation: "" });
158 const { amountMicros, reference, note } = payment.value;
159 const result = await admin.recordPayment(subject.slug, amountMicros, reference, note, staff.email);
160 if (!result.ok) return failed("payment", result.error.message, values);
161 return back("payment");
162 }
163
164 if (intent === "goodwill") {
165 // A goodwill credit from the workspace's page: the overage, as billing quotes it now.
166 const values = fields(form, "amount", "reason", "day");
167 if (subject.kind !== "workspace") return failed("top", "Goodwill is given to one workspace.");
168 const quote = await goodwillQuote(subject.slug);
169 const parsed = parseGoodwill(form, (amount) =>
170 // A reason past the one-click credit, a second within 12 months, or past the cap on real cost.
171 quote ? ((w) => w.needsReason || w.overCap)(goodwillWarning(amount, quote.goodwill, quote.lastGoodwillAt, quote.cap)) : amount != null,
172 );
173 if (!parsed.ok) return failed("goodwill", parsed.error, values);
174 const result = await admin.goodwill(subject.slug, parsed.value.amountMicros, parsed.value.reason, staff.email, parsed.value.day);
175 if (!result.ok) return failed("goodwill", result.error.message, values);
176 return back("goodwill");
177 }
178
179 if (intent === "billing-link") {
180 if (subject.kind !== "workspace") return failed("top", "Billing links are made from a workspace's page.");
181 if (!confirmed) return { review: { intent, workspace: subject.slug, fields: {} } } satisfies ActionData;
182 const result = await admin.billingLink(subject.slug, staff.email);
183 if (!result.ok) return failed("billing-link", result.error.message);
184 // Shown once, in this response only: it is never stored or redirected to.
185 return { link: result.value, workspace: subject.slug } satisfies ActionData;
186 }
187
188 if (intent === "billing-email") {
189 const values = fields(form, "email");
190 if (subject.kind !== "enterprise") return failed("top", "Only an enterprise has an invoice email.");
191 const email = parseEmail(values.email);
192 if (!email.ok) return failed("invoices", email.error, values);
193 if (email.value === subject.billingEmail) return failed("invoices", "That is already where its invoices go.", values);
194 if (!confirmed) {
195 return {
196 review: { intent, name: subject.name, before: subject.billingEmail, after: email.value, fields: { email: email.value } },
197 } satisfies ActionData;
198 }
199 const result = await admin.enterpriseBilling(subject.accountId, email.value, staff.email);
200 if (!result.ok) return failed("invoices", result.error.message, values);
201 return back("billing-email");
202 }
203
Merge Stripe Tax, the card fee on card payments, and one free workspace per person204 if (intent === "billing-address") {
205 // Where Stripe Tax places the enterprise: an invoice is not sent without it.
206 const values = fields(form, "line1", "line2", "city", "state", "postalCode", "country", "taxIdType", "taxId");
207 if (subject.kind !== "enterprise") return failed("top", "Only an enterprise's address is set here; a workspace's owners set their own.");
208 if (!subject.billingEmail) return failed("address", "Set where its invoices go first: that makes its Stripe customer.", values);
209 const country = values.country.trim().toUpperCase();
210 if (!/^[A-Z]{2}$/.test(country)) return failed("address", "The country is two letters, such as US or DE.", values);
211 if (country === "US" && !values.postalCode.trim()) return failed("address", "In the US, Stripe Tax needs the ZIP code.", values);
212 const taxIdType = values.taxIdType.trim();
213 const taxId = values.taxId.trim();
214 if (Boolean(taxIdType) !== Boolean(taxId)) return failed("address", "Give the tax ID's kind and its number together, or neither.", values);
215 const address = {
216 line1: values.line1.trim(),
217 line2: values.line2.trim(),
218 city: values.city.trim(),
219 state: values.state.trim(),
220 postalCode: values.postalCode.trim(),
221 country,
222 };
223 const result = await admin.enterpriseAddress(subject.accountId, address, taxIdType || null, taxId || null, staff.email);
224 if (!result.ok) return failed("address", result.error.message, values);
225 return back("billing-address");
226 }
227
Costs: a statement that keeps usage sold, running g1t, subscriptions and what was given away (comped, free use, trial, pool) apart, and says who was paid; free use carries its own cost; the run button says it is running228 if (intent === "invoice") {
229 if (subject.kind !== "enterprise") return failed("top", "Only an enterprise is invoiced from sudo.");
230 if (!confirmed) {
231 return {
232 review: { intent, name: subject.name, email: subject.billingEmail, workspaces: subject.workspaces.length, fields: {} },
233 } satisfies ActionData;
234 }
235 const result = await admin.invoiceEnterprise(subject.accountId, staff.email);
236 if (!result.ok) return failed("invoices", result.error.message);
237 // Shown in this response; the invoice is also in the list from now on.
238 return { invoice: result.value } satisfies ActionData;
239 }
240
241 return failed("top", "Unknown action.");
242}
243
244/** The workspace's goodwill quote from the Overages queue, if it is in it, and the cap on real cost. */
245export async function goodwillQuote(
246 workspace: string,
247): Promise<{ goodwill: { creditMicros: number; marginMicros: number }; lastGoodwillAt: string | null; cap: number } | null> {
248 const [queue, book] = await Promise.all([admin.overages().catch(() => []), priceBook().catch(() => null)]);
249 const row = queue.find((overage) => overage.workspace === workspace);
250 const cap = book?.free?.overageForgiveCostMicros || FORGIVE_COST_MICROS;
251 return row ? { goodwill: row.goodwill, lastGoodwillAt: row.lastGoodwillAt, cap } : null;
252}

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