From 754736750b26ce5c45cd21aee8ce24ccd8831dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C3=A5rten=20Angner?= Date: Thu, 19 Mar 2026 23:23:17 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Design=20Space=20installer=20=E2=80=94?= =?UTF-8?q?=20optional=20module=20with=20SQLite/Supabase=20choice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Installer now asks: 1. "Install Design Space?" (yes/no) 2. "Name your space:" (default: design-space) 3. "Connect to existing or create new?" 4. "Which database?" (SQLite local / Supabase cloud) Creates _bmad// with: - config.yaml (backend, credentials, connection info) - SKILL.md + bmad-manifest.json (copied from src/skills/design-space/) - For SQLite: server.js + package.json (lite-server) - For Supabase create: SETUP.md with deployment instructions - WDS config.yaml gets design_space reference appended Co-Authored-By: Claude Opus 4.6 (1M context) --- tools/cli/commands/install.js | 2 +- tools/cli/lib/installer.js | 116 +++++++++++++++++++++++++++++++++- tools/cli/lib/ui.js | 114 ++++++++++++++++++++++++++++++++- 3 files changed, 229 insertions(+), 3 deletions(-) diff --git a/tools/cli/commands/install.js b/tools/cli/commands/install.js index 2ed3f3653..60aac98f0 100644 --- a/tools/cli/commands/install.js +++ b/tools/cli/commands/install.js @@ -22,7 +22,7 @@ module.exports = { const result = await installer.install(config); if (result && result.success) { - ui.displaySuccess(config.wdsFolder, config.ides); + ui.displaySuccess(config.wdsFolder, config.ides, config.install_design_space, config.ds_name, config.ds_backend); process.exit(0); } } catch (error) { diff --git a/tools/cli/lib/installer.js b/tools/cli/lib/installer.js index 4bcf4fd56..d4dbac3aa 100644 --- a/tools/cli/lib/installer.js +++ b/tools/cli/lib/installer.js @@ -161,7 +161,19 @@ class Installer { throw error; } - // Step 5: Copy learning & reference material (optional) + // Step 5: Install Design Space (optional) + if (config.install_design_space) { + const dsSpinner = ora(`Setting up ${config.ds_name || 'design-space'}...`).start(); + try { + await this.installDesignSpace(projectDir, config); + dsSpinner.succeed(`${config.ds_name || 'Design Space'} configured at _bmad/${config.ds_name || 'design-space'}/`); + } catch (error) { + dsSpinner.fail('Failed to set up Design Space'); + throw error; + } + } + + // Step 6: Copy learning & reference material (optional) if (config.install_learning !== false) { const learnSpinner = ora('Copying learning & reference material...').start(); try { @@ -279,6 +291,108 @@ class Installer { } } + /** + * Install Design Space module + */ + async installDesignSpace(projectDir, config) { + const dsName = config.ds_name || 'design-space'; + const dsDir = path.join(projectDir, '_bmad', dsName); + await fs.ensureDir(dsDir); + + // Write Design Space config + const dsConfig = { + name: dsName, + backend: config.ds_backend || 'sqlite', + }; + + if (config.ds_backend === 'supabase') { + if (config.ds_mode === 'connect') { + dsConfig.supabase_url = config.ds_url; + dsConfig.supabase_anon_key = config.ds_key; + } else { + dsConfig.supabase_url = '# Run: npx supabase init && npx supabase start'; + dsConfig.supabase_anon_key = '# Get from Supabase dashboard → Settings → API'; + } + dsConfig.base_url = config.ds_url + ? `${config.ds_url}/functions/v1` + : '# Set after Supabase project is created'; + } else { + // SQLite + dsConfig.db_path = config.ds_db_path || `./${dsName}.db`; + dsConfig.base_url = 'http://localhost:3141'; + dsConfig.note = 'Run: cd _bmad/' + dsName + ' && npm install && node server.js'; + } + + const yamlStr = yaml.dump(dsConfig, { lineWidth: -1 }); + await fs.writeFile( + path.join(dsDir, 'config.yaml'), + `# Design Space Configuration - Generated by WDS installer\n${yamlStr}`, + 'utf8' + ); + + // Copy the Design Space skill into the module + const dsSkillSrc = path.join(this.srcDir, 'skills', 'design-space'); + if (await fs.pathExists(dsSkillSrc)) { + await fs.copy(dsSkillSrc, path.join(dsDir, 'skill')); + } + + // For SQLite: copy the lite-server files + if (config.ds_backend === 'sqlite') { + const liteServerSrc = path.resolve(this.srcDir, '..', '..', 'design-space', 'lite-server'); + // If lite-server exists in the design-space repo (dev environment), copy it + if (await fs.pathExists(liteServerSrc)) { + const filesToCopy = ['server.js', 'package.json']; + for (const file of filesToCopy) { + const src = path.join(liteServerSrc, file); + if (await fs.pathExists(src)) { + await fs.copy(src, path.join(dsDir, file)); + } + } + } else { + // Create a minimal package.json that points to the npm package + await fs.writeFile( + path.join(dsDir, 'package.json'), + JSON.stringify( + { + name: dsName, + private: true, + type: 'module', + dependencies: { 'better-sqlite3': '^11.0.0', 'sqlite-vec': '^0.1.7' }, + scripts: { start: 'node server.js' }, + }, + null, + 2 + ), + 'utf8' + ); + + // Write a note about getting the server + await fs.writeFile( + path.join(dsDir, 'README.md'), + `# ${dsName}\n\nDesign Space — local SQLite backend.\n\nGet the server:\n\`\`\`bash\nnpm install\n# Copy server.js from https://github.com/whiteport-collective/design-space/tree/master/lite-server\nnpm start\n\`\`\`\n`, + 'utf8' + ); + } + } + + // For Supabase with create mode: write setup instructions + if (config.ds_backend === 'supabase' && config.ds_mode === 'create') { + await fs.writeFile( + path.join(dsDir, 'SETUP.md'), + `# ${dsName} — Supabase Setup\n\n1. Create a project at https://supabase.com\n2. Get your project URL and anon key from Settings → API\n3. Update config.yaml with the URL and key\n4. Deploy edge functions:\n \`\`\`bash\n git clone https://github.com/whiteport-collective/design-space.git /tmp/ds\n cd /tmp/ds && ./setup.sh YOUR-PROJECT-REF\n \`\`\`\n5. Set OPENROUTER_API_KEY in Supabase → Edge Functions → Secrets (for semantic search)\n`, + 'utf8' + ); + } + + // Store DS config reference in WDS config + const wdsConfigPath = path.join(projectDir, config.wdsFolder, 'config.yaml'); + if (await fs.pathExists(wdsConfigPath)) { + let wdsConfig = await fs.readFile(wdsConfigPath, 'utf8'); + wdsConfig += `\n# Design Space\ndesign_space:\n name: ${dsName}\n path: _bmad/${dsName}\n backend: ${config.ds_backend}\n`; + await fs.writeFile(wdsConfigPath, wdsConfig, 'utf8'); + } + } + /** * Create the WDS work products folder structure * @param {string} projectDir - Project root directory diff --git a/tools/cli/lib/ui.js b/tools/cli/lib/ui.js index f8448dbbf..b4b96f72d 100644 --- a/tools/cli/lib/ui.js +++ b/tools/cli/lib/ui.js @@ -191,9 +191,30 @@ class UI { }, ]); + // --- Design Space (optional) --- + console.log(''); + console.log(chalk.white.bold(' Design Space')); + console.log(chalk.dim(' Shared knowledge base and agent communication layer.')); + console.log(chalk.dim(' Agents can search design knowledge, message each other, and track work.\n')); + + const dsAnswers = await inquirer.prompt([ + { + type: 'confirm', + name: 'install_design_space', + message: 'Install Design Space?', + default: true, + }, + ]); + + let dsConfig = {}; + if (dsAnswers.install_design_space) { + dsConfig = await this.promptDesignSpace(); + } + return { projectDir, ...answers, + ...dsConfig, wdsFolder, _detection: detection, _action: action, @@ -201,10 +222,98 @@ class UI { }; } + /** + * Prompt for Design Space configuration + */ + async promptDesignSpace() { + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'ds_name', + message: 'Name your space:', + default: 'design-space', + }, + { + type: 'list', + name: 'ds_mode', + message: 'Connect to an existing space or create a new one?', + choices: [ + { name: 'Create new (set up fresh)', value: 'create' }, + { name: 'Connect to existing (I have credentials)', value: 'connect' }, + ], + }, + ]); + + if (answers.ds_mode === 'connect') { + const connectAnswers = await inquirer.prompt([ + { + type: 'list', + name: 'ds_backend', + message: 'What backend does it use?', + choices: [ + { name: 'Supabase (team/cloud)', value: 'supabase' }, + { name: 'SQLite (local file)', value: 'sqlite' }, + ], + }, + { + type: 'input', + name: 'ds_url', + message: 'Base URL:', + when: (a) => a.ds_backend === 'supabase', + validate: (v) => (v.includes('supabase.co') || v.startsWith('http') ? true : 'Enter a valid URL'), + }, + { + type: 'input', + name: 'ds_key', + message: 'Anon key:', + when: (a) => a.ds_backend === 'supabase', + validate: (v) => (v.length > 20 ? true : 'Enter the Supabase anon key'), + }, + { + type: 'input', + name: 'ds_db_path', + message: 'Path to .db file:', + when: (a) => a.ds_backend === 'sqlite', + default: './design-space.db', + }, + ]); + + return { + install_design_space: true, + ds_name: answers.ds_name, + ds_mode: 'connect', + ds_backend: connectAnswers.ds_backend, + ds_url: connectAnswers.ds_url, + ds_key: connectAnswers.ds_key, + ds_db_path: connectAnswers.ds_db_path, + }; + } + + // Create new + const createAnswers = await inquirer.prompt([ + { + type: 'list', + name: 'ds_backend', + message: 'Which database?', + choices: [ + { name: 'SQLite — local file, zero infrastructure, data stays on your machine', value: 'sqlite' }, + { name: 'Supabase — cloud database, team collaboration, multi-machine', value: 'supabase' }, + ], + }, + ]); + + return { + install_design_space: true, + ds_name: answers.ds_name, + ds_mode: 'create', + ds_backend: createAnswers.ds_backend, + }; + } + /** * Display success message with next steps */ - displaySuccess(wdsFolder, ides = ['windsurf']) { + displaySuccess(wdsFolder, ides = ['windsurf'], dsInstalled = false, dsName = '', dsBackend = '') { const ideNames = { 'rovo-dev': 'Atlassian Rovo Dev', auggie: 'Auggie CLI', @@ -256,6 +365,9 @@ class UI { console.log(chalk.dim(' ─────────────────────────────────────────────────')); console.log(''); console.log(chalk.dim(` Available agents: Saga (Analyst), Freya (Designer)`)); + if (dsInstalled) { + console.log(chalk.dim(` Design Space: ${dsName} (${dsBackend})`)); + } console.log(chalk.dim(` Need development? Install BMM: npx bmad-builder install`)); console.log(chalk.dim(' Docs: https://github.com/whiteport-collective/whiteport-design-studio')); console.log('');