diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 7bddf2e..3399c0f 100644
--- a/.planning/ROADMAP.md
+++ b/.planning/ROADMAP.md
@@ -99,11 +99,11 @@ Plans:
2. Developer can run CLI command to scaffold a new theme with structure
3. Developer can run CLI command to scaffold a component within a plugin
4. Generated scaffolds follow project conventions and are immediately usable
-**Plans**: TBD
+**Plans**: 2 plans
Plans:
-- [ ] 05-01: CLI framework and plugin scaffolding
-- [ ] 05-02: Theme and component scaffolding
+- [ ] 05-01-PLAN.md - CLI framework setup with ZIO CLI and plugin scaffolding command
+- [ ] 05-02-PLAN.md - Theme scaffolding (blank/starter templates) and component scaffolding
### Phase 6: Backend Authentication
**Goal**: Secure admin backend with user accounts and role-based access
@@ -210,7 +210,7 @@ Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10
| 2. Plugin System | 0/3 | Planned | - |
| 3. Component System | 0/2 | Planned | - |
| 4. Theme Engine | 0/2 | Planned | - |
-| 5. CLI Scaffolding | 0/2 | Not started | - |
+| 5. CLI Scaffolding | 0/2 | Planned | - |
| 6. Backend Authentication | 0/3 | Not started | - |
| 7. Admin Forms & Lists | 0/3 | Not started | - |
| 8. Admin Dashboard | 0/2 | Not started | - |
diff --git a/.planning/phases/05-cli-scaffolding/05-01-PLAN.md b/.planning/phases/05-cli-scaffolding/05-01-PLAN.md
new file mode 100644
index 0000000..8f28060
--- /dev/null
+++ b/.planning/phases/05-cli-scaffolding/05-01-PLAN.md
@@ -0,0 +1,323 @@
+---
+phase: 05-cli-scaffolding
+plan: 01
+type: execute
+wave: 1
+depends_on: []
+files_modified:
+ - build.mill
+ - cli/src/Main.scala
+ - cli/src/commands/PluginCommands.scala
+ - cli/src/commands/VersionCommand.scala
+ - cli/src/scaffold/ScaffoldService.scala
+ - cli/src/scaffold/TemplateRenderer.scala
+ - cli/src/scaffold/templates/PluginTemplates.scala
+ - cli/src/output/ConsoleOutput.scala
+ - cli/src/config/ConfigDetector.scala
+ - cli/src/errors/ScaffoldError.scala
+autonomous: true
+
+must_haves:
+ truths:
+ - "Developer can run `./mill cli.run version` and see version info with Scala/JVM versions"
+ - "Developer can run `./mill cli.run plugin create Author.Name` and get a complete plugin scaffold"
+ - "Developer can run `./mill cli.run plugin create Author.Name --dry-run` to preview without creating"
+ - "Generated plugin scaffold has correct directory structure (Plugin.scala, plugin.yaml, empty dirs)"
+ - "Invalid plugin name format produces clear error message with suggestion"
+ artifacts:
+ - path: "cli/src/Main.scala"
+ provides: "CLI entry point with ZIO CLI"
+ contains: "ZIOCliDefault"
+ - path: "cli/src/commands/PluginCommands.scala"
+ provides: "Plugin subcommands"
+ exports: ["command"]
+ - path: "cli/src/scaffold/ScaffoldService.scala"
+ provides: "File generation service"
+ exports: ["ScaffoldService"]
+ - path: "cli/src/output/ConsoleOutput.scala"
+ provides: "Colored console output"
+ exports: ["ConsoleOutput"]
+ key_links:
+ - from: "cli/src/Main.scala"
+ to: "cli/src/commands/PluginCommands.scala"
+ via: "subcommands composition"
+ pattern: "PluginCommands\\.command"
+ - from: "cli/src/commands/PluginCommands.scala"
+ to: "cli/src/scaffold/ScaffoldService.scala"
+ via: "ZIO service dependency"
+ pattern: "ZIO\\.service\\[ScaffoldService\\]"
+---
+
+
+Create the `summer` CLI tool with ZIO CLI framework and implement plugin scaffolding command.
+
+Purpose: Establish CLI foundation for all developer tooling and deliver the first scaffolding capability (plugins).
+Output: Working `summer` CLI with `version` and `plugin create` commands.
+
+
+
+@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
+@/home/jin/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-cli-scaffolding/05-CONTEXT.md
+@.planning/phases/05-cli-scaffolding/05-RESEARCH.md
+@build.mill
+
+
+
+
+
+ Task 1: CLI Module Setup with ZIO CLI
+
+ build.mill
+ cli/src/Main.scala
+ cli/src/commands/VersionCommand.scala
+ cli/src/output/ConsoleOutput.scala
+ cli/src/config/ConfigDetector.scala
+ cli/src/errors/ScaffoldError.scala
+
+
+ Create a new `cli` Mill module alongside the existing `summercms` module.
+
+ 1. **Update build.mill:**
+ - Add `cli` module extending ScalaModule
+ - Same scalaVersion as summercms (3.3.4)
+ - Dependencies:
+ - `mvn"dev.zio::zio-cli:0.7.4"` (command parsing)
+ - `mvn"com.lihaoyi::os-lib:0.11.7"` (file operations)
+ - `mvn"com.lihaoyi::fansi:0.5.1"` (colored output)
+ - No dependency on summercms module (CLI is standalone)
+
+ 2. **Create cli/src/errors/ScaffoldError.scala:**
+ - Sealed trait `ScaffoldError` extending Exception
+ - Case classes: `InvalidName`, `AlreadyExists`, `FileSystemError`, `NotInProject`, `ConfigError`
+ - Each has clear message field
+
+ 3. **Create cli/src/output/ConsoleOutput.scala:**
+ - ZIO service trait with methods: `info`, `success`, `warning`, `error`, `nextSteps`
+ - Live layer using fansi for colors
+ - Accept `useColors: Boolean` parameter (for --no-color support)
+ - Use fansi.Color.Cyan for info, Green for success, Yellow for warning, Red for error
+ - `nextSteps` renders bold header with bulleted list
+
+ 4. **Create cli/src/config/ConfigDetector.scala:**
+ - ZIO service trait with `findProjectRoot` method
+ - Look for marker files: `build.mill`, `.summercms`
+ - Walk up directory tree from cwd until found or reach root
+ - Return `os.Path` of project root or fail with `NotInProject` error
+
+ 5. **Create cli/src/commands/VersionCommand.scala:**
+ - Define `VersionCommand` case object
+ - Create `command: Command[VersionCommand]` for "version" subcommand
+ - Handler prints: "SummerCMS 0.1.0 (Scala 3.3.4, JVM {java.version})"
+ - Note: Version hardcoded for now; BuildInfo generation deferred
+
+ 6. **Create cli/src/Main.scala:**
+ - Extend ZIOCliDefault
+ - Define global options:
+ - `--no-color` (Boolean, disables colors)
+ - `--verbose` / `-v` (Boolean)
+ - `--quiet` / `-q` (Boolean)
+ - `--no-interaction` (Boolean, for CI)
+ - Create top-level `summer` command with VersionCommand as subcommand
+ - Use CliApp.make with name="summer", version="0.1.0"
+ - Wire ConsoleOutput layer based on --no-color flag
+
+ **Do NOT use:** Complex dependency injection beyond ZIO layers. Keep it simple.
+
+
+ ```bash
+ ./mill cli.compile
+ ./mill cli.run version
+ # Should output: SummerCMS 0.1.0 (Scala 3.3.4, JVM 21)
+ ./mill cli.run --help
+ # Should show summer command with version subcommand
+ ```
+
+
+ CLI module compiles. `summer version` outputs version info. `summer --help` shows command structure.
+
+
+
+
+ Task 2: Plugin Scaffolding Command
+
+ cli/src/Main.scala
+ cli/src/commands/PluginCommands.scala
+ cli/src/scaffold/ScaffoldService.scala
+ cli/src/scaffold/TemplateRenderer.scala
+ cli/src/scaffold/templates/PluginTemplates.scala
+
+
+ Implement `summer plugin create Author.Name` command.
+
+ 1. **Create cli/src/scaffold/ScaffoldService.scala:**
+ - ZIO service trait with methods:
+ - `createDirectory(path: os.Path): IO[ScaffoldError, Unit]`
+ - `writeFile(path: os.Path, content: String): IO[ScaffoldError, Unit]`
+ - `exists(path: os.Path): UIO[Boolean]`
+ - Live layer using os-lib:
+ - `createDirectory`: `os.makeDir.all(path)`
+ - `writeFile`: `os.makeDir.all(path / os.up)` then `os.write(path, content)`
+ - `exists`: `os.exists(path)`
+ - Wrap all os-lib calls in `ZIO.attemptBlocking` with error mapping
+
+ 2. **Create cli/src/scaffold/TemplateRenderer.scala:**
+ - Case class `TemplateVars` with fields:
+ - `name`, `author`, `plugin` (raw inputs)
+ - Derived: `lowerName`, `lowerAuthor`, `lowerPlugin`
+ - Derived: `studlyName`, `studlyAuthor`, `studlyPlugin`
+ - Derived: `snakeName` (e.g., "BlogPost" -> "blog_post")
+ - Derived: `pluginId` (e.g., "acme.blog")
+ - Derived: `pluginNamespace` (e.g., "Acme.Blog")
+ - Derived: `timestamp` (yyyyMMdd_HHmmss format)
+ - Method `render(template: String, vars: TemplateVars): String`
+ - Replace placeholders: `{{name}}`, `{{lower_name}}`, `{{studly_name}}`, etc.
+ - Use distinctive delimiters to avoid collision with Scala code
+
+ 3. **Create cli/src/scaffold/templates/PluginTemplates.scala:**
+ - Object with template string vals:
+ - `pluginScala`: Plugin.scala with SummerPlugin trait stub
+ - `manifestYaml`: plugin.yaml with vendor, name, version, dependencies
+ - `langYaml`: Basic lang.yaml for en locale
+ - Include TODO comments showing where to add code
+ - Templates reference plugin types that will exist in Phase 2
+
+ 4. **Create cli/src/commands/PluginCommands.scala:**
+ - Sealed trait `PluginCommand` with case class `Create(name: String, dryRun: Boolean)`
+ - Define options:
+ - `--dry-run` / `-n`: Show what would be created
+ - Define args:
+ - `name`: Plugin name in Author.Name format
+ - Create `createCommand: Command[PluginCommand.Create]`
+ - Create `command: Command[PluginCommand]` with "plugin" as parent, "create" as subcommand
+ - Implement handler:
+ 1. Validate name format (Author.Name with regex `^[A-Z][a-zA-Z0-9]*\\.[A-Z][a-zA-Z0-9]*$`)
+ 2. Parse into (author, plugin) tuple
+ 3. Build plugin directory path: `plugins/{lower_author}/{lower_plugin}/`
+ 4. Check if directory exists -> fail with `AlreadyExists`
+ 5. Build file list with template content:
+ - `Plugin.scala`
+ - `plugin.yaml`
+ - `routes.scala` (empty routes placeholder)
+ - `models/.gitkeep`
+ - `controllers/.gitkeep`
+ - `components/.gitkeep`
+ - `resources/db/migration/.gitkeep`
+ - `resources/lang/en/lang.yaml`
+ - `resources/views/.gitkeep`
+ 6. If --dry-run: print what would be created
+ 7. Else: create files via ScaffoldService
+ 8. Print next steps (edit Plugin.scala, add models, add components)
+
+ 5. **Update cli/src/Main.scala:**
+ - Add PluginCommands.command to subcommands
+ - Wire ScaffoldService.live and ConfigDetector.live layers
+
+ **Pattern for name validation:**
+ ```scala
+ private val pluginNamePattern = "^([A-Z][a-zA-Z0-9]*)\\.([A-Z][a-zA-Z0-9]*)$".r
+
+ def parsePluginName(input: String): Either[String, (String, String)] =
+ input match
+ case pluginNamePattern(author, name) => Right((author, name))
+ case _ => Left(s"Invalid plugin name '$input'. Use Author.Name format (e.g., Acme.Blog)")
+ ```
+
+
+ ```bash
+ # Test plugin creation
+ ./mill cli.run plugin create Acme.Blog --dry-run
+ # Should list files that would be created
+
+ ./mill cli.run plugin create Acme.Blog
+ # Should create plugins/acme/blog/ with all files
+
+ ls -la plugins/acme/blog/
+ # Should show Plugin.scala, plugin.yaml, directories
+
+ cat plugins/acme/blog/Plugin.scala
+ # Should show valid Scala code with TODO comments
+
+ cat plugins/acme/blog/plugin.yaml
+ # Should show valid YAML manifest
+
+ # Test error case
+ ./mill cli.run plugin create invalidname
+ # Should error with suggestion
+
+ # Test already exists
+ ./mill cli.run plugin create Acme.Blog
+ # Should error with "already exists"
+ ```
+
+
+ `summer plugin create Author.Name` creates complete plugin scaffold. `--dry-run` previews changes. Invalid names produce helpful errors. Existing plugins are not overwritten.
+
+
+
+
+
+
+Full plan verification:
+
+1. CLI module builds independently:
+ ```bash
+ ./mill cli.compile
+ ```
+
+2. Version command works:
+ ```bash
+ ./mill cli.run version
+ ```
+
+3. Help shows all commands:
+ ```bash
+ ./mill cli.run --help
+ ./mill cli.run plugin --help
+ ./mill cli.run plugin create --help
+ ```
+
+4. Plugin scaffolding works end-to-end:
+ ```bash
+ rm -rf plugins/test/example # Cleanup if exists
+ ./mill cli.run plugin create Test.Example
+ ls plugins/test/example/
+ # Plugin.scala, plugin.yaml, routes.scala, models/, controllers/, components/, resources/
+ ```
+
+5. Dry-run mode works:
+ ```bash
+ ./mill cli.run plugin create Another.Plugin --dry-run
+ # Shows what would be created, but doesn't create
+ ls plugins/another/plugin 2>/dev/null || echo "Directory not created (correct)"
+ ```
+
+6. Error handling:
+ ```bash
+ ./mill cli.run plugin create badname
+ # Error: Invalid plugin name 'badname'. Use Author.Name format
+ ```
+
+
+
+- [ ] CLI module exists as separate Mill module with ZIO CLI, os-lib, fansi deps
+- [ ] `summer version` shows version with Scala/JVM info
+- [ ] `summer plugin create Author.Name` creates valid plugin scaffold
+- [ ] `--dry-run` shows preview without file creation
+- [ ] Invalid plugin names produce clear error with format suggestion
+- [ ] Existing plugin directories prevent overwrite with error
+- [ ] Generated Plugin.scala compiles (references future types with stubs)
+- [ ] Generated plugin.yaml is valid YAML
+- [ ] Colored output works with --no-color fallback
+- [ ] Next steps printed after successful scaffold
+
+
+
diff --git a/.planning/phases/05-cli-scaffolding/05-02-PLAN.md b/.planning/phases/05-cli-scaffolding/05-02-PLAN.md
new file mode 100644
index 0000000..781b0aa
--- /dev/null
+++ b/.planning/phases/05-cli-scaffolding/05-02-PLAN.md
@@ -0,0 +1,358 @@
+---
+phase: 05-cli-scaffolding
+plan: 02
+type: execute
+wave: 2
+depends_on: ["05-01"]
+files_modified:
+ - cli/src/Main.scala
+ - cli/src/commands/ThemeCommands.scala
+ - cli/src/commands/ComponentCommands.scala
+ - cli/src/scaffold/templates/ThemeTemplates.scala
+ - cli/src/scaffold/templates/ComponentTemplates.scala
+ - cli/src/output/Progress.scala
+autonomous: true
+
+must_haves:
+ truths:
+ - "Developer can run `summer theme create Author.Theme` and get a blank theme scaffold"
+ - "Developer can run `summer theme create Author.Theme --template=starter` and get a starter theme with Vite/Tailwind"
+ - "Developer can run `summer component create Author.Plugin MyComponent` and get a component scaffold"
+ - "Component scaffolding fails with clear error if plugin doesn't exist"
+ - "Theme scaffolding shows progress bar during file creation"
+ artifacts:
+ - path: "cli/src/commands/ThemeCommands.scala"
+ provides: "Theme scaffolding commands"
+ exports: ["command"]
+ - path: "cli/src/commands/ComponentCommands.scala"
+ provides: "Component scaffolding commands"
+ exports: ["command"]
+ - path: "cli/src/scaffold/templates/ThemeTemplates.scala"
+ provides: "Theme file templates (blank and starter)"
+ exports: ["blankThemeFiles", "starterThemeFiles"]
+ - path: "cli/src/scaffold/templates/ComponentTemplates.scala"
+ provides: "Component file templates"
+ exports: ["componentClass", "componentPartial", "componentYaml"]
+ key_links:
+ - from: "cli/src/Main.scala"
+ to: "cli/src/commands/ThemeCommands.scala"
+ via: "subcommands composition"
+ pattern: "ThemeCommands\\.command"
+ - from: "cli/src/commands/ComponentCommands.scala"
+ to: "cli/src/scaffold/ScaffoldService.scala"
+ via: "plugin existence check"
+ pattern: "exists.*Plugin\\.scala"
+---
+
+
+Implement theme and component scaffolding commands for the `summer` CLI.
+
+Purpose: Complete the core scaffolding capabilities for developer productivity (CORE-08, CORE-09).
+Output: Working `summer theme create` and `summer component create` commands with templates.
+
+
+
+@/home/jin/.claude/get-shit-done/workflows/execute-plan.md
+@/home/jin/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/05-cli-scaffolding/05-CONTEXT.md
+@.planning/phases/05-cli-scaffolding/05-RESEARCH.md
+@.planning/phases/05-cli-scaffolding/05-01-SUMMARY.md
+@build.mill
+@cli/src/Main.scala
+@cli/src/scaffold/ScaffoldService.scala
+
+
+
+
+
+ Task 1: Theme Scaffolding Command
+
+ cli/src/Main.scala
+ cli/src/commands/ThemeCommands.scala
+ cli/src/scaffold/templates/ThemeTemplates.scala
+ cli/src/output/Progress.scala
+
+
+ Implement `summer theme create Author.Theme [--template=blank|starter]` command.
+
+ 1. **Create cli/src/output/Progress.scala:**
+ - ZIO service trait with methods:
+ - `start(total: Int, description: String): UIO[ProgressHandle]`
+ - `update(handle: ProgressHandle, current: Int): UIO[Unit]`
+ - `complete(handle: ProgressHandle): UIO[Unit]`
+ - Case class `ProgressHandle(id: Long, total: Int, description: String)`
+ - Live layer implementation:
+ - Use `print` with `\r` for in-place updates
+ - Progress bar format: `[==== ] 4/10`
+ - Width: 30 characters for the bar
+ - On complete: print newline to preserve final state
+
+ 2. **Create cli/src/scaffold/templates/ThemeTemplates.scala:**
+ - Object with template string vals for BLANK theme:
+ - `themeYaml`: theme.yaml manifest (name, author, description, version)
+ - `defaultLayout`: layouts/default.htm with HTML5 boilerplate, {{page}} placeholder
+ - `homePage`: pages/home.htm with basic content, url="/", title="Home"
+ - `notFoundPage`: pages/404.htm with 404 content, url="/404"
+ - Additional templates for STARTER theme:
+ - `packageJson`: package.json with Vite, Tailwind dependencies
+ - `viteConfig`: vite.config.js with build output to assets/
+ - `tailwindCss`: assets/css/app.css with @tailwind directives
+ - `starterJs`: assets/js/app.js with basic imports
+ - `gitignore`: .gitignore for node_modules, dist
+ - Helper function `blankThemeFiles(dir: os.Path, vars: TemplateVars): List[(os.Path, String)]`
+ - Helper function `starterThemeFiles(dir: os.Path, vars: TemplateVars): List[(os.Path, String)]`
+ - Starter includes all blank files PLUS the npm/Vite files
+
+ 3. **Create cli/src/commands/ThemeCommands.scala:**
+ - Sealed trait `ThemeCommand` with case class `Create(name: String, template: String, dryRun: Boolean)`
+ - Define options:
+ - `--template`: String with default "blank", help "Theme template: blank or starter"
+ - `--dry-run` / `-n`: Show what would be created
+ - Define args:
+ - `name`: Theme name in Author.Name format
+ - Validate template option: must be "blank" or "starter"
+ - Create `createCommand: Command[ThemeCommand.Create]`
+ - Create `command: Command[ThemeCommand]` with "theme" parent, "create" subcommand
+ - Implement handler:
+ 1. Validate name format (same regex as plugin: `^[A-Z][a-zA-Z0-9]*\\.[A-Z][a-zA-Z0-9]*$`)
+ 2. Parse into (author, theme) tuple
+ 3. Build theme directory path: `themes/{lower_author}/{lower_theme}/`
+ 4. Check if directory exists -> fail with `AlreadyExists`
+ 5. Get file list based on template choice (blank or starter)
+ 6. If --dry-run: print file list
+ 7. Else:
+ - Start progress bar
+ - Create each file, update progress
+ - Complete progress bar
+ 8. Print next steps:
+ - Blank: "Add layouts, pages, and partials to your theme"
+ - Starter: "Run 'npm install' in theme directory, then 'npm run build'"
+
+ 4. **Update cli/src/Main.scala:**
+ - Add ThemeCommands.command to subcommands
+ - Wire Progress.live layer
+
+ **Template placeholder format (use {{var}} consistently):**
+ - `{{name}}`, `{{lower_name}}`, `{{studly_name}}`
+ - `{{author}}`, `{{lower_author}}`, `{{studly_author}}`
+
+
+ ```bash
+ # Test blank theme
+ ./mill cli.run theme create Acme.Corporate --dry-run
+ # Should list: theme.yaml, layouts/default.htm, pages/home.htm, pages/404.htm
+
+ ./mill cli.run theme create Acme.Corporate
+ # Should create themes/acme/corporate/ with blank structure
+
+ ls themes/acme/corporate/
+ # theme.yaml, layouts/, pages/, partials/, assets/
+
+ cat themes/acme/corporate/theme.yaml
+ # Valid YAML with name, author, version
+
+ # Test starter theme
+ ./mill cli.run theme create Acme.Modern --template=starter --dry-run
+ # Should list additional: package.json, vite.config.js, etc.
+
+ ./mill cli.run theme create Acme.Modern --template=starter
+ # Should create with npm files
+
+ ls themes/acme/modern/
+ # Includes package.json, vite.config.js, etc.
+
+ # Test invalid template
+ ./mill cli.run theme create Test.Theme --template=invalid
+ # Should error with valid options
+
+ # Test already exists
+ ./mill cli.run theme create Acme.Corporate
+ # Should error
+ ```
+
+
+ `summer theme create Author.Theme` creates blank theme. `--template=starter` adds Vite/Tailwind setup. Progress bar shows during creation. Invalid templates rejected.
+
+
+
+
+ Task 2: Component Scaffolding Command
+
+ cli/src/Main.scala
+ cli/src/commands/ComponentCommands.scala
+ cli/src/scaffold/templates/ComponentTemplates.scala
+
+
+ Implement `summer component create Author.Plugin ComponentName` command.
+
+ 1. **Create cli/src/scaffold/templates/ComponentTemplates.scala:**
+ - Object with template string vals:
+ - `componentScala`: Component class extending SummerComponent
+ - `componentDetails`: name and description
+ - `defineProperties`: returns List.empty (placeholder)
+ - `onRun`: returns Map.empty (placeholder)
+ - Include TODO comments for each section
+ - `componentPartial`: default.htm partial with basic HTML
+ - Include example of accessing component properties
+ - `componentYaml`: component.yaml with name, description, properties section
+ - Template variables used: `{{name}}`, `{{studly_name}}`, `{{lower_name}}`, `{{plugin_namespace}}`
+
+ 2. **Create cli/src/commands/ComponentCommands.scala:**
+ - Sealed trait `ComponentCommand` with case class `Create(plugin: String, name: String, dryRun: Boolean)`
+ - Define args:
+ - `plugin`: Plugin identifier in Author.Name format (required)
+ - `name`: Component name (single word, PascalCase)
+ - Define options:
+ - `--dry-run` / `-n`: Show what would be created
+ - Create `createCommand: Command[ComponentCommand.Create]`
+ - Create `command: Command[ComponentCommand]` with "component" parent, "create" subcommand
+ - Implement handler:
+ 1. Validate plugin name format (Author.Name)
+ 2. Parse plugin into (author, pluginName) tuple
+ 3. Validate component name (PascalCase: `^[A-Z][a-zA-Z0-9]*$`)
+ 4. Build plugin directory path: `plugins/{lower_author}/{lower_plugin}/`
+ 5. **Check if plugin exists** by verifying `Plugin.scala` exists in plugin dir
+ - If not: fail with `PluginNotFound` error (add to ScaffoldError if needed)
+ 6. Build component paths:
+ - `components/{StudlyName}.scala` - component class
+ - `components/{lower_name}/default.htm` - default partial
+ - `components/{lower_name}/component.yaml` - component config
+ 7. Check if component already exists -> fail with `AlreadyExists`
+ 8. If --dry-run: print file list
+ 9. Else: create files via ScaffoldService
+ 10. Print next steps:
+ - "Register component in Plugin.scala register() method"
+ - "Edit {StudlyName}.scala to add component logic"
+ - "Edit default.htm partial for component output"
+
+ 3. **Update cli/src/Main.scala:**
+ - Add ComponentCommands.command to subcommands
+
+ 4. **Add PluginNotFound to ScaffoldError if not present:**
+ - `case class PluginNotFound(pluginId: String) extends ScaffoldError`
+ - Message: "Plugin '{pluginId}' not found. Create it first with: summer plugin create {pluginId}"
+
+ **Component name validation pattern:**
+ ```scala
+ private val componentNamePattern = "^[A-Z][a-zA-Z0-9]*$".r
+
+ def validateComponentName(name: String): Either[String, String] =
+ if componentNamePattern.matches(name) then Right(name)
+ else Left(s"Invalid component name '$name'. Use PascalCase (e.g., BlogPost, FeaturedItems)")
+ ```
+
+
+ ```bash
+ # Ensure test plugin exists first
+ ./mill cli.run plugin create Test.Blog 2>/dev/null || true
+
+ # Test component creation
+ ./mill cli.run component create Test.Blog FeaturedPosts --dry-run
+ # Should list: FeaturedPosts.scala, featuredposts/default.htm, featuredposts/component.yaml
+
+ ./mill cli.run component create Test.Blog FeaturedPosts
+ # Should create component files
+
+ ls plugins/test/blog/components/
+ # FeaturedPosts.scala, featuredposts/
+
+ ls plugins/test/blog/components/featuredposts/
+ # default.htm, component.yaml
+
+ cat plugins/test/blog/components/FeaturedPosts.scala
+ # Valid Scala extending SummerComponent
+
+ # Test plugin not found
+ ./mill cli.run component create Nonexistent.Plugin MyComponent
+ # Error: Plugin 'Nonexistent.Plugin' not found...
+
+ # Test invalid component name
+ ./mill cli.run component create Test.Blog invalid_name
+ # Error: Invalid component name...
+
+ # Test component already exists
+ ./mill cli.run component create Test.Blog FeaturedPosts
+ # Error: Already exists...
+ ```
+
+
+ `summer component create Author.Plugin ComponentName` creates component scaffold inside existing plugin. Plugin existence is verified. Invalid names produce clear errors. Generated files include TODO comments.
+
+
+
+
+
+
+Full plan verification:
+
+1. Theme scaffolding works:
+ ```bash
+ rm -rf themes/verify # Cleanup
+ ./mill cli.run theme create Verify.Blank
+ ls themes/verify/blank/
+ # theme.yaml, layouts/, pages/, partials/, assets/
+ ```
+
+2. Starter theme includes npm files:
+ ```bash
+ rm -rf themes/verify/starter
+ ./mill cli.run theme create Verify.Starter --template=starter
+ ls themes/verify/starter/
+ # Includes package.json, vite.config.js
+ cat themes/verify/starter/package.json
+ # Valid JSON with Vite, Tailwind deps
+ ```
+
+3. Component scaffolding requires existing plugin:
+ ```bash
+ ./mill cli.run component create Missing.Plugin Test 2>&1 | grep -q "not found"
+ echo "Plugin check works: $?"
+ ```
+
+4. Component creates correct structure:
+ ```bash
+ # Use existing test plugin from above
+ ./mill cli.run component create Test.Blog LatestPosts
+ test -f plugins/test/blog/components/LatestPosts.scala && echo "Component class created"
+ test -f plugins/test/blog/components/latestposts/default.htm && echo "Partial created"
+ test -f plugins/test/blog/components/latestposts/component.yaml && echo "Config created"
+ ```
+
+5. All help commands work:
+ ```bash
+ ./mill cli.run theme --help
+ ./mill cli.run theme create --help
+ ./mill cli.run component --help
+ ./mill cli.run component create --help
+ ```
+
+6. Progress bar displays during theme creation:
+ ```bash
+ ./mill cli.run theme create Verify.Progress --template=starter 2>&1 | head -5
+ # Should show progress output
+ ```
+
+
+
+- [ ] `summer theme create Author.Theme` creates valid blank theme
+- [ ] `--template=starter` adds Vite/Tailwind configuration files
+- [ ] `--template=invalid` produces error listing valid options
+- [ ] Progress bar displays during theme file creation
+- [ ] `summer component create Plugin.Name ComponentName` creates component scaffold
+- [ ] Component command validates plugin exists before creating
+- [ ] Component command validates component name is PascalCase
+- [ ] Generated component includes .scala, .htm partial, .yaml config
+- [ ] All generated files contain appropriate TODO comments
+- [ ] Next steps printed after each successful scaffold
+- [ ] Dry-run mode works for both commands
+
+
+