From 9c9705509297c6054cef2482a22d34892dab572e Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Mon, 18 May 2026 20:18:09 -0400 Subject: [PATCH 1/4] feat(cli): add integrations list/get/add commands (stackwright-als, fixes #238) --- .beads/issues.jsonl | 12 +- packages/cli/src/cli.ts | 2 + packages/cli/src/commands/integration.ts | 194 +++++++++++++++++++++++ packages/cli/src/index.ts | 7 + 4 files changed, 209 insertions(+), 6 deletions(-) create mode 100644 packages/cli/src/commands/integration.ts diff --git a/.beads/issues.jsonl b/.beads/issues.jsonl index 80ba22fb..ae3edbf1 100644 --- a/.beads/issues.jsonl +++ b/.beads/issues.jsonl @@ -1,15 +1,15 @@ {"_type":"issue","id":"stackwright-a1g","title":"feat: Otter Agents AI-assisted site generation pipeline — complete remaining work","description":"Four-otter pipeline (Foreman, Brand, Theme, Page) core architecture is complete. Remaining work: end-to-end testing of full pipeline, create 3-5 example sites generated by the otter raft, refine handoff protocol between otters, design Collection Otter for Phase 2. Affects packages/otters. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/236","status":"open","priority":1,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:48Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:48Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-ufs","title":"feat(types,core): migrate to explicit type field discrimination in content renderer","description":"Content renderer currently uses Object.entries(item)[0] to discriminate content types — relies on JS object insertion order (not guaranteed), prevents TypeScript discriminated unions, produces poor error messages. Migrate to explicit type field on every content item (z.object({ type: z.literal('...'), ... })). Breaking change — coordinate with next major version bump. Acceptance: update Zod schemas, content renderer, TypeScript unions, all YAML files, tests, JSON schemas, AGENTS.md tables. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/344","status":"open","priority":1,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:44Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:44Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-ufs","title":"feat(types,core): migrate to explicit type field discrimination in content renderer","description":"Content renderer currently uses Object.entries(item)[0] to discriminate content types — relies on JS object insertion order (not guaranteed), prevents TypeScript discriminated unions, produces poor error messages. Migrate to explicit type field on every content item (z.object({ type: z.literal('...'), ... })). Breaking change — coordinate with next major version bump. Acceptance: update Zod schemas, content renderer, TypeScript unions, all YAML files, tests, JSON schemas, AGENTS.md tables. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/344","status":"closed","priority":1,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:44Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:10:14Z","closed_at":"2026-05-19T00:10:14Z","close_reason":"Already implemented: contentRenderer.tsx uses item.type for discrimination, content.ts uses z.literal('type') on all schemas. Shipped before this triage run.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-8je","title":"a11y: dark mode #FCC03E amber text on near-white backgrounds fails WCAG AA","description":"Dark theme uses #FCC03E (amber/yellow) for headings and sidebar links but darkColors resolves to near-white backgrounds (#fdfdfd, #f5f5f5, #f6f6f6), producing contrast ratios of 1.51–1.61. WCAG AA requires 4.5:1. Fix: darken darkColors backgrounds to ~#1a1a1a/#2a2a2a. Affects @stackwright/themes dark color configuration. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/439","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:07Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:34:07Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-6hl","title":"a11y: code_block tabindex=0 on \u003cpre\u003e creates keyboard trap (WCAG 2.1.2)","description":"code_block component adds tabindex=0 to \u003cpre\u003e elements making them focusable, but Tab cannot escape once focus enters. WCAG 2.1 SC 2.1.2 No Keyboard Trap violation. Fix: remove tabindex or add keyboard escape mechanism. Affects Getting Started, CLI Reference, and Otter Raft pages. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/440","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:02Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:34:02Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-mca","title":"a11y: skip link present but does not move focus to main content","description":"Skip link is in DOM but clicking/activating it does not move focus to main content area. Fix: add id='main-content' to \u003cmain\u003e wrapper in page layout and ensure skip link href points to #main-content. Affects @stackwright/core page layout. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/441","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:57Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-6hl","title":"a11y: code_block tabindex=0 on \u003cpre\u003e creates keyboard trap (WCAG 2.1.2)","description":"code_block component adds tabindex=0 to \u003cpre\u003e elements making them focusable, but Tab cannot escape once focus enters. WCAG 2.1 SC 2.1.2 No Keyboard Trap violation. Fix: remove tabindex or add keyboard escape mechanism. Affects Getting Started, CLI Reference, and Otter Raft pages. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/440","status":"in_progress","priority":2,"issue_type":"bug","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:02Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:15:57Z","started_at":"2026-05-19T00:15:57Z","dependency_count":0,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-mca","title":"a11y: skip link present but does not move focus to main content","description":"Skip link is in DOM but clicking/activating it does not move focus to main content area. Fix: add id='main-content' to \u003cmain\u003e wrapper in page layout and ensure skip link href points to #main-content. Affects @stackwright/core page layout. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/441","status":"closed","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:57Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:10:25Z","closed_at":"2026-05-19T00:10:25Z","close_reason":"Already fixed: PageLayout.tsx line 103 has href='#main-content' on skip link, line 173 has id='main-content' on \u003cmain\u003e element. Correct implementation already shipped.","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-a45","title":"feat(cli): stackwright test:a11y — portable WCAG accessibility audit for any Stackwright site","description":"Add stackwright test:a11y CLI command. Auto-discovers pages from prebuild output, tests WCAG 2.1 AA via axe-core + Playwright, tests both light/dark modes. Phased implementation: (1) page discovery utility, (2) portable axe runner, (3) CLI command, (4) launch-stackwright integration, (5) MCP/Otter integration. High-value differentiator for enterprise/regulated-environment angle. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/270","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:54Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:54Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-u4y","title":"a11y: tabbed_content component missing ARIA arrow-key keyboard pattern","description":"The tabbed_content component does not respond to ArrowLeft/ArrowRight keys to switch tabs, violating ARIA APG Tabs pattern. Only mouse/click works. Fix: add keydown handler on each tab button to move focus and activate prev/next tab on arrow keys. Affects @stackwright/ui-shadcn or @stackwright/core tabs component. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/442","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:53Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:53Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-als","title":"feat(cli): add CLI commands for integration management (list/get/add)","description":"Add stackwright integrations list/get/add CLI commands with interactive prompts (inquirer) and colored output (chalk). Depends on integration schema. Estimated 2-3 hours. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/238","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:51Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:51Z","dependencies":[{"issue_id":"stackwright-als","depends_on_id":"stackwright-5ak","type":"blocks","created_at":"2026-05-18T18:34:27Z","created_by":"Stackwright Bot","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-als","title":"feat(cli): add CLI commands for integration management (list/get/add)","description":"Add stackwright integrations list/get/add CLI commands with interactive prompts (inquirer) and colored output (chalk). Depends on integration schema. Estimated 2-3 hours. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/238","status":"in_progress","priority":2,"issue_type":"feature","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:51Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:16:44Z","started_at":"2026-05-19T00:16:44Z","dependencies":[{"issue_id":"stackwright-als","depends_on_id":"stackwright-5ak","type":"blocks","created_at":"2026-05-18T18:34:27Z","created_by":"Stackwright Bot","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-y6m","title":"a11y: Content Types page missing \u003ctitle\u003e element and lang attribute on \u003chtml\u003e","description":"The /content-types page is missing a non-empty \u003ctitle\u003e element and the lang attribute on \u003chtml\u003e. Both are WCAG 2.1 Level A requirements (SC 2.4.2 Page Titled, SC 3.1.1 Language of Page). Fix: ensure page \u003chead\u003e includes \u003ctitle\u003e and \u003chtml\u003e has lang='en'. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/443","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:49Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:49Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-2o8","title":"feat(mcp): add MCP tools for integration management (list/get/add)","description":"Add stackwright_list_integrations, stackwright_get_integration, stackwright_add_integration MCP tools. Reads/writes stackwright.yml. Depends on integrations config schema issue. Estimated 3-4 hours. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/239","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:47Z","dependencies":[{"issue_id":"stackwright-2o8","depends_on_id":"stackwright-5ak","type":"blocks","created_at":"2026-05-18T18:34:26Z","created_by":"Stackwright Bot","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} +{"_type":"issue","id":"stackwright-2o8","title":"feat(mcp): add MCP tools for integration management (list/get/add)","description":"Add stackwright_list_integrations, stackwright_get_integration, stackwright_add_integration MCP tools. Reads/writes stackwright.yml. Depends on integrations config schema issue. Estimated 3-4 hours. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/239","status":"in_progress","priority":2,"issue_type":"feature","assignee":"Stackwright Bot","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:47Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:16:44Z","started_at":"2026-05-19T00:16:44Z","dependencies":[{"issue_id":"stackwright-2o8","depends_on_id":"stackwright-5ak","type":"blocks","created_at":"2026-05-18T18:34:26Z","created_by":"Stackwright Bot","metadata":"{}"}],"dependency_count":1,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-5y5","title":"a11y: carousel white text on amber background fails WCAG AA (2.14:1 contrast)","description":"Carousel/feature cards use color:#FFFFFF on background:#f59e0b (amber), producing 2.14:1 contrast ratio. WCAG AA requires 4.5:1 normal / 3:1 large text. Fix: darken background, change text color, or adjust card background YAML field. Affects @stackwright/core carousel component and the hellostackwright showcase page. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/444","status":"open","priority":2,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:44Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:44Z","dependency_count":0,"dependent_count":0,"comment_count":0} -{"_type":"issue","id":"stackwright-5ak","title":"feat(types): add integrations config field to siteConfigSchema","description":"Add integrations field to siteConfigSchema in @stackwright/types. Schema accepts array of integration objects with type (openapi|graphql|rest), name, and passthrough additional properties. Estimated 1-2 hours. This is a prerequisite for the MCP and CLI integration management tools. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/240","status":"open","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:43Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:33:43Z","dependency_count":0,"dependent_count":2,"comment_count":0} +{"_type":"issue","id":"stackwright-5ak","title":"feat(types): add integrations config field to siteConfigSchema","description":"Add integrations field to siteConfigSchema in @stackwright/types. Schema accepts array of integration objects with type (openapi|graphql|rest), name, and passthrough additional properties. Estimated 1-2 hours. This is a prerequisite for the MCP and CLI integration management tools. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/240","status":"closed","priority":2,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:33:43Z","created_by":"Stackwright Bot","updated_at":"2026-05-19T00:10:14Z","closed_at":"2026-05-19T00:10:14Z","close_reason":"Already implemented: integrationConfigSchema fully built in siteConfig.ts with openapi|graphql|rest enum, name kebab-case validation, path traversal protection, and integrations field in siteConfigSchema. Unblocks stackwright-2o8 and stackwright-als.","dependency_count":0,"dependent_count":2,"comment_count":0} {"_type":"issue","id":"stackwright-34q","title":"a11y: TopAppBar renders empty \u003ch1\u003e when title empty and logo present","description":"When title is empty and wordmark logo is used, TopAppBar renders \u003ch1\u003e\u003c/h1\u003e. Screen readers skip it and rely solely on logo alt text. Fix: add visually-hidden \u003ch1\u003e (not display:none, but zero-size off-screen) to give screen reader users the site title. Affects @stackwright/core TopAppBar component. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/438","status":"open","priority":3,"issue_type":"bug","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:12Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:34:12Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-bls","title":"feat(collections): implement CollectionProvider interface, FileCollectionProvider, and S3CollectionProvider","description":"Full Collections system. Architecture confirmed (Option A): content/\u003cname\u003e/ dirs compiled to public/stackwright-content/collections/. Implement CollectionProvider interface + FileCollectionProvider + S3CollectionProvider (peer dep @aws-sdk/client-s3). New @stackwright/collections package. Full acceptance criteria: prebuild pipeline, type generation, scaffold template update, example posts/ collection. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/85","status":"open","priority":3,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:06Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:34:06Z","dependency_count":0,"dependent_count":0,"comment_count":0} {"_type":"issue","id":"stackwright-614","title":"feat: internationalization — multi-language content support","description":"No i18n support currently. Design needed: per-locale content dirs vs inline locale maps in YAML. Locale-aware routing. Longer-term — design must be scoped before implementation begins. GitHub: https://github.com/Per-Aspera-LLC/stackwright/issues/112","status":"open","priority":3,"issue_type":"feature","owner":"bot@per-aspera.dev","created_at":"2026-05-18T22:34:00Z","created_by":"Stackwright Bot","updated_at":"2026-05-18T22:34:00Z","dependency_count":0,"dependent_count":0,"comment_count":0} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index c241cc74..8c4cab86 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -12,6 +12,7 @@ import { registerGenerateAgentDocs } from './commands/generate-agent-docs'; import { registerGitOps } from './commands/git-ops'; import { registerBoard } from './commands/board'; import { registerCollection } from './commands/collection'; +import { registerIntegration } from './commands/integration'; import { registerCompose } from './commands/compose'; import { registerPreview } from './commands/preview'; import { registerSBOM } from './commands/sbom'; @@ -39,6 +40,7 @@ async function main(): Promise { registerGitOps(program); registerBoard(program); registerCollection(program); + registerIntegration(program); registerCompose(program); registerPreview(program); registerSBOM(program); diff --git a/packages/cli/src/commands/integration.ts b/packages/cli/src/commands/integration.ts new file mode 100644 index 00000000..f4225d0c --- /dev/null +++ b/packages/cli/src/commands/integration.ts @@ -0,0 +1,194 @@ +import { Command } from 'commander'; +import path from 'path'; +import fs from 'fs-extra'; +import chalk from 'chalk'; +import yaml from 'js-yaml'; +import { outputResult, outputError, formatError } from '../utils/json-output'; +import { readSiteConfig, writeSiteConfig } from './site'; + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +export interface IntegrationEntry { + type: 'openapi' | 'graphql' | 'rest'; + name: string; + [key: string]: unknown; +} + +export interface ListIntegrationsResult { + integrations: IntegrationEntry[]; + path: string; +} + +export interface GetIntegrationResult { + integration: IntegrationEntry | null; + path: string; +} + +export interface AddIntegrationResult { + path: string; + created: boolean; + updated: boolean; +} + +// --------------------------------------------------------------------------- +// Pure functions (exported for programmatic use and MCP) +// --------------------------------------------------------------------------- + +export function listIntegrations(siteConfigPath: string): ListIntegrationsResult { + const { content, path: resolvedPath } = readSiteConfig(siteConfigPath); + const raw = yaml.load(content) as Record; + const integrations = (raw?.integrations as IntegrationEntry[] | undefined) ?? []; + return { integrations, path: resolvedPath }; +} + +export function getIntegration(siteConfigPath: string, name: string): GetIntegrationResult { + const { integrations, path: resolvedPath } = listIntegrations(siteConfigPath); + const integration = integrations.find((i) => i.name === name) ?? null; + return { integration, path: resolvedPath }; +} + +export function addIntegration( + siteConfigPath: string, + entry: IntegrationEntry +): AddIntegrationResult { + const { content, path: resolvedPath } = readSiteConfig(siteConfigPath); + const raw = yaml.load(content) as Record; + const integrations = (raw?.integrations as IntegrationEntry[] | undefined) ?? []; + + const existingIdx = integrations.findIndex((i) => i.name === entry.name); + const updated = existingIdx >= 0; + if (updated) { + integrations[existingIdx] = entry; + } else { + integrations.push(entry); + } + + raw.integrations = integrations; + const newContent = yaml.dump(raw, { lineWidth: 120 }); + writeSiteConfig(resolvedPath, newContent); + return { path: resolvedPath, created: !updated, updated }; +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +function resolveSiteConfig(projectRoot: string): string { + const candidates = ['stackwright.yml', 'stackwright.yaml']; + for (const name of candidates) { + const p = path.join(projectRoot, name); + if (fs.existsSync(p)) return p; + } + return path.join(projectRoot, 'stackwright.yml'); +} + +// --------------------------------------------------------------------------- +// Commander registration +// --------------------------------------------------------------------------- + +export function registerIntegration(program: Command): void { + const integration = program + .command('integrations') + .description('Manage Stackwright integrations (OpenAPI, GraphQL, REST)'); + + integration + .command('list') + .description('List all configured integrations') + .option('--project-root ', 'Path to project root', process.cwd()) + .option('--json', 'Output as JSON') + .action((opts: { projectRoot: string; json?: boolean }) => { + const json = Boolean(opts.json); + try { + const siteConfigPath = resolveSiteConfig(opts.projectRoot); + const result = listIntegrations(siteConfigPath); + outputResult(result, { json }, () => { + if (result.integrations.length === 0) { + console.log(chalk.dim('No integrations configured.')); + return; + } + console.log(chalk.bold(`Integrations (${result.integrations.length}):`)); + for (const i of result.integrations) { + console.log(` ${chalk.cyan(i.name)} ${chalk.dim(`[${i.type}]`)}`); + } + }); + } catch (err) { + outputError(formatError(err), 'LIST_INTEGRATIONS_FAILED', { json }); + } + }); + + integration + .command('get ') + .description('Show details for a specific integration') + .option('--project-root ', 'Path to project root', process.cwd()) + .option('--json', 'Output as JSON') + .action((name: string, opts: { projectRoot: string; json?: boolean }) => { + const json = Boolean(opts.json); + try { + const siteConfigPath = resolveSiteConfig(opts.projectRoot); + const result = getIntegration(siteConfigPath, name); + if (!result.integration) { + outputError(`Integration "${name}" not found.`, 'NOT_FOUND', { json }); + } + outputResult(result.integration, { json }, () => { + console.log(chalk.bold(`Integration: ${result.integration!.name}`)); + console.log(yaml.dump(result.integration, { indent: 2 })); + }); + } catch (err) { + outputError(formatError(err), 'GET_INTEGRATION_FAILED', { json }); + } + }); + + integration + .command('add') + .description('Add or update an integration in stackwright.yml') + .requiredOption('--name ', 'Integration name (kebab-case)') + .requiredOption('--type ', 'Integration type: openapi, graphql, or rest') + .option('--spec ', 'Path to OpenAPI spec file (for openapi type)') + .option('--endpoint ', 'API endpoint URL (for graphql/rest type)') + .option('--project-root ', 'Path to project root', process.cwd()) + .option('--json', 'Output as JSON') + .action( + (opts: { + name: string; + type: string; + spec?: string; + endpoint?: string; + projectRoot: string; + json?: boolean; + }) => { + const json = Boolean(opts.json); + const { type } = opts; + if (!['openapi', 'graphql', 'rest'].includes(type)) { + outputError(`Invalid type "${type}". Must be: openapi, graphql, rest`, 'INVALID_TYPE', { + json, + }); + } + try { + const entry: IntegrationEntry = { + type: type as 'openapi' | 'graphql' | 'rest', + name: opts.name, + ...(opts.spec ? { spec: opts.spec } : {}), + ...(opts.endpoint ? { endpoint: opts.endpoint } : {}), + }; + const siteConfigPath = resolveSiteConfig(opts.projectRoot); + const result = addIntegration(siteConfigPath, entry); + const verb = result.updated ? 'Updated' : 'Added'; + outputResult({ verb, path: result.path, integration: entry }, { json }, () => { + console.log( + chalk.green(`✓ ${verb} integration "${entry.name}" [${entry.type}] in ${result.path}`) + ); + }); + } catch (err) { + const code = (err as NodeJS.ErrnoException).code; + outputError( + formatError(err), + code === 'VALIDATION_FAILED' ? 'VALIDATION_FAILED' : 'ADD_INTEGRATION_FAILED', + { json }, + 2 + ); + } + } + ); +} diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index a83594f1..e07dfcf4 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -17,6 +17,7 @@ export { generateAgentDocs } from './commands/generate-agent-docs'; export { stageChanges, openPr } from './commands/git-ops'; export { getBoard, parseBoard } from './commands/board'; export { listCollections, addCollection, resolveContentDir } from './commands/collection'; +export { listIntegrations, getIntegration, addIntegration } from './commands/integration'; export { composeSite } from './commands/compose'; export { preview } from './commands/preview'; export { validateSiteComposition } from './utils/site-validator'; @@ -55,6 +56,12 @@ export type { CollectionListResult, AddCollectionResult, } from './commands/collection'; +export type { + IntegrationEntry, + ListIntegrationsResult, + GetIntegrationResult, + AddIntegrationResult, +} from './commands/integration'; export type { ComposeSiteResult, ComposeSiteOptions } from './commands/compose'; export type { PreviewResult, PreviewOptions } from './commands/preview'; export type { From 507985511ed75544d7f9f34bc9543f09337f243a Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Mon, 18 May 2026 20:18:16 -0400 Subject: [PATCH 2/4] feat(mcp): add stackwright_list/get/add_integration MCP tools (stackwright-2o8, fixes #239) --- packages/mcp/src/server.ts | 2 + packages/mcp/src/tools/integrations.ts | 120 +++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 packages/mcp/src/tools/integrations.ts diff --git a/packages/mcp/src/server.ts b/packages/mcp/src/server.ts index 32ccbe15..1bf343d6 100644 --- a/packages/mcp/src/server.ts +++ b/packages/mcp/src/server.ts @@ -7,6 +7,7 @@ import { registerProjectTools } from './tools/project.js'; import { registerGitOpsTools } from './tools/git-ops.js'; import { registerBoardTools } from './tools/board.js'; import { registerCollectionTools } from './tools/collections.js'; +import { registerIntegrationTools } from './tools/integrations.js'; import { registerComposeTools } from './tools/compose.js'; import { registerRenderTools, closeBrowser } from './tools/render.js'; import { version } from '../package.json'; @@ -23,6 +24,7 @@ registerProjectTools(server); registerGitOpsTools(server); registerBoardTools(server); registerCollectionTools(server); +registerIntegrationTools(server); registerComposeTools(server); registerRenderTools(server); diff --git a/packages/mcp/src/tools/integrations.ts b/packages/mcp/src/tools/integrations.ts new file mode 100644 index 00000000..11759134 --- /dev/null +++ b/packages/mcp/src/tools/integrations.ts @@ -0,0 +1,120 @@ +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { z } from 'zod'; +import path from 'path'; +import fs from 'fs'; +import { listIntegrations, getIntegration, addIntegration } from '@stackwright/cli'; + +function resolveSiteConfig(projectRoot: string): string { + const candidates = ['stackwright.yml', 'stackwright.yaml']; + for (const name of candidates) { + const p = path.join(projectRoot, name); + if (fs.existsSync(p)) return p; + } + return path.join(projectRoot, 'stackwright.yml'); +} + +export function registerIntegrationTools(server: McpServer): void { + server.tool( + 'stackwright_list_integrations', + 'List all integrations configured in stackwright.yml (OpenAPI, GraphQL, REST).', + { + projectRoot: z.string().describe('Absolute path to the root of the Stackwright project'), + }, + async ({ projectRoot }) => { + try { + const siteConfigPath = resolveSiteConfig(projectRoot); + const result = listIntegrations(siteConfigPath); + if (result.integrations.length === 0) { + return { content: [{ type: 'text', text: 'No integrations configured.' }] }; + } + const lines = result.integrations.map((i) => { + const specPart = i.spec ? ` — spec: ${String(i.spec)}` : ''; + const endpointPart = i.endpoint ? ` — endpoint: ${String(i.endpoint)}` : ''; + return ` ${i.name} [${i.type}]${specPart}${endpointPart}`; + }); + return { + content: [ + { + type: 'text', + text: `Integrations (${result.integrations.length}):\n${lines.join('\n')}`, + }, + ], + }; + } catch (err) { + return { + content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], + isError: true, + }; + } + } + ); + + server.tool( + 'stackwright_get_integration', + 'Get details for a specific integration by name from stackwright.yml.', + { + projectRoot: z.string().describe('Absolute path to the root of the Stackwright project'), + name: z.string().describe('The integration name (e.g. "logistics", "inventory")'), + }, + async ({ projectRoot, name }) => { + try { + const siteConfigPath = resolveSiteConfig(projectRoot); + const result = getIntegration(siteConfigPath, name); + if (!result.integration) { + return { + content: [{ type: 'text', text: `Integration "${name}" not found.` }], + isError: true, + }; + } + return { + content: [{ type: 'text', text: JSON.stringify(result.integration, null, 2) }], + }; + } catch (err) { + return { + content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], + isError: true, + }; + } + } + ); + + server.tool( + 'stackwright_add_integration', + 'Add or update an integration in stackwright.yml. Supports OpenAPI, GraphQL, and REST integrations.', + { + projectRoot: z.string().describe('Absolute path to the root of the Stackwright project'), + name: z + .string() + .min(1) + .max(50) + .regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/, 'Name must be kebab-case') + .describe('Unique integration name (kebab-case, e.g. "logistics-api")'), + type: z.enum(['openapi', 'graphql', 'rest']).describe('Integration type'), + spec: z.string().optional().describe('Path to OpenAPI spec file (for openapi type)'), + endpoint: z.string().optional().describe('API endpoint URL (for graphql/rest type)'), + }, + async ({ projectRoot, name, type, spec, endpoint }) => { + try { + const siteConfigPath = resolveSiteConfig(projectRoot); + const entry = { + type, + name, + ...(spec ? { spec } : {}), + ...(endpoint ? { endpoint } : {}), + }; + const result = addIntegration(siteConfigPath, entry); + const verb = result.updated ? 'Updated' : 'Added'; + return { + content: [ + { type: 'text', text: `✓ ${verb} integration "${name}" [${type}] in ${result.path}` }, + ], + }; + } catch (err) { + return { + content: [{ type: 'text', text: `Error: ${(err as Error).message}` }], + isError: true, + }; + } + } + ); +} From ba6b73a56916da3a3791583a03aa5d86e2e489a4 Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Mon, 18 May 2026 20:18:28 -0400 Subject: [PATCH 3/4] chore: add changeset for integration management feature --- .changeset/feat-integration-management.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/feat-integration-management.md diff --git a/.changeset/feat-integration-management.md b/.changeset/feat-integration-management.md new file mode 100644 index 00000000..04e8e595 --- /dev/null +++ b/.changeset/feat-integration-management.md @@ -0,0 +1,6 @@ +--- +"@stackwright/cli": minor +"@stackwright/mcp": minor +--- + +Add integration management commands and MCP tools: `stackwright integrations list/get/add` CLI commands and `stackwright_list_integrations`, `stackwright_get_integration`, `stackwright_add_integration` MCP tools for managing OpenAPI, GraphQL, and REST integrations in stackwright.yml. From 512f306ef83497f36b0a0177ed49c070537b8541 Mon Sep 17 00:00:00 2001 From: Stackwright Bot Date: Mon, 18 May 2026 20:18:47 -0400 Subject: [PATCH 4/4] fix(core): remove keyboard trap tabIndex from code_block
 (stackwright-6hl, fixes #440)

---
 packages/core/src/components/base/CodeBlock.tsx | 1 -
 1 file changed, 1 deletion(-)

diff --git a/packages/core/src/components/base/CodeBlock.tsx b/packages/core/src/components/base/CodeBlock.tsx
index c288021b..6c6b7dbd 100644
--- a/packages/core/src/components/base/CodeBlock.tsx
+++ b/packages/core/src/components/base/CodeBlock.tsx
@@ -69,7 +69,6 @@ export function CodeBlock({ code, language, lineNumbers = false, background }: C
           
         )}