diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 438ab80..77f0883 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -148,6 +148,15 @@ "./skills/lean-jev-usage", "./skills/ask-jev" ] + }, + { + "name": "lean-flitz", + "source": "./plugins/lean-flitz", + "description": "Publish a Flitz bundle of the current Flutter project with `flitz publish` and get back the install link. Works in Claude Tag, where the Agent Proxy injects the organization API key, and locally with `flitz login`.", + "skills": [ + "./skills/lean-flitz-usage", + "./skills/publish-flitz-bundle" + ] } ] } diff --git a/README.md b/README.md index 58ba0f3..a241079 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ See [Discover and install plugins](https://code.claude.com/docs/en/discover-plug | [`flutter-patrol`](https://github.com/leancodepl/patrol) | Writing Patrol E2E tests, test architecture and key conventions | Patrol CLI and Patrol MCP | | [`flutter-marionette`](plugins/flutter-marionette/) | Driving a live debug app through Marionette MCP to explore, smoke-check and debug UI | Marionette MCP and an app-side binding | | [`flutter-read-logs`](plugins/flutter-read-logs/) | `/read-logs` gives Claude the logs of your last `flutter run` | One editor setting | +| [`lean-flitz`](plugins/lean-flitz/) | `/publish-flitz-bundle` publishes a [Flitz](https://docs.flitz.dev) bundle of the project and returns the install link, from Claude Code or Claude Tag | A Flitz organization and a project set up for Flitz | `flutter-forms` and `flutter-patrol` install straight from the repositories of the packages they cover, so they stay in step with each release. Each plugin's `README.md` covers its setup. diff --git a/plugins/lean-flitz/.claude-plugin/plugin.json b/plugins/lean-flitz/.claude-plugin/plugin.json new file mode 100644 index 0000000..2774a4d --- /dev/null +++ b/plugins/lean-flitz/.claude-plugin/plugin.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", + "name": "lean-flitz", + "displayName": "Lean Flitz", + "description": "Publish a Flitz bundle of the current Flutter project with `flitz publish` and get back the install link. Works in Claude Tag, where the Agent Proxy injects the organization API key, and locally with `flitz login`.", + "version": "0.1.0", + "author": { + "name": "LeanCode" + }, + "homepage": "https://github.com/leancodepl/ai-plugins", + "repository": "https://github.com/leancodepl/ai-plugins", + "keywords": [ + "flitz", + "flutter", + "preview", + "bundle", + "claude-tag" + ], + "skills": "./skills/" +} diff --git a/plugins/lean-flitz/CHANGELOG.md b/plugins/lean-flitz/CHANGELOG.md new file mode 100644 index 0000000..8ac49f7 --- /dev/null +++ b/plugins/lean-flitz/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## 0.1.0 + +First public release, carried over from LeanCode's internal marketplace at the same version. + +- `skills/publish-flitz-bundle/SKILL.md`: run `flitz publish --yes --json` in the project directory and report the landing page. Installs the CLI when missing, and sets a placeholder `FLITZ_APIKEY` for the Claude Tag Agent Proxy when no other credential exists. +- `skills/lean-flitz-usage/SKILL.md`: what the plugin does, where it runs, and what it needs. +- The README explains Flitz itself and what a project needs before the skill can publish, for readers who have not used it. diff --git a/plugins/lean-flitz/README.md b/plugins/lean-flitz/README.md new file mode 100644 index 0000000..c1126ba --- /dev/null +++ b/plugins/lean-flitz/README.md @@ -0,0 +1,126 @@ +# lean-flitz + +> **Experimental.** New, and not yet run in every setup it documents. + +Ask Claude for a build of your Flutter app, and get back a link a teammate opens on their phone. +In Slack with Claude Tag: + +> @Claude build a Flitz bundle of the app + +Claude runs `flitz publish` in the project and replies with the landing page: a QR code and an +install button for whoever asked. + +## What Flitz is + +[Flitz](https://docs.flitz.dev) is a tool from LeanCode for putting work-in-progress builds of a +Flutter app on a real device in seconds, without rebuilding, reinstalling, or going through a +store. It splits the app in two: + +- **The host** is a normal, signed build of your app with the Flitz loader embedded. You build + and install it once per device. +- **The bundle** is your Dart code and assets, compiled to portable bytecode. `flitz publish` + builds one from the project and uploads it, as often as you like. + +Opening the publish link on a device that has the host installed downloads the bundle and runs +it. Flitz is for your own team's development and test builds; production ships the normal way. +[How it works](https://docs.flitz.dev/concepts/how-it-works) has the long version. + +This plugin covers only the publish step. Setting Flitz up in an app is a one-time job described +in the [Flitz getting-started guide](https://docs.flitz.dev/getting-started). + +## Skills + +| Skill | What it does | +| ----- | ------------ | +| [`/lean-flitz-usage`](skills/lean-flitz-usage/SKILL.md) | Explains the plugin and where it runs | +| [`/lean-flitz:publish-flitz-bundle`](skills/publish-flitz-bundle/SKILL.md) | Publishes a bundle of the project and returns the landing page | + +## Before you start + +- **A Flitz organization with an active license.** The publish is a licensed step; without one it + fails with exit 3 before anything is compiled. +- **A project set up for Flitz:** a `flitz.yaml` pinning the SDK (written by `flitz sdk use`), + and a host build installed on the device that will open the link. The skill checks for + `flitz.yaml` and stops when there is none. +- **Linux x64 or an Apple Silicon Mac** to publish from. Windows, Intel Macs, and Linux arm64 are + not supported by the CLI. + +## How it works + +1. **Project.** The directory holding `flitz.yaml`. The bundle is built from the working tree as + it is; nothing is cloned, checked out, or stashed. +2. **CLI.** `flitz` from `PATH`, else the official installer, which verifies the binary's SHA-256. +3. **Credential**, in this order: `FLITZ_APIKEY` from the environment; the stored `flitz login`; + otherwise a placeholder `FLITZ_APIKEY` for the Claude Tag Agent Proxy to replace. The order + matters because inside the CLI a set `FLITZ_APIKEY` wins over a login, so the placeholder must + never shadow one. +4. **Publish.** `flitz publish --yes --json` from the project directory. stdout is the JSON result, + and the skill reports its `page_url`. + +## Setup + +### Claude Tag + +[Claude Tag](https://claude.com/docs/claude-tag/overview) runs Claude in Slack. An Owner or a +Claude Tag admin sets this up once per bundle (the named set of connectors, allowed domains, and +plugins that covers a channel); the channel then needs nothing else. The steps follow +[Add connections](https://claude.com/docs/claude-tag/admins/add-connections). + +1. **Attach the plugin.** Add `lean-flitz` to the bundle that covers the channel. +2. **Allow the domains.** Add `*.flitz.dev` (the CLI download, `api.flitz.dev`, and the bundle + host `d.flitz.dev`) and `pub.dev`, for Flutter dependencies, as domains on that bundle. +3. **Add a connector** whose allowed websites include `api.flitz.dev`, holding your Flitz + organization's API key. `flitz apikey status` shows whether a key exists, and + `flitz apikey create` mints one from a signed-in session (see + [flitz apikey](https://docs.flitz.dev/cli/apikey)). The Agent Proxy swaps the key into the + CLI's requests after they leave the sandbox, so the key is never in the sandbox, the + environment, or the conversation. + +The organization has one API key, shared with any CI that publishes. Reuse it for the connector +rather than rotating it, since rotating breaks every pipeline that holds the old one. + +Verify by asking in the channel for a bundle of any project with a `flitz.yaml`. A credential +problem surfaces as exit 3. + +### Local (Claude Code) + +Run `flitz login` once. The CLI is installed on first use, or by hand: + +```bash +curl -fsSL https://download.flitz.dev/install.sh | sh +``` + +In a sandboxed Claude Code session the publish needs to write `~/.local/bin`, `~/.cache/flitz`, +and `~/.pub-cache`, and to reach `*.flitz.dev` and `pub.dev`; allow those or run it outside the +sandbox. + +A first publish downloads the pinned SDK (several hundred MB), so it takes a few minutes. Later +ones take about a minute. + +## Troubleshooting + +| Symptom | Cause | Fix | +| ------- | ----- | --- | +| Skill stops with no `flitz.yaml` found | The app is not set up for Flitz | Follow the [getting-started guide](https://docs.flitz.dev/getting-started), then run `flitz sdk use` | +| Exit 3 with the placeholder key | No Flitz connector in the channel's bundle, or its key is wrong | Add or fix the connector above | +| Exit 3 locally | No login, an expired one, or an inactive license | `flitz login`; for a license, the organization's Owner restores it in the [portal](https://docs.flitz.dev/portal) | +| Exit 3 with `FLITZ_APIKEY` set in the environment | A stale key shadows your login | `unset FLITZ_APIKEY` | +| `curl: (22) … 403` while installing | `*.flitz.dev` is not an allowed domain | Add it to the bundle, or to the local sandbox's allowlist | +| Pub resolution fails during the build | `pub.dev` is blocked | Allow `pub.dev` the same way | +| Exit 4 ending in `no response within 30s` | The upload has a fixed 30-second limit | Re-run from a faster network, or trim large assets | + +Every exit code is described in the [Flitz CLI docs](https://docs.flitz.dev/cli/exit-codes). + +## What leaves the machine + +The app's compiled code and assets go to Flitz, and the returned link downloads them. Links do not +expire and cannot be revoked, and anyone holding one can install the bundle, so the skill posts a +link only where it was asked for. + +## Install + +``` +/plugin install lean-flitz@leancode-ai-plugins +``` + +See the [root README](../../README.md#install) for adding the marketplace itself. diff --git a/plugins/lean-flitz/skills/lean-flitz-usage/SKILL.md b/plugins/lean-flitz/skills/lean-flitz-usage/SKILL.md new file mode 100644 index 0000000..fc92cc7 --- /dev/null +++ b/plugins/lean-flitz/skills/lean-flitz-usage/SKILL.md @@ -0,0 +1,48 @@ +--- +name: lean-flitz-usage +description: Explain what the `lean-flitz` plugin does and how to use it. Use when someone invokes `/lean-flitz-usage`, asks what this plugin covers, or wants to know how to get a Flitz bundle of a Flutter app from Claude Tag or a local session. +--- + +# lean-flitz + +One skill, one job: publish a Flitz bundle of a Flutter app and hand back the link a teammate +opens on their phone. + +## What Flitz is + +[Flitz](https://docs.flitz.dev) puts work-in-progress builds of a Flutter app on a real device +without a rebuild or a store round-trip. The app is split in two: the **host**, a normal signed +build of the app with the Flitz loader embedded, installed once; and the **bundle**, the app's +Dart code and assets, which `flitz publish` builds and uploads as often as needed. Opening the +publish link on a device with the host installed downloads the bundle and runs it. + +## What it gives you + +`/lean-flitz:publish-flitz-bundle`, or simply asking for a Flitz bundle, runs +`flitz publish --yes --json` in the project directory (the one holding `flitz.yaml`), installs the +`flitz` CLI first when it is missing, and reports the landing page. The bundle is built from the +working tree as it is. + +## Where it runs + +- **Claude Tag**, Claude in Slack. The Agent Proxy injects the organization API key into requests + to `api.flitz.dev`, so the sandbox never holds the key; the skill gives the CLI a placeholder key + to get past its local check. +- **Locally**, the CLI uses your `flitz login`, or a `FLITZ_APIKEY` already in the environment. + The placeholder never shadows a real login. + +## What it needs + +- A Flitz organization with an active license. +- A project already set up for Flitz: a `flitz.yaml` pin from `flitz sdk use`, and a host build + installed on the device that will open the link. The skill does neither. +- Linux x64 or an Apple Silicon Mac, which are the hosts the CLI supports. + +Setup for Claude Tag (the Flitz connector and allowed domains on the channel's bundle) and for a +local session lives in the plugin `README.md`. + +## When to reach for something else + +- A pull request whose CI already publishes a bundle: the link is in the CI output. +- Setting up Flitz in an app (host build, loader plugins, `flitz sdk use`): the + [Flitz docs](https://docs.flitz.dev/getting-started). diff --git a/plugins/lean-flitz/skills/publish-flitz-bundle/SKILL.md b/plugins/lean-flitz/skills/publish-flitz-bundle/SKILL.md new file mode 100644 index 0000000..00c63d7 --- /dev/null +++ b/plugins/lean-flitz/skills/publish-flitz-bundle/SKILL.md @@ -0,0 +1,73 @@ +--- +name: publish-flitz-bundle +description: Publish a Flitz bundle of the Flutter app in the current project with `flitz publish` and return the install link. Use when someone asks for a Flitz bundle, preview build, or install link of the app, in Claude Tag or locally. +argument-hint: "[project dir]" +--- + +# Publish a Flitz bundle + +Run `flitz publish` in the project directory, as it is, and hand back the link. Do not clone, +check out, stash, or otherwise change the working tree: the bundle is built from whatever is in +the directory, uncommitted changes included. + +## Find the project + +The project is the directory holding `flitz.yaml`; the CLI reads the pinned SDK from there. Use +the current directory when it has one. Otherwise look for `flitz.yaml` in the repository: with one +match, use it; with several, pick the one the request names, or ask. With none, the app has not +been set up with `flitz sdk use`; say so, point at https://docs.flitz.dev/getting-started, and +stop. + +## Run it + +```bash +cd && flitz publish --yes --json +``` + +- `--yes` lets a fresh machine download the pinned SDK without a terminal to confirm on. +- `--json` makes stdout one JSON document, so the link is never parsed from progress output. +- Flags the request asks for go on the same line, for example + `--target lib/main_dev.dart --dart-define=ENV=dev`. Write a dart-define with `=` or + `-D KEY=VALUE` with a space, because the CLI reads a `v` inside an attached `-DKEY=value` as + `--verbose`. + +When `flitz` is not on `PATH`, install it with `curl -fsSL https://download.flitz.dev/install.sh | sh` +(it lands in `~/.local/bin`). The installer verifies the binary's SHA-256. + +**Claude Tag:** the Agent Proxy swaps the organization API key into requests to `api.flitz.dev` +after they leave the sandbox, but the CLI refuses to send anything without a non-empty +`FLITZ_APIKEY`. When `FLITZ_APIKEY` is unset and there is no stored `flitz login` +(`~/.config/flitz/credentials.json`, or under `$XDG_CONFIG_HOME` when that is set), prefix the +command with `FLITZ_APIKEY=injected-by-agent-proxy`. Never set it over a real login: inside the +CLI a set `FLITZ_APIKEY` wins over the login. + +A first publish on a machine downloads the pinned SDK (several hundred MB) and resolves pub +packages, so it takes a few minutes; a warm one takes about a minute. Run it with a timeout of at +least 15 minutes. In a local Claude Code sandbox the run writes to `~/.local/bin`, +`~/.cache/flitz`, and `~/.pub-cache`, and reaches `*.flitz.dev` and `pub.dev`; when the sandbox +refuses one of those, re-run outside it. + +## Report the result + +On success, stdout is one JSON document: `id`, `page_url`, `deeplink`, `bundle_url`. Lead with +`page_url`: it opens in any browser and shows the QR code and an install button, which is what a +teammate on a phone needs. Add the `deeplink` only when someone asks for it. + +The link does not expire and cannot be revoked, and anyone holding it can download the bundle. +Post it where it was asked for, not somewhere wider. + +## When it fails + +| Exit | Meaning | What to do | +|---|---|---| +| 1 | Bad input or project state, such as a missing `pubspec.yaml` | Fix it from the message; nothing was published | +| 2 | Local or unexpected: filesystem permissions, a held lock, an interrupted run, or a malformed `flitz.yaml` | Report the message; in a local sandbox, check the paths above are writable | +| 3 | Credential or license | With the placeholder key (Claude Tag), the channel's bundle has no working Flitz connector; tell the requester, since it is an admin fix. With a key from the environment, unset it to fall back to the login. Otherwise `flitz login`. An inactive license is for the organization's Owner to restore | +| 4 | Flitz service or network, including the 30-second upload limit | Re-run once; a re-run starts a fresh publish | +| 5 | The app does not compile | Report the compiler error; it is the code, not the setup | + +The full contract is at https://docs.flitz.dev/cli/exit-codes. A failed publish never prints +JSON, so never guess a link from partial output. + +Do not ask anyone for the API key or accept one pasted into a chat: Slack keeps it forever. +Credential setup belongs to the plugin `README.md`.