Skip to content
Closed
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
263 changes: 159 additions & 104 deletions .claude/skills/mewbo-cli-smoketest/SKILL.md

Large diffs are not rendered by default.

178 changes: 178 additions & 0 deletions .github/workflows/android-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#@doc
# * This workflow builds Mewbo Aura (apps/mewbo_aura) debug + release APKs and
# attaches them to the Gitea/GitHub release that triggered it.
#
# Flavor: builds the `public` distribution flavor ONLY (assemblePublicDebug /
# assemblePublicRelease). The `enterprise` flavor bakes the private root CA
# and is built locally + attached to Gitea by hand (its cert is gitignored and never
# available here) — so every CI artifact, including the GitHub mirror's, is CA-free.
#
# Triggered on `release: published` (or manually via workflow_dispatch with an
# explicit tag, for re-running against an already-published release).
#
# Signing: passes AURA_KEYSTORE_B64 / AURA_KEYSTORE_PASSWORD / AURA_KEY_ALIAS /
# AURA_KEY_PASSWORD through as env so a configured repo secret upgrades the
# release APK's signature automatically — see app/build.gradle.kts. With no
# secrets configured, the build still succeeds (debug-keystore fallback).

name: Android Release

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: Release tag to build and attach APKs to
required: true
type: string

concurrency:
# ONE global group, not per-tag: all runs share the persistent /opt/hostedtoolcache volume, and
# two concurrent runs provisioning the same SDK dir raced (colliding unzip/mv → mangled repo
# metadata → "Failed to find package"; caught live when two tags were dispatched together).
# Releases are rare — full serialization is the simple correct answer.
group: android-release
cancel-in-progress: false

permissions:
contents: write

defaults:
run:
working-directory: apps/mewbo_aura

