Metadata-Version: 2.4
Name: xiaokeer.android.ops
Version: 0.1.0
Summary: Agent-first macOS CLI for safe Android photo movement over ADB.
Author: xiaokeer
License-Expression: MIT
Keywords: android,adb,photos,cli,agent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Environment :: Console
Classifier: Operating System :: MacOS
Classifier: Topic :: System :: Archiving
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# xiaokeer-android-ops

`xiaokeer-android-ops` is a macOS-only, agent-first command line tool for reliable Android photo movement through ADB.

The MVP moves image files from an already authorized Android device to a Mac run directory with a two-stage workflow:

1. `photos plan` creates a stable manifest and does not copy or delete files.
2. `photos move --execute` copies only manifest items, verifies SHA-256 on both sides, then deletes only verified Android source files.
3. `photos resume --execute` resumes the same manifest without selecting new files.

The project is designed for AI agents first. Default output is JSON, commands never prompt for input, and destructive actions require explicit parameters.

## Identity

| Item | Value |
| --- | --- |
| GitHub repository | `xiaokeer-android-ops` |
| PyPI package | `xiaokeer.android.ops` |
| Python import namespace | `xiaokeer.android.ops` |
| CLI command | `xandroidops` |
| Initial version | `0.1.0` |
| License | MIT |

## Scope

Supported host:

- macOS
- Python `>=3.9`
- ADB installed separately

Supported device scope:

- authorized Android device connected through ADB
- normal shared storage image directories
- device-side `sha256sum` for deletion-capable execution

Default Android source directory:

```text
/storage/emulated/0/DCIM/Camera
```

Supported image extensions:

```text
.jpg
.jpeg
.png
.webp
.heic
.heif
.gif
```

## Non-Goals

This project is not a backup tool, sync tool, Android file manager, gallery replacement, GUI, TUI, video transfer tool, app manager, directory diff tool, WiFi pairing manager, or Android permission bypass tool.

The MVP does not support recursive traversal, arbitrary file management, videos, folder sync, hidden files, MIME validation, third-party Python runtime dependencies, Linux, or Windows.

## Install

After the package is published to PyPI:

```bash
pip install xiaokeer.android.ops
```

Recommended global CLI install after PyPI publication:

```bash
pipx install xiaokeer.android.ops
```

Current local source install:

```bash
cd /Users/chongwen002/project/xiaokeer-android-ops
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip
python -m pip install -e .
```

The project does not bundle ADB. Install Android platform tools separately and make sure `adb` is on `PATH`.

## Commands

```bash
xandroidops --version
xandroidops doctor
xandroidops photos count
xandroidops photos plan
xandroidops photos plan-all
xandroidops photos move --manifest /path/to/manifest.json --execute
xandroidops photos resume --manifest /path/to/manifest.json --execute
```

`photos verify` is reserved for a future release.

## Agent Workflow

Check host and device readiness:

```bash
xandroidops doctor
```

Count all album-visible Android images:

```bash
xandroidops photos count
```

Create a read-only movement plan:

```bash
xandroidops photos plan --limit 20
```

Create a read-only movement plan for all album-visible images:

```bash
xandroidops photos plan-all
```

Plan from a different Android shared storage directory:

```bash
xandroidops photos plan --device-dir /storage/emulated/0/Pictures/Screenshots --limit 10
```

Move only the files in the manifest:

```bash
xandroidops photos move --manifest /Users/example/Desktop/AndroidPhotoMove-20260604-163000/manifest.json --execute
```

Resume the same manifest after interruption or partial failure:

```bash
xandroidops photos resume --manifest /Users/example/Desktop/AndroidPhotoMove-20260604-163000/manifest.json --execute
```

Agents should read JSON output, exit codes, and manifest item states. They must not parse human prose for workflow state.

## Safety Model

`photos count` is read-only. It queries Android MediaStore for `content://media/external/images/media`, so the count reflects album-visible images indexed by Android rather than every image-like cache file on shared storage.

`photos plan` is the dry run. It is read-only, selects files, creates a run directory, and writes `manifest.json`.

`photos plan-all` is also read-only. It creates a manifest for every album-visible image returned by Android MediaStore, sets the manifest source to `mediastore`, and still requires `photos move --manifest ... --execute` before any source deletion can occur.

`photos move --execute` never creates a new plan. It executes only the manifest supplied through `--manifest`.

Android source deletion is allowed only when all of these facts are true:

- the item is listed in the manifest
- the connected device serial matches the manifest device serial
- the local copy exists
- remote SHA-256 was computed with device-side `sha256sum`
- local SHA-256 was computed with Python `hashlib.sha256`
- hashes match exactly
- the item is under the planned Android source directory
- `--execute` was explicitly provided

The tool never deletes directories, never uses wildcards for deletion, and never deletes manifest-external paths.

## Default Output

JSON is the default output for all commands.

Example no-device response:

```json
{
  "ok": false,
  "tool": "xiaokeer.android.ops",
  "tool_version": "0.1.0",
  "error": {
    "code": "NO_DEVICE",
    "message": "No authorized Android device found.",
    "category": "device_unavailable"
  },
  "next_action": {
    "type": "connect_device",
    "instructions": [
      "Connect Android device by USB.",
      "Enable USB debugging.",
      "Approve this Mac on the device."
    ]
  }
}
```

Example successful plan shape:

```json
{
  "ok": true,
  "tool": "xiaokeer.android.ops",
  "tool_version": "0.1.0",
  "manifest_path": "/Users/example/Desktop/AndroidPhotoMove-20260604-163000/manifest.json",
  "run_dir": "/Users/example/Desktop/AndroidPhotoMove-20260604-163000",
  "selected_count": 3,
  "summary": "plan_created",
  "next_action": {
    "type": "execute_move",
    "command": "xandroidops photos move --manifest /Users/example/Desktop/AndroidPhotoMove-20260604-163000/manifest.json --execute"
  }
}
```

Text output is available only when a command explicitly supports `--text`.

## Exit Codes

| Code | Meaning |
| --- | --- |
| `0` | success, including successful plan |
| `1` | generic error |
| `2` | device unavailable, unauthorized, or multiple devices without `--serial` |
| `3` | no matching photos or empty plan |
| `4` | copy failure |
| `5` | verification failure |
| `6` | delete failure |
| `7` | manifest or argument mismatch |
| `8` | ADB capability missing, such as missing device-side `sha256sum` |

## Manifest

The manifest is the source of truth for execution and recovery. It is a stable public contract and uses schema version `1`.

Run directory shape:

```text
~/Desktop/AndroidPhotoMove-YYYYMMDD-HHMMSS/
  manifest.json
  files/
```

Item states:

```text
selected
copied
verify_failed
verified
delete_failed
deleted
media_refresh_attempted
completed
failed
```

The manifest is preserved permanently as audit and recovery data.

## Development

```bash
cd /Users/chongwen002/project/xiaokeer-android-ops
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -U pip build twine
python -m pip install -e .
python -m unittest discover tests -v
python -m build
python -m twine check dist/*
```

Automated tests use fake ADB behavior and must not require a real Android device.

## Real Device Validation

Real device validation is manual and documented in `REAL_DEVICE_TEST.md`. It may delete real source files on the Android device, so use disposable test images only.

No one-off real-device validation scripts are allowed. If validation needs automation, it must become part of the official CLI or test harness.
