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:
parent
8b73573fcb
commit
754736750b
|
|
@ -22,7 +22,7 @@ module.exports = {
|
||||||
const result = await installer.install(config);
|
const result = await installer.install(config);
|
||||||
|
|
||||||
if (result && result.success) {
|
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);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,19 @@ class Installer {
|
||||||
throw error;
|
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) {
|
if (config.install_learning !== false) {
|
||||||
const learnSpinner = ora('Copying learning & reference material...').start();
|
const learnSpinner = ora('Copying learning & reference material...').start();
|
||||||
try {
|
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
|
* Create the WDS work products folder structure
|
||||||
* @param {string} projectDir - Project root directory
|
* @param {string} projectDir - Project root directory
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
return {
|
||||||
projectDir,
|
projectDir,
|
||||||
...answers,
|
...answers,
|
||||||
|
...dsConfig,
|
||||||
wdsFolder,
|
wdsFolder,
|
||||||
_detection: detection,
|
_detection: detection,
|
||||||
_action: action,
|
_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
|
* Display success message with next steps
|
||||||
*/
|
*/
|
||||||
displaySuccess(wdsFolder, ides = ['windsurf']) {
|
displaySuccess(wdsFolder, ides = ['windsurf'], dsInstalled = false, dsName = '', dsBackend = '') {
|
||||||
const ideNames = {
|
const ideNames = {
|
||||||
'rovo-dev': 'Atlassian Rovo Dev',
|
'rovo-dev': 'Atlassian Rovo Dev',
|
||||||
auggie: 'Auggie CLI',
|
auggie: 'Auggie CLI',
|
||||||
|
|
@ -256,6 +365,9 @@ class UI {
|
||||||
console.log(chalk.dim(' ─────────────────────────────────────────────────'));
|
console.log(chalk.dim(' ─────────────────────────────────────────────────'));
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(chalk.dim(` Available agents: Saga (Analyst), Freya (Designer)`));
|
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(` Need development? Install BMM: npx bmad-builder install`));
|
||||||
console.log(chalk.dim(' Docs: https://github.com/whiteport-collective/whiteport-design-studio'));
|
console.log(chalk.dim(' Docs: https://github.com/whiteport-collective/whiteport-design-studio'));
|
||||||
console.log('');
|
console.log('');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue