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
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,47 @@ jobs:

- name: Test Dart
run: dart test

bridge-release:
name: Bridge release package
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: apps/web/package-lock.json

- name: Set up Dart
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Install web dependencies
working-directory: apps/web
run: npm ci --legacy-peer-deps

- name: Build packaged Web workbench
working-directory: apps/web
run: npm run build:bridge

- name: Install bridge dependencies
working-directory: apps/bridge
run: dart pub get

- name: Test bridge release helpers
working-directory: apps/bridge
run: dart test test/release

- name: Validate bridge package layout
working-directory: apps/bridge
run: dart tool/validate_release_layout.dart

- name: Validate pub package dry run
working-directory: apps/bridge
run: dart pub publish --dry-run
73 changes: 73 additions & 0 deletions .github/workflows/publish_bridge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
name: Publish Bridge Package

on:
push:
tags:
- "ask_ui_bridge-v[0-9]+.[0-9]+.[0-9]+"

jobs:
publish:
name: Publish ask_ui_bridge to pub.dev
runs-on: ubuntu-latest
environment: pub.dev
permissions:
contents: read
id-token: write

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
cache-dependency-path: apps/web/package-lock.json

- name: Set up Dart
uses: dart-lang/setup-dart@v1
with:
sdk: stable

- name: Verify tag matches bridge version
working-directory: apps/bridge
run: |
tag="${GITHUB_REF_NAME#ask_ui_bridge-v}"
version="$(sed -n 's/^version: //p' pubspec.yaml)"
test "$tag" = "$version"

- name: Install web dependencies
working-directory: apps/web
run: npm ci --legacy-peer-deps

- name: Build packaged Web workbench
working-directory: apps/web
run: npm run build:bridge

- name: Verify packaged Web is committed
run: git diff --exit-code -- apps/bridge/web

- name: Install bridge dependencies
working-directory: apps/bridge
run: dart pub get

- name: Analyze bridge
working-directory: apps/bridge
run: dart analyze

- name: Test bridge
working-directory: apps/bridge
run: dart test

- name: Validate bridge package layout
working-directory: apps/bridge
run: dart tool/validate_release_layout.dart

- name: Dry-run pub publish
working-directory: apps/bridge
run: dart pub publish --dry-run

- name: Publish to pub.dev
working-directory: apps/bridge
run: dart pub publish --force
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Miscellaneous
*.class
*.lock
!apps/bridge/pubspec.lock
*.log
*.pyc
*.swp
Expand Down Expand Up @@ -116,4 +117,4 @@ app.*.symbols
!**/ios/**/default.pbxuser
!**/ios/**/default.perspectivev3
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
!/dev/ci/**/Gemfile.lock
64 changes: 64 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Ask UI is a Flutter developer workbench for selecting precise UI targets, collec
- `docs` - Product and user-facing documentation.
- `docs_internal` - Internal implementation notes, issue plans, and progress notes.
- `issues` - Issue tracking artifacts.
- `skills` - Coding-agent workflows for Ask UI.

## Bridge

Expand All @@ -19,6 +20,69 @@ dart pub get
dart run bin/ask_ui_bridge.dart --host 127.0.0.1 --port 8787
```

Installed Flutter projects should add `ask_ui_runtime`, register the runtime
before `runApp`, and start Ask UI through the globally activated bridge CLI:

```yaml
dependencies:
ask_ui_runtime: ^0.0.1
```

```dart
import 'package:ask_ui_runtime/ask_ui_runtime.dart';

void main() {
registerAskUiRuntime();
runApp(const MyApp());
}
```

```sh
dart pub global activate ask_ui_bridge
ask_ui_bridge launch
```

## Coding-Agent Skill

Ask UI includes two launch workflow skills:

- `skills/ask-ui/SKILL.md` for normal Flutter projects using the installed
`ask_ui_bridge` CLI.
- `skills/ask-ui-dev/SKILL.md` for Ask UI maintainers working from this source
checkout.

The skills are intentionally installed manually in this version. They tell the
agent to start Ask UI, handle device selection, and write Ask UI Chat replies
through the returned `agent poll` command.

Ask UI maintainers prepare the bridge package for release by building the Web
workbench into the bridge package before running pub validation:

```sh
cd apps/web
npm run build:bridge

cd ../bridge
dart run tool/validate_release_layout.dart
dart pub publish --dry-run
```

Formal bridge package publishing is handled by the
`Publish Bridge Package` GitHub Actions workflow. Configure pub.dev automated
publishing for this repository and the `ask_ui_bridge-v[0-9]+.[0-9]+.[0-9]+`
tag pattern, then publish by pushing a tag that matches the bridge package
version:

```sh
git tag ask_ui_bridge-v0.0.4
git push origin ask_ui_bridge-v0.0.4
```

The publish workflow rebuilds the Web workbench, verifies the generated
`apps/bridge/web` files match the committed package files, runs bridge analysis
and tests, validates the release layout, performs `dart pub publish --dry-run`,
and only then runs `dart pub publish --force`.

Workbench sessions require `vmServiceUri`, `projectRoot`, and `deviceId` query
parameters. `deviceId` must be the Android device or emulator serial used by
Flutter, ADB, and scrcpy.
Expand Down
6 changes: 6 additions & 0 deletions apps/bridge/.pubignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.dart_tool/
build/
coverage/
test/
*.log
.DS_Store
12 changes: 12 additions & 0 deletions apps/bridge/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Changelog

## 0.0.4

- Add the `ask_ui_bridge` executable for `ask_ui_bridge launch` after global activation.
- Add the launch workflow that starts Flutter, creates a Bridge Session, opens
the workbench, and returns the Agent Session Command.
- Add `--web-dev` for Ask UI contributors who need the Vite workbench during
local development.
- Package the built Ask UI Web workbench under the bridge package `web/`
directory for installed users.
- Add bridge release layout validation for pub publishing.
3 changes: 3 additions & 0 deletions apps/bridge/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Copyright (c) 2026 Ask UI contributors.

All rights reserved.
67 changes: 67 additions & 0 deletions apps/bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Ask UI Bridge

`ask_ui_bridge` is the local launcher and bridge server for Ask UI. It starts a
Flutter app, creates the Bridge Session used by the Ask UI workbench, serves the
packaged Web workbench, and exposes the Agent Session Command used by coding
agents.

## Installed Usage

Globally activate the bridge CLI:

```sh
dart pub global activate ask_ui_bridge
```

Start Ask UI from a Flutter project that has registered `ask_ui_runtime`:

```sh
ask_ui_bridge launch
```

Pass Flutter launch options when needed:

```sh
ask_ui_bridge launch \
--device <device-id> \
--flavor <flavor> \
--target lib/main_dev.dart \
--dart-define API_BASE_URL=http://localhost:3000 \
--project-root /path/to/flutter_app
```

Use `--no-open` when the command should print the workbench URL without opening
a browser.

## Coding-Agent Skill

The Ask UI repository includes launch workflow skills at `../../skills/ask-ui`
and `../../skills/ask-ui-dev`. Install `ask-ui` for normal Flutter projects, or
`ask-ui-dev` when developing this repository. The skills let Codex, Claude Code,
or a similar agent run the appropriate launch command, handle device selection,
run the returned Agent Session Command, and continue polling for Ask UI Chat
messages.

This version does not include an automatic skill installer command.

## Release Validation

Maintainers should build the Web workbench into the bridge package before
publishing:

```sh
cd apps/web
npm run build:bridge

cd ../bridge
dart run tool/validate_release_layout.dart
dart test
dart pub publish --dry-run
```

The published package must include `web/index.html` and the generated Web
assets under `web/assets`.

The repository publish workflow runs the same release validation for
`ask_ui_bridge-v<version>` tags, verifies the packaged Web files are committed,
and publishes to pub.dev after `dart pub publish --dry-run` succeeds.
38 changes: 31 additions & 7 deletions apps/bridge/lib/device/scrcpy_device_stream.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,40 @@ const _vendoredScrcpyServerPath = 'vendor/scrcpy/4.0/scrcpy-server-v4.0';
/// The bridge is commonly launched either from `apps/bridge` during local
/// development or from the repository root by automation. Prefer the bridge
/// package-local path when present and fall back to the repo-root path.
String defaultScrcpyServerPath({Directory? currentDirectory}) {
String defaultScrcpyServerPath({Directory? currentDirectory, Uri? scriptUri}) {
final cwd = currentDirectory ?? Directory.current;
final bridgeLocalArtifact = File('${cwd.path}/$_vendoredScrcpyServerPath');
if (bridgeLocalArtifact.existsSync()) {
return bridgeLocalArtifact.absolute.path;
final candidates = <File>[
..._scrcpyServerCandidatesForScript(scriptUri ?? Platform.script),
File('${cwd.path}/$_vendoredScrcpyServerPath'),
File('${cwd.path}/apps/bridge/$_vendoredScrcpyServerPath'),
];

for (final candidate in candidates) {
if (candidate.existsSync()) {
return candidate.absolute.path;
}
}

return candidates.first.absolute.path;
}

List<File> _scrcpyServerCandidatesForScript(Uri scriptUri) {
if (scriptUri.scheme != 'file') {
return const <File>[];
}

final scriptFile = File.fromUri(scriptUri);
final scriptDirectory = scriptFile.parent;
final scriptDirectorySegments =
scriptDirectory.uri.pathSegments.where((segment) => segment.isNotEmpty);
if (scriptDirectorySegments.isEmpty ||
scriptDirectorySegments.last != 'bin') {
return const <File>[];
}

return File('${cwd.path}/apps/bridge/$_vendoredScrcpyServerPath')
.absolute
.path;
return <File>[
File('${scriptDirectory.parent.path}/$_vendoredScrcpyServerPath'),
];
}

/// Result from a completed local command.
Expand Down
Loading
Loading