g1t/apps/web/app/lib/legal.ts

128 lines4,291 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.

Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look1/**
2 * Who makes g1t, how to reach them, and the policies that govern it: the
3 * one place the footer, the policy pages, /security, /support and
4 * security.txt read these from.
5 */
6
7/** The company behind g1t. */
8export const COMPANY = {
9 name: "Flagon, Inc.",
10 url: "https://www.flagon.io",
11 /** How the footer describes it. */
12 about: "a small, independent software company",
13};
14
15/**
16 * Flagon's other launched products, which the footer's maker line names
17 * ("…, the people behind X and Y"). None yet: add each here when it ships.
18 */
19export const MAKER_PRODUCTS: { name: string; url: string }[] = [];
20
21/** "X", "X and Y", "X, Y and Z". */
22export function listed(names: string[]): string {
23 if (names.length <= 1) return names.join("");
24 return `${names.slice(0, -1).join(", ")} and ${names[names.length - 1]}`;
25}
26
27/** The year in UTC, so a page rendered near midnight on 31 December agrees everywhere. */
28export function copyrightYear(now: Date = new Date()): number {
29 return now.getUTCFullYear();
30}
31
32/** The footer's legal line. */
33export function copyright(now: Date = new Date()): string {
34 return `© ${copyrightYear(now)} ${COMPANY.name} All rights reserved.`;
35}
36
37/**
38 * Where people reach g1t. Each must be a real, watched mailbox. For now
39 * every topic goes to Flagon's one address; give a topic its own address
40 * here once the g1t.sh aliases exist.
41 */
42export const CONTACT = {
43 support: "hey@flagon.io",
44 security: "hey@flagon.io",
45 privacy: "hey@flagon.io",
46 billing: "hey@flagon.io",
47 abuse: "hey@flagon.io",
48 legal: "hey@flagon.io",
49};
50
51export type Policy = {
52 slug: string;
53 title: string;
54 summary: string;
55};
56
57/** The policies, in the order the index lists them. Each is `/policies/<slug>`. */
58export const POLICIES: Policy[] = [
59 {
60 slug: "terms",
61 title: "Terms of Service",
62 summary: "The agreement between you and Flagon, Inc. when you use g1t: accounts, your content, agents, paying, and ending.",
63 },
64 {
65 slug: "privacy",
66 title: "Privacy Policy",
67 summary: "What g1t collects, why, where it goes, how long it is kept, and how to see, export or delete it.",
68 },
69 {
70 slug: "acceptable-use",
71 title: "Acceptable Use Policy",
72 summary: "What g1t may not be used for, from mining cryptocurrency to abuse, and what happens when it is.",
73 },
74 {
75 slug: "refunds",
76 title: "Refunds and Cancellation",
77 summary: "Ending the plan, accidental overages and goodwill credits, and when money goes back to your card.",
78 },
79 {
80 slug: "subprocessors",
81 title: "Subprocessors",
82 summary: "The companies that process data for g1t, what each does, and the services you choose to connect.",
83 },
84];
85
86/** When any policy last changed, `YYYY-MM-DD`. */
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily87export const POLICIES_UPDATED = "2026-10-06";
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look88
89/** Every change to the policies, newest first. */
90export const POLICY_HISTORY: { date: string; change: string }[] = [
91 {
Git storage hardened, pages in tens of milliseconds, honest security alerts, and costs reconciled daily92 date: "2026-10-06",
93 change:
94 "Privacy Policy: request logs are kept 7 days, and database history 30 days. Subprocessors: GitHub listed among the integrations you choose, in place of Slack, which g1t does not connect to.",
95 },
96 {
Invite-only launch: sign in with GitHub, repository access and lifecycle, many emails, a new look97 date: "2026-10-05",
98 change:
99 "First published: Terms of Service, Privacy Policy, Acceptable Use Policy, Refunds and Cancellation, and Subprocessors.",
100 },
101];
102
103/** `2026-10-05` as "October 5, 2026". */
104export function longDate(day: string): string {
105 const [year, month, date] = day.split("-").map(Number);
106 const months = [
107 "January", "February", "March", "April", "May", "June",
108 "July", "August", "September", "October", "November", "December",
109 ];
110 return `${months[month - 1]} ${date}, ${year}`;
111}
112
113/**
114 * security.txt (RFC 9116). `Expires` is required and should be less than a
115 * year away, so it is always computed: 180 days from now.
116 */
117export function securityTxt(now: Date = new Date(), site = "https://g1t.sh"): string {
118 const expires = new Date(now.getTime() + 180 * 24 * 60 * 60 * 1000);
119 expires.setUTCHours(0, 0, 0, 0);
120 return [
121 `Contact: mailto:${CONTACT.security}`,
122 `Expires: ${expires.toISOString().replace(".000Z", "Z")}`,
123 "Preferred-Languages: en",
124 `Policy: ${site}/security#responsible-disclosure`,
125 `Canonical: ${site}/.well-known/security.txt`,
126 "",
127 ].join("\n");
128}