Build the proton-drive Hermes skill following the Phase 4 spec
from ARCHITECTURE.md (§5). Primary path: rclone protondrive backend
with Drive SDK as a fallback option.
Skill components:
- skills/proton-drive/SKILL.md — YAML frontmatter + full docs for
all 9 tools (list, read, download, upload, search, mkdir,
delete, stat, sync) with usage, error handling, security notes
- skills/proton-drive/__init__.py — package init with exports
- skills/proton-drive/tools.py — Python subprocess wrappers for
each tool, plus rclone availability/remote checks
- tests/test_drive.py — 25 unit tests (all pass) with mocked
subprocess.run
All 9 Proton Drive tools implemented:
proton_drive_list, proton_drive_read, proton_drive_download,
proton_drive_upload, proton_drive_search, proton_drive_mkdir,
proton_drive_delete, proton_drive_stat, proton_drive_sync
Signed-off-by: Bee <bee@trentuna.com>
268 lines
8.2 KiB
Markdown
268 lines
8.2 KiB
Markdown
---
|
|
name: proton-drive
|
|
description: Hermes skill for Proton Drive — list, read, upload, download, search, and manage files using the rclone protondrive backend.
|
|
version: 1.0.0
|
|
author: Trentuna / Bee
|
|
license: MIT
|
|
platforms: [linux, macos, windows]
|
|
metadata:
|
|
hermes:
|
|
tags: [proton, drive, cloud-storage, rclone, file-management]
|
|
related_skills: [proton-mail, proton-pass, proton-vpn]
|
|
required_environment_variables:
|
|
- PROTON_RCLONE_REMOTE
|
|
optional_environment_variables:
|
|
- PROTON_RCLONE_PATH
|
|
- PROTON_DRIVE_USE_SDK
|
|
---
|
|
|
|
# Proton Drive Skill
|
|
|
|
> Agents can read, write, search, and manage files on Proton Drive through
|
|
> the battle-tested [rclone protondrive backend](https://rclone.org/protondrive/).
|
|
|
|
## Prerequisites
|
|
|
|
### 1. Install rclone
|
|
|
|
```bash
|
|
# Linux (official script)
|
|
curl https://rclone.org/install.sh | sudo bash
|
|
|
|
# macOS
|
|
brew install rclone
|
|
|
|
# Verify
|
|
rclone version
|
|
```
|
|
|
|
### 2. Configure the protondrive remote
|
|
|
|
```bash
|
|
rclone config create protondrive protondrive \
|
|
username=your@proton.me \
|
|
password=your_password \
|
|
--non-interactive
|
|
```
|
|
|
|
Or interactively:
|
|
|
|
```bash
|
|
rclone config
|
|
# → Choose "n" (new remote)
|
|
# → Name: protondrive
|
|
# → Type: protondrive
|
|
# → Follow prompts for username, password, 2FA
|
|
```
|
|
|
|
### 3. Set environment variables (optional)
|
|
|
|
```bash
|
|
export PROTON_RCLONE_REMOTE=protondrive # default: protondrive
|
|
export PROTON_RCLONE_PATH=/path/to/rclone # default: auto-detect from PATH
|
|
```
|
|
|
|
On first run, rclone will prompt for the mailbox password (your Proton
|
|
login password). After authentication, tokens are cached locally by
|
|
rclone's own credential store — no additional auth management needed
|
|
for subsequent calls.
|
|
|
|
## Tools
|
|
|
|
The skill provides 9 tools for interacting with Proton Drive. Each tool
|
|
shells out to `rclone` with the configured protondrive remote.
|
|
|
|
### `proton_drive_list`
|
|
|
|
List files and folders in a path on Proton Drive.
|
|
|
|
```
|
|
Usage: rclone lsf --json <remote>:<path>
|
|
Args:
|
|
path (string, default: "/") — Path to list (e.g. "Documents" or "/")
|
|
recursive (bool, default: false) — List recursively
|
|
dirs_only (bool, default: false) — Show directories only
|
|
files_only (bool, default: false) — Show files only
|
|
Returns: JSON array of {Name, Path, Size, ModTime, IsDir} items
|
|
```
|
|
|
|
### `proton_drive_read`
|
|
|
|
Read a file's content from Proton Drive.
|
|
|
|
```
|
|
Usage: rclone cat <remote>:<path>
|
|
Args:
|
|
path (string, required) — Path to the file (e.g. "Documents/notes.txt")
|
|
head (int, optional) — Only read first N lines
|
|
tail (int, optional) — Only read last N lines
|
|
Returns: File content as text string (up to 10MB; larger files use download instead)
|
|
```
|
|
|
|
Large files (>10MB) should be downloaded rather than read inline. The
|
|
tool will return an error suggesting `proton_drive_download` for files
|
|
exceeding the size threshold.
|
|
|
|
### `proton_drive_download`
|
|
|
|
Download a file from Proton Drive to a local path.
|
|
|
|
```
|
|
Usage: rclone copy <remote>:<path> <local_path>
|
|
Args:
|
|
remote_path (string, required) — Source path on Drive (e.g. "Documents/report.pdf")
|
|
local_path (string, required) — Destination local path (absolute or ~/expanded)
|
|
progress (bool, default: false) — Show transfer progress
|
|
Returns: JSON with {status, local_path, size_bytes}
|
|
```
|
|
|
|
The local path must be writable. If the path ends with `/`, the file
|
|
is downloaded into that directory preserving its filename.
|
|
|
|
### `proton_drive_upload`
|
|
|
|
Upload a local file or directory to Proton Drive.
|
|
|
|
```
|
|
Usage: rclone copy <local_path> <remote>:<path>
|
|
Args:
|
|
local_path (string, required) — Source path on local filesystem
|
|
remote_path (string, required) — Destination path on Drive (e.g. "Documents/")
|
|
create_parents (bool, default: true) — Create parent dirs if they don't exist
|
|
progress (bool, default: false) — Show transfer progress
|
|
Returns: JSON with {status, remote_path, size_bytes}
|
|
```
|
|
|
|
Supports both individual files and entire directories.
|
|
|
|
### `proton_drive_search`
|
|
|
|
Search for files by name across Proton Drive.
|
|
|
|
```
|
|
Usage: rclone lsf -R --files-only <remote>: | grep -i <query>
|
|
OR (for structured results): rclone lsf -R --json <remote>: | jq <filter>
|
|
Args:
|
|
query (string, required) — Search term or regex pattern
|
|
path (string, default: "/") — Root path to search under
|
|
regex (bool, default: false) — Treat query as regex instead of substring
|
|
max_results (int, default: 50) — Max results to return
|
|
Returns: JSON array of matching {Name, Path, Size, ModTime}
|
|
```
|
|
|
|
### `proton_drive_mkdir`
|
|
|
|
Create a folder on Proton Drive.
|
|
|
|
```
|
|
Usage: rclone mkdir <remote>:<path>
|
|
Args:
|
|
path (string, required) — Path of folder to create (e.g. "Documents/NewFolder")
|
|
Returns: JSON with {status: "created", path}
|
|
```
|
|
|
|
Creates all parent directories if they don't exist (like `mkdir -p`).
|
|
|
|
### `proton_drive_delete`
|
|
|
|
Delete a file or empty folder from Proton Drive.
|
|
|
|
```
|
|
Usage: rclone delete <remote>:<path>
|
|
Args:
|
|
path (string, required) — Path to the file/folder to delete
|
|
recursive (bool, default: false) — Recursively delete folder contents
|
|
Returns: JSON with {status: "deleted", path}
|
|
```
|
|
|
|
Non-recursive deletion of non-empty folders will fail. rclone's
|
|
`purge` command is used for recursive deletes.
|
|
|
|
### `proton_drive_stat`
|
|
|
|
Get detailed metadata for a file or folder.
|
|
|
|
```
|
|
Usage: rclone lsl <remote>:<path>
|
|
OR (detailed): rclone lsf --json <remote>:<path>
|
|
Args:
|
|
path (string, required) — Path to the file or folder
|
|
Returns: JSON with {Name, Path, Size, ModTime, IsDir, Hash, MimeType}
|
|
```
|
|
|
|
### `proton_drive_sync`
|
|
|
|
Synchronize a local directory with Proton Drive (or vice versa).
|
|
|
|
```
|
|
Usage: rclone sync <source> <destination> [flags]
|
|
Args:
|
|
source (string, required) — Source path (local: or remote:)
|
|
dest (string, required) — Destination (local: or remote:)
|
|
direction (enum, default: "upload") — "upload" (local→Drive), "download" (Drive→local), "bidirectional"
|
|
dry_run (bool, default: true) — If true, show what would change without syncing
|
|
delete_excluded (bool, default: false) — Delete files at dest not present at source
|
|
Returns: JSON with {status, changed, added, deleted, errors}
|
|
```
|
|
|
|
**IMPORTANT:** Defaults to `dry_run=true` for safety. The agent MUST
|
|
confirm with the user before running a live sync, especially when
|
|
`delete_excluded=true` — this can cause data loss.
|
|
|
|
## Implementation
|
|
|
|
All tools are implemented as Python subprocess wrappers in the
|
|
`tools/` subdirectory. The primary backend is `rclone`; the
|
|
TypeScript Drive SDK is available as a fallback via
|
|
`PROTON_DRIVE_USE_SDK=true`.
|
|
|
|
### Tool Handler Pattern
|
|
|
|
```python
|
|
def handle_proton_drive_list(args: dict, **kwargs) -> dict:
|
|
path = args.get("path", "/")
|
|
recursive = args.get("recursive", False)
|
|
cmd = _build_rclone_command("lsf", "--json", path, recursive=recursive)
|
|
result = _run_rclone(cmd, timeout=30)
|
|
if result.returncode != 0:
|
|
return {"error": result.stderr.strip()}
|
|
items = [json.loads(line) for line in result.stdout.strip().split("\n") if line]
|
|
return {"items": items}
|
|
```
|
|
|
|
### Rclone Configuration Check
|
|
|
|
Before any tool execution, verify the rclone remote exists:
|
|
|
|
```python
|
|
def _check_rclone_remote(remote: str) -> bool:
|
|
result = subprocess.run(
|
|
["rclone", "listremotes"],
|
|
capture_output=True, text=True, timeout=5
|
|
)
|
|
return f"{remote}:" in result.stdout
|
|
```
|
|
|
|
If the remote is not configured, return a clear error with setup
|
|
instructions referencing the README.
|
|
|
|
## Error Handling
|
|
|
|
| Error | Cause | Resolution |
|
|
|-------|-------|------------|
|
|
| `remote not found` | protondrive remote not configured | Run `rclone config` or refer to README |
|
|
| `authentication required` | Token expired or invalid | Run `rclone config reconnect <remote>` |
|
|
| `file not found` | Path doesn't exist | Check path with `proton_drive_list` |
|
|
| `file too large` | Read attempt on >10MB file | Use `proton_drive_download` instead |
|
|
| `rate limited` | Too many API calls | Retry with backoff (rclone does this auto) |
|
|
|
|
## Security Notes
|
|
|
|
- Files read by `proton_drive_read` enter the agent's context. For
|
|
sensitive documents, prefer `proton_drive_download` and specify a
|
|
local path.
|
|
- `proton_drive_sync` with `delete_excluded=true` is destructive.
|
|
Always default to `dry_run=true` and require confirmation.
|
|
- rclone stores tokens in its config file
|
|
(`~/.config/rclone/rclone.conf`). Ensure this file has appropriate
|
|
file permissions (`chmod 600`).
|