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

Merge main into rm-agents: mirroring removal, open signups, free-tier limits, the g1t CLI and runner fixes, with the agent removal kept. The CLI's table of operations is regenerated from the API without the agent, customize and AI credit operations; no CLI command used them.1/**
2 * What a workspace without the g1t plan may keep in files put in its pages
3 * and artifacts (images, attachments): `FREE_DOC_FILES_BYTES` (1 GiB, a
4 * wrangler var; `0` is no cap). Past it, a new upload is refused with a
5 * message saying so; nothing already kept is touched. Billing is asked
6 * only once the workspace is past the cap, and when it does not answer the
7 * upload goes ahead.
8 */
9import type { ServiceBinding } from "@g1t/contracts";
10
11export const DEFAULT_FREE_DOC_FILES_BYTES = 1024 * 1024 * 1024;
12
13export type QuotaEnv = { BILLING?: ServiceBinding; FREE_DOC_FILES_BYTES?: string };
14
15/** The cap from its var: the default when unset or not a whole number; 0 is none. */
16export function freeFilesCap(raw: string | undefined): number {
17 const value = Number((raw ?? "").trim());
18 return raw !== undefined && raw.trim() !== "" && Number.isInteger(value) && value >= 0 ? value : DEFAULT_FREE_DOC_FILES_BYTES;
19}
20
21/** `1 GB`, `1.5 GB`, `300 MB`. */
22export function shownBytes(bytes: number): string {
23 const gb = 1024 * 1024 * 1024;
24 if (bytes >= gb) {
25 const tenths = Math.ceil((bytes * 10) / gb);
26 return tenths % 10 === 0 ? `${tenths / 10} GB` : `${(tenths / 10).toFixed(1)} GB`;
27 }
28 return `${Math.ceil(bytes / (1024 * 1024))} MB`;
29}
30
31/** What a free workspace is told when `adding` would take it past `cap`; null while it fits. */
32export function filesRefusal(held: number, adding: number, cap: number): string | null {
33 if (cap <= 0 || held + adding <= cap) return null;
34 return `This workspace's files hold ${shownBytes(held)} of the ${shownBytes(cap)} a workspace on the free plan keeps, so this file is not kept. Delete files you no longer need, or start the g1t plan, which has no such cap.`;
35}
36
37/** Why an upload of `adding` bytes to `workspace` is refused, or null. */
38export async function uploadRefused(
39 db: D1Database,
40 env: QuotaEnv,
41 workspace: { id: string; slug: string },
42 adding: number,
43): Promise<string | null> {
44 const cap = freeFilesCap(env.FREE_DOC_FILES_BYTES);
45 if (cap <= 0) return null;
46 const row = await db
47 .prepare(
48 `SELECT (SELECT COALESCE(SUM(bytes), 0) FROM files WHERE workspace_id = ?1)
49 + (SELECT COALESCE(SUM(bytes), 0) FROM folio_files WHERE workspace_id = ?1) AS bytes`,
50 )
51 .bind(workspace.id)
52 .first<{ bytes: number }>();
53 const why = filesRefusal(row?.bytes ?? 0, adding, cap);
54 if (!why || !env.BILLING) return null;
55 try {
56 // Loaded here, so the rules above test under Node without the contracts' runtime.
57 const { billingClient } = await import("@g1t/contracts");
58 const free = await billingClient(env.BILLING).freeWorkspaces([workspace.slug]);
59 return free.map((slug) => slug.toLowerCase()).includes(workspace.slug.toLowerCase()) ? why : null;
60 } catch (error) {
61 console.error("artifacts: billing did not say whether the workspace is free", workspace.slug, error);
62 return null;
63 }
64}

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