Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions .claude/rules/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ path: "lib/src/cli/**/*.dart"

# CLI Commands

- Class hierarchy: `extends Command` from `magic_cli`
- Lifecycle: `configure(ArgParser parser)` for flags/options, `handle()` async for main logic
- Class hierarchy: `extends ArtisanCommand` (or `extends ArtisanInstallCommand` for install) from `fluttersdk_artisan`
- Lifecycle: `signature` DSL property (`'name {arg} {--flag}'`), `description` property, async `handle(ArtisanContext ctx)` method
- Boot mode: `CommandBoot.none` — commands run out-of-isolate (no Flutter app instance required)
- Overridable methods for testability: `getProjectRoot()`, `getStubSearchPaths()`, `runDartFormat()`
- Stub lookup: multiple search paths with fallback — use `_resolvePluginStubsDir()` helper
- Install command pattern: extends `ArtisanInstallCommand`, drives `install.yaml` manifest, fluent override for dynamic logic (feature toggles, conditional prompts, validation)
- Manifest-driven install: static scaffolding (config publish, provider injection) via `install.yaml` schema (plugin_name, publish, prompts, magic.provider, post_install)
- Stub lookup: standardized on `resolveMagicStubsDir().parent.parent` pattern — consistent across all plugins
- File operations via `FileHelper`: `fileExists()`, `findProjectRoot()`, `readFile()`, `writeFile()`
- Process execution: `Process.run('dart', ['format', '.'], workingDirectory: path)` — captured output, not streamed
- User feedback: `info('message')` for progress, `Log.warning('[MagicStarter] message')` for issues
- Non-interactive mode: support `--non-interactive` flag with `--features` option for CI
- User feedback: `ctx.output.info('message')` for progress, `ctx.output.error('message')` for errors
- Non-interactive mode: support `--non-interactive` flag with `--features` option for CI (via override, manifest cannot express conditional prompts)
- Validate host app is a Magic project before proceeding — check for `pubspec.yaml` with magic dependency
- Step-by-step file creation with `--force` flag to override existing files
- Transactional install ops: order writes before helper-backed mutations (helpers write synchronously, no rollback)
- Stub paths are relative to the plugin package — resolve via package URI, not hardcoded paths
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ coverage/
.ac/
.opencode/
.sisyphus/

# Local development path wiring (never committed; breaks CI which has no sibling paths)
pubspec_overrides.yaml
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

### Removed
- **Breaking: Standalone CLI entrypoint** — removed `bin/magic_starter.dart` and `dart run magic_starter:*` commands. Commands now surface via the host app's artisan dispatcher. Migrate: `dart run magic_starter:install` becomes `dart run <app>:artisan starter:install` (register `StarterArtisanProvider` in your app's `artisan.providers` list).
- **Removed magic_cli dependency** — CLI now builds on `fluttersdk_artisan ^0.0.7`.

### Changed
- **Install is manifest-driven** — static scaffolding (config publish, provider injection) now driven by `install.yaml` manifest; dynamic logic (feature toggles, interactive mode) handled by fluent override in `MagicStarterInstallCommand`.

### Added
- **Read-only MCP tool** — `starter_doctor` diagnostic command exposed as a read-only MCP tool via `StarterArtisanProvider.mcpTools()`.

