- 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)
205 lines
5.8 KiB
Markdown
205 lines
5.8 KiB
Markdown
# 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
|
|
|
|
- [x] **Article I (Shell-First)**: Implemented as Bash CLI with Python for JSON formatting.
|
|
- [x] **Article II (ADB Interface)**: All device ops go through `adb -s $DEVICE_SERIAL`.
|
|
- [x] **Article III (Test-First)**: Tests written in `tests/` using bats-core before implementation.
|
|
- [x] **Article IV (Idempotency)**: All operations check state before mutating.
|
|
- [x] **Article V (Non-Destructive)**: Destructive ops require `--force`; all writes logged.
|
|
- [x] **Article VI (Device Identity)**: Serial loaded from `.env`, validated on every run.
|
|
- [x] **Article VII (Simplicity)**: 3 modules: `fs5` (entrypoint), `lib/adb.sh` (ADB helpers), `lib/output.sh` (formatting).
|
|
- [x] **Article VIII (Structured Output)**: `--json` flag on all commands via Python `json` module.
|
|
- [x] **Article IX (Config over Hardcoding)**: `.env` file with `DEVICE_SERIAL`, `LOG_FILE`, `DEFAULT_PUSH_PATH`.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```text
|
|
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
|
|
|
|
```text
|
|
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 not
|
|
- `adb_cmd()` — wraps `adb -s $DEVICE_SERIAL` with error handling
|
|
- `adb_get_prop(key)` — reads device property
|
|
- `adb_shell(cmd)` — executes shell command on device
|
|
- `adb_push(src, dst)` — pushes file with existence check
|
|
- `adb_pull(src, dst)` — pulls file
|
|
- `adb_log_op(op, detail)` — appends timestamped entry to `fs5.log`
|
|
|
|
### `lib/output.sh` — Output Formatting
|
|
|
|
Key functions:
|
|
- `out_human(msg)` — prints to stdout
|
|
- `out_json(data)` — formats key=value pairs as JSON via Python
|
|
- `out_error(msg)` — prints to stderr with red color
|
|
- `out_success(msg)` — prints with green checkmark
|
|
|
|
---
|
|
|
|
## Data Model
|
|
|
|
### Config (`.env`)
|
|
|
|
```bash
|
|
DEVICE_SERIAL=RD51QE202392
|
|
LOG_FILE=./fs5.log
|
|
DEFAULT_PUSH_PATH=/sdcard/
|
|
ADB_TIMEOUT=10
|
|
```
|
|
|
|
### Device Status Object (JSON)
|
|
|
|
```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
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
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)
|
|
|
|
```bash
|
|
# 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
|
|
```
|