Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<div align="center">

<pre>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
agentflare · Optimize AI CLI agents for cost & performance
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
</pre>

# agentflare

**Run AI coding agents efficiently, and coordinate more than one of them.**
Expand Down
175 changes: 175 additions & 0 deletions TAURI_MOBILE_RESEARCH.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# Tauri 2 — Mobile & Tablet Build Research

> Research summary: top open-source Tauri 2 projects that ship to Android/iOS
> (phones + tablets) and the setup/config/plugins they use.

## 1. Does Tauri support tablets?

Yes. **Tauri v2** (stable since Aug 2024) is the first version with first-class
mobile support. Tablets run standard Android/iPadOS, so they use the same build
path as phones — no tablet-specific flag needed.

| Version | Mobile |
|---|---|
| Tauri v1 | Desktop only (no mobile) |
| Tauri v2 | Android + iOS (arm + x86-64 device targets) |

- Android: minSdk 24 (Android 7+; many apps target 30/Android 12+)
- iOS/iPadOS: 9+ (iPad supported)
- WebView: system Android System WebView / iOS WKWebView
Comment on lines +18 to +19

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files TAURI_MOBILE_RESEARCH.md

printf '\n== Relevant lines ==\n'
nl -ba TAURI_MOBILE_RESEARCH.md | sed -n '1,120p'

Repository: getappz/agentflare

Length of output: 260


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json, urllib.request, re, sys

urls = [
    "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-schema-generator/schemas/config.schema.json",
]
for url in urls:
    print(f"== {url} ==")
    try:
        with urllib.request.urlopen(url, timeout=20) as resp:
            data = resp.read().decode("utf-8", "replace")
        # look for iOS deployment target / minimum version hints
        for pat in [r'ios.*deployment', r'deploymentTarget', r'minimum.*ios', r'iOS.*[0-9]+\+']:
            m = re.search(pat, data, re.I)
            if m:
                start = max(0, m.start() - 200)
                end = min(len(data), m.end() + 400)
                print(data[start:end])
                break
        else:
            print("No obvious iOS deployment target string found")
    except Exception as e:
        print("ERR:", e)
PY

Repository: getappz/agentflare

Length of output: 413


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import urllib.request, re, json

# Search Tauri docs / schema references that mention iOS deployment target.
queries = [
    "site:tauri.app iOS deployment target Tauri v2",
    "site:github.com/tauri-apps/tauri iOS deployment target config schema",
]
print("No-op placeholder; please provide web_search results if available.")
PY

Repository: getappz/agentflare

Length of output: 223


🌐 Web query:

Tauri v2 iOS deployment target minimum version config.schema.json

💡 Result:

