- fs5 entrypoint with status, push, pull, app, shell, logs commands - lib/adb.sh: ADB wrapper functions with idempotency and --force guards - lib/output.sh: human + JSON output formatting via Python - 33 bats tests (6 status, 9 transfer, 7 app, 5 shell, 6 logs) — all passing - Zero shellcheck warnings - .env-based config (device serial, log path) - Spec-Driven Development artifacts in specs/001-android-fs5-management/ Device: exone GmbH FS5 (RD51QE202392, Android 9)
5.8 KiB
5.8 KiB
Implementation Plan: Android FS5 Device Management
Feature: 001-android-fs5-management Created: 2026-03-02 Tech Stack: Bash 5, ADB 1.0.41, Python 3 (for JSON output), ShellCheck
Constitution Check
- Article I (Shell-First): Implemented as Bash CLI with Python for JSON formatting.
- Article II (ADB Interface): All device ops go through
adb -s $DEVICE_SERIAL. - Article III (Test-First): Tests written in
tests/using bats-core before implementation. - Article IV (Idempotency): All operations check state before mutating.
- Article V (Non-Destructive): Destructive ops require
--force; all writes logged. - Article VI (Device Identity): Serial loaded from
.env, validated on every run. - Article VII (Simplicity): 3 modules:
fs5(entrypoint),lib/adb.sh(ADB helpers),lib/output.sh(formatting). - Article VIII (Structured Output):
--jsonflag on all commands via Pythonjsonmodule. - Article IX (Config over Hardcoding):
.envfile withDEVICE_SERIAL,LOG_FILE,DEFAULT_PUSH_PATH.
Architecture
android-fs5/
├── fs5 # Main entrypoint (Bash, chmod +x)
├── lib/
│ ├── adb.sh # ADB wrapper functions
│ └── output.sh # Human/JSON output formatting
├── tests/
│ ├── test_status.bats # bats-core tests for status command
│ ├── test_transfer.bats # bats-core tests for push/pull
│ ├── test_app.bats # bats-core tests for app management
│ ├── test_shell.bats # bats-core tests for shell command
│ └── test_logs.bats # bats-core tests for log capture
├── .env.example # Template config (committed)
├── .env # Local config (gitignored)
├── .gitignore
├── README.md
└── fs5.log # Operation log (gitignored)
Module Design
fs5 — Main Entrypoint
Usage: ./fs5 <command> [options]
Commands:
status Show device health and info
push <src> <dst> Push file/dir to device
pull <src> <dst> Pull file/dir from device
app list List installed apps
app install <apk> Install APK
app uninstall <pkg> Uninstall package
shell [cmd] Execute shell command on device
logs Stream logcat output
Global Options:
--json Output as JSON
--force Allow destructive operations
--help Show help
lib/adb.sh — ADB Helpers
Key functions:
adb_check_device()— validates device is connected, exits 2 if notadb_cmd()— wrapsadb -s $DEVICE_SERIALwith error handlingadb_get_prop(key)— reads device propertyadb_shell(cmd)— executes shell command on deviceadb_push(src, dst)— pushes file with existence checkadb_pull(src, dst)— pulls fileadb_log_op(op, detail)— appends timestamped entry tofs5.log
lib/output.sh — Output Formatting
Key functions:
out_human(msg)— prints to stdoutout_json(data)— formats key=value pairs as JSON via Pythonout_error(msg)— prints to stderr with red colorout_success(msg)— prints with green checkmark
Data Model
Config (.env)
DEVICE_SERIAL=RD51QE202392
LOG_FILE=./fs5.log
DEFAULT_PUSH_PATH=/sdcard/
ADB_TIMEOUT=10
Device Status Object (JSON)
{
"serial": "RD51QE202392",
"model": "FS5",
"manufacturer": "exone GmbH",
"android_version": "9",
"battery_level": 85,
"battery_charging": true,
"storage_total_kb": 54302616,
"storage_used_kb": 18228852,
"storage_free_kb": 35926308,
"storage_percent_used": 34,
"adb_state": "device",
"timestamp": "2026-03-02T15:43:00+01:00"
}
Log Entry Format
2026-03-02T15:43:00+01:00 PUSH /home/ja/file.txt -> /sdcard/file.txt [OK]
2026-03-02T15:44:00+01:00 INSTALL /tmp/app.apk -> com.example.app [OK]
Technical Decisions
Why Bash over Python?
- ADB is a CLI tool — Bash is the natural glue language.
- No dependency installation required on the host.
- Python used only for JSON serialization (available on all Manjaro systems).
Why bats-core for testing?
- Purpose-built for Bash script testing.
- Supports mocking ADB calls for offline testing.
- Integrates with CI/CD pipelines.
JSON Output Strategy
Use Python one-liner to convert shell variables to JSON:
python3 -c "import json,sys; d={...}; print(json.dumps(d, indent=2))"
This avoids jq as a hard dependency while producing valid JSON.
Exit Code Strategy
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General error (bad args, operation failed) |
| 2 | Device not found / ADB error |
Dependencies
| Tool | Version | Required | Install |
|---|---|---|---|
| adb | 1.0.41+ | YES | sudo pacman -S android-tools |
| bash | 5.x | YES | Pre-installed |
| python3 | 3.x | YES | Pre-installed |
| bats-core | 1.x | DEV | sudo pacman -S bash-bats |
| shellcheck | 0.9+ | DEV | sudo pacman -S shellcheck |
Validation Scenarios (Quickstart)
# 1. Verify device connected
./fs5 status
# 2. Test JSON output
./fs5 status --json | python3 -m json.tool
# 3. Test file transfer
echo "test" > /tmp/fs5-test.txt
./fs5 push /tmp/fs5-test.txt /sdcard/fs5-test.txt
./fs5 pull /sdcard/fs5-test.txt /tmp/fs5-pulled.txt
diff /tmp/fs5-test.txt /tmp/fs5-pulled.txt && echo "PASS"
# 4. Test idempotency
./fs5 push /tmp/fs5-test.txt /sdcard/fs5-test.txt # Should warn, not fail
# 5. Test app listing
./fs5 app list
# 6. Test shell passthrough
./fs5 shell "echo hello from device"
# 7. Test log capture
./fs5 logs --lines 5
# 8. Test disconnected behavior (unplug device first)
./fs5 status; echo "Exit code: $?" # Should be 2