@weirdfingers/baseboards 0.2.1 → 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.
Files changed (56) hide show
  1. package/README.md +14 -4
  2. package/dist/index.js +13 -4
  3. package/dist/index.js.map +1 -1
  4. package/package.json +1 -1
  5. package/templates/api/ARTIFACT_RESOLUTION_GUIDE.md +148 -0
  6. package/templates/api/Dockerfile +2 -2
  7. package/templates/api/README.md +138 -6
  8. package/templates/api/config/generators.yaml +41 -7
  9. package/templates/api/docs/TESTING_LIVE_APIS.md +417 -0
  10. package/templates/api/pyproject.toml +49 -9
  11. package/templates/api/src/boards/__init__.py +1 -1
  12. package/templates/api/src/boards/auth/adapters/__init__.py +9 -2
  13. package/templates/api/src/boards/auth/factory.py +16 -2
  14. package/templates/api/src/boards/generators/__init__.py +2 -2
  15. package/templates/api/src/boards/generators/artifact_resolution.py +372 -0
  16. package/templates/api/src/boards/generators/artifacts.py +4 -4
  17. package/templates/api/src/boards/generators/base.py +8 -4
  18. package/templates/api/src/boards/generators/implementations/__init__.py +4 -2
  19. package/templates/api/src/boards/generators/implementations/fal/__init__.py +25 -0
  20. package/templates/api/src/boards/generators/implementations/fal/audio/__init__.py +4 -0
  21. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_music_v2.py +173 -0
  22. package/templates/api/src/boards/generators/implementations/fal/audio/minimax_speech_2_6_turbo.py +221 -0
  23. package/templates/api/src/boards/generators/implementations/fal/image/__init__.py +17 -0
  24. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_kontext.py +216 -0
  25. package/templates/api/src/boards/generators/implementations/fal/image/flux_pro_ultra.py +197 -0
  26. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview.py +191 -0
  27. package/templates/api/src/boards/generators/implementations/fal/image/imagen4_preview_fast.py +179 -0
  28. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana.py +183 -0
  29. package/templates/api/src/boards/generators/implementations/fal/image/nano_banana_edit.py +212 -0
  30. package/templates/api/src/boards/generators/implementations/fal/utils.py +61 -0
  31. package/templates/api/src/boards/generators/implementations/fal/video/__init__.py +13 -0
  32. package/templates/api/src/boards/generators/implementations/fal/video/kling_video_v2_5_turbo_pro_text_to_video.py +168 -0
  33. package/templates/api/src/boards/generators/implementations/fal/video/sync_lipsync_v2.py +167 -0
  34. package/templates/api/src/boards/generators/implementations/fal/video/veo31_first_last_frame_to_video.py +180 -0
  35. package/templates/api/src/boards/generators/implementations/openai/__init__.py +1 -0
  36. package/templates/api/src/boards/generators/implementations/openai/audio/__init__.py +1 -0
  37. package/templates/api/src/boards/generators/implementations/{audio → openai/audio}/whisper.py +9 -6
  38. package/templates/api/src/boards/generators/implementations/openai/image/__init__.py +1 -0
  39. package/templates/api/src/boards/generators/implementations/{image → openai/image}/dalle3.py +8 -5
  40. package/templates/api/src/boards/generators/implementations/replicate/__init__.py +1 -0
  41. package/templates/api/src/boards/generators/implementations/replicate/image/__init__.py +1 -0
  42. package/templates/api/src/boards/generators/implementations/{image → replicate/image}/flux_pro.py +8 -5
  43. package/templates/api/src/boards/generators/implementations/replicate/video/__init__.py +1 -0
  44. package/templates/api/src/boards/generators/implementations/{video → replicate/video}/lipsync.py +9 -6
  45. package/templates/api/src/boards/generators/resolution.py +80 -20
  46. package/templates/api/src/boards/jobs/repository.py +49 -0
  47. package/templates/api/src/boards/storage/factory.py +16 -6
  48. package/templates/api/src/boards/workers/actors.py +69 -5
  49. package/templates/api/src/boards/workers/context.py +177 -21
  50. package/templates/web/package.json +2 -1
  51. package/templates/web/src/components/boards/GenerationInput.tsx +154 -52
  52. package/templates/web/src/components/boards/GeneratorSelector.tsx +57 -59
  53. package/templates/web/src/components/ui/dropdown-menu.tsx +200 -0
  54. package/templates/api/src/boards/generators/implementations/audio/__init__.py +0 -3
  55. package/templates/api/src/boards/generators/implementations/image/__init__.py +0 -3
  56. package/templates/api/src/boards/generators/implementations/video/__init__.py +0 -3
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
 
