#!/usr/bin/env node import fs from 'node:fs/promises'; import path from 'node:path'; import { plazaOpenApiExtensions } from './plaza-openapi.mjs'; const OUTPUT_PATH = path.join(import.meta.dirname, '..', 'openapi.json'); const plazaOpenApi = plazaOpenApiExtensions(); const jsonContent = { openapi: '3.0.3', info: { title: 'MindSpace H5 API', version: '0.1.0', description: 'Generated OpenAPI contract for MindSpace H5, Plaza, and public publication routes.', }, servers: [ { url: '/api', description: 'Authenticated H5 API' }, { url: '/', description: 'Public publication and homepage routes' }, ], tags: [ { name: 'Auth' }, { name: 'Space' }, { name: 'Uploads' }, { name: 'Assets' }, { name: 'Pages' }, { name: 'Publications' }, { name: 'Agent Jobs' }, { name: 'Public Web' }, ...plazaOpenApi.tags, ], paths: { '/auth/register': { post: { tags: ['Auth'], summary: 'Register a user and initialize a MindSpace workspace', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/RegisterRequest' }, }, }, }, responses: { 200: { description: 'Registration succeeded', content: { 'application/json': { schema: { $ref: '#/components/schemas/RegisterResponseEnvelope' }, }, }, }, }, }, }, '/auth/login': { post: { tags: ['Auth'], summary: 'Login with username and password', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/LoginRequest' }, }, }, }, responses: { 200: { description: 'Authenticated session', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthStatus' }, }, }, }, }, }, }, '/auth/logout': { post: { tags: ['Auth'], summary: 'Logout current user session', responses: { 200: { description: 'Logged out', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessEnvelope' } } }, }, }, }, }, '/auth/status': { get: { tags: ['Auth'], summary: 'Inspect current auth/session status', responses: { 200: { description: 'Current auth status', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthStatus' }, }, }, }, }, }, }, '/mindspace/v1/space': { get: { tags: ['Space'], summary: 'Get current MindSpace summary, quota, and categories', responses: { 200: { description: 'Current space', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' }, }, }, }, }, }, }, '/mindspace/v1/space/quota': { get: { tags: ['Space'], summary: 'Get current quota state', responses: { 200: { description: 'Quota state', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/space/categories': { get: { tags: ['Space'], summary: 'List current space categories', responses: { 200: { description: 'Category list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ListEnvelope' } } }, }, }, }, }, '/mindspace/v1/space/cleanup': { get: { tags: ['Space'], summary: 'Preview cleanup candidates', responses: { 200: { description: 'Cleanup candidates', content: { 'application/json': { schema: { $ref: '#/components/schemas/ListEnvelope' } } }, }, }, }, post: { tags: ['Space'], summary: 'Delete selected cleanup candidates', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CleanupRequest' }, }, }, }, responses: { 200: { description: 'Cleanup result', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/uploads': { post: { tags: ['Uploads'], summary: 'Create an upload session', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CreateUploadRequest' }, }, }, }, responses: { 201: { description: 'Upload session created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/uploads/{uploadId}/content': { put: { tags: ['Uploads'], summary: 'Upload raw file bytes', parameters: [{ $ref: '#/components/parameters/UploadId' }], requestBody: { required: true, content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' }, }, }, }, responses: { 200: { description: 'Bytes written', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/uploads/{uploadId}/complete': { post: { tags: ['Uploads'], summary: 'Complete an upload and materialize the asset', parameters: [{ $ref: '#/components/parameters/UploadId' }], responses: { 201: { description: 'Asset created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/uploads/{uploadId}': { delete: { tags: ['Uploads'], summary: 'Cancel upload session', parameters: [{ $ref: '#/components/parameters/UploadId' }], responses: { 200: { description: 'Upload cancelled', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/assets': { get: { tags: ['Assets'], summary: 'List assets', parameters: [ { name: 'category_id', in: 'query', schema: { type: 'string' } }, { name: 'category_code', in: 'query', schema: { type: 'string' } }, ], responses: { 200: { description: 'Asset list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ListEnvelope' } } }, }, }, }, }, '/mindspace/v1/assets/{assetId}/download': { get: { tags: ['Assets'], summary: 'Download asset bytes', parameters: [{ $ref: '#/components/parameters/AssetId' }], responses: { 200: { description: 'Asset content', content: { '*/*': { schema: { type: 'string', format: 'binary' } } }, }, }, }, }, '/mindspace/v1/assets/{assetId}/thumbnail': { get: { tags: ['Assets'], summary: 'Render asset thumbnail', parameters: [{ $ref: '#/components/parameters/AssetId' }], responses: { 200: { description: 'SVG thumbnail', content: { 'image/svg+xml': { schema: { type: 'string' } } }, }, }, }, }, '/mindspace/v1/assets/{assetId}/preview': { get: { tags: ['Assets'], summary: 'Render asset preview HTML', parameters: [{ $ref: '#/components/parameters/AssetId' }], responses: { 200: { description: 'Preview HTML', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, }, '/mindspace/v1/assets/{assetId}': { delete: { tags: ['Assets'], summary: 'Delete asset', parameters: [{ $ref: '#/components/parameters/AssetId' }], responses: { 200: { description: 'Delete result', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/analyze-chat-save': { post: { tags: ['Pages'], summary: 'Analyze whether an assistant message should be saved as page or asset', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/AnalyzeChatSaveRequest' } } }, }, responses: { 200: { description: 'Chat save analysis', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/save-from-chat': { post: { tags: ['Pages'], summary: 'Save assistant message into MindSpace', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/SaveFromChatRequest' } } }, }, responses: { 201: { description: 'Saved page or asset', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/from-asset': { post: { tags: ['Pages'], summary: 'Convert an owned asset into a draft page', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PageFromAssetRequest' } } }, }, responses: { 201: { description: 'Draft page created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages': { get: { tags: ['Pages'], summary: 'List pages', parameters: [{ name: 'status', in: 'query', schema: { type: 'string' } }], responses: { 200: { description: 'Page list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ListEnvelope' } } }, }, }, }, post: { tags: ['Pages'], summary: 'Create a draft page', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CreatePageRequest' } } }, }, responses: { 201: { description: 'Page created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}': { get: { tags: ['Pages'], summary: 'Get page detail and versions', parameters: [{ $ref: '#/components/parameters/PageId' }], responses: { 200: { description: 'Page detail', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, put: { tags: ['Pages'], summary: 'Create a new page version', parameters: [{ $ref: '#/components/parameters/PageId' }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/UpdatePageRequest' } } }, }, responses: { 200: { description: 'Updated page', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}/thumbnail': { get: { tags: ['Pages'], summary: 'Render page thumbnail', parameters: [{ $ref: '#/components/parameters/PageId' }], responses: { 200: { description: 'SVG thumbnail', content: { 'image/svg+xml': { schema: { type: 'string' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}/preview': { get: { tags: ['Pages'], summary: 'Render safe page preview HTML', parameters: [{ $ref: '#/components/parameters/PageId' }], responses: { 200: { description: 'Preview HTML', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}/publish-check': { post: { tags: ['Publications'], summary: 'Check whether page can be published', parameters: [{ $ref: '#/components/parameters/PageId' }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PublishCheckRequest' } } }, }, responses: { 200: { description: 'Publish check result', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}/redacted-copy': { post: { tags: ['Pages'], summary: 'Create a redacted copy of the current page version', parameters: [{ $ref: '#/components/parameters/PageId' }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/RedactedCopyRequest' } } }, }, responses: { 201: { description: 'Redacted page created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/pages/{pageId}/publish': { post: { tags: ['Publications'], summary: 'Publish the current page version', parameters: [{ $ref: '#/components/parameters/PageId' }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/PublishPageRequest' } } }, }, responses: { 201: { description: 'Publication created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/publications/{publicationId}/offline': { post: { tags: ['Publications'], summary: 'Offline an online publication', parameters: [{ $ref: '#/components/parameters/PublicationId' }], responses: { 200: { description: 'Publication offlined', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/publications/{publicationId}/stats': { get: { tags: ['Publications'], summary: 'Get publication statistics', parameters: [{ $ref: '#/components/parameters/PublicationId' }], responses: { 200: { description: 'Publication stats', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/agent/jobs': { get: { tags: ['Agent Jobs'], summary: 'List current user agent jobs', parameters: [{ name: 'limit', in: 'query', schema: { type: 'integer' } }], responses: { 200: { description: 'Job list', content: { 'application/json': { schema: { $ref: '#/components/schemas/ListEnvelope' } } }, }, }, }, post: { tags: ['Agent Jobs'], summary: 'Create an agent job bound to owned assets', requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CreateAgentJobRequest' } } }, }, responses: { 201: { description: 'Job created', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/agent/jobs/{jobId}': { get: { tags: ['Agent Jobs'], summary: 'Get agent job detail', parameters: [{ $ref: '#/components/parameters/JobId' }], responses: { 200: { description: 'Job detail', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/agent/jobs/{jobId}/cancel': { post: { tags: ['Agent Jobs'], summary: 'Cancel agent job', parameters: [{ $ref: '#/components/parameters/JobId' }], responses: { 200: { description: 'Cancelled job', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/agent/jobs/{jobId}/retry': { post: { tags: ['Agent Jobs'], summary: 'Retry agent job', parameters: [{ $ref: '#/components/parameters/JobId' }], responses: { 200: { description: 'Retried job', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/mindspace/v1/agent/jobs/{jobId}/run': { post: { tags: ['Agent Jobs'], summary: 'Start queued agent job execution', parameters: [{ $ref: '#/components/parameters/JobId' }], responses: { 202: { description: 'Execution started', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/internal/agent/jobs/{jobId}/claim': { post: { tags: ['Agent Jobs'], summary: 'Internal worker claims a queued job', parameters: [{ $ref: '#/components/parameters/JobId' }], security: [{ bearerAuth: [] }], responses: { 200: { description: 'Claimed job payload', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/internal/agent/jobs/{jobId}/assets/{assetId}': { get: { tags: ['Agent Jobs'], summary: 'Internal worker downloads an authorized job asset', parameters: [{ $ref: '#/components/parameters/JobId' }, { $ref: '#/components/parameters/AssetId' }], security: [{ bearerAuth: [] }], responses: { 200: { description: 'Asset content', content: { '*/*': { schema: { type: 'string', format: 'binary' } } }, }, }, }, }, '/internal/agent/jobs/{jobId}/heartbeat': { post: { tags: ['Agent Jobs'], summary: 'Internal worker posts heartbeat/progress updates', parameters: [{ $ref: '#/components/parameters/JobId' }], security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/HeartbeatRequest' } } }, }, responses: { 200: { description: 'Progress updated', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/internal/agent/jobs/{jobId}/complete': { post: { tags: ['Agent Jobs'], summary: 'Internal worker completes or fails a job', parameters: [{ $ref: '#/components/parameters/JobId' }], security: [{ bearerAuth: [] }], requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/CompleteJobRequest' } } }, }, responses: { 200: { description: 'Final job state', content: { 'application/json': { schema: { $ref: '#/components/schemas/DataEnvelope' } } }, }, }, }, }, '/u/{ownerSlug}': { get: { tags: ['Public Web'], summary: 'Render public homepage for a user slug', parameters: [{ $ref: '#/components/parameters/OwnerSlug' }], responses: { 200: { description: 'Public homepage HTML', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, }, '/u/{ownerSlug}/pages/{urlSlug}': { get: { tags: ['Public Web'], summary: 'Render a published page via long public route', parameters: [{ $ref: '#/components/parameters/OwnerSlug' }, { $ref: '#/components/parameters/UrlSlug' }], responses: { 200: { description: 'Published page HTML', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, post: { tags: ['Public Web'], summary: 'Submit password gate for a protected page', parameters: [{ $ref: '#/components/parameters/OwnerSlug' }, { $ref: '#/components/parameters/UrlSlug' }], requestBody: { required: true, content: { 'application/x-www-form-urlencoded': { schema: { type: 'object', required: ['password'], properties: { password: { type: 'string' } }, }, }, }, }, responses: { 200: { description: 'Published page HTML or password gate', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, }, '/s/{token}': { get: { tags: ['Public Web'], summary: 'Render a private-link publication', parameters: [{ name: 'token', in: 'path', required: true, schema: { type: 'string' } }], responses: { 200: { description: 'Published page HTML', content: { 'text/html': { schema: { type: 'string' } } }, }, }, }, }, ...plazaOpenApi.paths, }, components: { securitySchemes: { bearerAuth: { type: 'http', scheme: 'bearer', }, }, parameters: { UploadId: { name: 'uploadId', in: 'path', required: true, schema: { type: 'string' } }, AssetId: { name: 'assetId', in: 'path', required: true, schema: { type: 'string' } }, PageId: { name: 'pageId', in: 'path', required: true, schema: { type: 'string' } }, PublicationId: { name: 'publicationId', in: 'path', required: true, schema: { type: 'string' } }, JobId: { name: 'jobId', in: 'path', required: true, schema: { type: 'string' } }, OwnerSlug: { name: 'ownerSlug', in: 'path', required: true, schema: { type: 'string' } }, UrlSlug: { name: 'urlSlug', in: 'path', required: true, schema: { type: 'string' } }, ...plazaOpenApi.parameters, }, schemas: { SuccessEnvelope: { type: 'object', properties: { ok: { type: 'boolean' }, request_id: { type: 'string' }, }, }, DataEnvelope: { type: 'object', required: ['data', 'request_id'], properties: { data: { type: 'object', additionalProperties: true }, request_id: { type: 'string' }, }, }, ListEnvelope: { type: 'object', required: ['data', 'request_id'], properties: { data: { type: 'array', items: { type: 'object', additionalProperties: true } }, page: { type: 'object', properties: { next_cursor: { type: ['string', 'null'] }, has_more: { type: 'boolean' }, }, }, request_id: { type: 'string' }, }, }, AuthStatus: { type: 'object', additionalProperties: true, }, RegisterRequest: { type: 'object', required: ['username', 'password', 'displayName', 'email'], properties: { username: { type: 'string' }, password: { type: 'string' }, displayName: { type: 'string' }, email: { type: 'string' }, }, }, RegisterResponseEnvelope: { type: 'object', properties: { ok: { type: 'boolean' }, user: { type: 'object', additionalProperties: true }, }, }, LoginRequest: { type: 'object', required: ['username', 'password'], properties: { username: { type: 'string' }, password: { type: 'string' }, }, }, CleanupRequest: { type: 'object', properties: { item_ids: { type: 'array', items: { type: 'string' } }, }, }, CreateUploadRequest: { type: 'object', required: ['category_id', 'filename', 'size_bytes'], properties: { category_id: { type: 'string' }, filename: { type: 'string' }, size_bytes: { type: 'integer' }, declared_mime_type: { type: 'string' }, }, }, AnalyzeChatSaveRequest: { type: 'object', required: ['session_id', 'message_id'], properties: { session_id: { type: 'string' }, message_id: { type: 'string' }, selected_link_index: { type: 'integer' }, }, }, SaveFromChatRequest: { type: 'object', required: ['session_id', 'message_id'], properties: { session_id: { type: 'string' }, message_id: { type: 'string' }, title: { type: 'string' }, summary: { type: 'string' }, template_id: { type: 'string' }, category_code: { type: 'string' }, page_type: { type: 'string' }, selected_link_index: { type: 'integer' }, acknowledged_finding_ids: { type: 'array', items: { type: 'string' } }, }, }, PageFromAssetRequest: { type: 'object', required: ['asset_id'], properties: { asset_id: { type: 'string' }, title: { type: 'string' }, summary: { type: 'string' }, template_id: { type: 'string' }, }, }, CreatePageRequest: { type: 'object', required: ['title', 'content'], properties: { title: { type: 'string' }, summary: { type: 'string' }, content: { type: 'string' }, content_format: { type: 'string', enum: ['markdown', 'html'] }, template_id: { type: 'string' }, page_type: { type: 'string' }, category_code: { type: 'string' }, }, }, UpdatePageRequest: { type: 'object', required: ['expected_version', 'title', 'content'], properties: { expected_version: { type: 'integer' }, title: { type: 'string' }, summary: { type: 'string' }, content: { type: 'string' }, template_id: { type: 'string' }, page_type: { type: 'string' }, change_note: { type: 'string' }, }, }, PublishCheckRequest: { type: 'object', required: ['page_version_id', 'access_mode', 'url_slug'], properties: { page_version_id: { type: 'string' }, access_mode: { type: 'string' }, url_slug: { type: 'string' }, password: { type: 'string' }, expires_at: { type: 'integer' }, }, }, RedactedCopyRequest: { type: 'object', properties: { page_version_id: { type: 'string' }, }, }, PublishPageRequest: { type: 'object', required: ['page_version_id', 'access_mode', 'url_slug'], properties: { page_version_id: { type: 'string' }, access_mode: { type: 'string' }, url_slug: { type: 'string' }, password: { type: 'string' }, expires_at: { type: 'integer' }, acknowledged_finding_ids: { type: 'array', items: { type: 'string' } }, }, }, CreateAgentJobRequest: { type: 'object', required: ['job_type', 'instruction', 'allowed_asset_ids'], properties: { job_type: { type: 'string' }, instruction: { type: 'string' }, allowed_asset_ids: { type: 'array', items: { type: 'string' } }, output_category_id: { type: 'string' }, output_type: { type: 'string' }, idempotency_key: { type: 'string' }, locale: { type: 'string' }, timezone: { type: 'string' }, capabilities: { type: 'object', additionalProperties: { type: 'boolean' } }, }, }, HeartbeatRequest: { type: 'object', properties: { stage: { type: 'string' }, message: { type: 'string' }, }, }, CompleteJobRequest: { type: 'object', properties: { status: { type: 'string' }, error_code: { type: 'string' }, error_message: { type: 'string' }, output_type: { type: 'string' }, title: { type: 'string' }, summary: { type: 'string' }, content: { type: 'string' }, content_format: { type: 'string' }, page_type: { type: 'string' }, template_id: { type: 'string' }, source_asset_ids: { type: 'array', items: { type: 'string' } }, }, }, ErrorResponse: { type: 'object', properties: { error: { type: 'object', properties: { code: { type: 'string' }, message: { type: 'string' }, details: { type: 'object', additionalProperties: true }, }, }, request_id: { type: 'string' }, }, }, ...plazaOpenApi.schemas, }, }, }; async function main() { const rendered = `${JSON.stringify(jsonContent, null, 2)}\n`; if (process.argv.includes('--print')) { process.stdout.write(rendered); return; } await fs.writeFile(OUTPUT_PATH, rendered); process.stdout.write(`Generated ${OUTPUT_PATH}\n`); } await main();