Imported from axpdev-lab/aeroftp (
AGENTS.md). Install upstream withnpx skills add axpdev-lab/aeroftp. Copyright stays with the author.
AeroFTP CLI - Agent Integration Guide
Last updated: 2026-09-29
This file is for AI coding agents (Claude Code, Cursor, Codex, Devin, OpenClaw). It describes how to use AeroFTP CLI for remote operations without credentials. Development and pre-push gates live in CONTRIBUTING.md, not here.
Quick Start
# 1. See available servers (no credentials shown)
aeroftp-cli profiles --json
# 2. List files on a server
aeroftp-cli ls --profile "Server Name" /path/ --json
# 3. Upload a file
aeroftp-cli put --profile "Server Name" ./local-file.txt /remote/path/file.txt
# 4. Download a file
aeroftp-cli get --profile "Server Name" /remote/file.txt ./local-file.txt
# 5. Sync a directory
aeroftp-cli sync --profile "Server Name" ./local-dir/ /remote-dir/ --dry-run
# 6. Cross-profile copy between two saved servers
aeroftp-cli transfer "Source Server" "Destination Server" /src/path /dst/path --recursive
How Credentials Work
You do NOT need passwords, tokens, or API keys. The user has saved their servers in an encrypted vault. Use --profile "Name" to connect - credentials are resolved internally by the Rust backend and never exposed to your process.
# WRONG - do not ask the user for passwords
aeroftp-cli ls sftp://user:password@host /path/
# RIGHT - use saved profiles
aeroftp-cli ls --profile "My Server" /path/
Discovery
# List all saved servers with protocol, host, path
aeroftp-cli profiles --json
# Get full CLI capabilities as structured JSON
aeroftp-cli agent-info --json
The profiles --json output:
[
{
"id": "srv_123",
"name": "Production",
"protocol": "sftp",
"host": "prod.example.com",
"port": 22,
"username": "deploy",
"initialPath": "/var/www",
"cryptOverlay": null,
"protocolClass": "SFTP"
}
]
protocol is always the transport you connect over. cryptOverlay is the
encrypted-overlay binding: null when there is none, otherwise "aerocrypt"
(native, which is also what a binding that names no kind means) or
"rclone-crypt" (interop). protocolClass is the class the GUI
table shows for the same profile, so a profile with an enabled overlay reads
"protocol": "ftp" and "protocolClass": "Crypt" together.
Read cryptOverlay before writing to a path: on a bound profile the CLI
decrypts names and contents transparently, so what you upload lands encrypted
on the remote. The same two fields appear in agent-bootstrap --json,
agent-info --json and the agent's server_list_saved, and they survive
agent-info --redact-identifiers, which
hides who the account is (host, username) and not how the connection is shaped.
Common Commands
File Operations
| Command | Usage | Description |
|---|---|---|
ls |
aeroftp-cli ls --profile NAME /path/ [-l] [--json] |
List directory contents |
get |
aeroftp-cli get --profile NAME /remote/file [./local] |
Download file |
put |
aeroftp-cli put --profile NAME ./local /remote/path |
Upload file |
cat |
aeroftp-cli cat --profile NAME /remote/file |
Print file to stdout |
stat |
aeroftp-cli stat --profile NAME /remote/file [--json] |
File metadata |
find |
aeroftp-cli find --profile NAME /path/ "*.ext" [--json] |
Search files |
tree |
aeroftp-cli tree --profile NAME /path/ [-d depth] [--json] |
Directory tree |
Modify Operations
| Command | Usage | Description |
|---|---|---|
mkdir |
aeroftp-cli mkdir --profile NAME /remote/new-dir |
Create directory |
rm |
aeroftp-cli rm --profile NAME /remote/file |
Delete file |
rm -rf |
aeroftp-cli rm --profile NAME /remote/dir/ -rf |
Delete directory recursively |
mv |
aeroftp-cli mv --profile NAME /old/path /new/path |
Move or rename |
access |
aeroftp-cli access --profile NAME /path --to private|public|hidden |
Set privacy of an existing file/folder (OpenDrive; #252) |
Bulk Operations
| Command | Usage | Description |
|---|---|---|
get -r |
aeroftp-cli get --profile NAME /remote/dir/ ./local/ -r |
Download directory |
put -r |
aeroftp-cli put --profile NAME ./local/ /remote/dir/ -r |
Upload directory |
get glob |
aeroftp-cli get --profile NAME "/path/*.csv" |
Download matching files |
put glob |
aeroftp-cli put --profile NAME "./*.json" /remote/ |
Upload matching files |
sync |
aeroftp-cli sync --profile NAME ./local/ /remote/ |
Bidirectional sync |
sync --dry-run |
aeroftp-cli sync --profile NAME ./local/ /remote/ --dry-run |
Preview sync |
sync --immutable |
aeroftp-cli sync --profile NAME ./local/ /remote/ --immutable |
Never overwrite existing files: a same-size destination is skipped, one of another size (a cut upload's partial) is refused with exit 4 |
sync --update |
aeroftp-cli sync --profile NAME ./local/ /remote/ --direction upload --update |
One-way only: leave a destination copy that is newer than the source |
sync --conflict-mode |
aeroftp-cli sync --profile NAME ./local/ /remote/ --conflict-mode skip |
How a changed pair the dates cannot order is settled: source (default) or skip one way; newer (default), older, larger, smaller, rename or skip with --direction both. Pairs left alone are listed under conflicts_open |
sync --modify-window |
aeroftp-cli sync --profile NAME ./local/ /remote/ --modify-window 60 |
Seconds within which two times are the same instant (default 2, raised to the backend's precision) |
sync --checksum |
aeroftp-cli sync --profile NAME ./local/ /remote/ --checksum --dry-run |
Compare same-size files by server-side checksum instead of time |
sync --size-only |
aeroftp-cli sync --profile NAME ./local/ /remote/ --size-only |
Skip on size alone (alias --skip-matching); cannot be combined with --checksum |
sync --files-from |
aeroftp-cli sync --profile NAME ./local/ /remote/ --files-from list.txt |
Transfer only listed files |
sync --fast-list |
aeroftp-cli sync --profile NAME ./local/ /remote/ --fast-list |
S3 recursive listing (fewer API calls) |
cleanup |
aeroftp-cli cleanup --profile NAME /path/ [--force] |
Find/delete orphaned .aerotmp files |
dedupe |
aeroftp-cli dedupe --profile NAME /path/ --mode list |
Find duplicate files |
transfer |
aeroftp-cli transfer "SRC" "DST" /src /dst --recursive |
Cross-profile transfer between saved servers |
transfer-doctor |
aeroftp-cli transfer-doctor "SRC" "DST" /src /dst --json |
Preflight plan and risk summary |
Advanced Operations
| Command | Usage | Description |
|---|---|---|
mount |
aeroftp-cli --profile NAME mount /mnt/point |
Mount remote as local FUSE filesystem |
ncdu |
aeroftp-cli --profile NAME ncdu / [--json] |
Interactive disk usage explorer |
serve http |
aeroftp-cli --profile NAME serve http |
Expose remote as HTTP server |
serve webdav |
aeroftp-cli --profile NAME serve webdav |
Expose remote as WebDAV server |
serve ftp |
aeroftp-cli --profile NAME serve ftp |
Expose remote as FTP server |
serve sftp |
aeroftp-cli --profile NAME serve sftp |
Expose remote as SFTP server |
daemon start |
aeroftp-cli daemon start |
Start background service |
jobs add |
aeroftp-cli jobs add get --profile NAME /file |
Queue background transfer |
jobs list |
aeroftp-cli jobs list |
List queued/running jobs |
crypt init |
AEROFTP_CRYPT_PASSWORD=... aeroftp-cli --profile NAME crypt init _ /dir |
Init encrypted overlay |
crypt put |
AEROFTP_CRYPT_PASSWORD=... aeroftp-cli --profile NAME crypt put ./file _ /dir |
Upload encrypted |
crypt get |
AEROFTP_CRYPT_PASSWORD=... aeroftp-cli --profile NAME crypt get filename _ /dir ./out |
Download + decrypt |
crypt ls |
AEROFTP_CRYPT_PASSWORD=... aeroftp-cli --profile NAME crypt ls _ /dir |
List decrypted names |
crypt kit-verify |
aeroftp-cli crypt kit-verify --profile NAME --kit ./kit.txt |
Re-parse a saved Emergency Kit / marker and confirm it matches the active profile keystore (offline; no password) |
batch |
aeroftp-cli batch script.aeroftp-script |
Run batch script (.aeroftp-script) |
import |
aeroftp-cli import rclone [--json] |
Import profiles from rclone/FileZilla |
profile-export |
aeroftp-cli profile-export --output backup.aeroftp [--include-credentials] |
Export profiles to an encrypted .aeroftp backup (GUI-compatible; secrets opt-in) |
profile-import |
aeroftp-cli profile-import --input backup.aeroftp |
Import profiles from an encrypted .aeroftp backup (reads GUI-exported files; existing profiles are skipped, never overwritten) |
Info Operations
| Command | Usage | Description |
|---|---|---|
connect |
aeroftp-cli connect --profile NAME |
Test connection |
df |
aeroftp-cli df --profile NAME [--json] |
Storage quota |
about |
aeroftp-cli about --profile NAME [--json] |
Provider/server info with quota when available |
profiles |
aeroftp-cli profiles [--json] |
List saved servers |
pwd |
aeroftp-cli pwd NAME [--json] |
Base path every relative path resolves against, read from the saved profile without connecting |
agent-info |
aeroftp-cli agent-info --json |
Full capabilities JSON |
Output Modes
Always use --json when parsing output programmatically:
# Structured JSON - parse with jq or directly
aeroftp-cli ls --profile "Server" /path/ --json
# Plain text - human-readable, for display to user
aeroftp-cli ls --profile "Server" /path/ -l
stdout contains data only (file listings, file content, JSON). stderr contains status messages, progress bars, warnings.
# Pipe file content cleanly
aeroftp-cli cat --profile "Server" /remote/config.ini 2>/dev/null
# Parse JSON without noise
aeroftp-cli ls --profile "Server" / --json 2>/dev/null | jq '.entries[].name'
Exit Codes
| Code | Meaning | Agent Action |
|---|---|---|
| 0 | Success | Continue |
| 1 | Connection error | Retry or report to user |
| 2 | Not found | Check path spelling |
| 3 | Permission denied | Report to user |
| 4 | Transfer failed or partial | Read the JSON status and errors before retrying: a partial run can also mean a scan did not read the whole tree |
| 5 | Invalid usage | Fix command syntax (an invalid --exclude pattern or --multi-thread-cutoff value is one). Also a file over the provider's per-file size limit: do not retry the same file |
| 6 | Auth failed | Ask user to re-authorize |
| 7 | Not supported | Use alternative approach |
| 8 | Stopped at a limit, nothing failed | Raise the limit: a timeout on most commands, the --max-transfer budget on sync (JSON over_budget) |
| 9 | Already exists / directory not empty | Do not overwrite or delete unless the user asks: the target exists (--immutable, --no-clobber) or the directory is not empty |
| 10 | Server/parse error | Check server status |
| 11 | I/O error | Check disk space/permissions |
| 99 | Unknown | Report to user |
| 130 | Interrupted (SIGINT) | Stop: the user cancelled the run, also when it stopped a batch or a sync part way (JSON "status": "interrupted"). Do not retry |
Safety Guidelines
Safe operations (no confirmation needed)
ls,cat,stat,find,tree,df,profiles,connect,agent-info,cleanup(dry-run),dedupe --mode list,crypt kit-verify
Operations that modify remote state (inform user before executing)
put,mkdir,mv,access,sync,transfer,crypt put,crypt init
Destructive operations (always confirm with user first)
rm,rm -rf,sync --delete,cleanup --force,dedupe --mode newest/oldest/largest/smallest
Never do
- Do not ask the user for passwords - use
--profile - Do not pass credentials in URLs
- Do not pass crypt passwords directly on the command line when
AEROFTP_CRYPT_PASSWORDcan be used - Do not read the vault files directly
- Do not use
--insecureunless the user explicitly requests it
Profile Matching
Profiles match by name (case-insensitive). Use exact names to avoid ambiguity:
# Good - exact name
aeroftp-cli ls --profile "Production Server" /
# Risky - substring match, may be ambiguous
aeroftp-cli ls --profile "prod" /
# Best for scripting - use profile index number
aeroftp-cli ls --profile 1 /
GitHub Integration
AeroFTP treats GitHub repositories as filesystems. Every upload creates a Git commit.
# Browse repo
aeroftp-cli ls --profile "GitHub/myproject" /src/ -l
# Upload file → creates commit
aeroftp-cli put --profile "GitHub/myproject" ./fix.py /src/fix.py
# Read file
aeroftp-cli cat --profile "GitHub/myproject" /README.md
# Delete → creates commit
aeroftp-cli rm --profile "GitHub/myproject" /old-file.txt
For protected branches, AeroFTP auto-creates a working branch and offers PR creation. The token never leaves the vault.
Common Workflows
Deploy a website
aeroftp-cli put --profile "Production" ./dist/index.html /var/www/index.html
aeroftp-cli put --profile "Production" ./dist/app.js /var/www/app.js
aeroftp-cli ls --profile "Production" /var/www/ -l --json
Sync a project folder
# Preview first
aeroftp-cli sync --profile "Staging" ./build/ /var/www/ --dry-run --json
# Then execute
aeroftp-cli sync --profile "Staging" ./build/ /var/www/
Backup remote files
aeroftp-cli get --profile "Production" /var/www/database.sql ./backups/
aeroftp-cli get --profile "NAS" /shared/photos/ ./local-backup/ -r
Check server status
aeroftp-cli connect --profile "Production"
aeroftp-cli df --profile "Production" --json
AeroAgent Orchestration
External AI agents can invoke AeroAgent as a subprocess to perform AI-driven multi-step operations with credential isolation. AeroAgent resolves credentials from the vault, executes tool chains autonomously, and returns results - the orchestrating agent never sees passwords or tokens.
Discover AI Providers
# List configured AI providers (from vault and environment)
aeroftp-cli ai-models --json
The output includes provider name, active model, and source (vault, env, or vault+env). API keys are never included.
Agent One-Shot Mode
# Run a single instruction and exit
aeroftp-cli agent --provider xai --model grok-3-mini \
-m "List files on the Production server at /var/www/" \
--auto-approve all -y --json
If --provider is omitted, the CLI auto-detects from environment variables or vault-stored API keys.
Server Operations via Agent
The agent has two tools for vault-backed server operations:
server_list_saved - Lists all saved server profiles (names, protocols, hosts). No credentials exposed.
server_exec - Executes operations on any saved server. Credentials resolved internally.
| Operation | Description |
|---|---|
ls |
List directory contents |
cat |
Read file content (5 KB cap) |
stat |
File metadata |
find |
Search by pattern |
df |
Storage quota |
Example orchestration flow:
# Step 1: Discover servers
aeroftp-cli agent -p xai -m "List all saved servers" -y --json
# Step 2: Check a specific server
aeroftp-cli agent -p xai -m "List files on axpdev.it at /www.axpdev.it/" -y --json
# Step 3: Verify file integrity
aeroftp-cli agent -p xai -m "Compute SHA-256 of /var/www/app.js" -y --json
Auto-Approval Levels
| Level | Behavior |
|---|---|
--auto-approve safe |
Local read-only tools only (default) |
--auto-approve medium |
Also remote listings and metadata, local writes, and remote writes that delete nothing (upload, download, mkdir, rename, edit) |
--auto-approve high |
Also remote reads (remote_read, server_exec, file content sent to the model); never a delete, a trash, a sync control or the shell |
--auto-approve all or -y |
Everything, including delete, trash, sync control and shell |
A tool the level does not cover is asked for in an interactive terminal and refused in a non-interactive run. Before 4.2.1 high was the same level as all.
Security
- Credentials are never in command arguments, environment, stdout, or AI model context
- Shell denylist blocks 34 dangerous command patterns even with
--auto-approve all - Path validation blocks traversal, null bytes, and sensitive paths (
~/.ssh/, vault files) - The agent cannot read the vault database directly - path validator blocks it
Strict mode (recommended for unattended runs)
Pass --strict (or set AEROFTP_STRICT=1) to make any safety-relaxing flag a
hard error (exit 5) instead of a silent downgrade. This is the safest posture
for agent-generated or CI commands: a command that smuggles in a relaxation
flag fails loudly rather than proceeding. Refused under strict mode:
| Flag | What it would relax |
|---|---|
--insecure |
TLS certificate verification |
--trust-host-key |
SSH host-key (TOFU) verification |
--aimd-disable |
Backpressure controller (can hammer the backend) |
--drive-acknowledge-abuse |
Downloads files Google flagged as abusive |
--drive-cross-account-copy |
Cross-account / Shared-Drive copies |
--azure-archive-tier-delete |
Deletes archived blobs before overwrite |
--auto-approve medium/high/all, -y/--yes |
Auto-approval above the read-only safe tier |
Performance knobs (--limit-rate, concurrency, AIMD window sizing) and
safety-adding flags (--immutable) are unaffected.
Native MCP Server
External MCP clients can now connect directly through AeroFTP without wrapping CLI text output:
aeroftp-cli agent --mcp
Current MCP mode exposes:
- 39 primary tools across safe / medium / destructive tiers, plus 38 compatibility aliases (77 names in
tools/list, counted indocs/COMMAND-INVENTORY.json): file ops, batch (aeroftp_delete_many,aeroftp_upload_many), tree sync (aeroftp_sync_treewithdelta_files[]+plan[]), tree diff (aeroftp_check_treetwo-sided checksum + per-group caps +omit_match), preflight (aeroftp_sync_doctor,aeroftp_reconcile,aeroftp_dedupe), cross-profile copy (aeroftp_transfer,aeroftp_transfer_tree), agent ergonomics (aeroftp_agent_connect,aeroftp_speed,aeroftp_touch,aeroftp_cleanup) - resources for saved profiles, status, capabilities, and pooled connections
- prompt templates for deploy, backup, sync, and clean workflows
- async stdio transport, connection pooling, request cancellation, rate limiting, and audit logging
Use this mode for Claude Desktop, Cursor, VS Code, or any other MCP client that can spawn a stdio server.
JSON-RPC orchestration: aeroftp-cli agent --orchestrate runs a JSON-RPC 2.0 loop over stdin/stdout for programmatic agent-to-agent integration, emitting an agent/ready notification with the live CLI tool count on startup.
Coming Soon
- Mutative server operations: Available as dedicated tools -
remote_upload,remote_download,remote_mkdir,remote_delete,remote_rename - Cross-server operations:
server_diff,server_syncbetween two remote servers - Agent session tokens: Pre-authorized scoped sessions for headless automation
Full orchestration documentation with a verified field test report: Agent Orchestration
Transfer Engine
Starting with v4.0.0 AeroFTP has a shared, provider-agnostic DAG core and several production runners. Do not infer the runtime path from a builder or a capability flag alone; check the command path and provider binding.
- Single-file
get/putnormally reach the shaped-file runner. The router's table (transfer_router/hints.rs) sends plain WebDAV and Nextcloud downloads to the provider-direct path instead, and--transfer-engine dag|legacy(default fromAEROFTP_TRANSFER_ENGINE, the only override channel for the GUI) forces one engine for plain single-file transfers; completed SFTP delta transfers, resumes (--partial, a partial.aerotmp), the desktop app's segmented downloads, batch and sync keep their own paths. The multipart lifecycle is real. Independent wire-level part workers are currently present for S3, Backblaze B2, Azure Blob, Nextcloud chunked v2, Dropbox, Box, Filen, Drime and Uploadcare; other providers can serialize their provider calls behind a shared session even when their graph contains multiple part nodes. - Batch reaches
execute_batch_dagand non-dry-run sync reachesexecute_sync_dag; both stream per-file subgraphs through a bounded frontier, shaped from the provider's runtime capabilities. Providers with a clone or session pool (S3, Backblaze B2, Azure Blob, WebDAV, SFTP, FTP, Dropbox, Box, Filen, Drime and Uploadcare) run files in parallel up to their live session ceiling; single-session providers, a failed clone probe and every delta request stay serial. Sync scans and plans before the graph runs, and dry-run stays on the planning path. - Server-side copy in the GUI, CLI
cpand the CLI WebDAVCOPYhandler runsexecute_copy_dag, ashaped_copygraph: oneServerSideCopynode, or a download then upload when the provider rejects the native copy with a recoverable error. A native provider copy moves no payload bytes through the client. - Segmented download runs on the
shaped_rangesgraph, the only production range scheduler; there is no environment switch. Files at or above--multi-thread-cutoff(default250M) use--multi-thread-streamsrange streams (default 4) where the provider supports them; smaller files and other providers use one stream.
The GUI, CLI, and MCP surfaces share these primitives where their call paths reach them, but they do not guarantee identical wire behavior. Capabilities, concurrency flags, and AIMD settings apply only where the selected runner consumes them.
Architecture details: docs.aeroftp.app/architecture/dag-transfer-engine.
Supported Protocols
Saved profiles cover both direct-auth and browser-authorized providers.
Direct auth / token auth: FTP, FTPS, SFTP, WebDAV, WebDAVS, S3, Backblaze B2, Swift (OpenStack), Azure Blob, GitHub, GitLab, MEGA (Native + MEGAcmd), Filen, Internxt, kDrive, Koofr, Jottacloud, FileLu, OpenDrive, Yandex Disk, Immich, ImageKit, Uploadcare, Cloudinary, Drime Cloud, SourceForge (SFTP preset)
Browser-authorized or profile-backed API providers: Google Drive, Dropbox, OneDrive, Box, pCloud, Zoho WorkDrive, 4shared, Twake Drive (OAuth2 with PKCE on the user's own instance)
Through the vendor's own CLI: Proton Drive (the official proton-drive CLI, signed in with proton-drive auth login; its session stays in Proton's keyring and AeroFTP never sees it)
df and quota fields are provider-dependent. For several object-storage providers, about and df may omit quota data because the upstream API does not expose storage_info.
AeroFTP CLI v4.2.x - github.com/axpdev-lab/aeroftp