@@ -186,6 +186,16 @@ Templates are copied during build time via `scripts/prepare-templates.js`.
186
186
 
187
187
  When users run `baseboards up`, templates are copied to their machine and Docker Compose orchestrates the services.
188
188
 
189
+ ## Community & Social
190
+
191
+ Join the Weirdfingers community:
192
+
193
+ - **TikTok**: [https://www.tiktok.com/@weirdfingers](https://www.tiktok.com/@weirdfingers)
194
+ - **X (Twitter)**: [https://x.com/_Weirdfingers_](https://x.com/_Weirdfingers_)
195
+ - **YouTube**: [https://www.youtube.com/@Weirdfingers](https://www.youtube.com/@Weirdfingers)
196
+ - **Discord**: [https://discord.gg/rvVuHyuPEx](https://discord.gg/rvVuHyuPEx)
197
+ - **Instagram**: [https://www.instagram.com/_weirdfingers_/](https://www.instagram.com/_weirdfingers_/)
198
+
189
199
  ## License
190
200
 
191
201
  MIT
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) {
@@ -369,6 +369,12 @@ async function promptForApiKeys(ctx) {
369
369
  message: "Replicate API Key (https://replicate.com/account/api-tokens):",
370
370
  initial: ""
371
371
  },
372
+ {
373
+ type: "text",
374
+ name: "FAL_KEY",
375
+ message: "Fal AI API Key (https://fal.ai/dashboard/keys):",
376
+ initial: ""
377
+ },
372
378
  {
373
379
  type: "text",
374
380
  name: "OPENAI_API_KEY",
@@ -380,6 +386,9 @@ async function promptForApiKeys(ctx) {
380
386
  if (response.REPLICATE_API_TOKEN && response.REPLICATE_API_TOKEN.trim()) {
381
387
  apiKeys.REPLICATE_API_TOKEN = response.REPLICATE_API_TOKEN.trim();
382
388
  }
389
+ if (response.FAL_KEY && response.FAL_KEY.trim()) {
390
+ apiKeys.FAL_KEY = response.FAL_KEY.trim();
391
+ }
383
392
  if (response.OPENAI_API_KEY && response.OPENAI_API_KEY.trim()) {
384
393
  apiKeys.OPENAI_API_KEY = response.OPENAI_API_KEY.trim();
385
394
  }
@@ -850,7 +859,7 @@ var program = new Command();
850
859
  program.name("baseboards").description(
851
860
  "\u{1F3A8} One-command launcher for the Boards image generation platform"
852
861
  ).version(packageJson.version, "-v, --version", "Output the current version");
853
- 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);
854
863
  program.command("down").description("Stop Baseboards").argument("[directory]", "Project directory", ".").option("--volumes", "Also remove volumes").action(down);
855
864
  program.command("logs").description("View logs from services").argument("[directory]", "Project directory", ".").argument(
856
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: \"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.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.2.1",
3
+ "version": "0.4.0",
4
4
  "description": "One-command launcher for the Boards image generation application",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,148 @@
1
+ # Artifact Resolution Guide
2
+
3
+ This guide explains how artifact resolution works in the Boards generator system.
4
+
5
+ ## Overview
6
+
7
+ Generators can accept artifacts (images, videos, audio, etc.) from previous generations as inputs. The artifact resolution system automatically converts generation ID strings (UUIDs) into typed artifact objects with proper validation.
8
+
9
+ ## How It Works
10
+
11
+ ### Automatic Type Introspection
12
+
13
+ The system uses Pydantic type introspection to automatically detect which fields are artifacts. **No manual configuration needed!**
14
+
15
+ ```python
16
+ from pydantic import BaseModel, Field
17
+ from boards.generators.artifacts import ImageArtifact, VideoArtifact
18
+
19
+ class MyGeneratorInput(BaseModel):
20
+ """Input schema for my generator."""
21
+
22
+ prompt: str = Field(description="Text prompt")
23
+
24
+ # These are automatically detected as artifact fields
25
+ image_source: ImageArtifact = Field(description="Input image")
26
+ video_sources: list[VideoArtifact] = Field(description="Input videos")
27
+
28
+ # Regular fields are ignored
29
+ num_outputs: int = Field(default=1, ge=1, le=10)
30
+ ```
31
+
32
+ That's it! No decorators, no class variables, no manual registration needed.
33
+
34
+ ### What Happens Automatically
35
+
36
+ When a generation is submitted with input parameters like:
37
+
38
+ ```json
39
+ {
40
+ "prompt": "enhance the colors",
41
+ "image_source": "550e8400-e29b-41d4-a716-446655440000",
42
+ "video_sources": [
43
+ "7c9e6679-7425-40de-944b-e07fc1f90ae7",
44
+ "8f9e6679-7425-40de-944b-e07fc1f90ae8"
45
+ ]
46
+ }
47
+ ```
48
+
49
+ The system:
50
+
51
+ 1. **Detects** that `image_source` and `video_sources` are artifact fields (via type introspection)
52
+ 2. **Queries** the database to fetch the generations by ID
53
+ 3. **Validates** each generation:
54
+ - Belongs to the same tenant (access control)
55
+ - Has status="completed" (can't use pending generations)
56
+ - Has the correct artifact type (can't use a video where an image is expected)
57
+ 4. **Converts** generation records to typed artifact objects
58
+ 5. **Validates** the input with Pydantic
59
+
60
+ All of this happens automatically in [actors.py](src/boards/workers/actors.py).
61
+
62
+ ## Supported Patterns
63
+
64
+ ### Single Artifact
65
+
66
+ ```python
67
+ image_source: ImageArtifact = Field(...)
68
+ ```
69
+
70
+ **Input**: Single generation ID string (e.g., `"550e8400-e29b-41d4-a716-446655440000"`)
71
+ **Output**: Single `ImageArtifact` object
72
+
73
+ ### List of Artifacts
74
+
75
+ ```python
76
+ image_sources: list[ImageArtifact] = Field(min_length=1)
77
+ ```
78
+
79
+ **Input**: List of generation ID strings (e.g., `["id1", "id2"]`)
80
+ **Output**: List of `ImageArtifact` objects (always a list, even if input has one ID)
81
+
82
+ **Important**: If the field type is `list[ImageArtifact]`, the resolved value will ALWAYS be a list, even if only one generation ID is provided. The system respects the type annotation.
83
+
84
+ ### Optional Artifacts
85
+
86
+ ```python
87
+ reference_image: ImageArtifact | None = Field(default=None)
88
+ ```
89
+
90
+ Field can be omitted or set to `null`.
91
+
92
+ ## Supported Artifact Types
93
+
94
+ - `ImageArtifact` - Images (PNG, JPEG, WebP, etc.)
95
+ - `VideoArtifact` - Videos (MP4, WebM, etc.)
96
+ - `AudioArtifact` - Audio files (MP3, WAV, etc.)
97
+ - `TextArtifact` - Text content
98
+
99
+ ## Error Handling
100
+
101
+ The system provides clear error messages when validation fails:
102
+
103
+ - `"Generation {id} not found"` - Invalid generation ID
104
+ - `"Access denied to generation {id} - tenant mismatch"` - User doesn't own this generation
105
+ - `"Generation {id} is not completed (status: pending)"` - Can't use incomplete generations
106
+ - `"Generation {id} has wrong artifact type: expected image, got video"` - Type mismatch
107
+
108
+ ## Examples
109
+
110
+ ### Image-to-Image Editor
111
+
112
+ ```python
113
+ class ImageEditInput(BaseModel):
114
+ prompt: str = Field(description="Editing instructions")
115
+ image_source: ImageArtifact = Field(description="Image to edit")
116
+ strength: float = Field(default=0.8, ge=0.0, le=1.0)
117
+ ```
118
+
119
+ ### Multi-Image Collage
120
+
121
+ ```python
122
+ class CollageInput(BaseModel):
123
+ images: list[ImageArtifact] = Field(
124
+ description="Images to combine",
125
+ min_length=2,
126
+ max_length=9
127
+ )
128
+ layout: Literal["grid", "freeform"] = Field(default="grid")
129
+ ```
130
+
131
+ ### Video + Audio Mixer
132
+
133
+ ```python
134
+ class VideoAudioMixInput(BaseModel):
135
+ video_source: VideoArtifact = Field(description="Video track")
136
+ audio_source: AudioArtifact = Field(description="Audio track")
137
+ volume: float = Field(default=1.0, ge=0.0, le=2.0)
138
+ ```
139
+
140
+ ## Implementation Details
141
+
142
+ The automatic detection is handled by:
143
+
144
+ - [`extract_artifact_fields()`](src/boards/generators/artifact_resolution.py) - Introspects schema to find artifact fields
145
+ - [`resolve_input_artifacts()`](src/boards/generators/artifact_resolution.py) - Resolves generation IDs to artifacts
146
+ - [`resolve_generation_ids_to_artifacts()`](src/boards/generators/artifact_resolution.py) - Database queries and validation
147
+
148
+ See [artifact_resolution.py](src/boards/generators/artifact_resolution.py) for the full implementation.
@@ -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