diff --git a/docs/hosting/mirofish-hermes-integration-contract.md b/docs/hosting/mirofish-hermes-integration-contract.md
new file mode 100644
index 00000000..435e0990
--- /dev/null
+++ b/docs/hosting/mirofish-hermes-integration-contract.md
@@ -0,0 +1,93 @@
+# MiroFish Hermes Integration Contract
+
+## Purpose
+
+This document defines the minimum stable contract for Hermes and OpenClaw to use the MiroFish host safely.
+
+## Canonical Paths
+
+- Runtime: `/Users/Shared/OpenClaw/mirofish-runtime`
+- Transcribes: `/Users/Shared/OpenClaw/transcribes`
+- Source only: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish`
+
+## Service Identity
+
+- Service label: `com.openclaw.mirofish`
+- Runtime owner: `airstride`
+- Access model: LAN + SSH only
+
+## Network Endpoints
+
+- Frontend LAN URL: `http://10.0.0.161:3000`
+- Backend LAN URL: `http://10.0.0.161:5001`
+- Backend health: `http://10.0.0.161:5001/health`
+- Frontend proxy health path: `http://10.0.0.161:3000/api/graph/project/list`
+
+## SSH Access
+
+- SSH alias: `openclaw-mirofish`
+- SSH user: `airstride`
+
+Recommended tunnels:
+
+```bash
+ssh -L 3000:127.0.0.1:3000 openclaw-mirofish
+ssh -L 5001:127.0.0.1:5001 openclaw-mirofish
+```
+
+Tunnel-based local URLs:
+
+- Frontend: `http://127.0.0.1:3000`
+- Backend: `http://127.0.0.1:5001`
+
+## Required Health Checks
+
+Before Hermes or OpenClaw send operational work to MiroFish, the host should pass:
+
+```bash
+sudo launchctl print system/com.openclaw.mirofish
+curl -sS http://127.0.0.1:5001/health
+curl -sSI http://127.0.0.1:3000/
+curl -sS http://127.0.0.1:3000/api/graph/project/list
+```
+
+Expected results:
+
+- LaunchDaemon present in `system`
+- Backend returns `{"service":"MiroFish Backend","status":"ok"}`
+- Frontend returns `HTTP/1.1 200 OK`
+- Frontend proxy returns a successful JSON payload
+
+## Smoke Test
+
+Use the canonical smoke test after reboot or maintenance:
+
+```bash
+/Users/Shared/OpenClaw/mirofish-runtime/scripts/host-smoke-test.sh
+```
+
+## Operational Data Rules
+
+- Do not run MiroFish from iCloud.
+- Do not write operational runtime state back into the iCloud source tree.
+- Treat `/Users/Shared/OpenClaw/mirofish-runtime/backend/uploads` as persistent operational state.
+- Treat `/Users/Shared/OpenClaw/transcribes` as the canonical local operational transcribes dataset.
+
+## Consumption Rules
+
+- Prefer SSH tunnels for operator access from other machines.
+- Use LAN URLs only inside the trusted local network.
+- Do not expose ports `3000` or `5001` through router forwarding or public internet ingress.
+- Hermes and OpenClaw should assume MiroFish is a long-lived host service, not an ephemeral dev process.
+
+## Operational Commands
+
+From `/Users/Shared/OpenClaw/mirofish-runtime`:
+
+```bash
+./scripts/host-start.sh
+./scripts/host-stop.sh
+./scripts/host-status.sh
+tail -f runtime/logs/backend.log
+tail -f runtime/logs/frontend.log
+```
diff --git a/docs/hosting/mirofish-host-runbook.md b/docs/hosting/mirofish-host-runbook.md
new file mode 100644
index 00000000..bf642073
--- /dev/null
+++ b/docs/hosting/mirofish-host-runbook.md
@@ -0,0 +1,145 @@
+# MiroFish Host Runbook
+
+## Canonical Runtime
+
+- Shared source of truth: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish`
+- Canonical operational runtime: `/Users/Shared/OpenClaw/mirofish-runtime`
+- Canonical operational transcribes: `/Users/Shared/OpenClaw/transcribes`
+
+The runtime should live outside iCloud because always-on services need stable local files, virtual environments, logs, and uploads that are not subject to iCloud eviction or placeholder behavior.
+
+The iCloud source for breathwork transcribes remains:
+
+- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/BreathWork/Transcribes`
+
+That iCloud tree should be treated as source only. The shared operational copy should live in `/Users/Shared/OpenClaw/transcribes`.
+
+## Ports
+
+- Frontend: `3000`
+- Backend: `5001`
+
+## Commands
+
+Run from the runtime root:
+
+```bash
+./scripts/host-start.sh
+./scripts/host-stop.sh
+./scripts/host-status.sh
+tail -f runtime/logs/backend.log
+tail -f runtime/logs/frontend.log
+./scripts/host-smoke-test.sh
+```
+
+Sync transcribes from iCloud source to the shared operational path:
+
+```bash
+./scripts/sync-transcribes.sh
+```
+
+## SSH Tunnels
+
+```bash
+ssh -L 3000:127.0.0.1:3000 openclaw-mirofish
+ssh -L 5001:127.0.0.1:5001 openclaw-mirofish
+```
+
+## Suggested SSH Alias
+
+```sshconfig
+Host openclaw-mirofish
+ HostName 10.0.0.161
+ User airstride
+ ServerAliveInterval 30
+ ServerAliveCountMax 3
+ StrictHostKeyChecking accept-new
+```
+
+## Legacy LaunchAgent Template
+
+The current persistent service is the system LaunchDaemon below, running as `airstride`.
+Use this LaunchAgent template only for a user-session fallback.
+
+The validated plist is:
+
+- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish/ops/launchd/airshare.mirofish.plist`
+
+It should be installed by a privileged shell or by a shell already running as `airstride`. If you are running as `airstride`, use:
+
+```bash
+cp /Users/adrianlat/Library/Mobile\ Documents/com~apple~CloudDocs/airShare/MiroFish/ops/launchd/airshare.mirofish.plist ~/Library/LaunchAgents/airshare.mirofish.plist
+launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/airshare.mirofish.plist
+launchctl kickstart -k gui/$(id -u)/airshare.mirofish
+```
+
+If you are running from another admin account, bootstrap into `airstride` requires root privileges or an interactive `airstride` session.
+
+Template:
+
+```xml
+
+
+
+
+ Label
+ airshare.mirofish
+ ProgramArguments
+
+ /bin/zsh
+ /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh
+ /Users/Shared/OpenClaw/mirofish-runtime
+
+ WorkingDirectory
+ /Users/Shared/OpenClaw/mirofish-runtime
+ RunAtLoad
+
+ KeepAlive
+
+ AbandonProcessGroup
+
+ StandardOutPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log
+ StandardErrorPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log
+
+
+```
+
+## Validated Constraints
+
+- MiroFish is not run from iCloud.
+- Runtime path is local and shared.
+- SSH is LAN-only and should not be published through router/NAT.
+- Docker was not available during validation, so source deployment is the active path.
+
+## LaunchDaemon Migration
+
+For a true always-on host, prefer the system LaunchDaemon:
+
+- Source plist: `/Users/Shared/OpenClaw/mirofish-runtime/ops/launchd/com.openclaw.mirofish.plist`
+- Installed plist: `/Library/LaunchDaemons/com.openclaw.mirofish.plist`
+- Label: `com.openclaw.mirofish`
+- User: `airstride`
+
+Install with:
+
+```bash
+sudo /Users/Shared/OpenClaw/mirofish-runtime/scripts/install-mirofish-launchdaemon.sh
+```
+
+Validate with:
+
+```bash
+sudo launchctl print system/com.openclaw.mirofish
+curl -sS http://127.0.0.1:5001/health
+curl -sSI http://127.0.0.1:3000/
+curl -sS http://127.0.0.1:3000/api/graph/project/list
+```
+
+## Hermes Integration
+
+The canonical integration contract for Hermes and OpenClaw lives at:
+
+- `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish/docs/hosting/mirofish-hermes-integration-contract.md`
+- `/Users/Shared/OpenClaw/mirofish-runtime/docs/hosting/mirofish-hermes-integration-contract.md`
diff --git a/docs/hosting/mirofish-ops-contract.md b/docs/hosting/mirofish-ops-contract.md
new file mode 100644
index 00000000..0901f868
--- /dev/null
+++ b/docs/hosting/mirofish-ops-contract.md
@@ -0,0 +1,47 @@
+# MiroFish Ops Contract
+
+## Canonical Paths
+
+- Runtime: `/Users/Shared/OpenClaw/mirofish-runtime`
+- Transcribes: `/Users/Shared/OpenClaw/transcribes`
+- Source only: `/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/airShare/MiroFish`
+
+## Ownership
+
+- Service owner: `airstride`
+- LaunchDaemon label: `com.openclaw.mirofish`
+
+## Local Health Checks
+
+```bash
+sudo launchctl print system/com.openclaw.mirofish
+curl -sS http://127.0.0.1:5001/health
+curl -sSI http://127.0.0.1:3000/
+curl -sS http://127.0.0.1:3000/api/graph/project/list
+```
+
+## Post-Reboot Smoke Test
+
+```bash
+/Users/Shared/OpenClaw/mirofish-runtime/scripts/host-smoke-test.sh
+```
+
+## LAN + SSH Access
+
+- Frontend: `http://10.0.0.161:3000`
+- Backend: `http://10.0.0.161:5001`
+- SSH alias: `openclaw-mirofish`
+- SSH user: `airstride`
+
+Recommended tunnels:
+
+```bash
+ssh -L 3000:127.0.0.1:3000 openclaw-mirofish
+ssh -L 5001:127.0.0.1:5001 openclaw-mirofish
+```
+
+## Operational Rule
+
+- Do not run MiroFish from iCloud.
+- Do not change canonical runtime or transcribes paths without migration.
+- Treat `backend/uploads` and shared transcribes as operational state.
diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js
index e840e116..4a04d20b 100644
--- a/frontend/src/api/index.js
+++ b/frontend/src/api/index.js
@@ -3,7 +3,7 @@ import i18n from '../i18n'
// 创建axios实例
const service = axios.create({
- baseURL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:5001',
+ baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
timeout: 300000, // 5分钟超时(本体生成可能需要较长时间)
headers: {
'Content-Type': 'application/json'
diff --git a/frontend/vite.config.js b/frontend/vite.config.js
index 8f1e4c11..c3452587 100644
--- a/frontend/vite.config.js
+++ b/frontend/vite.config.js
@@ -12,8 +12,20 @@ export default defineConfig({
}
},
server: {
+ host: '0.0.0.0',
+ port: 3000,
+ open: false,
+ proxy: {
+ '/api': {
+ target: 'http://localhost:5001',
+ changeOrigin: true,
+ secure: false
+ }
+ }
+ },
+ preview: {
+ host: '0.0.0.0',
port: 3000,
- open: true,
proxy: {
'/api': {
target: 'http://localhost:5001',
diff --git a/ops/launchd/airshare.mirofish.plist b/ops/launchd/airshare.mirofish.plist
new file mode 100644
index 00000000..63b50fe4
--- /dev/null
+++ b/ops/launchd/airshare.mirofish.plist
@@ -0,0 +1,26 @@
+
+
+
+
+ Label
+ airshare.mirofish
+ ProgramArguments
+
+ /bin/zsh
+ /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh
+ /Users/Shared/OpenClaw/mirofish-runtime
+
+ WorkingDirectory
+ /Users/Shared/OpenClaw/mirofish-runtime
+ RunAtLoad
+
+ KeepAlive
+
+ AbandonProcessGroup
+
+ StandardOutPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log
+ StandardErrorPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log
+
+
diff --git a/ops/launchd/com.openclaw.mirofish.plist b/ops/launchd/com.openclaw.mirofish.plist
new file mode 100644
index 00000000..5f95b34c
--- /dev/null
+++ b/ops/launchd/com.openclaw.mirofish.plist
@@ -0,0 +1,41 @@
+
+
+
+
+ Label
+ com.openclaw.mirofish
+ ProgramArguments
+
+ /bin/zsh
+ /Users/Shared/OpenClaw/mirofish-runtime/scripts/host-start.sh
+ /Users/Shared/OpenClaw/mirofish-runtime
+
+ UserName
+ airstride
+ WorkingDirectory
+ /Users/Shared/OpenClaw/mirofish-runtime
+ EnvironmentVariables
+
+ HOME
+ /Users/airstride
+ CODEX_HOME
+ /Users/airstride/.codex-openclaw
+ PATH
+ /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+
+ RunAtLoad
+
+ KeepAlive
+
+ ProcessType
+ Background
+ AbandonProcessGroup
+
+ StandardOutPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.log
+ StandardErrorPath
+ /Users/Shared/OpenClaw/mirofish-runtime/runtime/logs/launchd.err.log
+ ThrottleInterval
+ 15
+
+
diff --git a/scripts/host-smoke-test.sh b/scripts/host-smoke-test.sh
new file mode 100755
index 00000000..9e71af2e
--- /dev/null
+++ b/scripts/host-smoke-test.sh
@@ -0,0 +1,119 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+ROOT_DIR="${1:-/Users/Shared/OpenClaw/mirofish-runtime}"
+TRANSCRIBES_DIR="${2:-/Users/Shared/OpenClaw/transcribes}"
+DAEMON_LABEL="${3:-com.openclaw.mirofish}"
+
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
+
+CURL_BIN="${CURL_BIN:-$(command -v curl)}"
+LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}"
+NC_BIN="${NC_BIN:-$(command -v nc || true)}"
+PLUTIL_BIN="${PLUTIL_BIN:-$(command -v plutil)}"
+
+pass() {
+ printf '[PASS] %s\n' "$1"
+}
+
+fail() {
+ printf '[FAIL] %s\n' "$1" >&2
+ exit 1
+}
+
+run_with_optional_sudo() {
+ "$@" 2>/tmp/mirofish-command.err || sudo -n "$@" 2>/tmp/mirofish-command.err
+}
+
+check_port() {
+ local port="$1"
+ local output_file="/tmp/mirofish-port${port}.txt"
+
+ if run_with_optional_sudo "$LSOF_BIN" -nP "-iTCP:$port" -sTCP:LISTEN >"$output_file"; then
+ cat "$output_file"
+ pass "Port $port is listening"
+ return 0
+ fi
+
+ if [[ -n "$NC_BIN" ]] && "$NC_BIN" -z 127.0.0.1 "$port" >/dev/null 2>&1; then
+ pass "Port $port accepts TCP connections"
+ return 0
+ fi
+
+ cat /tmp/mirofish-command.err >&2 || true
+ fail "Port $port is not listening"
+}
+
+printf 'MiroFish smoke test\n'
+printf 'runtime=%s\n' "$ROOT_DIR"
+printf 'transcribes=%s\n' "$TRANSCRIBES_DIR"
+printf 'daemon=%s\n\n' "$DAEMON_LABEL"
+
+if run_with_optional_sudo launchctl print "system/$DAEMON_LABEL" >/tmp/mirofish-daemon.txt; then
+ sed -n '1,40p' /tmp/mirofish-daemon.txt
+ if grep -Eq 'state = (running|spawn scheduled)' /tmp/mirofish-daemon.txt; then
+ pass "LaunchDaemon is installed and active in launchd"
+ else
+ fail "LaunchDaemon is not active in launchd"
+ fi
+else
+ cat /tmp/mirofish-command.err >&2 || true
+ fail "LaunchDaemon is not installed or not readable"
+fi
+
+if "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/tmp/mirofish-backend.json; then
+ cat /tmp/mirofish-backend.json
+ pass "Backend health endpoint responds"
+else
+ fail "Backend health endpoint failed"
+fi
+
+if "$CURL_BIN" -fsSI http://127.0.0.1:3000/ >/tmp/mirofish-frontend.headers; then
+ sed -n '1,8p' /tmp/mirofish-frontend.headers
+ pass "Frontend root responds"
+else
+ fail "Frontend root failed"
+fi
+
+if "$CURL_BIN" -fsS http://127.0.0.1:3000/api/graph/project/list >/tmp/mirofish-proxy.json; then
+ cat /tmp/mirofish-proxy.json
+ pass "Frontend proxy to backend responds"
+else
+ fail "Frontend proxy failed"
+fi
+
+check_port 3000
+check_port 5001
+
+if [[ -r "$ROOT_DIR/runtime/logs/backend.log" && -r "$ROOT_DIR/runtime/logs/frontend.log" ]]; then
+ tail -n 5 "$ROOT_DIR/runtime/logs/backend.log" || true
+ printf '\n'
+ tail -n 5 "$ROOT_DIR/runtime/logs/frontend.log" || true
+ pass "Logs are present"
+else
+ fail "Runtime logs are missing"
+fi
+
+if [[ -d "$TRANSCRIBES_DIR" ]]; then
+ transcribe_count="$(find "$TRANSCRIBES_DIR" -type f 2>/dev/null | wc -l | tr -d ' ')"
+ transcribe_size="$(du -sh "$TRANSCRIBES_DIR" 2>/dev/null | awk '{print $1}')"
+ printf 'transcribes_files=%s\n' "$transcribe_count"
+ printf 'transcribes_size=%s\n' "$transcribe_size"
+ if [[ "$transcribe_count" -gt 0 ]]; then
+ pass "Transcribes path is populated"
+ else
+ fail "Transcribes path is empty"
+ fi
+else
+ fail "Transcribes path does not exist"
+fi
+
+if [[ -f "/Library/LaunchDaemons/$DAEMON_LABEL.plist" ]]; then
+ "$PLUTIL_BIN" -lint "/Library/LaunchDaemons/$DAEMON_LABEL.plist"
+ pass "Installed LaunchDaemon plist is valid"
+else
+ fail "Installed LaunchDaemon plist is missing"
+fi
+
+printf '\nMiroFish smoke test completed successfully.\n'
diff --git a/scripts/host-start.sh b/scripts/host-start.sh
new file mode 100755
index 00000000..1fb334e5
--- /dev/null
+++ b/scripts/host-start.sh
@@ -0,0 +1,86 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
+LOG_DIR="$ROOT_DIR/runtime/logs"
+PID_DIR="$ROOT_DIR/runtime/pids"
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
+UV_BIN="${UV_BIN:-$(command -v uv)}"
+NPM_BIN="${NPM_BIN:-$(command -v npm)}"
+NPX_BIN="${NPX_BIN:-$(command -v npx)}"
+CURL_BIN="${CURL_BIN:-$(command -v curl)}"
+LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}"
+
+mkdir -p "$LOG_DIR" "$PID_DIR" "$ROOT_DIR/backend/uploads/reports" "$ROOT_DIR/backend/uploads/simulations"
+
+monitor_existing_runtime() {
+ echo "MiroFish already running"
+ while true; do
+ if ! "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then
+ echo "Backend health check failed"
+ exit 1
+ fi
+ if ! "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then
+ echo "Frontend health check failed"
+ exit 1
+ fi
+ sleep 30
+ done
+}
+
+if "$LSOF_BIN" -nP -iTCP:3000 -sTCP:LISTEN >/dev/null 2>&1; then
+ if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1 && \
+ "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then
+ monitor_existing_runtime
+ fi
+ echo "Port 3000 already in use by an unhealthy process"
+ exit 1
+fi
+
+if "$LSOF_BIN" -nP -iTCP:5001 -sTCP:LISTEN >/dev/null 2>&1; then
+ if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1 && \
+ "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then
+ monitor_existing_runtime
+ fi
+ echo "Port 5001 already in use by an unhealthy process"
+ exit 1
+fi
+
+cd "$ROOT_DIR/backend"
+env FLASK_DEBUG=False PYTHONUNBUFFERED=1 "$UV_BIN" run --no-sync python run.py < /dev/null > "$LOG_DIR/backend.log" 2>&1 &
+BACKEND_PID=$!
+echo "$BACKEND_PID" > "$PID_DIR/backend.pid"
+
+for _ in {1..30}; do
+ if "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then
+ break
+ fi
+ sleep 1
+done
+
+if ! "$CURL_BIN" -fsS http://127.0.0.1:5001/health >/dev/null 2>&1; then
+ echo "Backend failed to start"
+ exit 1
+fi
+
+cd "$ROOT_DIR/frontend"
+"$NPM_BIN" run build >/dev/null
+"$NPX_BIN" vite preview --host 0.0.0.0 --port 3000 --strictPort < /dev/null > "$LOG_DIR/frontend.log" 2>&1 &
+FRONTEND_PID=$!
+echo "$FRONTEND_PID" > "$PID_DIR/frontend.pid"
+
+for _ in {1..30}; do
+ if "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then
+ break
+ fi
+ sleep 1
+done
+
+if ! "$CURL_BIN" -fsS http://127.0.0.1:3000/ >/dev/null 2>&1; then
+ echo "Frontend failed to start"
+ exit 1
+fi
+
+echo "MiroFish started"
+wait "$BACKEND_PID" "$FRONTEND_PID"
diff --git a/scripts/host-status.sh b/scripts/host-status.sh
new file mode 100755
index 00000000..1a329584
--- /dev/null
+++ b/scripts/host-status.sh
@@ -0,0 +1,23 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
+PID_DIR="$ROOT_DIR/runtime/pids"
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
+CURL_BIN="${CURL_BIN:-$(command -v curl)}"
+LSOF_BIN="${LSOF_BIN:-$(command -v lsof)}"
+
+echo "Backend health:"
+"$CURL_BIN" -fsS http://127.0.0.1:5001/health || true
+echo
+echo
+echo "Frontend headers:"
+"$CURL_BIN" -fsSI http://127.0.0.1:3000/ || true
+echo
+echo "Ports:"
+"$LSOF_BIN" -nP -iTCP:3000 -sTCP:LISTEN || true
+"$LSOF_BIN" -nP -iTCP:5001 -sTCP:LISTEN || true
+echo
+echo "PID files:"
+ls -la "$PID_DIR" 2>/dev/null || true
diff --git a/scripts/host-stop.sh b/scripts/host-stop.sh
new file mode 100755
index 00000000..dfc71965
--- /dev/null
+++ b/scripts/host-stop.sh
@@ -0,0 +1,35 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+ROOT_DIR="${1:-$(cd "$(dirname "$0")/.." && pwd)}"
+PID_DIR="$ROOT_DIR/runtime/pids"
+export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
+PKILL_BIN="${PKILL_BIN:-$(command -v pkill)}"
+
+stop_pid_file() {
+ local pid_file="$1"
+ if [[ -f "$pid_file" ]]; then
+ local pid
+ pid="$(cat "$pid_file")"
+ if kill -0 "$pid" >/dev/null 2>&1; then
+ kill "$pid" >/dev/null 2>&1 || true
+ for _ in {1..20}; do
+ if ! kill -0 "$pid" >/dev/null 2>&1; then
+ break
+ fi
+ sleep 1
+ done
+ kill -9 "$pid" >/dev/null 2>&1 || true
+ fi
+ rm -f "$pid_file"
+ fi
+}
+
+stop_pid_file "$PID_DIR/frontend.pid"
+stop_pid_file "$PID_DIR/backend.pid"
+
+"$PKILL_BIN" -f "vite preview --host 0.0.0.0 --port 3000" >/dev/null 2>&1 || true
+"$PKILL_BIN" -f "uv run --no-sync python run.py" >/dev/null 2>&1 || true
+
+echo "MiroFish stopped"
diff --git a/scripts/install-mirofish-launchdaemon.sh b/scripts/install-mirofish-launchdaemon.sh
new file mode 100755
index 00000000..03b5507a
--- /dev/null
+++ b/scripts/install-mirofish-launchdaemon.sh
@@ -0,0 +1,27 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+PLIST_SOURCE="/Users/Shared/OpenClaw/mirofish-runtime/ops/launchd/com.openclaw.mirofish.plist"
+PLIST_TARGET="/Library/LaunchDaemons/com.openclaw.mirofish.plist"
+
+if [[ ! -f "$PLIST_SOURCE" ]]; then
+ echo "Missing source plist: $PLIST_SOURCE"
+ exit 1
+fi
+
+if [[ "$(id -u)" -ne 0 ]]; then
+ echo "Run this script with sudo."
+ exit 1
+fi
+
+cp "$PLIST_SOURCE" "$PLIST_TARGET"
+chown root:wheel "$PLIST_TARGET"
+chmod 644 "$PLIST_TARGET"
+plutil -lint "$PLIST_TARGET"
+
+launchctl bootout system/com.openclaw.mirofish 2>/dev/null || true
+launchctl bootstrap system "$PLIST_TARGET"
+launchctl kickstart -k system/com.openclaw.mirofish
+
+echo "Installed LaunchDaemon at $PLIST_TARGET"
diff --git a/scripts/install-openclaw-launchagent.sh b/scripts/install-openclaw-launchagent.sh
new file mode 100755
index 00000000..94f77ac7
--- /dev/null
+++ b/scripts/install-openclaw-launchagent.sh
@@ -0,0 +1,27 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+if [[ "$(id -un)" != "airstride" ]]; then
+ echo "Run this script as airstride."
+ exit 1
+fi
+
+RUNTIME_ROOT="/Users/Shared/OpenClaw/mirofish-runtime"
+PLIST_SOURCE="$RUNTIME_ROOT/ops/launchd/airshare.mirofish.plist"
+PLIST_TARGET="$HOME/Library/LaunchAgents/airshare.mirofish.plist"
+
+mkdir -p "$HOME/Library/LaunchAgents"
+cp "$PLIST_SOURCE" "$PLIST_TARGET"
+
+if launchctl print "gui/$(id -u)" >/dev/null 2>&1; then
+ DOMAIN="gui/$(id -u)"
+else
+ DOMAIN="user/$(id -u)"
+fi
+
+launchctl bootout "$DOMAIN/airshare.mirofish" 2>/dev/null || true
+launchctl bootstrap "$DOMAIN" "$PLIST_TARGET"
+launchctl kickstart -k "$DOMAIN/airshare.mirofish"
+
+echo "Installed LaunchAgent at $PLIST_TARGET in $DOMAIN"
diff --git a/scripts/sync-transcribes.sh b/scripts/sync-transcribes.sh
new file mode 100755
index 00000000..6b5e934c
--- /dev/null
+++ b/scripts/sync-transcribes.sh
@@ -0,0 +1,14 @@
+#!/bin/zsh
+
+set -euo pipefail
+
+SOURCE_DIR="${1:-/Users/adrianlat/Library/Mobile Documents/com~apple~CloudDocs/BreathWork/Transcribes}"
+TARGET_DIR="${2:-/Users/Shared/OpenClaw/transcribes}"
+
+mkdir -p "$TARGET_DIR"
+rsync -a --delete --human-readable "$SOURCE_DIR/" "$TARGET_DIR/"
+chgrp -R openclawshare "$TARGET_DIR"
+chmod -R g+rwX "$TARGET_DIR"
+find "$TARGET_DIR" -type d -exec chmod g+s {} +
+
+echo "Transcribes synced to $TARGET_DIR"