In Tauri v2, you can configure the minimum iOS deployment target using the bundle.iOS.minimumSystemVersion setting in your tauri.conf.json file [1][2]. Configuration Details: - Path: bundle > iOS > minimumSystemVersion [1][2] - Type: string [1] - Purpose: Maps to the IPHONEOS_DEPLOYMENT_TARGET value for your iOS build [1][2]. - Default: While early versions defaulted to 13.0 [2][3], recent updates have moved this default to 14.0 [1][4]. Example usage in tauri.conf.json: { "bundle": { "iOS": { "minimumSystemVersion": "15.0" } } } This setting ensures your application correctly specifies its minimum supported iOS version during the build process [1][2]. If you are using platform-specific configuration files, you can also place this setting within tauri.ios.conf.json [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '== TAURI_MOBILE_RESEARCH.md excerpt ==\n'
sed -n '1,120p' TAURI_MOBILE_RESEARCH.md | cat -n

Repository: getappz/agentflare

Length of output: 5670


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' TAURI_MOBILE_RESEARCH.md | cat -n

Repository: getappz/agentflare

Length of output: 5631


Align the iOS minimum with Tauri v2

TAURI_MOBILE_RESEARCH.md#L18-L19 and the HuLa row at #L43-L44 still say iOS 9+, but Tauri v2’s iOS floor is 14.0. Update both spots so the guide doesn’t point readers to an obsolete target.

📍 Affects 1 file
  • TAURI_MOBILE_RESEARCH.md#L18-L19 (this comment)
  • TAURI_MOBILE_RESEARCH.md#L43-L44
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TAURI_MOBILE_RESEARCH.md` around lines 18 - 19, Update the iOS minimum
version from 9+ to 14.0 in both referenced ranges of TAURI_MOBILE_RESEARCH.md:
the platform requirements entry at lines 18-19 and the HuLa row at lines 43-44.
Keep the surrounding platform and WebView information unchanged.


## 2. How the UI adapts to mobile vs desktop

Tauri ships **no** UI framework — it is a webview shell. Adaptation is your web
code's job (same as any website):

- **Responsive CSS**: media queries (`@media (max-width: 768px)`), Flexbox/Grid,
`clamp()`/`vw`, frameworks (Tailwind, UnoCSS, Bootstrap).
- **Platform detection (JS)**:
```js
import { platform } from '@tauri-apps/plugin-os'
const os = await platform() // 'android' | 'ios' | 'linux' | ...
```
Compile-time flag in some projects: `process.env.__TAURI_MOBILE__`.
- **Native feel**: `env(safe-area-inset-*)` for notches/gesture bars; ≥44px touch
targets; momentum scroll; swipe/gesture handlers.
- **Tauri hooks**: `plugin-device` (screen size, DPR), `Window` API (resize,
fullscreen, safe-area), `plugin-shell` / deep links, notifications.
Comment on lines +34 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File excerpt ==\n'
nl -ba TAURI_MOBILE_RESEARCH.md | sed -n '28,42p'

printf '\n== Repo search for plugin-device ==\n'
rg -n --hidden --no-ignore-vcs 'plugin-device|device-info|tauri-plugin-device' .

printf '\n== File inventory for related docs ==\n'
git ls-files | rg 'TAURI_MOBILE_RESEARCH\.md|tauri|plugin'

Repository: getappz/agentflare

Length of output: 216


🌐 Web query:

Tauri v2 plugin device info official plugin ecosystem tauri-plugin-device-info plugins workspace

💡 Result:

There is no official Tauri v2 plugin named tauri-plugin-device-info within the official tauri-apps/plugins-workspace repository [1][2][3]. The official workspace contains plugins such as os, which provides basic operating system information (e.g., platform, version, arch) [1][4]. The tauri-plugin-device-info crate is a community-maintained, third-party plugin [5][6]. It provides a comprehensive, cross-platform API for accessing detailed device information, including battery status, network details, storage statistics, display information, and hardware identifiers across Windows, macOS, Linux, iOS, and Android [5][6][7]. For projects requiring deeper device or hardware metrics beyond what the official os plugin offers, you can integrate this third-party plugin using the standard Tauri plugin installation process [5][7]: 1. Installation: Use the Tauri CLI to add the plugin to your project: npm run tauri add device-info [5][7] 2. Configuration: Initialize the plugin in your Rust code (main.rs or lib.rs):.plugin(tauri_plugin_device_info::init) [6][7] 3. Usage: Access the API in your frontend via the tauri-plugin-device-info-api package [6][7]. Always verify third-party dependencies before integrating them into production environments. You can review the plugin's source code and documentation via its GitHub repository [5] or on crates.io [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File excerpt ==\n'
awk 'NR>=28 && NR<=42 { printf "%4d  %s\n", NR, $0 }' TAURI_MOBILE_RESEARCH.md

printf '\n== Related references in repo ==\n'
rg -n --hidden --no-ignore-vcs 'plugin-device|device-info|plugin-os|os info|Window API|plugin-shell' TAURI_MOBILE_RESEARCH.md .

Repository: getappz/agentflare

Length of output: 1617


Name the actual device-info plugin or remove this bullet. plugin-device isn’t a Tauri v2 plugin name; use the real package name (for example tauri-plugin-device-info) so readers can install the right dependency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TAURI_MOBILE_RESEARCH.md` around lines 34 - 37, Update the “Tauri hooks”
bullet in TAURI_MOBILE_RESEARCH.md to replace the invalid `plugin-device`
reference with the actual installable Tauri v2 device-information plugin package
name, such as `tauri-plugin-device-info`; keep the existing Window,
shell/deep-link, and notification references unchanged.


## 3. Top projects to learn setup/config/plugins from

| Project | Stars | Stack | Mobile | Why |
|---|---|---|---|---|
| **HuLa** | 7.4k | Rust + Vue3 + Vite7 + UnoCSS | Android 12+, iOS 9+, desktop | Best real production example: one codebase → desktop + phone + tablet |
| **quantum** | 529 | Tauri + SolidStart (TS) | iOS, Android, desktop | Scaffold template w/ tuned release Cargo profile |
| **vscode-android** | ~small | React + TS + Vite + Tailwind | Android | Cleanest concrete `tauri.conf.json` + `capabilities/android.json` |
| **bastion** | — | React (shared web) | iOS + Android | `__TAURI_MOBILE__` detection + per-platform `tauri.*.conf.json` |
| **two** | 11 | SvelteKit + Tauri | iOS, Android | Minimal Svelte mobile reference |
| **brenogonzaga/tauri-plugin-*** | 15–19 | Rust plugins | iOS + Android | Cross-platform mobile plugins: stt, tts, audio-recorder |

### How each adapts UI
- **HuLa**: separate `locales/*/mobile_*.json` + `public/Mobile/*.png`; one Vue app,
responsive via UnoCSS breakpoints; ships `docs/android_startup_guide.md`.
- **bastion**: `if (process.env.__TAURI_MOBILE__)` swaps nav drawer ↔ sidebar;
same React frontend for android/ios.
- **vscode-android**: Tailwind responsive + 44px touch targets + gestures + fullscreen.

## 4. Concrete setup you can copy (from `vscode-android`)

### `src-tauri/Cargo.toml`
```toml
[package]
name = "vscode-android"
version = "1.0.0"
edition = "2021"

[lib]
name = "vscode_android_lib"
crate-type = ["staticlib", "cdylib", "rlib"]

[dependencies]
tauri = { version = "^2", features = [] }
tauri-build = { version = "^2" }
tauri-plugin-fs = "^2"
tauri-plugin-http = "^2"
tauri-plugin-shell = "^2"
tauri-plugin-store = "^2"
tauri-plugin-websocket = "^2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
tokio = { version = "1.36", features = ["full"] }

[profile.release]
codegen-units = 1
lto = true
opt-level = "s"
strip = true
panic = "abort" # from quantum's tuned release profile
```

### `tauri.conf.json`
```json
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "VSCode Android",
"version": "1.0.0",
"identifier": "com.vscode.android",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:1420",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [{ "title": "VSCode Android", "width": 1920, "height": 1080, "resizable": true }],
"security": {
"csp": "default-src 'self'; connect-src 'self' https://api.github.com wss://*; img-src 'self' data: https://*; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'"
}
Comment on lines +105 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not present this CSP as a hardened copy-paste baseline.

https://*, wss://*, 'unsafe-eval', and 'unsafe-inline' substantially widen the attack surface. Tauri recommends allowing only trusted, required hosts and sources; replace these wildcards with the exact endpoints the sample needs. (v2.tauri.app)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TAURI_MOBILE_RESEARCH.md` around lines 105 - 107, The CSP example in the
security configuration is overly permissive. Update the security CSP
documentation to remove wildcard network sources and unsafe script/style
directives, replacing them with only the exact trusted endpoints and source
directives required by the sample; do not present the result as a hardened
baseline unless it is restrictive.

},
"bundle": {
"active": true,
"icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"android": { "minSdkVersion": 24 }
},
"plugins": {
"http": { "scope": ["https://api.github.com", "https://github.com", "wss://*"] },
"websocket": { "scope": ["wss://*.github.dev", "wss://api.github.com"] },
"store": { "path": "store.bin" }
}
}
```

### `src-tauri/capabilities/android.json`
```json
{
"identifier": "android-capability",
"description": "Capability for Android platform",
"windows": ["main"],
"permissions": ["core:default", "fs:allow-read-text-file", "websocket:default"],
"platforms": ["android"]
Comment on lines +114 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the referenced doc and related Tauri config files
git ls-files | rg '(^|/)(TAURI_MOBILE_RESEARCH\.md|tauri\.conf(\.[jt]s|\.json)?|android\.json|capabilities/.*\.json)$'

# Show the relevant section of TAURI_MOBILE_RESEARCH.md
if git ls-files --error-unmatch TAURI_MOBILE_RESEARCH.md >/dev/null 2>&1; then
  echo '--- TAURI_MOBILE_RESEARCH.md (around lines 100-140) ---'
  sed -n '100,140p' TAURI_MOBILE_RESEARCH.md
fi

# Show any capability files mentioning http/websocket/store
echo '--- capability files mentioning http/websocket/store ---'
for f in $(git ls-files | rg 'capabilities/.*\.json$'); do
  if rg -n '"(http|websocket|store)"|http:default|websocket:default|fs:allow-read-text-file' "$f" >/dev/null; then
    echo "### $f"
    rg -n '"(http|websocket|store)"|http:default|websocket:default|fs:allow-read-text-file' "$f"
  fi
done

# Show any tauri config files mentioning plugins/http/websocket/store
echo '--- tauri config files mentioning plugins/http/websocket/store ---'
for f in $(git ls-files | rg 'tauri\.conf(\.[jt]s|\.json)?$|TAURI_MOBILE_RESEARCH\.md$'); do
  if rg -n '"plugins"|http\.scope|websocket\.scope|store\.bin|http:default|websocket:default' "$f" >/dev/null; then
    echo "### $f"
    rg -n '"plugins"|http\.scope|websocket\.scope|store\.bin|http:default|websocket:default' "$f"
  fi
done

Repository: getappz/agentflare

Length of output: 1952


🌐 Web query:

Tauri v2 http plugin scope capabilities permission entries android json websocket scope docs

💡 Result:

In Tauri v2, managing plugin access is handled through a system of Capabilities and Permissions defined in your src-tauri directory [1][2]. This system is platform-agnostic, meaning the same configuration structure applies to Android as it does to desktop platforms [2]. HTTP and WebSocket Scope Configuration The HTTP and WebSocket plugins use scopes to restrict which URLs can be accessed [3][4][5]. You configure these within your capability files (JSON or TOML) by defining an allow or deny list using glob patterns [4][2][6]. Example HTTP Scope Configuration To restrict HTTP access to specific domains, define a permission entry in a capability file (e.g., src-tauri/capabilities/default.json) [3][7]: { "permissions": [ { "identifier": "http:default", "allow": [{ "url": "https://.tauri.app" }], "deny": [{ "url": "https://private.tauri.app" }] } ] } If you attempt to execute a fetch request to a URL not covered by your allow scope, the request will be rejected [4]. WebSocket Configuration Similarly, the WebSocket plugin requires explicit permission in your capabilities to enable connectivity [5]. Because WebSocket scopes are plugin-specific, you must ensure your capability includes the necessary plugin permissions [5][6]: { "identifier": "main-capability", "windows": ["main"], "permissions": ["websocket:default"] } Platform-Specific Capabilities If you need to limit these permissions specifically to Android, you can use the platforms field within your capability definition [2][8]: { "identifier": "android-http-capability", "platforms": ["android"], "permissions": [ { "identifier": "http:default", "allow": [{ "url": "https://api.example.com/" }] } ] } Key Concepts: - Capabilities: Files located in src-tauri/capabilities/ that group permissions and assign them to windows or webviews [2][8]. - Permissions: Define which commands are enabled and apply granular scope restrictions [1][9]. - Scopes: Granular filters (like URL patterns for HTTP or WebSocket) passed to commands and enforced by the plugin implementation [4][6][9]. - Android: There is no special Android-only JSON format; the standard Tauri ACL (Access Control List) system is used across all supported platforms [2][8].

Citations:


Move the URL allowlists into capability permissions. Tauri v2 keeps HTTP/WebSocket scopes in capability permission entries, not under plugins.http.scope or plugins.websocket.scope. Add http:default to src-tauri/capabilities/android.json and remove the generic plugin scope block from the example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TAURI_MOBILE_RESEARCH.md` around lines 114 - 129, Update the Android
capability configuration to add the http:default permission alongside the
existing permissions, and remove the generic plugins.http.scope and
plugins.websocket.scope entries from the configuration example. Keep the
websocket capability permission and other settings unchanged.

}
```
Mirror with `capabilities/ios.json` (`"platforms": ["ios"]`), and platform
overrides via `tauri.android.conf.json` / `tauri.ios.conf.json`.

## 5. Build & signing commands

```bash
# init mobile projects (generates src-tauri/gen/android|ios)
cargo tauri android init
cargo tauri ios init

# dev on device/simulator
cargo tauri android dev
cargo tauri ios dev --open

# release builds
cargo tauri android build # unsigned APK
cargo tauri android build --apk # APK
cargo tauri android build --aab # AAB for Play Store
cargo tauri ios build
```

Android signing is configured in `tauri.conf.json` (`bundle.android.*`) or via
`src-tauri/gen/android/gradle.properties` + `build.gradle.kts` signingConfigs.
Comment on lines +153 to +154

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- TAURI_MOBILE_RESEARCH.md excerpt ---'
nl -ba TAURI_MOBILE_RESEARCH.md | sed -n '145,165p'

echo
echo '--- search for Android signing references in repo ---'
python3 - <<'PY'
import os
from pathlib import Path
targets = []
for p in Path('.').rglob('*'):
    if p.is_file() and p.suffix in {'.md', '.json', '.json5', '.toml', '.ts', '.js', '.kts', '.kt', '.rs', '.yml', '.yaml'}:
        try:
            text = p.read_text(errors='ignore')
        except Exception:
            continue
        if any(k in text for k in ['keystore.properties', 'signingConfigs', 'bundle.android', 'android.signing', 'signing']):
            targets.append(str(p))
for p in targets[:200]:
    print(p)
PY

Repository: getappz/agentflare

Length of output: 237


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path

path = Path("TAURI_MOBILE_RESEARCH.md")
text = path.read_text(errors="ignore").splitlines()
for i in range(145, 166):
    if 1 <= i <= len(text):
        print(f"{i:4d}: {text[i-1]}")
PY

Repository: getappz/agentflare

Length of output: 1174


🌐 Web query:

Tauri Android signing keystore.properties signingConfigs bundle.android tauri.conf.json official docs

💡 Result:

To configure Android signing for a Tauri application, you do not use tauri.conf.json directly for keystore credentials; instead, you configure the native Android Gradle project generated by Tauri [1][2]. Tauri's official documentation provides a standard approach using a keystore.properties file and Gradle signingConfigs [1][2]. 1. Create a keystore.properties File In your project directory, create a file named src-tauri/gen/android/keystore.properties. This file should contain the necessary information to access your Java Keystore (JKS) [1][2]: password= keyAlias= storeFile= Note: For CI/CD pipelines, these values are typically injected via environment variables rather than stored in a plaintext file [1][2][3]. 2. Configure Gradle (build.gradle.kts) Edit the file at src-tauri/gen/android/app/build.gradle.kts to load these properties and configure the release signing config [1][2]. Add the signingConfigs block before the buildTypes block: signingConfigs { create("release") { val keystorePropertiesFile = rootProject.file("keystore.properties") val keystoreProperties = Properties if (keystorePropertiesFile.exists) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } keyAlias = keystoreProperties["keyAlias"] as String keyPassword = keystoreProperties["password"] as String storeFile = file(keystoreProperties["storeFile"] as String) storePassword = keystoreProperties["password"] as String } } Then, apply this configuration in the buildTypes block: buildTypes { getByName("release") { signingConfig = signingConfigs.getByName("release") } } Tauri configuration files (tauri.conf.json or tauri.android.conf.json) are primarily used for application metadata, window settings, and other bundle configurations, but they do not manage the native Android keystore signing process [4][5]. Always refer to the official Tauri Android signing documentation for the most accurate, up-to-date implementation details [2].

Citations:


Fix the Android signing workflow. Tauri’s Android docs use src-tauri/gen/android/keystore.properties plus Gradle signingConfigs; tauri.conf.json’s bundle.android.* doesn’t configure the signing key. Keep the keystore out of source control and sign release APK/AAB artifacts before distribution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@TAURI_MOBILE_RESEARCH.md` around lines 153 - 154, Update the Android signing
guidance in TAURI_MOBILE_RESEARCH.md to use
src-tauri/gen/android/keystore.properties with Gradle signingConfigs, rather
than tauri.conf.json bundle.android settings. Instruct developers to keep
keystore credentials out of source control and ensure release APK/AAB artifacts
are signed before distribution.


## 6. Recommendation

- **Fork as a production base** → **HuLa** (monorepo, `.cargo/config.toml`,
`docs/android_startup_guide.md`, mobile locales, desktop+tablet+phone+iOS).
- **Minimal clean config reference** → copy `tauri.conf.json` +
`capabilities/android.json` + `Cargo.toml` from **vscode-android**.
- **Cross-platform mobile features** → **brenogonzaga** plugins (stt / tts /
audio-recorder).
- **Tuned release binary size** → **quantum**'s `panic="abort"`, `lto`,
`opt-level="s"`, `strip`, `codegen-units=1` profile.

## 7. Sources

- https://github.com/HuLaSpark/HuLa
- https://github.com/atilafassina/quantum
- https://github.com/swadhinbiswas/vscode-android
- https://github.com/Calmingstorm/bastion
- https://github.com/minosiants/two
- https://github.com/brenogonzaga/tauri-plugin-stt (et al.)
- https://v2.tauri.app/reference/config/
3 changes: 3 additions & 0 deletions assets/banner.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
agentflare · Optimize AI CLI agents for cost & performance
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20 changes: 19 additions & 1 deletion install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ param(

$ErrorActionPreference = 'Stop'

# --- branding banner (mirror of assets/banner.txt; keep in sync) ---
function Write-Banner {
if ($env:AGENTFLARE_QUIET_INSTALL -eq '1' -or $env:AGENTFLARE_QUIET_INSTALL -eq 'true') { return }
$useColor = (-not [Console]::IsOutputRedirected) -and (-not (Test-Path Env:NO_COLOR))
$line = '━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'
$text = ' agentflare · Optimize AI CLI agents for cost & performance'
if ($useColor) {
$e = [char]27
Write-Host "$e[2;36m$line$e[0m"
Write-Host "$e[1;35m$text$e[0m"
Write-Host "$e[2;36m$line$e[0m"
} else {
Write-Host $line
Write-Host $text
Write-Host $line
}
}

if ($Help) {
Write-Host 'Usage: .\install.ps1 [-BuildOnly] [-Help]'
Write-Host ''
Expand Down Expand Up @@ -47,7 +65,7 @@ $cargoBinDir = Get-CargoBinDir
$builtBinary = Join-Path $scriptDir 'target\release\agentflare.exe'
$installedBinary = Join-Path $cargoBinDir 'agentflare.exe'

Write-Host 'agentflare Windows installer'
Write-Banner
Write-Host 'Mode: build from source'
Write-Host ''
Write-Host 'Building agentflare (release)...'
Expand Down
16 changes: 15 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@

set -eu

# --- branding banner (mirror of assets/banner.txt; keep in sync) ---
print_banner() {
# Suppress for non-interactive, NO_COLOR, or quiet install.
case "${AGENTFLARE_QUIET_INSTALL:-}" in 1|true) return 0 ;; esac
if [ -t 1 ] && [ -z "${NO_COLOR+x}" ]; then
CY=$'\033[2;36m'; MG=$'\033[1;35m'; RS=$'\033[0m'

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the shebang of install.sh to determine shell compatibility
head -1 install.sh

Repository: getappz/agentflare

Length of output: 166


🏁 Script executed:

sed -n '1,40p' install.sh | cat -n

Repository: getappz/agentflare

Length of output: 2227


install.sh needs POSIX-compatible color escapes. The $'...' assignments are Bash-only, but this script runs under #!/bin/sh, so the installer breaks on dash-based systems. Replace them with POSIX-safe escapes or printf.

🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 24-24: In POSIX sh, $'..' is undefined.

(SC3003)


[warning] 24-24: In POSIX sh, $'..' is undefined.

(SC3003)


[warning] 24-24: In POSIX sh, $'..' is undefined.

(SC3003)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@install.sh` at line 24, Update the CY, MG, and RS color assignments in
install.sh to use POSIX-compatible escape handling instead of Bash-only $'...'
syntax, while preserving the existing color values and /bin/sh compatibility.

else
CY=""; MG=""; RS=""
fi
printf '%s\n' "${CY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RS}"
printf '%s\n' "${MG} agentflare · Optimize AI CLI agents for cost & performance${RS}"
printf '%s\n' "${CY}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RS}"
}

