fluttersdk_artisan 0.0.2
fluttersdk_artisan: ^0.0.2 copied to clipboard
Composable Dart CLI framework and stdio MCP server for Flutter. Scaffolding, code generation, transactional plugin installs, hot reload, REPL, and AI agent tooling.
Changelog #
All notable changes to this project will be documented in this file.
This project follows Semantic Versioning 2.0.0. Entries follow the Keep a Changelog shape.
[Unreleased] #
0.0.2 - 2026-05-20 #
Breaking #
consumer:scaffoldrenamed toinstall: the commandconsumer:scaffoldno longer exists. Consumers must usedart run fluttersdk_artisan installgoing forward.bin/artisan.dartrenamed tobin/dispatcher.dart: the scaffold output path has changed. Migration: re-rundart run fluttersdk_artisan install --forceto scaffold the new file layout, then update any scripts or CI steps that referencebin/artisan.dart.- Old stub removed:
consumer_artisan_bin.dart.stubis gone; the replacement stub isdispatcher.dart.stub. InstallCommand->InstallArtisanCommand: the public class on thepackage:fluttersdk_artisan/artisan.dartbarrel now carries theArtisanprefix so plugins exporting their ownInstallCommand(notifications, deeplink, etc.) no longer collide with the substrate at import time.
Added #
installauto-chainsmake:fast-cli: after writing the consumer entry and barrels,installautomatically runsmake:fast-clisobin/fsa(the AOT-compiled fast startup wrapper) is ready without a separate manual step.- New stub
dispatcher.dart.stub: replaces the formerconsumer_artisan_bin.dart.stub; rendered tobin/dispatcher.dartduringinstall. artisan start --cdp-port=Nopt-in flag (lib/src/commands/start_command.dart): when set, pre-launches Chrome with--remote-debugging-port=N --remote-allow-origins=* --user-data-dir=/tmp/dusk-chrome-N, runsflutter run -d web-server --web-port=N --web-experimental-hot-reload --host-vmservice-port=N(silent remap from--device=chrome), waits for the "is being served at" log line, navigates Chrome to the served URL via inline CDP, then scrapesvmServiceUrifrom the DWDS log once the debugger client connected. WriteschromePid+cdpPort+tmpProfileDirto~/.artisan/state.json. Default flow (no--cdp-port) unchanged. Gates the branch onflutter --version --machine>= 3.30.0 with an actionable upgrade error.artisan stopChrome cleanup: whenstate['chromePid'] != null, sends SIGTERM, waits the grace period, escalates to SIGKILL if the process is still alive, deletestmpProfileDir. Inlines the SIGTERM-grace-SIGKILL pattern fromfluttersdk_dusk/lib/src/utils/chrome_reaper.dart:216-264to avoid inverting the plugin dependency direction (see Deferred Ideas: V1.x consolidation).artisan doctorFlutter SDK gate: new checkflutter sdk >= 3.30.0 (for --cdp-port)registered in the existing_Checklist. Advisory_cdpUpgradeWarningwriteln (mirrors_checkStaleMcpJsonpattern) surfaces an upgrade message when the SDK is too old. Required forflutter/flutter#170612(DWDS WebSocket hot reload on-d web-server).StateFileschema: newcdpPortfield (int | null, --cdp-port value passed to start; null when CDP not enabled). Roundtrip test added.- GitHub Release auto-creation in
publish.yml: newgithub-releasejob (depends on the OIDCpublishjob) extracts the## [<version>] - <date>block fromCHANGELOG.mdviaawkand creates a matching GitHub Release usingsoftprops/action-gh-release@v2. Falls back to a stub body linking toCHANGELOG.mdwhen the section is missing. make:fast-clibuiltin command +bin/fsawrapper (lib/src/commands/make_fast_cli_command.dart,assets/stubs/bin_fsa.sh.stub): scaffold a POSIX shell wrapper that compilesbin/dispatcher.dartinto an AOT binary viadart build cli, cached at.artisan/cli-bundle/bundle/bin/dispatcher. Wrapper auto-detects staleness (pubspec.lock SHA256 + Dart SDK version + pubspec.yaml mtime greater than pubspec.lock) and re-compiles transparently. Result: ~50ms startup for./bin/fsa <cmd>vs ~3s fordart run fluttersdk_artisan <cmd>(no "Running build hooks..." overhead). Idempotent on re-run;--forceoverwrites the wrapper. POSIX-only V1 (macOS + Linux); Windows .cmd variant deferred. The existingdart run fluttersdk_artisanpath is unchanged and remains the canonical CLI entry.
Changed #
plugin:installpreflight scope: the wrapper-presence check (bin/artisan.dartmust exist) moved out of the shared preflight into the legacy-injection branch only. Canonical-scaffold projects (lib/app/_plugins.g.dartpresent) now route through.artisan/plugins.jsonregistration without tripping the legacy gate, even when the consumer never wrote abin/artisan.dartfile.artisan start --vm-service-portnow plumbs through toflutter runas--host-vmservice-port=Nand is recorded instate.jsonso downstream tools see the actual bound port. The option was declared but never read in 0.0.1.publish.ymltriggers narrowed topush.tagsandworkflow_dispatch. Removed therelease.types: [published]trigger to avoid release/publish recursion (the workflow creates the release itself now). Tag-first flow:git tag X.Y.Z && git push origin X.Y.Z-> validate -> pub.dev publish via OIDC -> GitHub Release with CHANGELOG-driven notes.
Fixed #
start --cdp-portordering deadlock: 0.0.1 scraped the VM Service URI before navigating Chrome, which deadlocked under DWDS (the URI emits only after a debugger client connects). Restructured the branch to wait for the "is being served at" log line, navigate Chrome to the served URL, then scrape. Three end-to-end Chrome / CDP automation issues fixed alongside:--no-first-run+--no-default-browser-checkon the launch argv,Page.navigatenow targets the page-level WebSocket from/jsoninstead of the browser-level/json/version, automation-noise suppression flags added.start --cdp-port=<non-int>now returns exit 1 with an actionable error instead of silently falling through to the non-CDP path.stopno longer emitsChrome SIGTERM sent...unconditionally: the boolean fromProcess.killPidis checked and anot deliveredwarning surfaces when the signal could not land (process already gone, permission denied).doctorSDK gate now tolerates beta channel strings like3.30.0-1.0.preand missing trailing segments (3.30), matchingStartCommand.compareSemverexactly so the doctor cannot flag a version the start command would accept.- VM Service retries once on the transient DWDS
WipError: Promise was collectedand on the stale-isolate sentinel fromcallServiceExtension, so a single device-target switch or DWDS hiccup does not surface to consumers. installconsumer-wrapper detection accepts bothbin/dispatcher.dart(canonical post-rename) andbin/artisan.dart(legacy) as valid wrappers for auto-delegation.InstallArtisanCommand.scaffoldIntoauto-triggersPluginsRefreshCommandin-process when<root>/.artisan/plugins.jsonexists so the codegen barrel does not get overwritten with an empty list.bin/fsaPID-aware lock recovery: when a prior./bin/fsainvocation crashed mid-build the wrapper used to deadlock on.artisan/.fsa.lockfor every subsequent run. The stub now reads the holder PID, verifies the process is still alive, and reclaims the lock when it is not.
Known limitations #
- MCP schema drift for
artisan_start: the hand-authored_commandInputSchema('start')atlib/src/mcp/mcp_server.dartdoes NOT advertise the new--cdp-portflag. The substrate dispatch still routes CLI args through correctly, but agents drivingartisan_startvia MCP cannot discover the flag from the schema. V1.x backlog: auto-derive the schema fromArtisanCommand.signature/configure(ArgParser)so it cannot drift.
0.0.1 - 2026-05-19 #
Initial public release of fluttersdk_artisan. Pure Dart 3.4+ CLI framework and stdio MCP server for Flutter and Dart projects. Pana score 160 / 160 on first publish.
Commands #
21 builtin commands across 6 groups:
- Lifecycle:
start [--device],stop,restart,status,logs [--follow],reload,hot-restart. - Scaffolding:
consumer:scaffold(canonical wrapper for plain Flutter),make:plugin <name>(plugin package skeleton with workspace enrollment + magic-mode upgrade detection),make:command <Name>(context-aware command scaffold for plugin or consumer). - Plugin management:
plugin:install <name>(manifest-driven, scaffold-aware, or legacy injection),plugin:uninstall <name>,plugins:refresh,commands:refresh. - MCP:
mcp:serve(stdio JSON-RPC server with three-layer filter),mcp:install(writes.mcp.jsonentry, idempotent),mcp:uninstall. - Introspection:
doctor(preflight checks),list(all registered commands grouped by:namespace),help <cmd>. - REPL:
tinker [--eval=<expr>](VM Service evaluate against the running Flutter app; interactive mode falls back when--evalis absent).
Stdio MCP server #
- Built on
dart_mcp ^0.5.1. Entry point:dart run fluttersdk_artisan:mcp. - 10 substrate tools (always-on) surface artisan's own CLI as MCP tools so an LLM agent can bootstrap a Flutter app without leaving the chat: lifecycle quartet (
artisan_start/artisan_stop/artisan_restart/artisan_reload/artisan_hot_restart) plusartisan_status,artisan_logs,artisan_doctor,artisan_list,artisan_tinker. - Plugin tools register via
ArtisanServiceProvider.mcpTools(). The MCP server collects them at startup;ArtisanMcpToolCollisionExceptionattributes name clashes to specific providers. - Three-layer Cargo-style filter:
.artisan/mcp.json(file) +ARTISAN_MCP_TOOLS_*/ARTISAN_MCP_PACKAGES_*(env) +--include-tool/--exclude-tool/--include-package/--exclude-packageCLI flags. Allow uses first-non-null; deny is the union; deny wins everywhere. - Soft-fail at initialize when no Flutter app is running; lazy-reconnects to VM Service on the next tool call. Tool calls without a running app return an actionable
CallToolResult(isError: true)so the model can self-correct.
Plugin protocol #
- Declarative
install.yamlmanifest:publish,magic.provider,magic.configFactory,magic.routes,native.android(permissions / metaData / gradle plugins / dependencies),native.ios/native.macos(plistEntries / podEntries),native.web(headInjections / metaTags),env,prompts,placeholders,bootstrap_command. - Procedural escape hatch: subclass
ArtisanInstallCommandand drivePluginInstallerfor plugins that need runtime branching the schema cannot express.
PluginInstaller DSL #
Fluent builder for install operations across file ops (publishConfig, writeFile, mergeJson), source-injection ops (injectImport, injectBefore, injectAfter, injectProvider, injectConfigFactory, injectRoute), native ops (injectAndroidPermission, injectAndroidMetaData, injectAndroidGradlePlugin, injectAndroidGradleDependency, injectIosPlistEntry, injectIosPodEntry, injectMacosPlistEntry, injectMacosPodEntry, injectIntoWebHead, addWebMetaTag), and env ops (injectEnvVar). Operations enqueue against a sealed InstallOperation hierarchy with 26 final variants.
Idempotency, atomicity, reversibility #
ConflictDetectorflagsunmanaged-filewhen a target exists outside any recorded install (--forceoverride + scaffold-fingerprint heuristic auto-allows the default Flutter counter-app overwrite).InstallTransactionwrites via.tmp+ atomic rename; concurrent readers never observe partial state.ConfigEditor.insertCodeAfterPattern+insertCodeBeforePatternearly-return when the target already contains the code (idempotent re-install).PluginInstaller.injectProvider+injectConfigFactoryappend to the END of the list using lookahead-anchored regex(?=\s*\n\s*\])so new entries appear where readers expect them (6-space indent matches the scaffold style).InstallTransactionrecords every applied op to.artisan/installed/<plugin>.json(op type, target path, content hash).plugin:uninstallreversesWriteFile(delete + stub-hash tamper check);InjectImportandInjectAfterPatternlog[skipped](anchor-bracketed inject markers pending V1.1).
Signature DSL #
Command surface declared inline: String get signature => 'cmd:name {arg} {--flag=default}'. configure(ArgParser) remains available as an explicit fallback. The MCP server's per-command inputSchema is verified against the underlying command's argument declarations so the wire contract cannot drift from the CLI surface.
Codegen barrels #
lib/app/commands/_index.g.dart(consumer commands), regenerated bymake:commandandcommands:refresh.lib/app/_plugins.g.dart(plugin providers), regenerated byplugin:install <name>andplugins:refreshfrom the.artisan/plugins.jsonregistry.
Both write through .tmp + atomic rename; never hand-edit.
VM Service hooks #
tinkerevaluates Dart expressions against the connected isolate via the VM Service evaluate RPC. Magic facade autocomplete + Eloquent model casting come from the optionalmagic_tinkerintegration when registered.reload/hot-restartwriter/Rto theflutter runprocess's stdin via a POSIX FIFO bridge so detached processes still accept interactive commands.
Testable primitives #
VirtualFsinterface +InMemoryFsimplementation. Every installer pathway is unit-testable without touching the host filesystem.InstallContext.test(fs, prompt, stubs, clock, projectRoot)fixture builder.ArtisanContext.bare(MapInput, BufferedOutput)for command-level tests.BufferedOutputcapturesinfo/success/warning/errorlines for assertion.
Programmatic API #
runArtisan(args, baseProviders:, delegateToConsumer:, collectMcpTools:): universal entry point.- Single barrel:
package:fluttersdk_artisan/artisan.dartexposes the full public surface (Application,Command,Input/Output,ServiceProvider,Context,VmServiceClient,StateFile, stub system, helpers, installer, registry).
CI + automated publishing #
.github/workflows/ci.yml: format + analyze + tests + 80 % line-coverage floor (viacoverage:format_coverage+ awk gate) + dry-run archive on every push to master and every pull request..github/workflows/publish.yml: SemVer tag push triggers validate -> pub.dev publish via the officialdart-lang/setup-dart/.github/workflows/publish.yml@v1reusable workflow with OIDC authentication (no long-lived secret stored). Requires "Automated publishing from GitHub Actions" enabled on the pub.dev package admin page with the repository pinned tofluttersdk/artisan..github/dependabot.yml: weekly pub bumps (root +example/) plus weekly GitHub Actions version bumps..github/ISSUE_TEMPLATE/: structuredbug_report.yml,feature_request.yml,documentation.yml, plus aconfig.ymlthat disables blank issues. Bug + feature templates use a 14-option Subsystem dropdown matching thelib/src/layout.
Documentation #
README.mdtwo-path Quick Start (plain Flutter viaconsumer:scaffold; Magic-managed viamagic:install).- 17-file
doc/tree underhttps://fluttersdk.com/artisan/X/Y:getting-started/,commands/,mcp/,plugins/,reference/. skills/fluttersdk-artisan/: LLM-agent skill (SKILL.md+ 5 references:commands.md,install-yaml-schema.md,installer-dsl.md,mcp-server.md,plugin-authoring.md).llms.txtat repo root per llmstxt.org spec.
Compatibility #
- Dart SDK
>=3.4.0 <4.0.0. Pure Dart core; Flutter optional (only required by plugins that consume Flutter SDK APIs). - Platforms: Android, iOS, macOS, Linux, Windows. Web unsupported (relies on
dart:io). - V1 lifecycle commands (
start,stop,reload,hot-restart) use POSIX FIFO stdin pipes viamkfifo. macOS and Linux only; Windows unsupported for the lifecycle quartet (other commands work).