Public: anyone can read this; the crew can change it.
/api/v1/here names the OpenAPI document among what an anonymous visitor can do #8368
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/8331-api-v1-here-names-the-openapi-document-a → main 2 commits fork
5 files
changed +25
−1
src/actions/here.ts +5
| @@ -306,6 +306,11 @@ function forgeActions( | ||
| 306 | 306 | mcp: "contribute", |
| 307 | 307 | account: false, |
| 308 | 308 | }, |
| 309 | { | |
| 310 | do: "read the HTTP contract", | |
| 311 | http: "GET /contract/openapi.json", | |
| 312 | account: false, | |
| 313 | }, | |
| 309 | 314 | ...(viewer === undefined |
| 310 | 315 | ? [{ do: "use the MCP", http: "POST /mcp", account: true }] |
| 311 | 316 | : [{ do: "use the MCP", http: "POST /mcp" }]), |
src/contract/openapi/routes/getContractOpenapiJson.ts added +3
| @@ -0,0 +1,3 @@ | ||
| 1 | import type { Route } from "../shared.ts"; | |
| 2 | ||
| 3 | export const route: Route = { method: "get", path: "/contract/openapi.json", summary: "This document: the OpenAPI 3.1 description of the forge's public API; /api/v1/openapi.json redirects here", auth: false }; |
src/contract/openapi/routes/index.ts +1
| @@ -172,6 +172,7 @@ export { route as getApiV1UserPins } from "./getApiV1UserPins.ts"; | ||
| 172 | 172 | export { route as getApiV1UsersUsername } from "./getApiV1UsersUsername.ts"; |
| 173 | 173 | export { route as getApiV1UsersUsernameRepos } from "./getApiV1UsersUsernameRepos.ts"; |
| 174 | 174 | export { route as getApiV1UsersUsernameSignins } from "./getApiV1UsersUsernameSignins.ts"; |
| 175 | export { route as getContractOpenapiJson } from "./getContractOpenapiJson.ts"; | |
| 175 | 176 | export { route as getMetrics } from "./getMetrics.ts"; |
| 176 | 177 | export { route as patchApiV1AdminSettings } from "./patchApiV1AdminSettings.ts"; |
| 177 | 178 | export { route as patchApiV1AgentsUsername } from "./patchApiV1AgentsUsername.ts"; |
test/here-manifest.test.ts +15
| @@ -269,6 +269,21 @@ describe("the manifest: what you can do here, as who you are", () => { | ||
| 269 | 269 | } |
| 270 | 270 | }); |
| 271 | 271 | |
| 272 | test("the forge's manifest names the OpenAPI document, and it answers", async () => { | |
| 273 | for (const who of [undefined, "carol"]) { | |
| 274 | const { body, size } = await manifest("/", who); | |
| 275 | expect(body.actions).toContainEqual({ | |
| 276 | do: "read the HTTP contract", | |
| 277 | http: "GET /contract/openapi.json", | |
| 278 | account: false, | |
| 279 | }); | |
| 280 | expect(size).toBeLessThan(2048); | |
| 281 | } | |
| 282 | const res = await fetch(`${base}/contract/openapi.json`); | |
| 283 | expect(res.status).toBe(200); | |
| 284 | expect(res.headers.get("content-type")).toContain("application/json"); | |
| 285 | }); | |
| 286 | ||
| 272 | 287 | test("/explore and /search name their query API and MCP tool, the query carried over", async () => { |
| 273 | 288 | for (const who of [undefined, "carol"]) { |
| 274 | 289 | const e = (await manifest("/explore?q=kind:app", who)).body; |
test/openapi.test.ts +1 −1
| @@ -30,7 +30,7 @@ test("every http route the manifest names is described", () => { | ||
| 30 | 30 | }); |
| 31 | 31 | |
| 32 | 32 | test("every described route is registered in the router", () => { |
| 33 | const src = ["src/http/server.ts", "src/http/api.ts", "src/http/mcp.ts", "src/http/passport_edge.ts", "src/http/feed.ts", "src/http/checks_recheck.ts", | |
| 33 | const src = ["src/http/server.ts", "src/http/api.ts", "src/http/mcp.ts", "src/http/passport_edge.ts", "src/http/feed.ts", "src/http/checks_recheck.ts", "src/contract/served.ts", | |
| 34 | 34 | ...readdirSync("src/http/api").map((f) => `src/http/api/${f}`)] |
| 35 | 35 | .map((f) => readFileSync(f, "utf8")) |
| 36 | 36 | .join("\n") |
new above
offline