REPO="getappz/agentflare"
INSTALL_DIR="${AGENTFLARE_INSTALL_DIR:-$HOME/.local/bin}"
# Resolve the script's directory when invoked as a file. When piped via
Expand All @@ -34,7 +48,7 @@ SCRIPT_DIR="$(
fi
)"

echo "agentflare installer"
print_banner

finish() {
case ":$PATH:" in
Expand Down
25 changes: 25 additions & 0 deletions src/about.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//! `agentflare about` — show the branding banner, version, and where to go next.
//!
//! Also the target of a bare `agentflare` invocation (no subcommand), per the
//! "invisible once configured" design: the splash appears on explicit request,
//! never on the stdio/MCP path.

use clap::Args;

use crate::banner::print_banner;
use crate::cli::AGENTFLARE_VERSION;

#[derive(Args)]
#[command(about = "Show agentflare version and branding")]
pub struct AboutArgs {}

pub fn run(_args: AboutArgs) {
print_banner();
println!();
println!(" version {}", AGENTFLARE_VERSION.as_str());
println!();
println!(
" Get started: agentflare init --agent <claude-code|codex|cursor|windsurf|vscode-copilot|cline|continue>"
);
println!(" Docs: https://github.com/getappz/agentflare");
}
22 changes: 11 additions & 11 deletions src/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,32 +153,32 @@ pub fn cli_install(agent: &str, dry_run: bool) {
match agent_install::run_install(agent_registry::REGISTRY, agent, None, dry_run) {
Outcome::Ok(msg) => println!("{msg}"),
Outcome::Skipped(msg) => println!("skip: {msg}"),
Outcome::Err(msg) => eprintln!("error: {msg}"),
Outcome::Err(msg) => crate::ui::error(&format!("error: {msg}")),
}
}

pub fn cli_update(agent: &str, dry_run: bool) {
match agent_install::run_update(agent_registry::REGISTRY, agent, dry_run) {
Outcome::Ok(msg) => println!("{msg}"),
Outcome::Skipped(msg) => println!("skip: {msg}"),
Outcome::Err(msg) => eprintln!("error: {msg}"),
Outcome::Err(msg) => crate::ui::error(&format!("error: {msg}")),
}
}

pub fn cli_uninstall(agent: &str, dry_run: bool) {
match agent_install::run_uninstall(agent_registry::REGISTRY, agent, dry_run) {
Outcome::Ok(msg) => println!("{msg}"),
Outcome::Skipped(msg) => println!("skip: {msg}"),
Outcome::Err(msg) => eprintln!("error: {msg}"),
Outcome::Err(msg) => crate::ui::error(&format!("error: {msg}")),
}
}

pub fn cli_launch(agent: &str, model: Option<&str>, mode: Option<&str>, args: &[String]) {
match agent_launch::run_launch(agent_registry::REGISTRY, agent, model, mode, args) {
LaunchOutcome::Launched => {}
LaunchOutcome::NotFound(msg) => eprintln!("error: {msg}"),
LaunchOutcome::UnknownAgent(msg) => eprintln!("error: unknown agent: {msg}"),
LaunchOutcome::Extension(msg) => eprintln!("error: {msg}"),
LaunchOutcome::NotFound(msg) => crate::ui::error(&format!("error: {msg}")),
LaunchOutcome::UnknownAgent(msg) => crate::ui::error(&format!("error: unknown agent: {msg}")),
LaunchOutcome::Extension(msg) => crate::ui::error(&format!("error: {msg}")),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
}

Expand All @@ -204,7 +204,7 @@ pub fn cli_run(
}
None => {
if let Some(s) = stage {
eprintln!("agentflare run: no .dev.vars.{s} or .dev.vars found");
crate::ui::warning("agentflare run: no .dev.vars.{s} or .dev.vars found");
}
Vec::new()
}
Expand All @@ -219,9 +219,9 @@ pub fn cli_run(
true,
) {
LaunchOutcome::Launched => {}
LaunchOutcome::NotFound(msg) => eprintln!("error: {msg}"),
LaunchOutcome::UnknownAgent(msg) => eprintln!("error: unknown agent: {msg}"),
LaunchOutcome::Extension(msg) => eprintln!("error: {msg}"),
LaunchOutcome::NotFound(msg) => crate::ui::error(&format!("error: {msg}")),
LaunchOutcome::UnknownAgent(msg) => crate::ui::error(&format!("error: unknown agent: {msg}")),
LaunchOutcome::Extension(msg) => crate::ui::error(&format!("error: {msg}")),
}
}

Expand All @@ -238,7 +238,7 @@ pub fn cli_run_headless(agent: &str, prompt: &str, timeout: std::time::Duration)
| agent_launch::HeadlessOutcome::NotHeadless(msg)
| agent_launch::HeadlessOutcome::NotFound(msg)
| agent_launch::HeadlessOutcome::Failed(msg) => {
eprintln!("error: {msg}");
crate::ui::error(&format!("error: {msg}"));
1
}
}
Expand Down
6 changes: 3 additions & 3 deletions src/alias.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ pub fn run(
Err(name) => {
let err = format!("unknown shell: {name}");
if !json {
eprintln!("error: {err}");
crate::ui::error(&format!("error: {err}"));
std::process::exit(1);
}
emit_json(JsonOutput {
Expand All @@ -77,7 +77,7 @@ pub fn run(
Err(()) => {
let err = format!("cannot determine profile path for {}", shell.name());
if !json {
eprintln!("error: {err}");
crate::ui::error(&format!("error: {err}"));
std::process::exit(1);
}
emit_json(JsonOutput {
Expand All @@ -102,7 +102,7 @@ pub fn run(
Err(e) => {
let err = format!("cannot read profile {}: {e}", profile.display());
if !json {
eprintln!("error: {err}");
crate::ui::error(&format!("error: {err}"));
std::process::exit(1);
}
emit_json(JsonOutput {
Expand Down
6 changes: 3 additions & 3 deletions src/artifacts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ pub fn serve(host: &str, port: u16, dir: Option<std::path::PathBuf>) {
let server =
ArtifactServer::start_on(store, host, port).expect("failed to start artifact server");
let url = server.base_url();
eprintln!("agentflare artifacts server listening on {url}");
eprintln!(" store: {}", dir.display());
crate::ui::info(&format!("agentflare artifacts server listening on {url}"));
crate::ui::info(&format!(" store: {}", dir.display()));
if host != "127.0.0.1" && host != "localhost" {
eprintln!(" warning: bound to {host} — anyone on your network can view these artifacts");
crate::ui::warning(&format!("bound to {host} — anyone on your network can view these artifacts"));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
}
loop {
std::thread::park();
Expand Down
Loading
Loading