feat: Design Space installer — optional module with SQLite/Supabase choice

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/<space-name>/ 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) <noreply@anthropic.com>
This commit is contained in:
Mårten Angner 2026-03-19 23:23:17 +01:00
parent 8b73573fcb
commit 754736750b
3 changed files with 229 additions and 3 deletions

View File

@ -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) {

View File

@ -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

View File

@ -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('');