| 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] |
| 3 | import assert from "node:assert/strict"; |
| 4 | import { execFileSync } from "node:child_process"; |
| 5 | import { verifyMcp } from "./mcp-readiness.mjs"; |
| 6 | |
| 7 | let failures = 0; |
| 8 | async 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 | } |
| 17 | async function get(url, headers = {}) { |
| 18 | const response = await fetch(url, { headers, signal: AbortSignal.timeout(20_000) }); |
| 19 | return { response, body: await response.text() }; |
| 20 | } |
| 21 | const varies = (response) => |
| 22 | (response.headers.get("vary") ?? "") |
| 23 | .toLowerCase() |
| 24 | .split(",") |
| 25 | .map((v) => v.trim()) |
| 26 | .includes("accept"); |
| 27 | for (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 | } |
| 45 | await check("unsupported homepage representation", async () => |
| 46 | assert.equal((await get("https://g1t.sh/", { accept: "application/json" })).response.status, 406), |
| 47 | ); |
| 48 | await 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 | }); |
| 58 | await 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 | }); |
| 66 | await 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 | }); |
| 77 | for (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 | }); |
| 85 | await 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 | ); |
| 88 | for (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 | } |
| 107 | await check("published versioning documentation", async () => assert.match((await get("https://docs.g1t.sh/reference/api/")).body, /X-G1t-Api-Version/)); |
| 108 | await 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 | }); |
| 119 | if (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 | }); |
| 125 | process.exitCode = failures ? 1 : 0; |