jobs:
build:
name: Build + attach APKs
runs-on: ubuntu-22.04
timeout-minutes: 45
env:
# /opt/hostedtoolcache is the runner's PERSISTENT `act-toolcache` docker volume — the only
# real cache on this Gitea runner: actions/cache is a no-op here (the action itself warns
# "only supported on GHES >= 3.5" and never restores). JDK/SDK/Gradle all live there so
# every run after the first skips provisioning entirely.
ANDROID_HOME: /opt/hostedtoolcache/android-sdk
GRADLE_USER_HOME: /opt/hostedtoolcache/gradle-home
AURA_KEYSTORE_B64: ${{ secrets.AURA_KEYSTORE_B64 }}
AURA_KEYSTORE_PASSWORD: ${{ secrets.AURA_KEYSTORE_PASSWORD }}
AURA_KEY_ALIAS: ${{ secrets.AURA_KEY_ALIAS }}
AURA_KEY_PASSWORD: ${{ secrets.AURA_KEY_PASSWORD }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Build the RELEASE TAG's source, not the dispatch ref: a workflow_dispatch rerun
# executes this file from main but must still compile the tagged commit — without this
# pin, a rerun attached main-content APKs under an older tag's name (caught live on the
# 0.0.20-debug rerun).
ref: ${{ github.event.release.tag_name || inputs.tag }}

- name: Resolve release tag
id: tag
run: |
TAG="${{ github.event.release.tag_name }}"
if [ -z "$TAG" ]; then TAG="${{ inputs.tag }}"; fi
test -n "$TAG" || { echo "no release tag resolved" >&2; exit 1; }
echo "tag=$TAG" >> "$GITHUB_OUTPUT"

- name: Ensure jq is available
run: command -v jq >/dev/null 2>&1 || { sudo apt-get update -qq && sudo apt-get install -y -qq jq; }

- name: Provision Temurin JDK 21 (persistent toolcache, skipped when present)
# Tarball straight into the persistent toolcache — one download EVER, vs the old
# apt-repo dance (~60s of apt update + install on every single run).
run: |
set -euo pipefail
JDK_DIR=/opt/hostedtoolcache/temurin-21-jdk
if [ ! -x "$JDK_DIR/bin/java" ]; then
curl -fsSL -o /tmp/jdk.tar.gz "https://api.adoptium.net/v3/binary/latest/21/ga/linux/x64/jdk/hotspot/normal/eclipse"
mkdir -p "$JDK_DIR"
tar -xzf /tmp/jdk.tar.gz -C "$JDK_DIR" --strip-components=1
rm /tmp/jdk.tar.gz
fi
echo "JAVA_HOME=$JDK_DIR" >> "$GITHUB_ENV"
echo "$JDK_DIR/bin" >> "$GITHUB_PATH"

- name: Provision Android SDK (persistent toolcache, skipped when present)
# The old actions/cache step was a provable no-op on this runner (the action warns
# "only supported on GHES >= 3.5" and never restored), so the SDK re-downloaded every
# run — and the `yes | sdkmanager --licenses` pipe under `set -o pipefail` died of
# SIGPIPE (exit 141) whenever sdkmanager closed stdin before `yes`'s next write: the
# timing-dependent failure that broke release builds. `(yes || true)` absorbs the
# SIGPIPE while pipefail still reports sdkmanager's own status.
run: |
set -euo pipefail
if [ ! -d "$ANDROID_HOME/platforms/android-37.0" ] || [ ! -d "$ANDROID_HOME/build-tools/37.0.0" ]; then
mkdir -p "$ANDROID_HOME/cmdline-tools"
cd "$ANDROID_HOME/cmdline-tools"
rm -rf latest cmdline-tools.zip
# Pinned cmdline-tools build; bump this if Google retires the archive. 13114758 (v16)
# replaces 11076708 (v11): the old build's repository schema predates platform 37 and
# sdkmanager reported "Failed to find package". ALSO: the platform package id is
# "platforms;android-37.0" — Android 17 uses the minor-versioned SDK naming (the
# working host SDK's directory is platforms/android-37.0); plain "android-37" does
# not exist in any channel, which was the real "Failed to find package" cause.
curl -fsSL -o cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip
unzip -q cmdline-tools.zip
mv cmdline-tools latest
rm cmdline-tools.zip
(yes || true) | "$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" --licenses >/dev/null
"$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager" --sdk_root="$ANDROID_HOME" "platform-tools" "platforms;android-37.0" "build-tools;37.0.0"
fi

- name: Build public debug + release APKs
# PUBLIC flavor only — the `enterprise` flavor's CA cert isn't available in CI and must
# never ship to GitHub. One invocation for both build types (shared configuration +
# parallel task graph); org.gradle.caching reuses task outputs across runs via the
# persistent GRADLE_USER_HOME above (wrapper dist + dependency cache + build cache all
# survive).
run: ./gradlew :app:assemblePublicDebug :app:assemblePublicRelease -Dorg.gradle.caching=true

- name: Rename artifacts
id: artifacts
run: |
set -euo pipefail
TAG="${{ steps.tag.outputs.tag }}"
DEBUG_NAME="aura-${TAG}-debug.apk"
RELEASE_NAME="aura-${TAG}-release.apk"
cp app/build/outputs/apk/public/debug/app-public-debug.apk "$DEBUG_NAME"
cp app/build/outputs/apk/public/release/app-public-release.apk "$RELEASE_NAME"
echo "debug_name=$DEBUG_NAME" >> "$GITHUB_OUTPUT"
echo "release_name=$RELEASE_NAME" >> "$GITHUB_OUTPUT"

- name: Upload APKs as release assets
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SERVER_URL: ${{ github.server_url }}
API_URL: ${{ github.api_url }}
REPOSITORY: ${{ github.repository }}
TAG: ${{ steps.tag.outputs.tag }}
DEBUG_NAME: ${{ steps.artifacts.outputs.debug_name }}
RELEASE_NAME: ${{ steps.artifacts.outputs.release_name }}
run: |
set -euo pipefail
# Gitea act_runner may leave github.api_url empty; derive it the same
# way agent-pickup.yml does.
api="$API_URL"
if [ -z "$api" ]; then
if [ "$SERVER_URL" = "https://github.com" ]; then
api="https://api.github.com"
else
api="${SERVER_URL%/}/api/v1"
fi
fi

release_id=$(curl --fail-with-body --silent --show-error \
-H "Authorization: token $GH_TOKEN" \
"$api/repos/$REPOSITORY/releases/tags/$TAG" | jq -r '.id // empty')
test -n "$release_id" || { echo "could not resolve release id for tag $TAG" >&2; exit 1; }

for pair in "$DEBUG_NAME" "$RELEASE_NAME"; do
curl --fail-with-body --silent --show-error \
-H "Authorization: token $GH_TOKEN" \
-H "Content-Type: application/vnd.android.package-archive" \
-X POST \
"$api/repos/$REPOSITORY/releases/$release_id/assets?name=$pair" \
--data-binary "@$pair" | jq -r '"uploaded \(.name // "'"$pair"'")"'
done
2 changes: 2 additions & 0 deletions .github/workflows/console.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
run: npm run typecheck

test:
name: Unit tests
runs-on: ubuntu-22.04
steps:
- name: Checkout
Expand All @@ -62,6 +63,7 @@ jobs:
run: npm run test:ci

build:
name: Build & upload artifact
runs-on: ubuntu-22.04
steps:
- name: Checkout
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ name: Python Tests

on:
pull_request:
paths:
- "packages/**"
- "apps/mewbo_api/**"
- "apps/mewbo_cli/**"
- "apps/mewbo_mcp/**"
- "apps/mewbo_ha_conversation/**"
- "tests/**"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/coverage.yml"
workflow_dispatch:
inputs:
ref:
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/docker-buildx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ permissions:

jobs:
docker:
name: Build & push images
runs-on: ubuntu-22.04
timeout-minutes: 60
steps:
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/docs-cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,24 @@ name: Docs Cleanup
on:
pull_request:
types: [closed]
paths:
- "docs/**"
- "mkdocs.yml"
- "configs/**"
- "apps/mewbo_api/**"
- "packages/mewbo_core/src/mewbo_core/config.py"
- "scripts/ci/generate_openapi_spec.py"
- "scripts/ci/generate_config_schema.py"
- ".github/workflows/docs.yml"
- ".github/workflows/docs-cleanup.yml"
workflow_dispatch:

permissions:
contents: write

jobs:
cleanup-pr-version:
name: Delete PR preview (GitHub Pages)
if: |
github.server_url == 'https://github.com' &&
github.event_name == 'pull_request' &&
Expand Down Expand Up @@ -47,6 +58,7 @@ jobs:
PATH=".venv/bin:$PATH" .venv/bin/mike delete --push "$VERSION" || true

bulk-delete-pr-versions:
name: Bulk-delete PR previews (manual)
if: github.server_url == 'https://github.com' && github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
Expand Down
26 changes: 24 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,26 @@ name: Docs
on:
push:
branches: [main, master]
paths:
- "docs/**"
- "mkdocs.yml"
- "configs/**"
- "apps/mewbo_api/**"
- "packages/mewbo_core/src/mewbo_core/config.py"
- "scripts/ci/generate_openapi_spec.py"
- "scripts/ci/generate_config_schema.py"
- ".github/workflows/docs.yml"
pull_request:
types: [opened, synchronize, reopened, closed]
paths:
- "docs/**"
- "mkdocs.yml"
- "configs/**"
- "apps/mewbo_api/**"
- "packages/mewbo_core/src/mewbo_core/config.py"
- "scripts/ci/generate_openapi_spec.py"
- "scripts/ci/generate_config_schema.py"
- ".github/workflows/docs.yml"
workflow_dispatch:

permissions:
Expand All @@ -27,17 +45,20 @@ env:

jobs:
deploy:
name: Build & deploy docs
if: >-
github.event.action != 'closed' &&
(github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- uses: actions/setup-python@v5
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

Expand Down Expand Up @@ -95,6 +116,7 @@ jobs:
"https://${DOCS_HOST}/${slug}/"

cleanup:
name: Remove internal-host PR preview
# Remove a PR preview from the internal docs host when the PR closes.
# (GitHub Pages / mike PR cleanup lives in docs-cleanup.yml.)
if: >-
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ name: Python Lint

on:
pull_request:
paths:
- "packages/**"
- "apps/mewbo_api/**"
- "apps/mewbo_cli/**"
- "apps/mewbo_mcp/**"
- "apps/mewbo_ha_conversation/**"
- "tests/**"
- "pyproject.toml"
- "uv.lock"
- ".github/workflows/lint.yml"
workflow_dispatch:

permissions:
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/stale.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ permissions:

jobs:
stale:
name: Close stale issues & PRs
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v10
- name: Mark & close stale issues/PRs
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 45
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/uv-lock-refresh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:

jobs:
refresh-lock:
name: Refresh uv.lock & open PR
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ docker.env
docker-compose.override.yml
docker/init.d/*
!docker/init.d/10-git-setup.sh
!docker/init.d/15-tea-setup.sh
/.claude/settings.json
/.playwright-mcp

Expand Down
Loading
Loading