@weirdfingers/baseboards 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -19,13 +19,13 @@ open http://localhost:3300
19
19
 
20
20
  ### `up [directory]`
21
21
 
22
- Scaffold and start Baseboards. If the directory doesn't exist, creates a new project from templates.
22
+ Scaffold and start Baseboards. If the directory doesn't exist, creates a new project from templates. Runs in detached mode (background) by default.
23
23
 
24
24
  ```bash
25
- baseboards up # Current directory
26
- baseboards up my-app # New directory
25
+ baseboards up # Current directory (detached)
26
+ baseboards up my-app # New directory (detached)
27
27
  baseboards up --prod # Production mode
28
- baseboards up --detached # Background mode
28
+ baseboards up --attach # Attach to logs (foreground)
29
29
  baseboards up --ports web=3300 # Custom ports
30
30
  ```
31
31
 
package/dist/index.js CHANGED
@@ -127,7 +127,7 @@ function generateSecret(length = 32) {
127
127
  return crypto.randomBytes(length).toString("hex");
128
128
  }
129
129
  function generatePassword(length = 24) {
130
- const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*";
130
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%^&*";
131
131
  let password = "";
132
132
  const bytes = crypto.randomBytes(length);
133
133
  for (let i = 0; i < length; i++) {
@@ -269,8 +269,8 @@ async function up(directory, options) {
269
269
  await startDockerCompose(ctx, true);
270
270
  await waitForHealthy(ctx);
271
271
  await runMigrations(ctx);
272
- printSuccessMessage(ctx, options.detached || false, missingKeys.length > 0);
273
- if (!options.detached) {
272
+ printSuccessMessage(ctx, !options.attach, missingKeys.length > 0);
273
+ if (options.attach) {
274
274
  try {
275
275
  await attachToLogs(ctx);
276
276
  } catch (error) {
@@ -859,7 +859,7 @@ var program = new Command();
859
859
  program.name("baseboards").description(
860
860
  "\u{1F3A8} One-command launcher for the Boards image generation platform"
861
861
  ).version(packageJson.version, "-v, --version", "Output the current version");
862
- program.command("up").description("Start Baseboards (scaffolds if needed)").argument("[directory]", "Project directory", ".").option("--dev", "Development mode with hot reload (default)", true).option("--prod", "Production mode with prebuilt images").option("--detached", "Run in detached mode (background)").option("--ports <ports>", "Custom ports (e.g., web=3300 api=8800)").action(up);
862
+ program.command("up").description("Start Baseboards (scaffolds if needed)").argument("[directory]", "Project directory", ".").option("--dev", "Development mode with hot reload (default)", true).option("--prod", "Production mode with prebuilt images").option("--attach", "Attach to logs (runs in foreground)").option("--ports <ports>", "Custom ports (e.g., web=3300 api=8800)").action(up);
863
863
  program.command("down").description("Stop Baseboards").argument("[directory]", "Project directory", ".").option("--volumes", "Also remove volumes").action(down);
864
864
  program.command("logs").description("View logs from services").argument("[directory]", "Project directory", ".").argument(
865
865
  "[services...]",
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/commands/up.ts","../src/utils.ts","../src/commands/down.ts","../src/commands/logs.ts","../src/commands/status.ts","../src/commands/clean.ts","../src/commands/update.ts","../src/commands/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Baseboards CLI\n *\n * Main entry point for the @weirdfingers/baseboards command-line interface.\n * Provides commands to scaffold, run, and manage Baseboards installations.\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"fs\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport chalk from \"chalk\";\n\n// Import commands\nimport { up } from \"./commands/up.js\";\nimport { down } from \"./commands/down.js\";\nimport { logs } from \"./commands/logs.js\";\nimport { status } from \"./commands/status.js\";\nimport { clean } from \"./commands/clean.js\";\nimport { update } from \"./commands/update.js\";\nimport { doctor } from \"./commands/doctor.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Read package.json for version\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, \"../package.json\"), \"utf-8\")\n);\n\nconst program = new Command();\n\nprogram\n .name(\"baseboards\")\n .description(\n \"šŸŽØ One-command launcher for the Boards image generation platform\"\n )\n .version(packageJson.version, \"-v, --version\", \"Output the current version\");\n\n// up command\nprogram\n .command(\"up\")\n .description(\"Start Baseboards (scaffolds if needed)\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--dev\", \"Development mode with hot reload (default)\", true)\n .option(\"--prod\", \"Production mode with prebuilt images\")\n .option(\"--detached\", \"Run in detached mode (background)\")\n .option(\"--ports <ports>\", \"Custom ports (e.g., web=3300 api=8800)\")\n .action(up);\n\n// down command\nprogram\n .command(\"down\")\n .description(\"Stop Baseboards\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--volumes\", \"Also remove volumes\")\n .action(down);\n\n// logs command\nprogram\n .command(\"logs\")\n .description(\"View logs from services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .argument(\n \"[services...]\",\n \"Services to show logs for (web, api, db, cache)\",\n []\n )\n .option(\"-f, --follow\", \"Follow log output\")\n .option(\"--since <time>\", \"Show logs since timestamp (e.g., 1h, 30m)\")\n .option(\"--tail <lines>\", \"Number of lines to show from end\", \"100\")\n .action(logs);\n\n// status command\nprogram\n .command(\"status\")\n .description(\"Show status of services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(status);\n\n// clean command\nprogram\n .command(\"clean\")\n .description(\"Clean up Docker resources\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--hard\", \"Remove volumes and images (WARNING: deletes data)\")\n .action(clean);\n\n// update command\nprogram\n .command(\"update\")\n .description(\"Update Baseboards to latest version\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--force\", \"Force update without safety checks\")\n .option(\"--version <version>\", \"Update to specific version\")\n .action(update);\n\n// doctor command\nprogram\n .command(\"doctor\")\n .description(\"Run diagnostics and show system info\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(doctor);\n\n// Parse without exitOverride - let commander handle exits naturally\ntry {\n await program.parseAsync(process.argv);\n} catch (error: unknown) {\n const err = error as { message?: string; stderr?: string };\n // Actual error (not help/version)\n console.error(chalk.red(\"\\nāŒ Error:\"), err.message || \"Unknown error\");\n\n if (err.stderr) {\n console.error(chalk.gray(\"\\nDetails:\"));\n console.error(chalk.gray(err.stderr));\n }\n\n console.error(\n chalk.yellow(\"\\nšŸ’” Try running:\"),\n chalk.cyan(\"baseboards doctor\")\n );\n console.error(\n chalk.yellow(\"šŸ“– Documentation:\"),\n chalk.cyan(\"https://baseboards.dev/docs\")\n );\n\n process.exit(1);\n}\n","/**\n * up command - Scaffold and start Baseboards\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport type { ProjectContext, UpOptions } from \"../types.js\";\nimport {\n assertPrerequisites,\n findAvailablePort,\n generatePassword,\n generateSecret,\n getCliVersion,\n getTemplatesDir,\n isScaffolded,\n parsePortsOption,\n detectMissingProviderKeys,\n waitFor,\n} from \"../utils.js\";\n\nexport async function up(directory: string, options: UpOptions): Promise<void> {\n console.log(chalk.blue.bold(\"\\nšŸŽØ Baseboards CLI\\n\"));\n\n // Step 1: Check prerequisites\n const spinner = ora(\"Checking prerequisites...\").start();\n await assertPrerequisites();\n spinner.succeed(\"Prerequisites OK\");\n\n // Step 2: Resolve project context\n const dir = path.resolve(process.cwd(), directory);\n const name = path.basename(dir);\n const version = getCliVersion();\n const mode = options.prod ? \"prod\" : \"dev\";\n\n // Parse custom ports\n let customPorts = {};\n if (options.ports) {\n customPorts = parsePortsOption(options.ports);\n }\n\n // Default ports\n const defaultPorts = {\n web: 3300,\n api: 8800,\n db: 5432,\n redis: 6379,\n ...customPorts,\n };\n\n const ctx: ProjectContext = {\n dir,\n name,\n isScaffolded: isScaffolded(dir),\n ports: defaultPorts,\n mode,\n version,\n };\n\n // Track if this is a fresh scaffold to prompt for API keys later\n const isFreshScaffold = !ctx.isScaffolded;\n\n // Step 3: Scaffold if needed\n if (!ctx.isScaffolded) {\n console.log(\n chalk.cyan(`\\nšŸ“¦ Scaffolding new project: ${chalk.bold(name)}`)\n );\n await scaffoldProject(ctx);\n } else {\n console.log(\n chalk.green(`\\nāœ… Project already scaffolded: ${chalk.bold(name)}`)\n );\n }\n\n // Step 4: Check ports availability\n spinner.start(\"Checking port availability...\");\n ctx.ports.web = await findAvailablePort(ctx.ports.web);\n ctx.ports.api = await findAvailablePort(ctx.ports.api);\n spinner.succeed(\n `Ports available: web=${ctx.ports.web}, api=${ctx.ports.api}`\n );\n\n // Step 5: Ensure environment files\n await ensureEnvFiles(ctx);\n\n // Step 5.5: Prompt for API keys (only on fresh scaffold)\n if (isFreshScaffold) {\n await promptForApiKeys(ctx);\n }\n\n // Step 6: Detect missing API keys\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const missingKeys = detectMissingProviderKeys(apiEnvPath);\n\n if (missingKeys.length > 0) {\n console.log(chalk.yellow(\"\\nāš ļø Provider API keys not configured!\"));\n console.log(chalk.gray(\" Add at least one API key to api/.env:\"));\n console.log(\n chalk.cyan(\" • REPLICATE_API_TOKEN\") +\n chalk.gray(\" - https://replicate.com/account/api-tokens\")\n );\n console.log(\n chalk.cyan(\" • FAL_KEY\") +\n chalk.gray(\" - https://fal.ai/dashboard/keys\")\n );\n console.log(\n chalk.cyan(\" • OPENAI_API_KEY\") +\n chalk.gray(\" - https://platform.openai.com/api-keys\")\n );\n console.log(\n chalk.cyan(\" • GOOGLE_API_KEY\") +\n chalk.gray(\" - https://makersuite.google.com/app/apikey\")\n );\n console.log(\n chalk.gray(\n \"\\n The app will start, but image generation won't work without keys.\\n\"\n )\n );\n }\n\n // Step 7: Start Docker Compose (always detached initially)\n await startDockerCompose(ctx, true);\n\n // Step 8: Wait for health checks\n await waitForHealthy(ctx);\n\n // Step 9: Run database migrations\n await runMigrations(ctx);\n\n // Step 10: Print success message\n printSuccessMessage(ctx, options.detached || false, missingKeys.length > 0);\n\n // Step 11: Attach to logs if not detached\n if (!options.detached) {\n try {\n await attachToLogs(ctx);\n } catch (error: any) {\n // Handle Ctrl+C gracefully\n if (error.signal === \"SIGINT\" || error.exitCode === 130) {\n console.log(chalk.yellow(\"\\n\\nāš ļø Interrupted - services stopped\"));\n process.exit(0);\n }\n throw error;\n }\n }\n}\n\n/**\n * Scaffold a new project from templates\n */\nasync function scaffoldProject(ctx: ProjectContext): Promise<void> {\n const templatesDir = getTemplatesDir();\n const spinner = ora(\"Copying templates...\").start();\n\n // Create project directory\n fs.ensureDirSync(ctx.dir);\n\n // Copy web and api directly to root\n fs.copySync(path.join(templatesDir, \"web\"), path.join(ctx.dir, \"web\"));\n fs.copySync(path.join(templatesDir, \"api\"), path.join(ctx.dir, \"api\"));\n\n // Copy root files (compose, docker, README, .gitignore)\n const rootFiles = [\n \"compose.yaml\",\n \"compose.dev.yaml\",\n \"README.md\",\n \".gitignore\",\n ];\n for (const file of rootFiles) {\n const src = path.join(templatesDir, file);\n const dest = path.join(ctx.dir, file);\n if (fs.existsSync(src)) {\n fs.copySync(src, dest);\n }\n }\n\n // Copy docker directory\n fs.copySync(path.join(templatesDir, \"docker\"), path.join(ctx.dir, \"docker\"));\n\n spinner.succeed(\"Templates copied\");\n\n // Create data/storage directory\n spinner.start(\"Creating data directories...\");\n fs.ensureDirSync(path.join(ctx.dir, \"data/storage\"));\n spinner.succeed(\"Data directories created\");\n\n console.log(chalk.green(\" ✨ Project scaffolded successfully!\"));\n}\n\n/**\n * Ensure .env files exist and are populated\n */\nasync function ensureEnvFiles(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Configuring environment...\").start();\n\n // Web .env\n const webEnvPath = path.join(ctx.dir, \"web/.env\");\n const webEnvExamplePath = path.join(ctx.dir, \"web/.env.example\");\n\n if (!fs.existsSync(webEnvPath) && fs.existsSync(webEnvExamplePath)) {\n let webEnv = fs.readFileSync(webEnvExamplePath, \"utf-8\");\n webEnv = webEnv.replace(\n \"http://localhost:8800\",\n `http://localhost:${ctx.ports.api}`\n );\n fs.writeFileSync(webEnvPath, webEnv);\n }\n\n // API .env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const apiEnvExamplePath = path.join(ctx.dir, \"api/.env.example\");\n\n if (!fs.existsSync(apiEnvPath) && fs.existsSync(apiEnvExamplePath)) {\n let apiEnv = fs.readFileSync(apiEnvExamplePath, \"utf-8\");\n\n // Generate JWT secret if not present\n if (\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\n\") ||\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\r\\n\")\n ) {\n const jwtSecret = generateSecret(32);\n apiEnv = apiEnv.replace(\n /BOARDS_JWT_SECRET=.*$/m,\n `BOARDS_JWT_SECRET=${jwtSecret}`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n }\n\n // Docker .env\n const dockerEnvPath = path.join(ctx.dir, \"docker/.env\");\n const dockerEnvExamplePath = path.join(ctx.dir, \"docker/env.example\");\n\n if (!fs.existsSync(dockerEnvPath) && fs.existsSync(dockerEnvExamplePath)) {\n let dockerEnv = fs.readFileSync(dockerEnvExamplePath, \"utf-8\");\n\n // Generate database password\n const dbPassword = generatePassword(24);\n // URL-encode the password for use in database URLs\n const dbPasswordEncoded = encodeURIComponent(dbPassword);\n\n dockerEnv = dockerEnv.replace(\n /POSTGRES_PASSWORD=.*/g,\n `POSTGRES_PASSWORD=${dbPassword}`\n );\n dockerEnv = dockerEnv.replace(\n /REPLACE_WITH_GENERATED_PASSWORD/g,\n dbPasswordEncoded\n );\n\n // Set ports\n dockerEnv = dockerEnv.replace(/WEB_PORT=.*/g, `WEB_PORT=${ctx.ports.web}`);\n dockerEnv = dockerEnv.replace(/API_PORT=.*/g, `API_PORT=${ctx.ports.api}`);\n\n // Set version\n dockerEnv = dockerEnv.replace(/VERSION=.*/g, `VERSION=${ctx.version}`);\n\n // Set project name\n dockerEnv = dockerEnv.replace(\n /PROJECT_NAME=.*/g,\n `PROJECT_NAME=${ctx.name}`\n );\n\n fs.writeFileSync(dockerEnvPath, dockerEnv);\n }\n\n spinner.succeed(\"Environment configured\");\n}\n\n/**\n * Prompt user for API keys during initial scaffold\n */\nasync function promptForApiKeys(ctx: ProjectContext): Promise<void> {\n console.log(chalk.cyan(\"\\nšŸ”‘ API Key Configuration\"));\n console.log(chalk.gray(\"Add API keys to enable image generation providers\"));\n console.log(chalk.gray(\"Press Enter to skip any key\\n\"));\n\n const response = await prompts([\n {\n type: \"text\",\n name: \"REPLICATE_API_TOKEN\",\n message: \"Replicate API Key (https://replicate.com/account/api-tokens):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"FAL_KEY\",\n message: \"Fal AI API Key (https://fal.ai/dashboard/keys):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"OPENAI_API_KEY\",\n message: \"OpenAI API Key (https://platform.openai.com/api-keys):\",\n initial: \"\",\n },\n ]);\n\n // Build the API keys dictionary (only include non-empty keys)\n const apiKeys: Record<string, string> = {};\n\n if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {\n apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();\n }\n\n if (response.FAL_KEY && response.FAL_KEY.trim()) {\n apiKeys.FAL_KEY = response.FAL_KEY.trim();\n }\n\n if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {\n apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();\n }\n\n // Only write if we have at least one key\n if (Object.keys(apiKeys).length > 0) {\n // Read current api/.env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n let apiEnv = fs.readFileSync(apiEnvPath, \"utf-8\");\n\n // Format as JSON string for the environment variable\n const jsonKeys = JSON.stringify(apiKeys);\n\n // Update or add BOARDS_GENERATOR_API_KEYS\n if (apiEnv.includes(\"BOARDS_GENERATOR_API_KEYS=\")) {\n apiEnv = apiEnv.replace(\n /BOARDS_GENERATOR_API_KEYS=.*$/m,\n `BOARDS_GENERATOR_API_KEYS=${jsonKeys}`\n );\n } else {\n // Add it after the JWT secret section\n apiEnv = apiEnv.replace(\n /(BOARDS_JWT_SECRET=.*\\n)/,\n `$1\\n# Generator API Keys (JSON format)\\nBOARDS_GENERATOR_API_KEYS=${jsonKeys}\\n`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n\n console.log(chalk.green(\"\\nāœ… API keys saved to api/.env\"));\n console.log(\n chalk.gray(\" You can edit this file anytime to add/update keys\\n\")\n );\n } else {\n console.log(chalk.yellow(\"\\nāš ļø No API keys provided\"));\n console.log(chalk.gray(\" You can add them later by editing api/.env\\n\"));\n }\n}\n\n/**\n * Start Docker Compose (always in detached mode)\n */\nasync function startDockerCompose(\n ctx: ProjectContext,\n detached: boolean\n): Promise<void> {\n const spinner = ora(\"Starting Docker Compose...\").start();\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"up\",\n \"-d\",\n \"--remove-orphans\",\n ];\n\n try {\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n spinner.succeed(\"Docker Compose started\");\n } catch (error: any) {\n spinner.fail(\"Failed to start Docker Compose\");\n throw error;\n }\n}\n\n/**\n * Attach to Docker Compose logs (foreground)\n */\nasync function attachToLogs(ctx: ProjectContext): Promise<void> {\n console.log(\n chalk.gray(\"\\n─────────────────────────────────────────────────────\")\n );\n console.log(chalk.gray(\"Streaming logs... (Press Ctrl+C to stop)\\n\"));\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"logs\",\n \"-f\",\n ];\n\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Wait for services to become healthy\n */\nasync function waitForHealthy(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Waiting for services to be healthy...\").start();\n\n const services = [\"db\", \"cache\", \"api\", \"worker\", \"web\"];\n const maxWaitMs = 120_000; // 2 minutes\n\n const checkHealth = async (): Promise<boolean> => {\n try {\n const { stdout } = await execa(\n \"docker\",\n [\"compose\", \"ps\", \"--format\", \"json\"],\n {\n cwd: ctx.dir,\n }\n );\n\n const containers = stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line));\n\n const allHealthy = services.every((service) => {\n const container = containers.find((c: any) => c.Service === service);\n return (\n container &&\n (container.Health === \"healthy\" || container.State === \"running\")\n );\n });\n\n return allHealthy;\n } catch (e) {\n return false;\n }\n };\n\n const success = await waitFor(checkHealth, {\n timeoutMs: maxWaitMs,\n intervalMs: 2000,\n onProgress: (elapsed) => {\n const seconds = Math.floor(elapsed / 1000);\n spinner.text = `Waiting for services to be healthy... (${seconds}s)`;\n },\n });\n\n if (success) {\n spinner.succeed(\"All services healthy\");\n } else {\n spinner.warn(\"Services taking longer than expected...\");\n console.log(\n chalk.yellow(\n \"\\nāš ļø Health check timeout. Services may still be starting.\"\n )\n );\n console.log(\n chalk.gray(\" Run\"),\n chalk.cyan(\"baseboards logs\"),\n chalk.gray(\"to check progress.\")\n );\n }\n}\n\n/**\n * Run database migrations\n */\nasync function runMigrations(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Running database migrations...\").start();\n\n try {\n await execa(\n \"docker\",\n [\"compose\", \"exec\", \"-T\", \"api\", \"alembic\", \"upgrade\", \"head\"],\n {\n cwd: ctx.dir,\n }\n );\n spinner.succeed(\"Database migrations complete\");\n } catch (error) {\n spinner.fail(\"Database migrations failed\");\n console.log(\n chalk.yellow(\n \"\\nāš ļø Database migrations failed. You may need to run them manually:\"\n )\n );\n console.log(error);\n console.log(chalk.cyan(\" docker compose exec api alembic upgrade head\"));\n // Don't throw - app can still start\n }\n}\n\n/**\n * Print success message with URLs and next steps\n */\nfunction printSuccessMessage(\n ctx: ProjectContext,\n detached: boolean,\n hasKeyWarning: boolean\n): void {\n console.log(chalk.green.bold(\"\\n✨ Baseboards is running!\\n\"));\n console.log(\n chalk.cyan(\" 🌐 Web:\"),\n chalk.underline(`http://localhost:${ctx.ports.web}`)\n );\n console.log(\n chalk.cyan(\" šŸ”Œ API:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}`)\n );\n console.log(\n chalk.cyan(\" šŸ“Š GraphQL:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}/graphql`)\n );\n\n if (hasKeyWarning) {\n console.log(chalk.yellow(\"\\nāš ļø Remember to configure provider API keys!\"));\n console.log(chalk.gray(\" Edit:\"), chalk.cyan(\"api/.env\"));\n console.log(\n chalk.gray(\" Docs:\"),\n chalk.cyan(\"https://baseboards.dev/docs/setup\")\n );\n }\n\n console.log(chalk.gray(\"\\nšŸ“– Commands:\"));\n console.log(chalk.gray(\" Stop:\"), chalk.cyan(\"baseboards down\"));\n console.log(chalk.gray(\" Logs:\"), chalk.cyan(\"baseboards logs\"));\n console.log(chalk.gray(\" Status:\"), chalk.cyan(\"baseboards status\"));\n console.log();\n}\n","/**\n * Utility functions for the Baseboards CLI\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport which from \"which\";\nimport type { Prerequisites, ProjectContext } from \"./types.js\";\nimport chalk from \"chalk\";\nimport crypto from \"crypto\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Get the templates directory (bundled with the CLI package)\n */\nexport function getTemplatesDir(): string {\n // In built package, templates are at root level next to dist/\n return path.join(__dirname, \"../templates\");\n}\n\n/**\n * Check system prerequisites\n */\nexport async function checkPrerequisites(): Promise<Prerequisites> {\n const prereqs: Prerequisites = {\n docker: { installed: false },\n node: { installed: true, satisfies: false },\n platform: { name: process.platform },\n };\n\n // Check Docker\n try {\n const dockerPath = await which(\"docker\");\n prereqs.docker.installed = !!dockerPath;\n\n if (dockerPath) {\n const { stdout } = await execa(\"docker\", [\"--version\"]);\n const match = stdout.match(/Docker version ([\\d.]+)/);\n if (match) {\n prereqs.docker.version = match[1];\n }\n\n // Check Docker Compose\n try {\n const { stdout: composeStdout } = await execa(\"docker\", [\n \"compose\",\n \"version\",\n ]);\n const composeMatch = composeStdout.match(/version v?([\\d.]+)/);\n if (composeMatch) {\n prereqs.docker.composeVersion = composeMatch[1];\n }\n } catch (e) {\n // Compose not available\n }\n }\n } catch (e) {\n // Docker not installed\n }\n\n // Check Node version\n const nodeVersion = process.version.replace(\"v\", \"\");\n prereqs.node.version = nodeVersion;\n\n const majorVersion = parseInt(nodeVersion.split(\".\")[0], 10);\n prereqs.node.satisfies = majorVersion >= 20;\n\n // Check if WSL\n if (process.platform === \"linux\") {\n try {\n const { stdout } = await execa(\"uname\", [\"-r\"]);\n if (\n stdout.toLowerCase().includes(\"microsoft\") ||\n stdout.toLowerCase().includes(\"wsl\")\n ) {\n prereqs.platform.isWSL = true;\n }\n } catch (e) {\n // Not WSL or can't determine\n }\n }\n\n return prereqs;\n}\n\n/**\n * Assert that prerequisites are met, or exit with helpful error\n */\nexport async function assertPrerequisites(): Promise<void> {\n const prereqs = await checkPrerequisites();\n\n const errors: string[] = [];\n\n if (!prereqs.docker.installed) {\n errors.push(\n \"🐳 Docker is not installed.\",\n \" Install from: https://docs.docker.com/get-docker/\"\n );\n } else if (!prereqs.docker.composeVersion) {\n errors.push(\n \"🐳 Docker Compose (v2) is not available.\",\n \" Update Docker to get Compose v2: https://docs.docker.com/compose/install/\"\n );\n }\n\n if (!prereqs.node.satisfies) {\n errors.push(\n `āš ļø Node.js ${prereqs.node.version} is too old (need v20+).`,\n \" Install from: https://nodejs.org/\"\n );\n }\n\n if (errors.length > 0) {\n console.error(chalk.red(\"\\nāŒ Prerequisites not met:\\n\"));\n errors.forEach((err) => console.error(chalk.yellow(err)));\n console.error(\"\");\n process.exit(1);\n }\n}\n\n/**\n * Check if a directory is already scaffolded\n */\nexport function isScaffolded(dir: string): boolean {\n // Check for key files that indicate scaffolding\n const keyFiles = [\"compose.yaml\", \"web/package.json\", \"api/pyproject.toml\"];\n\n return keyFiles.every((file) => fs.existsSync(path.join(dir, file)));\n}\n\n/**\n * Find an available port\n */\nexport async function findAvailablePort(\n preferred: number,\n maxAttempts = 50\n): Promise<number> {\n for (let port = preferred; port < preferred + maxAttempts; port++) {\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found near ${preferred}`);\n}\n\n/**\n * Check if a port is available\n */\nasync function isPortAvailable(port: number): Promise<boolean> {\n const { createServer } = await import(\"net\");\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Generate a random secure string\n */\nexport function generateSecret(length = 32): string {\n return crypto.randomBytes(length).toString(\"hex\");\n}\n\n/**\n * Generate a random password\n */\nexport function generatePassword(length = 24): string {\n const charset =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*\";\n let password = \"\";\n const bytes = crypto.randomBytes(length);\n\n for (let i = 0; i < length; i++) {\n password += charset[bytes[i] % charset.length];\n }\n\n return password;\n}\n\n/**\n * Parse custom ports string (e.g., \"web=3300 api=8800\")\n */\nexport function parsePortsOption(portsStr: string): Partial<{\n web: number;\n api: number;\n db: number;\n redis: number;\n}> {\n const ports: any = {};\n const pairs = portsStr.split(/\\s+/);\n\n for (const pair of pairs) {\n const [service, port] = pair.split(\"=\");\n const portNum = parseInt(port, 10);\n if (\n service &&\n [\"web\", \"api\", \"db\", \"redis\"].includes(service) &&\n !isNaN(portNum)\n ) {\n ports[service] = portNum;\n }\n }\n\n return ports;\n}\n\n/**\n * Read package.json version\n */\nexport function getCliVersion(): string {\n const packagePath = path.join(__dirname, \"../package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n return packageJson.version;\n}\n\n/**\n * Detect missing provider API keys in .env file\n */\nexport function detectMissingProviderKeys(envPath: string): string[] {\n if (!fs.existsSync(envPath)) {\n return [];\n }\n\n const envContent = fs.readFileSync(envPath, \"utf-8\");\n const providerKeys = [\n \"REPLICATE_API_TOKEN\",\n \"FAL_KEY\",\n \"OPENAI_API_KEY\",\n \"GOOGLE_API_KEY\",\n ];\n\n const missingKeys: string[] = [];\n\n for (const key of providerKeys) {\n const regex = new RegExp(`^${key}=(.*)$`, \"m\");\n const match = envContent.match(regex);\n\n // Key is missing if not found or if value is empty\n if (!match || !match[1] || match[1].trim() === \"\") {\n missingKeys.push(key);\n }\n }\n\n // If all keys are missing, user hasn't configured any\n if (missingKeys.length === providerKeys.length) {\n return missingKeys;\n }\n\n return [];\n}\n\n/**\n * Wait for a condition with timeout\n */\nexport async function waitFor(\n condition: () => Promise<boolean>,\n options: {\n timeoutMs: number;\n intervalMs?: number;\n onProgress?: (elapsed: number) => void;\n }\n): Promise<boolean> {\n const { timeoutMs, intervalMs = 1000, onProgress } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (await condition()) {\n return true;\n }\n\n if (onProgress) {\n onProgress(Date.now() - startTime);\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n return false;\n}\n","/**\n * down command - Stop Baseboards\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport type { DownOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function down(directory: string, options: DownOptions): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const spinner = ora('Stopping services...').start();\n\n const args = ['compose', 'down'];\n if (options.volumes) {\n args.push('--volumes');\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n });\n\n spinner.succeed('Services stopped');\n\n if (options.volumes) {\n console.log(chalk.yellow('āš ļø Volumes removed (database data deleted)'));\n }\n } catch (error: any) {\n spinner.fail('Failed to stop services');\n throw error;\n }\n}\n","/**\n * logs command - View service logs\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { LogsOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function logs(\n directory: string,\n services: string[],\n options: LogsOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const args = ['compose', 'logs'];\n\n if (options.follow) {\n args.push('--follow');\n }\n\n if (options.since) {\n args.push('--since', options.since);\n }\n\n if (options.tail) {\n args.push('--tail', options.tail);\n }\n\n // Add specific services if provided\n if (services.length > 0) {\n args.push(...services);\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n // Ctrl+C is expected, don't treat as error\n if (error.signal !== 'SIGINT') {\n throw error;\n }\n }\n}\n","/**\n * status command - Show service status\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport { isScaffolded } from '../utils.js';\n\nexport async function status(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\nšŸ“Š Service Status\\n'));\n\n try {\n await execa('docker', ['compose', 'ps'], {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n console.error(chalk.red('\\nāŒ Failed to get status'));\n throw error;\n }\n}\n","/**\n * clean command - Clean up Docker resources\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport prompts from 'prompts';\nimport type { CleanOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function clean(\n directory: string,\n options: CleanOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n if (options.hard) {\n console.log(chalk.yellow('\\nāš ļø WARNING: This will delete:'));\n console.log(chalk.yellow(' • All containers'));\n console.log(chalk.yellow(' • All volumes (database data will be lost)'));\n console.log(chalk.yellow(' • All images'));\n\n const response = await prompts({\n type: 'confirm',\n name: 'confirmed',\n message: 'Are you sure?',\n initial: false,\n });\n\n if (!response.confirmed) {\n console.log(chalk.gray('\\nCancelled'));\n return;\n }\n }\n\n const spinner = ora('Cleaning up...').start();\n\n try {\n // Stop containers\n await execa('docker', ['compose', 'down', '--volumes', '--remove-orphans'], {\n cwd: dir,\n });\n\n if (options.hard) {\n // Remove images\n try {\n const { stdout } = await execa('docker', ['compose', 'images', '-q'], {\n cwd: dir,\n });\n\n const imageIds = stdout.split('\\n').filter(Boolean);\n if (imageIds.length > 0) {\n await execa('docker', ['rmi', ...imageIds]);\n }\n } catch (e) {\n // Images might not exist or already removed\n }\n }\n\n spinner.succeed('Cleanup complete');\n\n if (options.hard) {\n console.log(chalk.green('\\n✨ All Docker resources removed'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to start fresh.'));\n } else {\n console.log(chalk.green('\\n✨ Containers and volumes removed'));\n }\n } catch (error: any) {\n spinner.fail('Cleanup failed');\n throw error;\n }\n}\n","/**\n * update command - Update Baseboards to latest version\n */\n\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { UpdateOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function update(\n directory: string,\n options: UpdateOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\nšŸ”„ Update Command\\n'));\n console.log(chalk.yellow('āš ļø This feature is coming soon!'));\n console.log(chalk.gray('\\nFor now, to update:'));\n console.log(chalk.gray('1. Update the CLI:'), chalk.cyan('npm install -g @weirdfingers/baseboards@latest'));\n console.log(chalk.gray('2. Pull new images:'), chalk.cyan('docker compose pull'));\n console.log(chalk.gray('3. Restart:'), chalk.cyan('baseboards down && baseboards up'));\n console.log();\n\n // TODO: Implement update logic:\n // 1. Check for new version on npm\n // 2. Scan for modified source files (git diff or timestamp check)\n // 3. If no modifications:\n // - Copy new templates (preserving config)\n // - Pull new Docker images\n // - Update package.json versions\n // 4. If modifications detected:\n // - Warn user\n // - Offer git-based merge if repo detected\n // - Create backup otherwise\n // 5. Preserve:\n // - All .env files\n // - config/*.yaml files\n // - data/storage/ directory\n}\n","/**\n * doctor command - Run diagnostics\n */\n\nimport path from 'path';\nimport fs from 'fs-extra';\nimport chalk from 'chalk';\nimport { checkPrerequisites, isScaffolded, detectMissingProviderKeys, getCliVersion } from '../utils.js';\n\nexport async function doctor(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n console.log(chalk.blue.bold('\\n🩺 Baseboards Diagnostics\\n'));\n\n // CLI Version\n console.log(chalk.cyan('CLI Version:'), getCliVersion());\n\n // Prerequisites\n console.log(chalk.cyan('\\nšŸ“‹ Prerequisites:'));\n const prereqs = await checkPrerequisites();\n\n console.log(\n chalk.gray(' Node.js:'),\n prereqs.node.installed\n ? prereqs.node.satisfies\n ? chalk.green(`āœ“ v${prereqs.node.version}`)\n : chalk.yellow(`āš ļø v${prereqs.node.version} (need v20+)`)\n : chalk.red('āœ— Not installed')\n );\n\n console.log(\n chalk.gray(' Docker:'),\n prereqs.docker.installed\n ? chalk.green(`āœ“ v${prereqs.docker.version}`)\n : chalk.red('āœ— Not installed')\n );\n\n if (prereqs.docker.composeVersion) {\n console.log(\n chalk.gray(' Docker Compose:'),\n chalk.green(`āœ“ v${prereqs.docker.composeVersion}`)\n );\n } else if (prereqs.docker.installed) {\n console.log(chalk.gray(' Docker Compose:'), chalk.red('āœ— Not available'));\n }\n\n console.log(chalk.gray(' Platform:'), prereqs.platform.name);\n if (prereqs.platform.isWSL) {\n console.log(chalk.gray(' WSL:'), chalk.blue('āœ“ Detected'));\n }\n\n // Project info\n console.log(chalk.cyan('\\nšŸ“‚ Project:'));\n const scaffolded = isScaffolded(dir);\n console.log(\n chalk.gray(' Scaffolded:'),\n scaffolded ? chalk.green('āœ“ Yes') : chalk.yellow('āœ— No')\n );\n\n if (scaffolded) {\n console.log(chalk.gray(' Directory:'), dir);\n\n // Check for key files\n const webPkg = path.join(dir, 'web/package.json');\n const apiPkg = path.join(dir, 'api/pyproject.toml');\n const composeFile = path.join(dir, 'compose.yaml');\n\n console.log(\n chalk.gray(' Web package:'),\n fs.existsSync(webPkg) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n console.log(\n chalk.gray(' API package:'),\n fs.existsSync(apiPkg) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n console.log(\n chalk.gray(' Compose file:'),\n fs.existsSync(composeFile) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n\n // Check .env files\n console.log(chalk.cyan('\\nšŸ” Environment:'));\n const webEnv = path.join(dir, 'web/.env');\n const apiEnv = path.join(dir, 'api/.env');\n const dockerEnv = path.join(dir, 'docker/.env');\n\n console.log(\n chalk.gray(' Web .env:'),\n fs.existsSync(webEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' API .env:'),\n fs.existsSync(apiEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' Docker .env:'),\n fs.existsSync(dockerEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n\n // Check provider keys\n if (fs.existsSync(apiEnv)) {\n const missingKeys = detectMissingProviderKeys(apiEnv);\n if (missingKeys.length > 0) {\n console.log(\n chalk.gray(' Provider keys:'),\n chalk.yellow(`āš ļø ${missingKeys.length} missing`)\n );\n console.log(chalk.gray(' Missing:'), missingKeys.map(k => chalk.cyan(k)).join(', '));\n } else {\n console.log(chalk.gray(' Provider keys:'), chalk.green('āœ“ Configured'));\n }\n }\n\n // Check config files\n console.log(chalk.cyan('\\nāš™ļø Configuration:'));\n const generatorsYaml = path.join(dir, 'api/config/generators.yaml');\n const storageYaml = path.join(dir, 'api/config/storage_config.yaml');\n\n console.log(\n chalk.gray(' generators.yaml:'),\n fs.existsSync(generatorsYaml) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' storage_config.yaml:'),\n fs.existsSync(storageYaml) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n\n // Check storage directory\n const storageDir = path.join(dir, 'data/storage');\n console.log(\n chalk.gray(' Storage directory:'),\n fs.existsSync(storageDir) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n }\n\n // Recommendations\n console.log(chalk.cyan('\\nšŸ’” Recommendations:'));\n const recommendations: string[] = [];\n\n if (!prereqs.node.satisfies) {\n recommendations.push('Upgrade Node.js to v20 or higher');\n }\n\n if (!prereqs.docker.installed) {\n recommendations.push('Install Docker Desktop: https://docs.docker.com/get-docker/');\n } else if (!prereqs.docker.composeVersion) {\n recommendations.push('Update Docker to get Compose v2');\n }\n\n if (!scaffolded) {\n recommendations.push('Run ' + chalk.cyan('baseboards up') + ' to scaffold a project');\n }\n\n if (recommendations.length === 0) {\n console.log(chalk.green(' āœ“ Everything looks good!'));\n } else {\n recommendations.forEach((rec) => console.log(chalk.yellow(' •'), rec));\n }\n\n console.log();\n}\n"],"mappings":"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","names":["fileURLToPath","chalk","execa","fs","path","chalk","packageJson","chalk","path","fs","execa","execa","path","chalk","ora","path","chalk","ora","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","ora","prompts","path","chalk","prompts","ora","execa","path","chalk","path","chalk","path","fs","chalk","path","chalk","fs","__filename","fileURLToPath","__dirname","chalk"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/commands/up.ts","../src/utils.ts","../src/commands/down.ts","../src/commands/logs.ts","../src/commands/status.ts","../src/commands/clean.ts","../src/commands/update.ts","../src/commands/doctor.ts"],"sourcesContent":["#!/usr/bin/env node\n\n/**\n * Baseboards CLI\n *\n * Main entry point for the @weirdfingers/baseboards command-line interface.\n * Provides commands to scaffold, run, and manage Baseboards installations.\n */\n\nimport { Command } from \"commander\";\nimport { readFileSync } from \"fs\";\nimport { fileURLToPath } from \"url\";\nimport { dirname, join } from \"path\";\nimport chalk from \"chalk\";\n\n// Import commands\nimport { up } from \"./commands/up.js\";\nimport { down } from \"./commands/down.js\";\nimport { logs } from \"./commands/logs.js\";\nimport { status } from \"./commands/status.js\";\nimport { clean } from \"./commands/clean.js\";\nimport { update } from \"./commands/update.js\";\nimport { doctor } from \"./commands/doctor.js\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\n// Read package.json for version\nconst packageJson = JSON.parse(\n readFileSync(join(__dirname, \"../package.json\"), \"utf-8\")\n);\n\nconst program = new Command();\n\nprogram\n .name(\"baseboards\")\n .description(\n \"šŸŽØ One-command launcher for the Boards image generation platform\"\n )\n .version(packageJson.version, \"-v, --version\", \"Output the current version\");\n\n// up command\nprogram\n .command(\"up\")\n .description(\"Start Baseboards (scaffolds if needed)\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--dev\", \"Development mode with hot reload (default)\", true)\n .option(\"--prod\", \"Production mode with prebuilt images\")\n .option(\"--attach\", \"Attach to logs (runs in foreground)\")\n .option(\"--ports <ports>\", \"Custom ports (e.g., web=3300 api=8800)\")\n .action(up);\n\n// down command\nprogram\n .command(\"down\")\n .description(\"Stop Baseboards\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--volumes\", \"Also remove volumes\")\n .action(down);\n\n// logs command\nprogram\n .command(\"logs\")\n .description(\"View logs from services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .argument(\n \"[services...]\",\n \"Services to show logs for (web, api, db, cache)\",\n []\n )\n .option(\"-f, --follow\", \"Follow log output\")\n .option(\"--since <time>\", \"Show logs since timestamp (e.g., 1h, 30m)\")\n .option(\"--tail <lines>\", \"Number of lines to show from end\", \"100\")\n .action(logs);\n\n// status command\nprogram\n .command(\"status\")\n .description(\"Show status of services\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(status);\n\n// clean command\nprogram\n .command(\"clean\")\n .description(\"Clean up Docker resources\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--hard\", \"Remove volumes and images (WARNING: deletes data)\")\n .action(clean);\n\n// update command\nprogram\n .command(\"update\")\n .description(\"Update Baseboards to latest version\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .option(\"--force\", \"Force update without safety checks\")\n .option(\"--version <version>\", \"Update to specific version\")\n .action(update);\n\n// doctor command\nprogram\n .command(\"doctor\")\n .description(\"Run diagnostics and show system info\")\n .argument(\"[directory]\", \"Project directory\", \".\")\n .action(doctor);\n\n// Parse without exitOverride - let commander handle exits naturally\ntry {\n await program.parseAsync(process.argv);\n} catch (error: unknown) {\n const err = error as { message?: string; stderr?: string };\n // Actual error (not help/version)\n console.error(chalk.red(\"\\nāŒ Error:\"), err.message || \"Unknown error\");\n\n if (err.stderr) {\n console.error(chalk.gray(\"\\nDetails:\"));\n console.error(chalk.gray(err.stderr));\n }\n\n console.error(\n chalk.yellow(\"\\nšŸ’” Try running:\"),\n chalk.cyan(\"baseboards doctor\")\n );\n console.error(\n chalk.yellow(\"šŸ“– Documentation:\"),\n chalk.cyan(\"https://baseboards.dev/docs\")\n );\n\n process.exit(1);\n}\n","/**\n * up command - Scaffold and start Baseboards\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport prompts from \"prompts\";\nimport type { ProjectContext, UpOptions } from \"../types.js\";\nimport {\n assertPrerequisites,\n findAvailablePort,\n generatePassword,\n generateSecret,\n getCliVersion,\n getTemplatesDir,\n isScaffolded,\n parsePortsOption,\n detectMissingProviderKeys,\n waitFor,\n} from \"../utils.js\";\n\nexport async function up(directory: string, options: UpOptions): Promise<void> {\n console.log(chalk.blue.bold(\"\\nšŸŽØ Baseboards CLI\\n\"));\n\n // Step 1: Check prerequisites\n const spinner = ora(\"Checking prerequisites...\").start();\n await assertPrerequisites();\n spinner.succeed(\"Prerequisites OK\");\n\n // Step 2: Resolve project context\n const dir = path.resolve(process.cwd(), directory);\n const name = path.basename(dir);\n const version = getCliVersion();\n const mode = options.prod ? \"prod\" : \"dev\";\n\n // Parse custom ports\n let customPorts = {};\n if (options.ports) {\n customPorts = parsePortsOption(options.ports);\n }\n\n // Default ports\n const defaultPorts = {\n web: 3300,\n api: 8800,\n db: 5432,\n redis: 6379,\n ...customPorts,\n };\n\n const ctx: ProjectContext = {\n dir,\n name,\n isScaffolded: isScaffolded(dir),\n ports: defaultPorts,\n mode,\n version,\n };\n\n // Track if this is a fresh scaffold to prompt for API keys later\n const isFreshScaffold = !ctx.isScaffolded;\n\n // Step 3: Scaffold if needed\n if (!ctx.isScaffolded) {\n console.log(\n chalk.cyan(`\\nšŸ“¦ Scaffolding new project: ${chalk.bold(name)}`)\n );\n await scaffoldProject(ctx);\n } else {\n console.log(\n chalk.green(`\\nāœ… Project already scaffolded: ${chalk.bold(name)}`)\n );\n }\n\n // Step 4: Check ports availability\n spinner.start(\"Checking port availability...\");\n ctx.ports.web = await findAvailablePort(ctx.ports.web);\n ctx.ports.api = await findAvailablePort(ctx.ports.api);\n spinner.succeed(\n `Ports available: web=${ctx.ports.web}, api=${ctx.ports.api}`\n );\n\n // Step 5: Ensure environment files\n await ensureEnvFiles(ctx);\n\n // Step 5.5: Prompt for API keys (only on fresh scaffold)\n if (isFreshScaffold) {\n await promptForApiKeys(ctx);\n }\n\n // Step 6: Detect missing API keys\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const missingKeys = detectMissingProviderKeys(apiEnvPath);\n\n if (missingKeys.length > 0) {\n console.log(chalk.yellow(\"\\nāš ļø Provider API keys not configured!\"));\n console.log(chalk.gray(\" Add at least one API key to api/.env:\"));\n console.log(\n chalk.cyan(\" • REPLICATE_API_TOKEN\") +\n chalk.gray(\" - https://replicate.com/account/api-tokens\")\n );\n console.log(\n chalk.cyan(\" • FAL_KEY\") +\n chalk.gray(\" - https://fal.ai/dashboard/keys\")\n );\n console.log(\n chalk.cyan(\" • OPENAI_API_KEY\") +\n chalk.gray(\" - https://platform.openai.com/api-keys\")\n );\n console.log(\n chalk.cyan(\" • GOOGLE_API_KEY\") +\n chalk.gray(\" - https://makersuite.google.com/app/apikey\")\n );\n console.log(\n chalk.gray(\n \"\\n The app will start, but image generation won't work without keys.\\n\"\n )\n );\n }\n\n // Step 7: Start Docker Compose (always detached initially)\n await startDockerCompose(ctx, true);\n\n // Step 8: Wait for health checks\n await waitForHealthy(ctx);\n\n // Step 9: Run database migrations\n await runMigrations(ctx);\n\n // Step 10: Print success message\n printSuccessMessage(ctx, !options.attach, missingKeys.length > 0);\n\n // Step 11: Attach to logs if --attach flag is provided\n if (options.attach) {\n try {\n await attachToLogs(ctx);\n } catch (error: any) {\n // Handle Ctrl+C gracefully\n if (error.signal === \"SIGINT\" || error.exitCode === 130) {\n console.log(chalk.yellow(\"\\n\\nāš ļø Interrupted - services stopped\"));\n process.exit(0);\n }\n throw error;\n }\n }\n}\n\n/**\n * Scaffold a new project from templates\n */\nasync function scaffoldProject(ctx: ProjectContext): Promise<void> {\n const templatesDir = getTemplatesDir();\n const spinner = ora(\"Copying templates...\").start();\n\n // Create project directory\n fs.ensureDirSync(ctx.dir);\n\n // Copy web and api directly to root\n fs.copySync(path.join(templatesDir, \"web\"), path.join(ctx.dir, \"web\"));\n fs.copySync(path.join(templatesDir, \"api\"), path.join(ctx.dir, \"api\"));\n\n // Copy root files (compose, docker, README, .gitignore)\n const rootFiles = [\n \"compose.yaml\",\n \"compose.dev.yaml\",\n \"README.md\",\n \".gitignore\",\n ];\n for (const file of rootFiles) {\n const src = path.join(templatesDir, file);\n const dest = path.join(ctx.dir, file);\n if (fs.existsSync(src)) {\n fs.copySync(src, dest);\n }\n }\n\n // Copy docker directory\n fs.copySync(path.join(templatesDir, \"docker\"), path.join(ctx.dir, \"docker\"));\n\n spinner.succeed(\"Templates copied\");\n\n // Create data/storage directory\n spinner.start(\"Creating data directories...\");\n fs.ensureDirSync(path.join(ctx.dir, \"data/storage\"));\n spinner.succeed(\"Data directories created\");\n\n console.log(chalk.green(\" ✨ Project scaffolded successfully!\"));\n}\n\n/**\n * Ensure .env files exist and are populated\n */\nasync function ensureEnvFiles(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Configuring environment...\").start();\n\n // Web .env\n const webEnvPath = path.join(ctx.dir, \"web/.env\");\n const webEnvExamplePath = path.join(ctx.dir, \"web/.env.example\");\n\n if (!fs.existsSync(webEnvPath) && fs.existsSync(webEnvExamplePath)) {\n let webEnv = fs.readFileSync(webEnvExamplePath, \"utf-8\");\n webEnv = webEnv.replace(\n \"http://localhost:8800\",\n `http://localhost:${ctx.ports.api}`\n );\n fs.writeFileSync(webEnvPath, webEnv);\n }\n\n // API .env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n const apiEnvExamplePath = path.join(ctx.dir, \"api/.env.example\");\n\n if (!fs.existsSync(apiEnvPath) && fs.existsSync(apiEnvExamplePath)) {\n let apiEnv = fs.readFileSync(apiEnvExamplePath, \"utf-8\");\n\n // Generate JWT secret if not present\n if (\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\n\") ||\n apiEnv.includes(\"BOARDS_JWT_SECRET=\\r\\n\")\n ) {\n const jwtSecret = generateSecret(32);\n apiEnv = apiEnv.replace(\n /BOARDS_JWT_SECRET=.*$/m,\n `BOARDS_JWT_SECRET=${jwtSecret}`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n }\n\n // Docker .env\n const dockerEnvPath = path.join(ctx.dir, \"docker/.env\");\n const dockerEnvExamplePath = path.join(ctx.dir, \"docker/env.example\");\n\n if (!fs.existsSync(dockerEnvPath) && fs.existsSync(dockerEnvExamplePath)) {\n let dockerEnv = fs.readFileSync(dockerEnvExamplePath, \"utf-8\");\n\n // Generate database password\n const dbPassword = generatePassword(24);\n // URL-encode the password for use in database URLs\n const dbPasswordEncoded = encodeURIComponent(dbPassword);\n\n dockerEnv = dockerEnv.replace(\n /POSTGRES_PASSWORD=.*/g,\n `POSTGRES_PASSWORD=${dbPassword}`\n );\n dockerEnv = dockerEnv.replace(\n /REPLACE_WITH_GENERATED_PASSWORD/g,\n dbPasswordEncoded\n );\n\n // Set ports\n dockerEnv = dockerEnv.replace(/WEB_PORT=.*/g, `WEB_PORT=${ctx.ports.web}`);\n dockerEnv = dockerEnv.replace(/API_PORT=.*/g, `API_PORT=${ctx.ports.api}`);\n\n // Set version\n dockerEnv = dockerEnv.replace(/VERSION=.*/g, `VERSION=${ctx.version}`);\n\n // Set project name\n dockerEnv = dockerEnv.replace(\n /PROJECT_NAME=.*/g,\n `PROJECT_NAME=${ctx.name}`\n );\n\n fs.writeFileSync(dockerEnvPath, dockerEnv);\n }\n\n spinner.succeed(\"Environment configured\");\n}\n\n/**\n * Prompt user for API keys during initial scaffold\n */\nasync function promptForApiKeys(ctx: ProjectContext): Promise<void> {\n console.log(chalk.cyan(\"\\nšŸ”‘ API Key Configuration\"));\n console.log(chalk.gray(\"Add API keys to enable image generation providers\"));\n console.log(chalk.gray(\"Press Enter to skip any key\\n\"));\n\n const response = await prompts([\n {\n type: \"text\",\n name: \"REPLICATE_API_TOKEN\",\n message: \"Replicate API Key (https://replicate.com/account/api-tokens):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"FAL_KEY\",\n message: \"Fal AI API Key (https://fal.ai/dashboard/keys):\",\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"OPENAI_API_KEY\",\n message: \"OpenAI API Key (https://platform.openai.com/api-keys):\",\n initial: \"\",\n },\n ]);\n\n // Build the API keys dictionary (only include non-empty keys)\n const apiKeys: Record<string, string> = {};\n\n if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {\n apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();\n }\n\n if (response.FAL_KEY && response.FAL_KEY.trim()) {\n apiKeys.FAL_KEY = response.FAL_KEY.trim();\n }\n\n if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {\n apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();\n }\n\n // Only write if we have at least one key\n if (Object.keys(apiKeys).length > 0) {\n // Read current api/.env\n const apiEnvPath = path.join(ctx.dir, \"api/.env\");\n let apiEnv = fs.readFileSync(apiEnvPath, \"utf-8\");\n\n // Format as JSON string for the environment variable\n const jsonKeys = JSON.stringify(apiKeys);\n\n // Update or add BOARDS_GENERATOR_API_KEYS\n if (apiEnv.includes(\"BOARDS_GENERATOR_API_KEYS=\")) {\n apiEnv = apiEnv.replace(\n /BOARDS_GENERATOR_API_KEYS=.*$/m,\n `BOARDS_GENERATOR_API_KEYS=${jsonKeys}`\n );\n } else {\n // Add it after the JWT secret section\n apiEnv = apiEnv.replace(\n /(BOARDS_JWT_SECRET=.*\\n)/,\n `$1\\n# Generator API Keys (JSON format)\\nBOARDS_GENERATOR_API_KEYS=${jsonKeys}\\n`\n );\n }\n\n fs.writeFileSync(apiEnvPath, apiEnv);\n\n console.log(chalk.green(\"\\nāœ… API keys saved to api/.env\"));\n console.log(\n chalk.gray(\" You can edit this file anytime to add/update keys\\n\")\n );\n } else {\n console.log(chalk.yellow(\"\\nāš ļø No API keys provided\"));\n console.log(chalk.gray(\" You can add them later by editing api/.env\\n\"));\n }\n}\n\n/**\n * Start Docker Compose (always in detached mode)\n */\nasync function startDockerCompose(\n ctx: ProjectContext,\n detached: boolean\n): Promise<void> {\n const spinner = ora(\"Starting Docker Compose...\").start();\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"up\",\n \"-d\",\n \"--remove-orphans\",\n ];\n\n try {\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n spinner.succeed(\"Docker Compose started\");\n } catch (error: any) {\n spinner.fail(\"Failed to start Docker Compose\");\n throw error;\n }\n}\n\n/**\n * Attach to Docker Compose logs (foreground)\n */\nasync function attachToLogs(ctx: ProjectContext): Promise<void> {\n console.log(\n chalk.gray(\"\\n─────────────────────────────────────────────────────\")\n );\n console.log(chalk.gray(\"Streaming logs... (Press Ctrl+C to stop)\\n\"));\n\n const composeFiles = [\"compose.yaml\"];\n if (ctx.mode === \"dev\") {\n composeFiles.push(\"compose.dev.yaml\");\n }\n\n const composeArgs = [\n \"compose\",\n ...composeFiles.flatMap((f) => [\"-f\", f]),\n \"logs\",\n \"-f\",\n ];\n\n await execa(\"docker\", composeArgs, {\n cwd: ctx.dir,\n stdio: \"inherit\",\n });\n}\n\n/**\n * Wait for services to become healthy\n */\nasync function waitForHealthy(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Waiting for services to be healthy...\").start();\n\n const services = [\"db\", \"cache\", \"api\", \"worker\", \"web\"];\n const maxWaitMs = 120_000; // 2 minutes\n\n const checkHealth = async (): Promise<boolean> => {\n try {\n const { stdout } = await execa(\n \"docker\",\n [\"compose\", \"ps\", \"--format\", \"json\"],\n {\n cwd: ctx.dir,\n }\n );\n\n const containers = stdout\n .split(\"\\n\")\n .filter(Boolean)\n .map((line) => JSON.parse(line));\n\n const allHealthy = services.every((service) => {\n const container = containers.find((c: any) => c.Service === service);\n return (\n container &&\n (container.Health === \"healthy\" || container.State === \"running\")\n );\n });\n\n return allHealthy;\n } catch (e) {\n return false;\n }\n };\n\n const success = await waitFor(checkHealth, {\n timeoutMs: maxWaitMs,\n intervalMs: 2000,\n onProgress: (elapsed) => {\n const seconds = Math.floor(elapsed / 1000);\n spinner.text = `Waiting for services to be healthy... (${seconds}s)`;\n },\n });\n\n if (success) {\n spinner.succeed(\"All services healthy\");\n } else {\n spinner.warn(\"Services taking longer than expected...\");\n console.log(\n chalk.yellow(\n \"\\nāš ļø Health check timeout. Services may still be starting.\"\n )\n );\n console.log(\n chalk.gray(\" Run\"),\n chalk.cyan(\"baseboards logs\"),\n chalk.gray(\"to check progress.\")\n );\n }\n}\n\n/**\n * Run database migrations\n */\nasync function runMigrations(ctx: ProjectContext): Promise<void> {\n const spinner = ora(\"Running database migrations...\").start();\n\n try {\n await execa(\n \"docker\",\n [\"compose\", \"exec\", \"-T\", \"api\", \"alembic\", \"upgrade\", \"head\"],\n {\n cwd: ctx.dir,\n }\n );\n spinner.succeed(\"Database migrations complete\");\n } catch (error) {\n spinner.fail(\"Database migrations failed\");\n console.log(\n chalk.yellow(\n \"\\nāš ļø Database migrations failed. You may need to run them manually:\"\n )\n );\n console.log(error);\n console.log(chalk.cyan(\" docker compose exec api alembic upgrade head\"));\n // Don't throw - app can still start\n }\n}\n\n/**\n * Print success message with URLs and next steps\n */\nfunction printSuccessMessage(\n ctx: ProjectContext,\n detached: boolean,\n hasKeyWarning: boolean\n): void {\n console.log(chalk.green.bold(\"\\n✨ Baseboards is running!\\n\"));\n console.log(\n chalk.cyan(\" 🌐 Web:\"),\n chalk.underline(`http://localhost:${ctx.ports.web}`)\n );\n console.log(\n chalk.cyan(\" šŸ”Œ API:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}`)\n );\n console.log(\n chalk.cyan(\" šŸ“Š GraphQL:\"),\n chalk.underline(`http://localhost:${ctx.ports.api}/graphql`)\n );\n\n if (hasKeyWarning) {\n console.log(chalk.yellow(\"\\nāš ļø Remember to configure provider API keys!\"));\n console.log(chalk.gray(\" Edit:\"), chalk.cyan(\"api/.env\"));\n console.log(\n chalk.gray(\" Docs:\"),\n chalk.cyan(\"https://baseboards.dev/docs/setup\")\n );\n }\n\n console.log(chalk.gray(\"\\nšŸ“– Commands:\"));\n console.log(chalk.gray(\" Stop:\"), chalk.cyan(\"baseboards down\"));\n console.log(chalk.gray(\" Logs:\"), chalk.cyan(\"baseboards logs\"));\n console.log(chalk.gray(\" Status:\"), chalk.cyan(\"baseboards status\"));\n console.log();\n}\n","/**\n * Utility functions for the Baseboards CLI\n */\n\nimport { execa } from \"execa\";\nimport fs from \"fs-extra\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport which from \"which\";\nimport type { Prerequisites, ProjectContext } from \"./types.js\";\nimport chalk from \"chalk\";\nimport crypto from \"crypto\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\n/**\n * Get the templates directory (bundled with the CLI package)\n */\nexport function getTemplatesDir(): string {\n // In built package, templates are at root level next to dist/\n return path.join(__dirname, \"../templates\");\n}\n\n/**\n * Check system prerequisites\n */\nexport async function checkPrerequisites(): Promise<Prerequisites> {\n const prereqs: Prerequisites = {\n docker: { installed: false },\n node: { installed: true, satisfies: false },\n platform: { name: process.platform },\n };\n\n // Check Docker\n try {\n const dockerPath = await which(\"docker\");\n prereqs.docker.installed = !!dockerPath;\n\n if (dockerPath) {\n const { stdout } = await execa(\"docker\", [\"--version\"]);\n const match = stdout.match(/Docker version ([\\d.]+)/);\n if (match) {\n prereqs.docker.version = match[1];\n }\n\n // Check Docker Compose\n try {\n const { stdout: composeStdout } = await execa(\"docker\", [\n \"compose\",\n \"version\",\n ]);\n const composeMatch = composeStdout.match(/version v?([\\d.]+)/);\n if (composeMatch) {\n prereqs.docker.composeVersion = composeMatch[1];\n }\n } catch (e) {\n // Compose not available\n }\n }\n } catch (e) {\n // Docker not installed\n }\n\n // Check Node version\n const nodeVersion = process.version.replace(\"v\", \"\");\n prereqs.node.version = nodeVersion;\n\n const majorVersion = parseInt(nodeVersion.split(\".\")[0], 10);\n prereqs.node.satisfies = majorVersion >= 20;\n\n // Check if WSL\n if (process.platform === \"linux\") {\n try {\n const { stdout } = await execa(\"uname\", [\"-r\"]);\n if (\n stdout.toLowerCase().includes(\"microsoft\") ||\n stdout.toLowerCase().includes(\"wsl\")\n ) {\n prereqs.platform.isWSL = true;\n }\n } catch (e) {\n // Not WSL or can't determine\n }\n }\n\n return prereqs;\n}\n\n/**\n * Assert that prerequisites are met, or exit with helpful error\n */\nexport async function assertPrerequisites(): Promise<void> {\n const prereqs = await checkPrerequisites();\n\n const errors: string[] = [];\n\n if (!prereqs.docker.installed) {\n errors.push(\n \"🐳 Docker is not installed.\",\n \" Install from: https://docs.docker.com/get-docker/\"\n );\n } else if (!prereqs.docker.composeVersion) {\n errors.push(\n \"🐳 Docker Compose (v2) is not available.\",\n \" Update Docker to get Compose v2: https://docs.docker.com/compose/install/\"\n );\n }\n\n if (!prereqs.node.satisfies) {\n errors.push(\n `āš ļø Node.js ${prereqs.node.version} is too old (need v20+).`,\n \" Install from: https://nodejs.org/\"\n );\n }\n\n if (errors.length > 0) {\n console.error(chalk.red(\"\\nāŒ Prerequisites not met:\\n\"));\n errors.forEach((err) => console.error(chalk.yellow(err)));\n console.error(\"\");\n process.exit(1);\n }\n}\n\n/**\n * Check if a directory is already scaffolded\n */\nexport function isScaffolded(dir: string): boolean {\n // Check for key files that indicate scaffolding\n const keyFiles = [\"compose.yaml\", \"web/package.json\", \"api/pyproject.toml\"];\n\n return keyFiles.every((file) => fs.existsSync(path.join(dir, file)));\n}\n\n/**\n * Find an available port\n */\nexport async function findAvailablePort(\n preferred: number,\n maxAttempts = 50\n): Promise<number> {\n for (let port = preferred; port < preferred + maxAttempts; port++) {\n if (await isPortAvailable(port)) {\n return port;\n }\n }\n throw new Error(`No available port found near ${preferred}`);\n}\n\n/**\n * Check if a port is available\n */\nasync function isPortAvailable(port: number): Promise<boolean> {\n const { createServer } = await import(\"net\");\n return new Promise((resolve) => {\n const server = createServer();\n server.once(\"error\", () => resolve(false));\n server.once(\"listening\", () => {\n server.close();\n resolve(true);\n });\n server.listen(port);\n });\n}\n\n/**\n * Generate a random secure string\n */\nexport function generateSecret(length = 32): string {\n return crypto.randomBytes(length).toString(\"hex\");\n}\n\n/**\n * Generate a random password\n */\nexport function generatePassword(length = 24): string {\n const charset =\n \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#%^&*\";\n let password = \"\";\n const bytes = crypto.randomBytes(length);\n\n for (let i = 0; i < length; i++) {\n password += charset[bytes[i] % charset.length];\n }\n\n return password;\n}\n\n/**\n * Parse custom ports string (e.g., \"web=3300 api=8800\")\n */\nexport function parsePortsOption(portsStr: string): Partial<{\n web: number;\n api: number;\n db: number;\n redis: number;\n}> {\n const ports: any = {};\n const pairs = portsStr.split(/\\s+/);\n\n for (const pair of pairs) {\n const [service, port] = pair.split(\"=\");\n const portNum = parseInt(port, 10);\n if (\n service &&\n [\"web\", \"api\", \"db\", \"redis\"].includes(service) &&\n !isNaN(portNum)\n ) {\n ports[service] = portNum;\n }\n }\n\n return ports;\n}\n\n/**\n * Read package.json version\n */\nexport function getCliVersion(): string {\n const packagePath = path.join(__dirname, \"../package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packagePath, \"utf-8\"));\n return packageJson.version;\n}\n\n/**\n * Detect missing provider API keys in .env file\n */\nexport function detectMissingProviderKeys(envPath: string): string[] {\n if (!fs.existsSync(envPath)) {\n return [];\n }\n\n const envContent = fs.readFileSync(envPath, \"utf-8\");\n const providerKeys = [\n \"REPLICATE_API_TOKEN\",\n \"FAL_KEY\",\n \"OPENAI_API_KEY\",\n \"GOOGLE_API_KEY\",\n ];\n\n const missingKeys: string[] = [];\n\n for (const key of providerKeys) {\n const regex = new RegExp(`^${key}=(.*)$`, \"m\");\n const match = envContent.match(regex);\n\n // Key is missing if not found or if value is empty\n if (!match || !match[1] || match[1].trim() === \"\") {\n missingKeys.push(key);\n }\n }\n\n // If all keys are missing, user hasn't configured any\n if (missingKeys.length === providerKeys.length) {\n return missingKeys;\n }\n\n return [];\n}\n\n/**\n * Wait for a condition with timeout\n */\nexport async function waitFor(\n condition: () => Promise<boolean>,\n options: {\n timeoutMs: number;\n intervalMs?: number;\n onProgress?: (elapsed: number) => void;\n }\n): Promise<boolean> {\n const { timeoutMs, intervalMs = 1000, onProgress } = options;\n const startTime = Date.now();\n\n while (Date.now() - startTime < timeoutMs) {\n if (await condition()) {\n return true;\n }\n\n if (onProgress) {\n onProgress(Date.now() - startTime);\n }\n\n await new Promise((resolve) => setTimeout(resolve, intervalMs));\n }\n\n return false;\n}\n","/**\n * down command - Stop Baseboards\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport type { DownOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function down(directory: string, options: DownOptions): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const spinner = ora('Stopping services...').start();\n\n const args = ['compose', 'down'];\n if (options.volumes) {\n args.push('--volumes');\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n });\n\n spinner.succeed('Services stopped');\n\n if (options.volumes) {\n console.log(chalk.yellow('āš ļø Volumes removed (database data deleted)'));\n }\n } catch (error: any) {\n spinner.fail('Failed to stop services');\n throw error;\n }\n}\n","/**\n * logs command - View service logs\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { LogsOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function logs(\n directory: string,\n services: string[],\n options: LogsOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n const args = ['compose', 'logs'];\n\n if (options.follow) {\n args.push('--follow');\n }\n\n if (options.since) {\n args.push('--since', options.since);\n }\n\n if (options.tail) {\n args.push('--tail', options.tail);\n }\n\n // Add specific services if provided\n if (services.length > 0) {\n args.push(...services);\n }\n\n try {\n await execa('docker', args, {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n // Ctrl+C is expected, don't treat as error\n if (error.signal !== 'SIGINT') {\n throw error;\n }\n }\n}\n","/**\n * status command - Show service status\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport { isScaffolded } from '../utils.js';\n\nexport async function status(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\nšŸ“Š Service Status\\n'));\n\n try {\n await execa('docker', ['compose', 'ps'], {\n cwd: dir,\n stdio: 'inherit',\n });\n } catch (error: any) {\n console.error(chalk.red('\\nāŒ Failed to get status'));\n throw error;\n }\n}\n","/**\n * clean command - Clean up Docker resources\n */\n\nimport { execa } from 'execa';\nimport path from 'path';\nimport chalk from 'chalk';\nimport ora from 'ora';\nimport prompts from 'prompts';\nimport type { CleanOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function clean(\n directory: string,\n options: CleanOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n if (options.hard) {\n console.log(chalk.yellow('\\nāš ļø WARNING: This will delete:'));\n console.log(chalk.yellow(' • All containers'));\n console.log(chalk.yellow(' • All volumes (database data will be lost)'));\n console.log(chalk.yellow(' • All images'));\n\n const response = await prompts({\n type: 'confirm',\n name: 'confirmed',\n message: 'Are you sure?',\n initial: false,\n });\n\n if (!response.confirmed) {\n console.log(chalk.gray('\\nCancelled'));\n return;\n }\n }\n\n const spinner = ora('Cleaning up...').start();\n\n try {\n // Stop containers\n await execa('docker', ['compose', 'down', '--volumes', '--remove-orphans'], {\n cwd: dir,\n });\n\n if (options.hard) {\n // Remove images\n try {\n const { stdout } = await execa('docker', ['compose', 'images', '-q'], {\n cwd: dir,\n });\n\n const imageIds = stdout.split('\\n').filter(Boolean);\n if (imageIds.length > 0) {\n await execa('docker', ['rmi', ...imageIds]);\n }\n } catch (e) {\n // Images might not exist or already removed\n }\n }\n\n spinner.succeed('Cleanup complete');\n\n if (options.hard) {\n console.log(chalk.green('\\n✨ All Docker resources removed'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to start fresh.'));\n } else {\n console.log(chalk.green('\\n✨ Containers and volumes removed'));\n }\n } catch (error: any) {\n spinner.fail('Cleanup failed');\n throw error;\n }\n}\n","/**\n * update command - Update Baseboards to latest version\n */\n\nimport path from 'path';\nimport chalk from 'chalk';\nimport type { UpdateOptions } from '../types.js';\nimport { isScaffolded } from '../utils.js';\n\nexport async function update(\n directory: string,\n options: UpdateOptions\n): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n if (!isScaffolded(dir)) {\n console.error(chalk.red('\\nāŒ Error: Not a Baseboards project'));\n console.log(chalk.gray(' Run'), chalk.cyan('baseboards up'), chalk.gray('to scaffold a project first.'));\n process.exit(1);\n }\n\n console.log(chalk.blue.bold('\\nšŸ”„ Update Command\\n'));\n console.log(chalk.yellow('āš ļø This feature is coming soon!'));\n console.log(chalk.gray('\\nFor now, to update:'));\n console.log(chalk.gray('1. Update the CLI:'), chalk.cyan('npm install -g @weirdfingers/baseboards@latest'));\n console.log(chalk.gray('2. Pull new images:'), chalk.cyan('docker compose pull'));\n console.log(chalk.gray('3. Restart:'), chalk.cyan('baseboards down && baseboards up'));\n console.log();\n\n // TODO: Implement update logic:\n // 1. Check for new version on npm\n // 2. Scan for modified source files (git diff or timestamp check)\n // 3. If no modifications:\n // - Copy new templates (preserving config)\n // - Pull new Docker images\n // - Update package.json versions\n // 4. If modifications detected:\n // - Warn user\n // - Offer git-based merge if repo detected\n // - Create backup otherwise\n // 5. Preserve:\n // - All .env files\n // - config/*.yaml files\n // - data/storage/ directory\n}\n","/**\n * doctor command - Run diagnostics\n */\n\nimport path from 'path';\nimport fs from 'fs-extra';\nimport chalk from 'chalk';\nimport { checkPrerequisites, isScaffolded, detectMissingProviderKeys, getCliVersion } from '../utils.js';\n\nexport async function doctor(directory: string): Promise<void> {\n const dir = path.resolve(process.cwd(), directory);\n\n console.log(chalk.blue.bold('\\n🩺 Baseboards Diagnostics\\n'));\n\n // CLI Version\n console.log(chalk.cyan('CLI Version:'), getCliVersion());\n\n // Prerequisites\n console.log(chalk.cyan('\\nšŸ“‹ Prerequisites:'));\n const prereqs = await checkPrerequisites();\n\n console.log(\n chalk.gray(' Node.js:'),\n prereqs.node.installed\n ? prereqs.node.satisfies\n ? chalk.green(`āœ“ v${prereqs.node.version}`)\n : chalk.yellow(`āš ļø v${prereqs.node.version} (need v20+)`)\n : chalk.red('āœ— Not installed')\n );\n\n console.log(\n chalk.gray(' Docker:'),\n prereqs.docker.installed\n ? chalk.green(`āœ“ v${prereqs.docker.version}`)\n : chalk.red('āœ— Not installed')\n );\n\n if (prereqs.docker.composeVersion) {\n console.log(\n chalk.gray(' Docker Compose:'),\n chalk.green(`āœ“ v${prereqs.docker.composeVersion}`)\n );\n } else if (prereqs.docker.installed) {\n console.log(chalk.gray(' Docker Compose:'), chalk.red('āœ— Not available'));\n }\n\n console.log(chalk.gray(' Platform:'), prereqs.platform.name);\n if (prereqs.platform.isWSL) {\n console.log(chalk.gray(' WSL:'), chalk.blue('āœ“ Detected'));\n }\n\n // Project info\n console.log(chalk.cyan('\\nšŸ“‚ Project:'));\n const scaffolded = isScaffolded(dir);\n console.log(\n chalk.gray(' Scaffolded:'),\n scaffolded ? chalk.green('āœ“ Yes') : chalk.yellow('āœ— No')\n );\n\n if (scaffolded) {\n console.log(chalk.gray(' Directory:'), dir);\n\n // Check for key files\n const webPkg = path.join(dir, 'web/package.json');\n const apiPkg = path.join(dir, 'api/pyproject.toml');\n const composeFile = path.join(dir, 'compose.yaml');\n\n console.log(\n chalk.gray(' Web package:'),\n fs.existsSync(webPkg) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n console.log(\n chalk.gray(' API package:'),\n fs.existsSync(apiPkg) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n console.log(\n chalk.gray(' Compose file:'),\n fs.existsSync(composeFile) ? chalk.green('āœ“') : chalk.red('āœ—')\n );\n\n // Check .env files\n console.log(chalk.cyan('\\nšŸ” Environment:'));\n const webEnv = path.join(dir, 'web/.env');\n const apiEnv = path.join(dir, 'api/.env');\n const dockerEnv = path.join(dir, 'docker/.env');\n\n console.log(\n chalk.gray(' Web .env:'),\n fs.existsSync(webEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' API .env:'),\n fs.existsSync(apiEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' Docker .env:'),\n fs.existsSync(dockerEnv) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n\n // Check provider keys\n if (fs.existsSync(apiEnv)) {\n const missingKeys = detectMissingProviderKeys(apiEnv);\n if (missingKeys.length > 0) {\n console.log(\n chalk.gray(' Provider keys:'),\n chalk.yellow(`āš ļø ${missingKeys.length} missing`)\n );\n console.log(chalk.gray(' Missing:'), missingKeys.map(k => chalk.cyan(k)).join(', '));\n } else {\n console.log(chalk.gray(' Provider keys:'), chalk.green('āœ“ Configured'));\n }\n }\n\n // Check config files\n console.log(chalk.cyan('\\nāš™ļø Configuration:'));\n const generatorsYaml = path.join(dir, 'api/config/generators.yaml');\n const storageYaml = path.join(dir, 'api/config/storage_config.yaml');\n\n console.log(\n chalk.gray(' generators.yaml:'),\n fs.existsSync(generatorsYaml) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n console.log(\n chalk.gray(' storage_config.yaml:'),\n fs.existsSync(storageYaml) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n\n // Check storage directory\n const storageDir = path.join(dir, 'data/storage');\n console.log(\n chalk.gray(' Storage directory:'),\n fs.existsSync(storageDir) ? chalk.green('āœ“') : chalk.yellow('āœ— Missing')\n );\n }\n\n // Recommendations\n console.log(chalk.cyan('\\nšŸ’” Recommendations:'));\n const recommendations: string[] = [];\n\n if (!prereqs.node.satisfies) {\n recommendations.push('Upgrade Node.js to v20 or higher');\n }\n\n if (!prereqs.docker.installed) {\n recommendations.push('Install Docker Desktop: https://docs.docker.com/get-docker/');\n } else if (!prereqs.docker.composeVersion) {\n recommendations.push('Update Docker to get Compose v2');\n }\n\n if (!scaffolded) {\n recommendations.push('Run ' + chalk.cyan('baseboards up') + ' to scaffold a project');\n }\n\n if (recommendations.length === 0) {\n console.log(chalk.green(' āœ“ Everything looks good!'));\n } else {\n recommendations.forEach((rec) => console.log(chalk.yellow(' •'), rec));\n }\n\n console.log();\n}\n"],"mappings":"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","names":["fileURLToPath","chalk","execa","fs","path","chalk","packageJson","chalk","path","fs","execa","execa","path","chalk","ora","path","chalk","ora","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","path","chalk","execa","execa","path","chalk","ora","prompts","path","chalk","prompts","ora","execa","path","chalk","path","chalk","path","fs","chalk","path","chalk","fs","__filename","fileURLToPath","__dirname","chalk"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weirdfingers/baseboards",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "One-command launcher for the Boards image generation application",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,8 +14,8 @@ RUN pip install --no-cache-dir uv
14
14
  # Copy application code first (needed for version detection in pyproject.toml)
15
15
  COPY . .
16
16
 
17
- # Install Python dependencies with provider support
18
- RUN uv pip install --system --no-cache-dir .[providers]
17
+ # Install Python dependencies with all generator providers
18
+ RUN uv pip install --system --no-cache-dir .[generators-all]
19
19
 
20
20
  # Create non-root user
21
21
  RUN useradd -m -u 1001 apiuser && chown -R apiuser:apiuser /app
@@ -309,7 +309,7 @@ class TestMyGeneratorLive:
309
309
  # Verify
310
310
  assert result.outputs is not None
311
311
  assert len(result.outputs) > 0
312
- assert result.outputs[0].storage_url.startswith("https://")
312
+ assert result.outputs[0].storage_url is not None
313
313
  ```
314
314
 
315
315
  ### 3. Add Provider-Specific Skip Fixture (If New Provider)
@@ -3,7 +3,7 @@ Boards Backend SDK
3
3
  Open-source creative toolkit for AI-generated content
4
4
  """
5
5
 
6
- __version__ = "0.3.0"
6
+ __version__ = "0.4.0"
7
7
 
8
8
  from .config import settings
9
9
 
@@ -6,15 +6,22 @@ from .clerk import ClerkAuthAdapter
6
6
  from .jwt import JWTAuthAdapter
7
7
  from .none import NoAuthAdapter
8
8
  from .oidc import OIDCAdapter
9
- from .supabase import SupabaseAuthAdapter
10
9
 
10
+ # Always available adapters
11
11
  __all__ = [
12
12
  "AuthAdapter",
13
13
  "Principal",
14
- "SupabaseAuthAdapter",
15
14
  "JWTAuthAdapter",
16
15
  "NoAuthAdapter",
17
16
  "ClerkAuthAdapter",
18
17
  "Auth0OIDCAdapter",
19
18
  "OIDCAdapter",
20
19
  ]
20
+
21
+ # Optional auth providers - imported conditionally to avoid import errors
22
+ try:
23
+ from .supabase import SupabaseAuthAdapter
24
+
25
+ __all__.append("SupabaseAuthAdapter")
26
+ except ImportError:
27
+ pass
@@ -11,7 +11,15 @@ from .adapters.clerk import ClerkAuthAdapter
11
11
  from .adapters.jwt import JWTAuthAdapter
12
12
  from .adapters.none import NoAuthAdapter
13
13
  from .adapters.oidc import OIDCAdapter
14
- from .adapters.supabase import SupabaseAuthAdapter
14
+
15
+ # Optional Supabase adapter - imported conditionally
16
+ try:
17
+ from .adapters.supabase import SupabaseAuthAdapter
18
+
19
+ SUPABASE_AVAILABLE = True
20
+ except ImportError:
21
+ SUPABASE_AVAILABLE = False
22
+ SupabaseAuthAdapter = None # type: ignore
15
23
 
16
24
 
17
25
  def get_auth_adapter() -> AuthAdapter:
@@ -46,6 +54,12 @@ def get_auth_adapter() -> AuthAdapter:
46
54
  )
47
55
 
48
56
  elif provider == "supabase":
57
+ if not SUPABASE_AVAILABLE:
58
+ raise ValueError(
59
+ "Supabase auth provider is not available. "
60
+ "Install the supabase package: pip install 'weirdfingers-boards[auth-supabase]'"
61
+ )
62
+
49
63
  url = config.get("url") or os.getenv("SUPABASE_URL")
50
64
  service_role_key = config.get("service_role_key") or os.getenv("SUPABASE_SERVICE_ROLE_KEY")
51
65
 
@@ -55,7 +69,7 @@ def get_auth_adapter() -> AuthAdapter:
55
69
  "Set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY or provide in config."
56
70
  )
57
71
 
58
- return SupabaseAuthAdapter(url=url, service_role_key=service_role_key)
72
+ return SupabaseAuthAdapter(url=url, service_role_key=service_role_key) # type: ignore
59
73
 
60
74
  elif provider == "clerk":
61
75
  secret_key = config.get("secret_key") or os.getenv("CLERK_SECRET_KEY")
@@ -136,10 +136,6 @@ def _generation_to_artifact[T: (ImageArtifact, VideoArtifact, AudioArtifact, Tex
136
136
  if artifact_class == ImageArtifact:
137
137
  width = metadata.get("width")
138
138
  height = metadata.get("height")
139
- if width is None or height is None:
140
- raise ValueError(
141
- f"Generation {generation.id} missing image dimensions in output_metadata"
142
- )
143
139
  return ImageArtifact(
144
140
  generation_id=str(generation.id),
145
141
  storage_url=generation.storage_url,
@@ -151,10 +147,6 @@ def _generation_to_artifact[T: (ImageArtifact, VideoArtifact, AudioArtifact, Tex
151
147
  elif artifact_class == VideoArtifact:
152
148
  width = metadata.get("width")
153
149
  height = metadata.get("height")
154
- if width is None or height is None:
155
- raise ValueError(
156
- f"Generation {generation.id} missing video dimensions in output_metadata"
157
- )
158
150
  return VideoArtifact(
159
151
  generation_id=str(generation.id),
160
152
  storage_url=generation.storage_url,
@@ -28,16 +28,16 @@ class VideoArtifact(DigitalArtifact):
28
28
  """Represents a video file artifact from a generation."""
29
29
 
30
30
  duration: float | None = Field(None, description="Duration in seconds")
31
- width: int = Field(description="Video width in pixels")
32
- height: int = Field(description="Video height in pixels")
31
+ width: int | None = Field(None, description="Video width in pixels")
32
+ height: int | None = Field(None, description="Video height in pixels")
33
33
  fps: float | None = Field(None, description="Frames per second")
34
34
 
35
35
 
36
36
  class ImageArtifact(DigitalArtifact):
37
37
  """Represents an image file artifact from a generation."""
38
38
 
39
- width: int = Field(description="Image width in pixels")
40
- height: int = Field(description="Image height in pixels")
39
+ width: int | None = Field(None, description="Image width in pixels")
40
+ height: int | None = Field(None, description="Image height in pixels")
41
41
 
42
42
 
43
43
  class TextArtifact(DigitalArtifact):
@@ -94,8 +94,8 @@ class GeneratorExecutionContext(Protocol):
94
94
  self,
95
95
  storage_url: str,
96
96
  format: str,
97
- width: int,
98
- height: int,
97
+ width: int | None = None,
98
+ height: int | None = None,
99
99
  output_index: int = 0,
100
100
  ) -> ImageArtifact:
101
101
  """Store an image result to permanent storage."""
@@ -105,8 +105,8 @@ class GeneratorExecutionContext(Protocol):
105
105
  self,
106
106
  storage_url: str,
107
107
  format: str,
108
- width: int,
109
- height: int,
108
+ width: int | None = None,
109
+ height: int | None = None,
110
110
  duration: float | None = None,
111
111
  fps: float | None = None,
112
112
  output_index: int = 0,
@@ -157,8 +157,8 @@ class FalNanoBananaGenerator(BaseGenerator):
157
157
  artifacts = []
158
158
  for idx, image_data in enumerate(images):
159
159
  image_url = image_data.get("url")
160
- width = image_data.get("width", 1024)
161
- height = image_data.get("height", 1024)
160
+ width = image_data.get("width")
161
+ height = image_data.get("height")
162
162
 
163
163
  if not image_url:
164
164
  raise ValueError(f"Image {idx} missing URL in fal.ai response")
@@ -304,8 +304,8 @@ async def store_image_result(
304
304
  board_id: str,
305
305
  storage_url: str,
306
306
  format: str,
307
- width: int,
308
- height: int,
307
+ width: int | None = None,
308
+ height: int | None = None,
309
309
  ) -> ImageArtifact:
310
310
  """
311
311
  Store an image result by downloading from provider URL and uploading to storage.
@@ -317,8 +317,8 @@ async def store_image_result(
317
317
  board_id: Board ID for organization
318
318
  storage_url: Provider's temporary URL to download from
319
319
  format: Image format (png, jpg, etc.)
320
- width: Image width in pixels
321
- height: Image height in pixels
320
+ width: Image width in pixels (optional)
321
+ height: Image height in pixels (optional)
322
322
 
323
323
  Returns:
324
324
  ImageArtifact with permanent storage URL
@@ -374,8 +374,8 @@ async def store_video_result(
374
374
  board_id: str,
375
375
  storage_url: str,
376
376
  format: str,
377
- width: int,
378
- height: int,
377
+ width: int | None = None,
378
+ height: int | None = None,
379
379
  duration: float | None = None,
380
380
  fps: float | None = None,
381
381
  ) -> VideoArtifact:
@@ -389,8 +389,8 @@ async def store_video_result(
389
389
  board_id: Board ID for organization
390
390
  storage_url: Provider's temporary URL to download from
391
391
  format: Video format (mp4, webm, etc.)
392
- width: Video width in pixels
393
- height: Video height in pixels
392
+ width: Video width in pixels (optional)
393
+ height: Video height in pixels (optional)
394
394
  duration: Video duration in seconds (optional)
395
395
  fps: Frames per second (optional)
396
396
 
@@ -62,8 +62,8 @@ class GeneratorExecutionContext:
62
62
  self,
63
63
  storage_url: str,
64
64
  format: str,
65
- width: int,
66
- height: int,
65
+ width: int | None = None,
66
+ height: int | None = None,
67
67
  output_index: int = 0,
68
68
  ) -> ImageArtifact:
69
69
  """Store image generation result.
@@ -71,8 +71,8 @@ class GeneratorExecutionContext:
71
71
  Args:
72
72
  storage_url: URL to download the image from
73
73
  format: Image format (png, jpg, etc.)
74
- width: Image width in pixels
75
- height: Image height in pixels
74
+ width: Image width in pixels (optional)
75
+ height: Image height in pixels (optional)
76
76
  output_index: Index of this output in a batch (0 for primary, 1+ for additional)
77
77
 
78
78
  Returns:
@@ -111,8 +111,8 @@ class GeneratorExecutionContext:
111
111
  self,
112
112
  storage_url: str,
113
113
  format: str,
114
- width: int,
115
- height: int,
114
+ width: int | None = None,
115
+ height: int | None = None,
116
116
  duration: float | None = None,
117
117
  fps: float | None = None,
118
118
  output_index: int = 0,
@@ -122,8 +122,8 @@ class GeneratorExecutionContext:
122
122
  Args:
123
123
  storage_url: URL to download the video from
124
124
  format: Video format (mp4, webm, etc.)
125
- width: Video width in pixels
126
- height: Video height in pixels
125
+ width: Video width in pixels (optional)
126
+ height: Video height in pixels (optional)
127
127
  duration: Video duration in seconds (optional)
128
128
  fps: Frames per second (optional)
129
129
  output_index: Index of this output in a batch (0 for primary, 1+ for additional)
@@ -10,10 +10,11 @@
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
13
14
  "@radix-ui/react-navigation-menu": "^1.2.14",
14
15
  "@radix-ui/react-slot": "^1.2.3",
15
16
  "@tailwindcss/postcss": "^4.1.13",
16
- "@weirdfingers/boards": "^0.3.0",
17
+ "@weirdfingers/boards": "^0.4.0",
17
18
  "class-variance-authority": "^0.7.1",
18
19
  "clsx": "^2.0.0",
19
20
  "graphql": "^16.11.0",
@@ -1,8 +1,13 @@
1
1
  "use client";
2
2
 
3
- import { useState } from "react";
4
3
  import { Zap, Check } from "lucide-react";
5
4
  import type { JSONSchema7 } from "@weirdfingers/boards";
5
+ import {
6
+ DropdownMenu,
7
+ DropdownMenuContent,
8
+ DropdownMenuItem,
9
+ DropdownMenuTrigger,
10
+ } from "@/components/ui/dropdown-menu";
6
11
 
7
12
  export interface GeneratorInfo {
8
13
  name: string;
@@ -22,69 +27,61 @@ export function GeneratorSelector({
22
27
  selectedGenerator,
23
28
  onSelect,
24
29
  }: GeneratorSelectorProps) {
25
- const [isOpen, setIsOpen] = useState(false);
26
-
27
30
  const getGeneratorIcon = (name: string) => {
28
31
  // You can customize icons per generator here
29
32
  return <Zap className="w-4 h-4" />;
30
33
  };
31
34
 
32
35
  return (
33
- <div className="relative">
34
- <button
35
- onClick={() => setIsOpen(!isOpen)}
36
- className="px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 flex items-center gap-2"
37
- >
38
- {selectedGenerator ? (
39
- <>
40
- {getGeneratorIcon(selectedGenerator.name)}
41
- <span className="font-medium">{selectedGenerator.name}</span>
42
- </>
43
- ) : (
44
- <span className="text-gray-500">Select Generator</span>
45
- )}
46
- </button>
36
+ <DropdownMenu>
37
+ <DropdownMenuTrigger asChild>
38
+ <button className="px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 flex items-center gap-2">
39
+ {selectedGenerator ? (
40
+ <>
41
+ {getGeneratorIcon(selectedGenerator.name)}
42
+ <span className="font-medium">{selectedGenerator.name}</span>
43
+ </>
44
+ ) : (
45
+ <span className="text-gray-500">Select Generator</span>
46
+ )}
47
+ </button>
48
+ </DropdownMenuTrigger>
47
49
 
48
- {isOpen && (
49
- <>
50
- <div
51
- className="fixed inset-0 z-10"
52
- onClick={() => setIsOpen(false)}
53
- />
54
- <div className="absolute top-full mt-2 left-0 bg-white border border-gray-200 rounded-lg shadow-lg z-20 min-w-[250px] max-h-[400px] overflow-y-auto">
55
- {generators.map((generator) => (
56
- <button
57
- key={generator.name}
58
- onClick={() => {
59
- onSelect(generator);
60
- setIsOpen(false);
61
- }}
62
- className="w-full px-4 py-3 hover:bg-gray-50 flex items-start gap-3 text-left border-b border-gray-100 last:border-b-0"
63
- >
64
- <div className="flex-shrink-0 mt-0.5">
65
- {getGeneratorIcon(generator.name)}
66
- </div>
67
- <div className="flex-1 min-w-0">
68
- <div className="flex items-center gap-2">
69
- <span className="font-medium text-sm">
70
- {generator.name}
71
- </span>
72
- <span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded">
73
- {generator.artifactType}
74
- </span>
75
- </div>
76
- <p className="text-xs text-gray-600 mt-1">
77
- {generator.description}
78
- </p>
79
- </div>
80
- {selectedGenerator?.name === generator.name && (
81
- <Check className="w-4 h-4 text-green-600 flex-shrink-0" />
82
- )}
83
- </button>
84
- ))}
85
- </div>
86
- </>
87
- )}
88
- </div>
50
+ <DropdownMenuContent
51
+ className="min-w-[250px] max-w-[400px] max-h-[400px] overflow-y-auto"
52
+ align="start"
53
+ side="bottom"
54
+ sideOffset={8}
55
+ collisionPadding={8}
56
+ >
57
+ {generators.map((generator) => (
58
+ <DropdownMenuItem
59
+ key={generator.name}
60
+ onClick={() => onSelect(generator)}
61
+ className="px-4 py-3 flex items-start gap-3 cursor-pointer"
62
+ >
63
+ <div className="flex-shrink-0 mt-0.5">
64
+ {getGeneratorIcon(generator.name)}
65
+ </div>
66
+ <div className="flex-1 min-w-0">
67
+ <div className="flex items-center gap-2">
68
+ <span className="font-medium text-sm">
69
+ {generator.name}
70
+ </span>
71
+ <span className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded">
72
+ {generator.artifactType}
73
+ </span>
74
+ </div>
75
+ <p className="text-xs text-gray-600 mt-1">
76
+ {generator.description}
77
+ </p>
78
+ </div>
79
+ {selectedGenerator?.name === generator.name && (
80
+ <Check className="w-4 h-4 text-green-600 flex-shrink-0" />
81
+ )}
82
+ </DropdownMenuItem>
83
+ ))}
84
+ </DropdownMenuContent>
85
+ </DropdownMenu>
89
86
  );
90
87
  }
@@ -0,0 +1,200 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { Check, ChevronRight, Circle } from "lucide-react";
6
+
7
+ import { cn } from "@/lib/utils";
8
+
9
+ const DropdownMenu = DropdownMenuPrimitive.Root;
10
+
11
+ const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger;
12
+
13
+ const DropdownMenuGroup = DropdownMenuPrimitive.Group;
14
+
15
+ const DropdownMenuPortal = DropdownMenuPrimitive.Portal;
16
+
17
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
18
+
19
+ const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
20
+
21
+ const DropdownMenuSubTrigger = React.forwardRef<
22
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
23
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
24
+ inset?: boolean;
25
+ }
26
+ >(({ className, inset, children, ...props }, ref) => (
27
+ <DropdownMenuPrimitive.SubTrigger
28
+ ref={ref}
29
+ className={cn(
30
+ "flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
31
+ inset && "pl-8",
32
+ className
33
+ )}
34
+ {...props}
35
+ >
36
+ {children}
37
+ <ChevronRight className="ml-auto h-4 w-4" />
38
+ </DropdownMenuPrimitive.SubTrigger>
39
+ ));
40
+ DropdownMenuSubTrigger.displayName =
41
+ DropdownMenuPrimitive.SubTrigger.displayName;
42
+
43
+ const DropdownMenuSubContent = React.forwardRef<
44
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
45
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
46
+ >(({ className, ...props }, ref) => (
47
+ <DropdownMenuPrimitive.SubContent
48
+ ref={ref}
49
+ className={cn(
50
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
51
+ className
52
+ )}
53
+ {...props}
54
+ />
55
+ ));
56
+ DropdownMenuSubContent.displayName =
57
+ DropdownMenuPrimitive.SubContent.displayName;
58
+
59
+ const DropdownMenuContent = React.forwardRef<
60
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
61
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
62
+ >(({ className, sideOffset = 4, ...props }, ref) => (
63
+ <DropdownMenuPrimitive.Portal>
64
+ <DropdownMenuPrimitive.Content
65
+ ref={ref}
66
+ sideOffset={sideOffset}
67
+ className={cn(
68
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
69
+ className
70
+ )}
71
+ {...props}
72
+ />
73
+ </DropdownMenuPrimitive.Portal>
74
+ ));
75
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
76
+
77
+ const DropdownMenuItem = React.forwardRef<
78
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
79
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
80
+ inset?: boolean;
81
+ }
82
+ >(({ className, inset, ...props }, ref) => (
83
+ <DropdownMenuPrimitive.Item
84
+ ref={ref}
85
+ className={cn(
86
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
87
+ inset && "pl-8",
88
+ className
89
+ )}
90
+ {...props}
91
+ />
92
+ ));
93
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
94
+
95
+ const DropdownMenuCheckboxItem = React.forwardRef<
96
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
97
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
98
+ >(({ className, children, checked, ...props }, ref) => (
99
+ <DropdownMenuPrimitive.CheckboxItem
100
+ ref={ref}
101
+ className={cn(
102
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
103
+ className
104
+ )}
105
+ checked={checked}
106
+ {...props}
107
+ >
108
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
109
+ <DropdownMenuPrimitive.ItemIndicator>
110
+ <Check className="h-4 w-4" />
111
+ </DropdownMenuPrimitive.ItemIndicator>
112
+ </span>
113
+ {children}
114
+ </DropdownMenuPrimitive.CheckboxItem>
115
+ ));
116
+ DropdownMenuCheckboxItem.displayName =
117
+ DropdownMenuPrimitive.CheckboxItem.displayName;
118
+
119
+ const DropdownMenuRadioItem = React.forwardRef<
120
+ React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
121
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
122
+ >(({ className, children, ...props }, ref) => (
123
+ <DropdownMenuPrimitive.RadioItem
124
+ ref={ref}
125
+ className={cn(
126
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
127
+ className
128
+ )}
129
+ {...props}
130
+ >
131
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
132
+ <DropdownMenuPrimitive.ItemIndicator>
133
+ <Circle className="h-2 w-2 fill-current" />
134
+ </DropdownMenuPrimitive.ItemIndicator>
135
+ </span>
136
+ {children}
137
+ </DropdownMenuPrimitive.RadioItem>
138
+ ));
139
+ DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName;
140
+
141
+ const DropdownMenuLabel = React.forwardRef<
142
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
143
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
144
+ inset?: boolean;
145
+ }
146
+ >(({ className, inset, ...props }, ref) => (
147
+ <DropdownMenuPrimitive.Label
148
+ ref={ref}
149
+ className={cn(
150
+ "px-2 py-1.5 text-sm font-semibold",
151
+ inset && "pl-8",
152
+ className
153
+ )}
154
+ {...props}
155
+ />
156
+ ));
157
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
158
+
159
+ const DropdownMenuSeparator = React.forwardRef<
160
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
161
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
162
+ >(({ className, ...props }, ref) => (
163
+ <DropdownMenuPrimitive.Separator
164
+ ref={ref}
165
+ className={cn("-mx-1 my-1 h-px bg-muted", className)}
166
+ {...props}
167
+ />
168
+ ));
169
+ DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName;
170
+
171
+ const DropdownMenuShortcut = ({
172
+ className,
173
+ ...props
174
+ }: React.HTMLAttributes<HTMLSpanElement>) => {
175
+ return (
176
+ <span
177
+ className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
178
+ {...props}
179
+ />
180
+ );
181
+ };
182
+ DropdownMenuShortcut.displayName = "DropdownMenuShortcut";
183
+
184
+ export {
185
+ DropdownMenu,
186
+ DropdownMenuTrigger,
187
+ DropdownMenuContent,
188
+ DropdownMenuItem,
189
+ DropdownMenuCheckboxItem,
190
+ DropdownMenuRadioItem,
191
+ DropdownMenuLabel,
192
+ DropdownMenuSeparator,
193
+ DropdownMenuShortcut,
194
+ DropdownMenuGroup,
195
+ DropdownMenuPortal,
196
+ DropdownMenuSub,
197
+ DropdownMenuSubContent,
198
+ DropdownMenuSubTrigger,
199
+ DropdownMenuRadioGroup,
200
+ };