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)
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
# 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
|
||||
```
|
||||
@@ -0,0 +1,173 @@
|
||||
# Feature Specification: Android FS5 Device Management
|
||||
|
||||
**Feature Branch**: `001-android-fs5-management`
|
||||
**Created**: 2026-03-02
|
||||
**Status**: Draft
|
||||
**Device**: exone GmbH FS5 (Serial: RD51QE202392, Android 9)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
A CLI toolset for managing the exone GmbH FS5 Android device connected via USB/ADB.
|
||||
Enables infrastructure operators to inspect device state, transfer files, manage apps,
|
||||
and automate device operations from the command line on Manjaro Linux.
|
||||
|
||||
---
|
||||
|
||||
## User Scenarios & Testing
|
||||
|
||||
### User Story 1 — Device Status & Health Check (Priority: P1)
|
||||
|
||||
**Why this priority**: Operators need to verify device connectivity and health before
|
||||
any operation. This is the entry point for all other workflows.
|
||||
|
||||
**Independent Test**: Run `./fs5 status` with device connected → shows device info.
|
||||
Run with device disconnected → exits with code 2 and clear error message.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** the FS5 is connected via USB, **When** I run `./fs5 status`,
|
||||
**Then** I see device model, Android version, serial, battery level, storage usage,
|
||||
and ADB connection state.
|
||||
|
||||
2. **Given** no device is connected, **When** I run `./fs5 status`,
|
||||
**Then** the tool exits with code 2 and prints "Device not found: RD51QE202392".
|
||||
|
||||
3. **Given** the FS5 is connected, **When** I run `./fs5 status --json`,
|
||||
**Then** I receive a valid JSON object with all device properties.
|
||||
|
||||
---
|
||||
|
||||
### User Story 2 — File Transfer (Push/Pull) (Priority: P1)
|
||||
|
||||
**Why this priority**: File transfer is the most common daily operation for device management.
|
||||
|
||||
**Independent Test**: Push a test file to `/sdcard/test/` → verify it exists on device.
|
||||
Pull it back → verify local copy matches original.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a local file exists, **When** I run `./fs5 push <local-path> <device-path>`,
|
||||
**Then** the file is transferred to the device and a success message with file size is shown.
|
||||
|
||||
2. **Given** a file exists on the device, **When** I run `./fs5 pull <device-path> <local-path>`,
|
||||
**Then** the file is downloaded locally and a success message with file size is shown.
|
||||
|
||||
3. **Given** the destination already contains the file, **When** I run `./fs5 push` without `--force`,
|
||||
**Then** the operation is skipped with a warning "File exists, use --force to overwrite".
|
||||
|
||||
4. **Given** a directory path, **When** I run `./fs5 push <local-dir>/ <device-dir>/`,
|
||||
**Then** all files in the directory are transferred recursively.
|
||||
|
||||
---
|
||||
|
||||
### User Story 3 — App Management (Priority: P2)
|
||||
|
||||
**Why this priority**: Installing, listing, and removing APKs is a frequent maintenance task.
|
||||
|
||||
**Independent Test**: List installed packages → verify output contains known system apps.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** an APK file, **When** I run `./fs5 app install <apk-path>`,
|
||||
**Then** the app is installed and the package name is confirmed in output.
|
||||
|
||||
2. **Given** an installed package, **When** I run `./fs5 app uninstall <package-name>`,
|
||||
**Then** the app is removed and exit code is 0.
|
||||
|
||||
3. **When** I run `./fs5 app list`,
|
||||
**Then** I see all installed non-system packages with their version codes.
|
||||
|
||||
4. **When** I run `./fs5 app list --all`,
|
||||
**Then** I see all packages including system apps.
|
||||
|
||||
---
|
||||
|
||||
### User Story 4 — Shell Command Execution (Priority: P2)
|
||||
|
||||
**Why this priority**: Enables ad-hoc device inspection and scripted automation.
|
||||
|
||||
**Independent Test**: Run `./fs5 shell "echo hello"` → output is "hello".
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **Given** a shell command, **When** I run `./fs5 shell "<command>"`,
|
||||
**Then** the command executes on the device and stdout/stderr are forwarded.
|
||||
|
||||
2. **Given** a multi-line script, **When** I pipe it via `cat script.sh | ./fs5 shell`,
|
||||
**Then** the script executes on the device.
|
||||
|
||||
---
|
||||
|
||||
### User Story 5 — Log Capture (Priority: P3)
|
||||
|
||||
**Why this priority**: Debugging device issues requires access to Android logcat.
|
||||
|
||||
**Independent Test**: Run `./fs5 logs --lines 10` → outputs exactly 10 log lines.
|
||||
|
||||
**Acceptance Scenarios**:
|
||||
|
||||
1. **When** I run `./fs5 logs`,
|
||||
**Then** logcat output streams to stdout until Ctrl+C.
|
||||
|
||||
2. **When** I run `./fs5 logs --lines <N>`,
|
||||
**Then** the last N log lines are printed and the command exits.
|
||||
|
||||
3. **When** I run `./fs5 logs --filter <tag>`,
|
||||
**Then** only log lines matching the tag are shown.
|
||||
|
||||
4. **When** I run `./fs5 logs --output <file>`,
|
||||
**Then** logs are written to the specified file.
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
### Functional Requirements
|
||||
|
||||
- **FR-001**: System MUST detect and validate device connectivity before every operation.
|
||||
- **FR-002**: System MUST support `--json` output on all commands.
|
||||
- **FR-003**: System MUST use device serial `RD51QE202392` from configuration, not hardcoded.
|
||||
- **FR-004**: System MUST log all write/mutating operations to a local log file with timestamps.
|
||||
- **FR-005**: System MUST require `--force` flag for any destructive operation.
|
||||
- **FR-006**: System MUST exit with code 2 when device is not found.
|
||||
- **FR-007**: System MUST provide `--help` on all commands and subcommands.
|
||||
- **FR-008**: System MUST be idempotent — re-running any command must not cause errors.
|
||||
|
||||
### Non-Functional Requirements
|
||||
|
||||
- **NFR-001**: Device status check MUST complete in < 3 seconds.
|
||||
- **NFR-002**: File transfer MUST show progress for files > 1MB.
|
||||
- **NFR-003**: All scripts MUST pass ShellCheck with zero warnings.
|
||||
- **NFR-004**: Tool MUST work on Manjaro Linux with ADB 1.0.41+.
|
||||
- **NFR-005**: No root access required on the device.
|
||||
|
||||
### Key Entities
|
||||
|
||||
- **Device**: The exone FS5 identified by serial number, with properties (model, OS, battery, storage).
|
||||
- **Operation**: A command executed against the device (push, pull, install, shell, logs).
|
||||
- **Config**: Project-level settings file (`.env`) storing device serial and default paths.
|
||||
- **Log Entry**: Timestamped record of every mutating operation performed.
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
- **SC-001**: All 5 user stories have passing acceptance tests.
|
||||
- **SC-002**: `./fs5 status` returns device info in < 3 seconds.
|
||||
- **SC-003**: File push/pull operations are idempotent (safe to re-run).
|
||||
- **SC-004**: Zero ShellCheck warnings across all scripts.
|
||||
- **SC-005**: `--json` flag produces valid JSON on all commands.
|
||||
- **SC-006**: Tool works correctly when device is disconnected (graceful error, exit code 2).
|
||||
- **SC-007**: All destructive operations require explicit confirmation flag.
|
||||
|
||||
---
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- GUI or web interface
|
||||
- Wireless ADB (WiFi) setup automation
|
||||
- Device rooting or system partition access
|
||||
- Multi-device simultaneous management (future)
|
||||
- Android backup/restore via `adb backup` (deprecated in Android 9+)
|
||||
@@ -0,0 +1,98 @@
|
||||
# Task Breakdown: Android FS5 Device Management
|
||||
|
||||
**Feature**: 001-android-fs5-management
|
||||
**Created**: 2026-03-02
|
||||
**Status**: Pending
|
||||
|
||||
---
|
||||
|
||||
## Phase 1: Project Setup
|
||||
|
||||
- [ ] T001 Create project skeleton: `.gitignore`, `.env.example`, `README.md`, `lib/`, `tests/` dirs
|
||||
- [ ] T002 Create `.env` from `.env.example` with device serial `RD51QE202392`
|
||||
- [ ] T003 Verify bats-core and shellcheck available (`bats --version`, `shellcheck --version`)
|
||||
|
||||
---
|
||||
|
||||
## Phase 2: Foundational Library (lib/)
|
||||
|
||||
- [ ] T004 Create `lib/output.sh` — `out_human`, `out_json`, `out_error`, `out_success` functions
|
||||
- [ ] T005 Create `lib/adb.sh` — `adb_check_device`, `adb_cmd`, `adb_get_prop`, `adb_shell`, `adb_log_op`
|
||||
- [ ] T006 Create `lib/adb.sh` — `adb_push`, `adb_pull` with existence checks and `--force` support
|
||||
|
||||
---
|
||||
|
||||
## Phase 3: Tests (RED phase — must fail before implementation)
|
||||
|
||||
- [ ] T007 [US1] Write `tests/test_status.bats` — tests for `status` command (connected, disconnected, --json)
|
||||
- [ ] T008 [US2] Write `tests/test_transfer.bats` — tests for push/pull (basic, idempotent, --force, directory)
|
||||
- [ ] T009 [US3] Write `tests/test_app.bats` — tests for `app list`, `app install`, `app uninstall`
|
||||
- [ ] T010 [US4] Write `tests/test_shell.bats` — tests for `shell` command passthrough
|
||||
- [ ] T011 [US5] Write `tests/test_logs.bats` — tests for `logs` command (stream, --lines, --filter, --output)
|
||||
- [ ] T012 Verify ALL tests FAIL (RED phase confirmed) before writing `fs5` entrypoint
|
||||
|
||||
---
|
||||
|
||||
## Phase 4: Implementation — P1 User Stories
|
||||
|
||||
- [ ] T013 [US1] Create `fs5` entrypoint: argument parsing, `--help`, config loading, `status` command
|
||||
- [ ] T014 [US1] Implement `status` command: collect device props, battery, storage, format human + JSON output
|
||||
- [ ] T015 [US2] Implement `push` command: file existence check, transfer, idempotency guard, logging
|
||||
- [ ] T016 [US2] Implement `pull` command: device file check, transfer, local overwrite guard, logging
|
||||
- [ ] T017 Verify T007 + T008 tests PASS (GREEN phase for P1)
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Implementation — P2 User Stories
|
||||
|
||||
- [ ] T018 [US3] Implement `app list` command: parse `pm list packages`, filter system/user, version lookup
|
||||
- [ ] T019 [US3] Implement `app install` command: APK validation, `adb install`, package name confirmation
|
||||
- [ ] T020 [US3] Implement `app uninstall` command: `--force` guard, `pm uninstall`, logging
|
||||
- [ ] T021 [US4] Implement `shell` command: passthrough to `adb shell`, stdin pipe support
|
||||
- [ ] T022 Verify T009 + T010 tests PASS (GREEN phase for P2)
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Implementation — P3 User Stories
|
||||
|
||||
- [ ] T023 [US5] Implement `logs` command: `adb logcat` stream, `--lines` (dump mode), `--filter`, `--output`
|
||||
- [ ] T024 Verify T011 tests PASS (GREEN phase for P3)
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Polish & Quality Gates
|
||||
|
||||
- [ ] T025 [P] Run `shellcheck fs5 lib/adb.sh lib/output.sh` — fix all warnings to zero
|
||||
- [ ] T026 [P] Run full test suite `bats tests/` — all tests must pass
|
||||
- [ ] T027 [P] Write `README.md` with installation, usage examples, and quickstart scenarios
|
||||
- [ ] T028 Validate all 8 quickstart scenarios from `plan.md` manually against live device
|
||||
- [ ] T029 Initial git commit: `feat: add android-fs5 CLI toolset`
|
||||
|
||||
---
|
||||
|
||||
## Dependency Graph
|
||||
|
||||
```text
|
||||
T001 → T002 → T003
|
||||
T003 → T004 → T005 → T006
|
||||
T006 → T007 → T008 → T009 → T010 → T011 → T012
|
||||
T012 → T013 → T014 → T015 → T016 → T017
|
||||
T017 → T018 → T019 → T020 → T021 → T022
|
||||
T022 → T023 → T024
|
||||
T024 → T025 [P] T026 [P] T027 [P]
|
||||
T025 + T026 + T027 → T028 → T029
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Progress Summary
|
||||
|
||||
| Phase | Tasks | Status |
|
||||
|-------|-------|--------|
|
||||
| 1. Setup | T001-T003 | Pending |
|
||||
| 2. Foundation | T004-T006 | Pending |
|
||||
| 3. Tests (RED) | T007-T012 | Pending |
|
||||
| 4. P1 Impl | T013-T017 | Pending |
|
||||
| 5. P2 Impl | T018-T022 | Pending |
|
||||
| 6. P3 Impl | T023-T024 | Pending |
|
||||
| 7. Polish | T025-T029 | Pending |
|
||||
Reference in New Issue
Block a user