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.

125 linesCodeBlameRaw
1// Read-only production verification. --mcp uses the existing CLI login without logging it.
2// Run: node --use-system-ca scripts/check-agent-readiness.mjs [--mcp]
3import assert from "node:assert/strict";
4import { execFileSync } from "node:child_process";
5import { verifyMcp } from "./mcp-readiness.mjs";
6
7let failures = 0;
8async function check(name, run) {
9 try {
10 await run();
11 console.log(`PASS ${name}`);
12 } catch (error) {
13 failures++;
14 console.error(`FAIL ${name}: ${error.message}`);
15 }
16}
17async function get(url, headers = {}) {
18 const response = await fetch(url, { headers, signal: AbortSignal.timeout(20_000) });
19 return { response, body: await response.text() };
20}
21const varies = (response) =>
22 (response.headers.get("vary") ?? "")
23 .toLowerCase()
24 .split(",")
25 .map((v) => v.trim())
26 .includes("accept");
27for (const [path, status] of [
28 ["/", 200],
29 ["/__agent-readiness-nonexistent", 404],
30]) {
31 for (const accept of ["text/markdown", "text/html", "text/html;q=0.2,text/markdown;q=1"]) {
32 await check(`${path} Accept: ${accept}`, async () => {
33 const { response, body } = await get(`https://g1t.sh${path}`, { accept });
34 assert.equal(response.status, status);
35 assert.ok(varies(response), "Vary must contain Accept");
36 const markdown = accept !== "text/html";
37 assert.ok(response.headers.get("content-type")?.startsWith(markdown ? "text/markdown" : "text/html"));
38 if (markdown) {
39 assert.ok(body.length > 20);
40 assert.match(body, /\[.*\]\(https:\/\/g1t.sh\/llms.txt\)/);
41 } else assert.match(body, /<html/);
42 });
43 }
44}
45await check("unsupported homepage representation", async () =>
46 assert.equal((await get("https://g1t.sh/", { accept: "application/json" })).response.status, 406),
47);
48await check("homepage metadata", async () => {
49 const { body } = await get("https://g1t.sh/", { accept: "text/html" });
50 assert.match(body, /<html[^>]*lang="en"/);
51 assert.match(body, /<link[^>]*rel="canonical"[^>]*href="https:\/\/g1t.sh\/"/);
52 assert.match(body, /property="og:image"/);
53 assert.match(body, /property="og:type"/);
54 const schemas = [...body.matchAll(/<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/g)].map((m) => JSON.parse(m[1]));
55 const organization = schemas.find((schema) => schema["@type"] === "Organization");
56 assert.equal(organization?.contactPoint?.email, "hey@flagon.io");
57});
58await check("llms.txt instructions and developer links", async () => {
59 const { response, body } = await get("https://g1t.sh/llms.txt");
60 assert.equal(response.status, 200);
61 assert.match(body, /^# g1t\s/m);
62 assert.match(body, /## (When to use g1t|Developer resources)/);
63 for (const url of ["https://api.g1t.sh/openapi.json", "https://mcp.g1t.sh/.well-known/oauth-protected-resource", "https://g1t.sh/settings/tokens"])
64 assert.ok(body.includes(url));
65});
66await check("OpenAPI scopes and unprefixed versioned API", async () => {
67 const { body, response } = await get("https://api.g1t.sh/openapi.json");
68 assert.equal(response.status, 200);
69 const spec = JSON.parse(body);
70 assert.equal(spec.servers[0].url, "https://api.g1t.sh");
71 const scopes = spec.components.securitySchemes.oauth.flows.authorizationCode.scopes;
72 const metadata = JSON.parse((await get("https://mcp.g1t.sh/.well-known/oauth-protected-resource")).body);
73 assert.deepEqual(Object.keys(scopes).sort(), metadata.scopes_supported.sort());
74 assert.equal(spec.components.parameters.ApiVersion.name, "X-G1t-Api-Version");
75 assert.ok(spec.components.responses.RateLimited.headers["Retry-After"]);
76});
77for (const path of ["/repos", "/v1/repos"])
78 await check(`REST ${path} version and limit headers`, async () => {
79 const { response, body } = await get(`https://api.g1t.sh${path}`, { "X-G1t-Api-Version": "1" });
80 assert.equal(response.status, 200);
81 assert.ok(Array.isArray(JSON.parse(body)));
82 assert.equal(response.headers.get("x-g1t-api-version"), "1");
83 assert.equal(response.headers.get("ratelimit-policy"), '"anonymous";q=60;w=60');
84 });
85await check("unsupported REST version fails", async () =>
86 assert.equal((await get("https://api.g1t.sh/repos", { "X-G1t-Api-Version": "not-a-version" })).response.status, 422),
87);
88for (const url of [
89 "https://g1t.sh/developers",
90 "https://g1t.sh/openapi.json",
91 "https://g1t.sh/mcp",
92 "https://g1t.sh/sitemap.xml",
93 "https://g1t.sh/robots.txt",
94 "https://docs.g1t.sh/reference/api/",
95 "https://docs.g1t.sh/reference/rate-limits/",
96 "https://docs.g1t.sh/guides/authentication/",
97 "https://docs.g1t.sh/guides/cli/",
98 "https://docs.g1t.sh/guides/self-hosting/",
99 "https://g1t.sh/downloads/cli/latest.json",
100]) {
101 await check(url, async () => {
102 const { response, body } = await get(url);
103 assert.equal(response.status, 200);
104 assert.ok(body.length > 20);
105 });
106}
107await check("published versioning documentation", async () => assert.match((await get("https://docs.g1t.sh/reference/api/")).body, /X-G1t-Api-Version/));
108await check("MCP still requires authorization", async () => {
109 const response = await fetch("https://mcp.g1t.sh", {
110 method: "POST",
111 headers: { "content-type": "application/json" },
112 body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }),
113 signal: AbortSignal.timeout(20_000),
114 });
115 assert.equal(response.status, 401);
116 assert.match(response.headers.get("www-authenticate") ?? "", /resource_metadata/);
117 await response.body?.cancel();
118});
119if (process.argv.includes("--mcp"))
120 await check("authorized MCP initialize and tools/list", async () => {
121 const token = execFileSync("g1t", ["auth", "token", "--hostname", "g1t.sh"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
122 const result = await verifyMcp(token);
123 console.log(`Verified ${result.tools} protected tools and ${result.actions} documented action scopes.`);
124 });
125process.exitCode = failures ? 1 : 0;