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

Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily1/**
2 * Every page's <title> and the tags a link preview reads: its description,
3 * Open Graph and Twitter's card. The image is the page's own card from the
4 * og service (services/og), which draws it as an anonymous visitor would
Merge branch 'worktree-agent-aaf03bdceac799c89'5 * see the page, so a private page's card never names anything. A g1t without
6 * the og service (`OG_URL` set empty) leaves the image tags out.
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily7 *
8 * React Router renders only the deepest route's `meta`, so each route
9 * returns `page(args, …)` rather than a bare title.
10 */
11import type { MetaDescriptor } from "react-router";
12import { ogVersion } from "@g1t/contracts/og";
13
Merge branch 'worktree-agent-aaf03bdceac799c89'14import type { Addresses } from "./addresses";
15
16/**
17 * g1t.sh's site and card service, the same as HOSTED_ADDRESSES in
18 * ./addresses (kept here so this module has no imports to run in tests).
19 * Pages use the addresses in the root loader's data when it has them.
20 */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily21export const SITE = "https://g1t.sh";
22export const OG = "https://og.g1t.sh";
23
24/** What g1t is, for pages with nothing more particular to say. */
25export const DESCRIPTION =
Chat and workspace agents: channels, DMs and named agents you talk to26 "One workspace where a team and its agents talk, work and ship: chat with people and agents, agents with a job and a budget, and code that lands through checks. Open source, never per seat.";
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily27
28/** As much of `MetaArgs` as the tags need. */
29export type PageArgs = {
30 location: { pathname: string };
31 matches: ReadonlyArray<{ id: string; loaderData?: unknown } | undefined>;
32};
33
34export type PageMeta = {
35 /** The whole <title>, ending in "· g1t". */
36 title: string;
37 /** Defaults to the project's or workspace's description, then g1t's. */
38 description?: string | null;
39 /**
40 * Whatever the page's card shows, so that the card is drawn again when it
41 * changes. Defaults to the project's details on project pages.
42 */
43 version?: unknown;
44 type?: "website" | "article";
45};
46
47type ProjectData = {
48 repo?: { description?: string | null };
49 project?: { name?: string; description?: string | null; descriptionInherited?: boolean } | null;
50 open?: { issues?: number; pulls?: number };
51};
52type WorkspaceData = { workspace?: { name?: string; description?: string | null; avatar?: string | null } };
53
54function loaded<T>(args: PageArgs, id: string): T | undefined {
55 return args.matches.find((match) => match?.id === id)?.loaderData as T | undefined;
56}
57
58/** A short, stable fingerprint, for the card's `v`. */
59export function fingerprint(value: unknown): string {
60 const text = JSON.stringify(value) ?? "";
61 // FNV-1a, 32 bits.
62 let hash = 0x811c9dc5;
63 for (let i = 0; i < text.length; i++) {
64 hash ^= text.charCodeAt(i);
65 hash = Math.imul(hash, 0x01000193);
66 }
67 return (hash >>> 0).toString(36);
68}
69
70/** A description fit for a preview: one line, at most 200 characters. */
71function summary(text: string): string {
72 const line = text.replace(/\s+/g, " ").trim();
73 return line.length > 200 ? `${line.slice(0, 199).trimEnd()}…` : line;
74}
75
76/** The opening of some Markdown as plain text, for a description. */
77export function excerpt(markdown: string | null | undefined, max = 160): string {
78 const text = (markdown ?? "")
79 .replace(/```[\s\S]*?(```|$)/g, " ")
80 .replace(/<[^>]+>/g, " ")
81 .replace(/!\[[^\]]*\]\([^)]*\)/g, " ")
82 .replace(/\[([^\]]*)\]\([^)]*\)/g, "$1")
83 .replace(/^\s{0,3}(#{1,6}|>|[-*+]|\d+\.)\s+/gm, "")
84 .replace(/[`*_~|]/g, "")
85 .replace(/\s+/g, " ")
86 .trim();
87 return text.length > max ? `${text.slice(0, max - 1).trimEnd()}…` : text;
88}
89
90/**
91 * The address of a page's card. Its `v` always carries the cards' render
92 * version, so a new card design is a new address for og's cache, browsers
93 * and every site that has fetched the old card; then, when the page has one,
94 * a fingerprint of what the card shows.
95 */
Merge branch 'worktree-agent-aaf03bdceac799c89'96export function cardUrl(pathname: string, version?: unknown, og: string = OG): string {
97 const url = new URL("/image", og);
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily98 url.searchParams.set("path", pathname);
99 url.searchParams.set("v", ogVersion(version === undefined ? undefined : fingerprint(version)));
100 return url.toString();
101}
102
103/** A project's own description; null while it follows its repository's. */
104function ownDescription(project: { description?: string | null; descriptionInherited?: boolean } | null | undefined): string | null {
105 return project && !project.descriptionInherited ? (project.description ?? null) : null;
106}
107
108export function page(args: PageArgs, meta: PageMeta): MetaDescriptor[] {
109 const project = loaded<ProjectData>(args, "routes/repo/layout");
110 const workspace = loaded<WorkspaceData>(args, "routes/workspace/layout");
111 const description = summary(
112 meta.description ||
113 ownDescription(project?.project) ||
114 project?.repo?.description ||
115 workspace?.workspace?.description ||
116 DESCRIPTION,
117 );
118 const version =
119 meta.version ??
120 (project
121 ? [project.project?.name, ownDescription(project.project) ?? project.repo?.description, project.open]
122 : workspace
123 ? [
124 workspace.workspace?.name,
125 workspace.workspace?.description,
126 // Only when there is one, so cards without an icon keep their address.
127 ...(workspace.workspace?.avatar ? [workspace.workspace.avatar] : []),
128 ]
129 : undefined);
130 const pathname = args.location.pathname.replace(/\/+$/, "") || "/";
131 // The title a preview shows needs no "· g1t": the site name is beside it.
132 const shareTitle = meta.title.replace(/ · g1t$/, "");
Merge branch 'worktree-agent-aaf03bdceac799c89'133 const addresses = loaded<{ addresses?: Pick<Addresses, "site" | "og"> }>(args, "root")?.addresses;
134 const site = addresses?.site ?? SITE;
135 const og = addresses ? addresses.og : OG;
136 const image = og ? cardUrl(pathname, version, og) : null;
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily137 return [
138 { title: meta.title },
139 { name: "description", content: description },
140 { property: "og:site_name", content: "g1t" },
141 { property: "og:type", content: meta.type ?? "website" },
Merge branch 'worktree-agent-aaf03bdceac799c89'142 { property: "og:url", content: `${site}${pathname === "/" ? "/" : pathname}` },
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily143 { property: "og:title", content: shareTitle },
144 { property: "og:description", content: description },
Merge branch 'worktree-agent-aaf03bdceac799c89'145 ...(image
146 ? [
147 { property: "og:image", content: image },
148 { property: "og:image:type", content: "image/png" },
149 { property: "og:image:width", content: "1200" },
150 { property: "og:image:height", content: "630" },
151 { property: "og:image:alt", content: shareTitle },
152 ]
153 : []),
154 { name: "twitter:card", content: image ? "summary_large_image" : "summary" },
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily155 { name: "twitter:title", content: shareTitle },
156 { name: "twitter:description", content: description },
Merge branch 'worktree-agent-aaf03bdceac799c89'157 ...(image
158 ? [
159 { name: "twitter:image", content: image },
160 { name: "twitter:image:alt", content: shareTitle },
161 ]
162 : []),
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily163 ];
164}

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