Skip to content
Merged
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
9 changes: 9 additions & 0 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
]
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
20 changes: 20 additions & 0 deletions plugins/lean-flitz/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -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/"
}
9 changes: 9 additions & 0 deletions plugins/lean-flitz/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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.
126 changes: 126 additions & 0 deletions plugins/lean-flitz/README.md
Original file line number Diff line number Diff line change
@@ -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.
48 changes: 48 additions & 0 deletions plugins/lean-flitz/skills/lean-flitz-usage/SKILL.md
Original file line number Diff line number Diff line change
@@ -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).
73 changes: 73 additions & 0 deletions plugins/lean-flitz/skills/publish-flitz-bundle/SKILL.md
Original file line number Diff line number Diff line change
@@ -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 <project dir> && 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`.
Loading