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