### 🐛 Bug Fixes
- **Mobile Header Brand**: `MagicStarterAppLayout` mobile topbar now honors `navigationTheme.brandBuilder`, so custom brand widgets render consistently across breakpoints when provided ([#65](https://github.com/fluttersdk/magic_starter/issues/65))
- **Page Header Title Truncation**: `MagicStarterPageHeaderTheme` defaults now use `line-clamp-2` instead of `truncate` for `titleClassName` and `subtitleClassName`, so long titles wrap to a second line on narrow viewports (e.g. iPhone-width screens) instead of clipping to "AI sett..." ([#67](https://github.com/fluttersdk/magic_starter/issues/67))
Expand Down
36 changes: 26 additions & 10 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ Flutter starter kit for the Magic Framework. Pre-built Auth, Profile, Teams & No

## Commands

### Development and Testing

| Command | Description |
|---------|-------------|
| `flutter test --coverage` | Run all tests (~52 files, ~752 cases) with coverage |
Expand All @@ -16,11 +18,16 @@ Flutter starter kit for the Magic Framework. Pre-built Auth, Profile, Teams & No
| `flutter analyze --no-fatal-infos` | Static analysis (flutter_lints ^6.0) |
| `dart format .` | Format all code |
| `dart fix --apply` | Auto-fix lint issues |
| `dart run magic_starter:install` | Scaffold config + provider into consumer project |
| `dart run magic_starter:configure` | Interactive feature toggle configuration |
| `dart run magic_starter:doctor` | Diagnose project setup issues |
| `dart run magic_starter:publish` | Publish (copy) views/layouts for customization |
| `dart run magic_starter:uninstall` | Remove scaffolded files from consumer project |

### CLI Commands (via host app's artisan)

| Command | Description |
|---------|-------------|
| `dart run <app>:artisan starter:install` | Scaffold config + provider into consumer project (manifest-driven, hybrid for feature toggles) |
| `dart run <app>:artisan starter:configure` | Interactively toggle features and update config |
| `dart run <app>:artisan starter:doctor` | Diagnose project setup and installation issues (also exposed as read-only MCP tool) |
| `dart run <app>:artisan starter:publish --tag=views:auth.login` | Publish (copy) a view/layout for customization |
| `dart run <app>:artisan starter:uninstall` | Remove scaffolded files from consumer project |

## Architecture

Expand All @@ -44,12 +51,21 @@ lib/
│ ├── layouts/ # AppLayout (authenticated), GuestLayout (auth pages)
│ ├── views/ # auth/, profile/, teams/, notifications/
│ └── widgets/ # 13 reusable Wind UI components
└── cli/ # install, configure, doctor, publish, uninstall
bin/
└── magic_starter.dart # CLI entry point — registers commands with Kernel
assets/stubs/ # Stub templates for code generation
└── cli/ # Install command + provider (no bin/ — surfaces via host app's artisan)
├── starter_artisan_provider.dart # 5 commands (install, configure, doctor, publish, uninstall) + read-only mcpTool (starter_doctor)
├── commands/
│ ├── magic_starter_install_command.dart # ArtisanInstallCommand + install.yaml manifest
│ ├── magic_starter_configure_command.dart # Feature toggle configuration
│ ├── magic_starter_doctor_command.dart # Diagnose setup issues (exposed as MCP tool)
│ ├── magic_starter_publish_command.dart # Publish views for customization
│ └── magic_starter_uninstall_command.dart # Remove scaffolded files
└── cli.dart # Barrel export (provider + commands)
install.yaml # Manifest for static install scaffolding (config publish, provider injection)
assets/stubs/ # Stub templates for code generation
```

**CLI Surface**: Commands surface via the host app's artisan registry, not a standalone bin/. The host app registers `StarterArtisanProvider` in its `artisan.providers`, exposing 5 commands and 1 read-only MCP tool.

**Data flow:** App boot → `MagicStarterServiceProvider.register()` → binds manager + 9 Gate abilities → `boot()` → resolves config, registers default views → Feature-gated routes registered → Controllers handle HTTP via `Http.*` → Views render via `MagicStatefulView` + Wind UI

**Pure Dart** — no android/, ios/, or native platform code.
Expand Down Expand Up @@ -111,7 +127,7 @@ Every feature, fix, or refactor must go through the red-green-refactor cycle:
| Hardcoding dialog classNames | All modal classNames must come from `MagicStarter.manager.modalTheme` — never hardcode in widget build methods |
| Theme sub-theme ordering | `useTheme()` sets all 7 sub-themes at once; individual `useFormTheme()` etc. can override after. Call unified first if using both |
| Slot not rendering | `MagicStarter.view.slot(viewKey, slotName, builder)` must be called before the view is built. Views call `buildSlot()` at build time |
| Published view not loading | `dart run magic_starter:publish` copies views to `lib/resources/views/starter/`. Auto-wire adds `MagicStarter.view.register()` to AppServiceProvider |
| Published view not loading | `dart run <app>:artisan starter:publish` copies views to `lib/resources/views/starter/`. Auto-wire adds `MagicStarter.view.register()` to AppServiceProvider |
| `Icons.*` in `build()` | Extract as `static const _iconName = Icons.xxx` — required for Flutter web tree-shaking |

## Skills & Extensions
Expand Down
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,13 @@ dependencies:
### 2. Install configuration

```bash
dart run magic_starter:install
dart run <app>:artisan starter:install
```

This generates `lib/config/magic_starter.dart`, injects `MagicStarterServiceProvider` into `lib/config/app.dart`, and wires the `magicStarterConfig` factory into `lib/main.dart`.

(If `magic_starter` is not yet registered as a provider in your host app, register `StarterArtisanProvider` in your app's `artisan.providers` list first.)

### 3. Boot the provider

The `MagicStarterServiceProvider` is automatically registered during install. On app boot, it:
Expand Down Expand Up @@ -412,28 +414,30 @@ Copy a view file directly into your host app for complete control. Changes are p

```bash
# Publish a single view
dart run magic_starter:publish --tag=views:auth.login
dart run <app>:artisan starter:publish --tag=views:auth.login

# Publish all auth views
dart run magic_starter:publish --tag=views:auth
dart run <app>:artisan starter:publish --tag=views:auth

# Run doctor to verify wiring
dart run magic_starter:doctor
dart run <app>:artisan starter:doctor
```

The publish command copies the view to `lib/resources/views/starter/` and auto-wires it into `AppServiceProvider` so it takes effect immediately. Use `dart run magic_starter:doctor` to confirm registration status.
The publish command copies the view to `lib/resources/views/starter/` and auto-wires it into `AppServiceProvider` so it takes effect immediately. Use `dart run <app>:artisan starter:doctor` to confirm registration status.

---

## CLI Tools

Run any of these commands via your host app's artisan dispatcher:

| Command | Description |
|---------|-------------|
| `dart run magic_starter:install` | Scaffold config, provider, and routes into your Magic project |
| `dart run magic_starter:configure` | Interactively toggle features and update config |
| `dart run magic_starter:doctor` | Verify installation, check dependencies, diagnose issues |
| `dart run magic_starter:publish` | Publish starter views and layouts for full customization |
| `dart run magic_starter:uninstall` | Remove starter config, provider, and routes from your project |
| `dart run <app>:artisan starter:install` | Scaffold config, provider, and routes into your Magic project |
| `dart run <app>:artisan starter:configure` | Interactively toggle features and update config |
| `dart run <app>:artisan starter:doctor` | Verify installation, check dependencies, diagnose issues |
| `dart run <app>:artisan starter:publish --tag=views:auth.login` | Publish a specific view/layout for full customization |
| `dart run <app>:artisan starter:uninstall` | Remove starter config, provider, and routes from your project |

---

Expand Down
23 changes: 0 additions & 23 deletions bin/magic_starter.dart

This file was deleted.

39 changes: 39 additions & 0 deletions install.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# magic_starter install manifest (PluginInstaller DSL schema v1).
#
# Hybrid layout:
#
# - The STATIC layer below declares only the manifest-expressible work:
# injecting MagicStarterServiceProvider into the host's
# lib/config/app.dart providers list (`magic.provider`). The injection is
# idempotent (ConfigEditor lookahead regex no-ops on re-run).
#
# - The DYNAMIC layer lives in MagicStarterInstallCommand's fluent override
# (_applyFluentOverride). It is manifest-inexpressible because it is
# feature-gated: the 13 opt-in feature toggles drive which config flags,
# route registrations, models, and AppServiceProvider boot blocks are
# scaffolded. Manifest `prompts:` run unconditionally during prepare(),
# so feature-gated selection stays in code (--non-interactive / --features
# branch + interactive Prompt.confirm loop).
#
# - Stub-backed publishes (config, middleware, models, dashboard, routes,
# translations) route through the command's getStubSearchPaths() seam, not
# the manifest publish: section, because that seam is overridable for tests
# and resolves the plugin's assets/stubs/ without a consumer .dart_tool.
#
# See doc/install_yaml_schema.md (in fluttersdk_artisan) for the full schema.

plugin_name: magic_starter

magic:
# Static layer: inject the starter service provider into the host's
# lib/config/app.dart providers list. Import defaults to
# package:magic_starter/magic_starter.dart (derived from plugin_name).
provider: MagicStarterServiceProvider

post_install:
message: |
Magic Starter installed. Next steps:

1. Review lib/config/magic_starter.dart feature toggles. Re-run
`dart run <app>:artisan starter:configure` to adjust them.
2. Run `flutter pub get && flutter run` to verify the bootstrap.
29 changes: 29 additions & 0 deletions lib/cli.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/// CLI barrel for Magic Starter.
///
/// Exposes ONLY the artisan-CLI surface (MagicStarterArtisanProvider). Does
/// NOT export the Flutter runtime barrel (lib/magic_starter.dart), so this
/// barrel is safe for consumption from pure-Dart artisan dispatchers that must
/// not import Flutter or dart:ui.
///
/// Plugin auto-discovery in `fluttersdk_artisan` imports this barrel and
/// instantiates [MagicStarterArtisanProvider] by convention. Consumer apps
/// that register Magic Starter's CLI commands in a `bin/artisan.dart` should
/// import this barrel:
///
/// ```dart
/// import 'package:fluttersdk_artisan/artisan.dart';
/// import 'package:magic_starter/cli.dart' show MagicStarterArtisanProvider;
///
/// Future<void> main(List<String> args) async {
/// final registry = ArtisanRegistry()
/// ..registerProvider(MagicStarterArtisanProvider());
/// exit(await ArtisanApplication(registry: registry).dispatch(args));
/// }
/// ```
///
/// Runtime consumers (lib/main.dart of a Magic Starter app) continue to
/// import `package:magic_starter/magic_starter.dart` for facades and the
/// full Flutter surface.
library;

export 'src/cli/starter_artisan_provider.dart';
1 change: 1 addition & 0 deletions lib/magic_starter.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Magic Starter plugin exports

export 'src/cli/starter_artisan_provider.dart';
export 'src/facades/magic_starter.dart';
export 'src/magic_starter_manager.dart';
export 'src/providers/magic_starter_service_provider.dart';
Comment on lines 1 to 6
Expand Down
31 changes: 0 additions & 31 deletions lib/src/cli/cli.dart

This file was deleted.

Loading
Loading