Files
Jane Alesi e6e5f34455 feat: add android-fs5 CLI toolset
- 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)
2026-03-02 15:55:09 +01:00

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): --json flag on all commands via Python json module.
  • Article IX (Config over Hardcoding): .env file with DEVICE_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 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)

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