github axpdev-lab/aeroftp v3.6.5
AeroFTP v3.6.5

latest releases: v4.0.7, v4.0.6, v4.0.5...
one month ago

[3.6.5] - 2026-04-26

CLI polish + cross-provider correctness sweep

Drop-in patch driven by an end-to-end stress test of aeroftp-cli 3.6.4 against real provider accounts (S3, Backblaze, Storj, FTPS, Dropbox, Google Drive, OneDrive, Box, Koofr, Zoho WorkDrive, kDrive, Drime). The session surfaced four recurring correctness defects that span 8 providers, plus a dozen ergonomics rough edges that became visible only when the CLI is driven by an external AI agent rather than a human typing one command at a time. Fixes are surface-level and contained to the CLI binary plus per-provider trait impls; no architectural change.

Fixed

  • FTP CLI now respects the server-provided home directory - aeroftp-cli was issuing a hard CWD / after login, which overrode the home directory negotiated by the FTP server (typically /home/user on non-chroot installations like default vsftpd) and landed the session at the filesystem root, which is usually non-writable. This surfaced in a head-to-head benchmark vs rclone where the same put rel/path/file.txt worked under rclone but produced 550 Permission denied here. Three coordinated fixes: the FTP provider skips the post-login CWD when initial_path is bare / (aligning with rclone, lftp, FileZilla, ftp(1) and curl which all defer to PWD post-login); the CLI path resolver now returns empty for empty user input so the provider's canonical default kicks in instead of an artificial absolute root; and mkdir -p preserves relative-vs-absolute semantics instead of always prefixing /, so mkdir -p rel/path issues MKD rel/MKD rel/path (which the server can satisfy under the user's home) instead of MKD /rel/path (which non-chroot servers reject for the same write-permission reason). Absolute paths like /etc continue to target the filesystem root verbatim. Validated end-to-end against vsftpd in a Docker harness, no regression on the existing chroot case.
  • OAuth saved servers can now be renamed from the Edit dialog (issue #127) - In My Servers, hovering over an OAuth tile (Google Drive, Dropbox, OneDrive, Box, pCloud, Zoho WorkDrive, Yandex Disk, 4shared) and clicking the Edit pen now opens a form whose display name is editable, matching the behaviour of WebDAV / E2E / API providers. Two root causes: the OAuthConnect "Active" branch (rendered when tokens already exist for the provider) skipped the Save toggle and the Connection Name input entirely, so there was no field to type into. And the OAuth save callback in ConnectionScreen searched for the existing profile by name === saveName, so the moment the user changed the name the lookup failed and a brand-new profile was created next to the original instead of renaming it. Fix: render the Save toggle + name input in the Active branch (gated on the same wantToSave flag the inactive branch uses), and prefer the explicit editingProfileId over the name-match heuristic when persisting an OAuth edit. OAuth-specific fields (clientId, clientSecret, scope, region) stay locked - renaming a saved server is purely a local label change.
  • find glob matched as substring across 7 providersaeroftp-cli find /path "*.txt" was returning report.TXT.rtf and any file whose name contained the literal txt, because the per-provider find() implementations forwarded the pattern straight to the upstream search API (which is substring-by-name on most clouds) without re-applying glob semantics on the response. Affected: Dropbox, OneDrive, Box, Koofr, Zoho WorkDrive, Drime, kDrive. Fix: server-side query is now broad-prefiltered (glob characters stripped, only the literal portion sent), then the response is re-filtered client-side via the shared matches_find_pattern helper that powers the rest of the CLI. Google Drive (already filtered against its own catalog) gets the same belt-and-braces second pass.
  • mkdir + put failing on empty-prefix object storesaeroftp-cli put file.bin s3://bucket/key returned exit=2 "Parent does not exist" on a fresh bucket because the put plumbing was issuing a separate mkdir call before the upload, which is a hard error on S3 / Azure / Backblaze (no real directory primitive). Fix: skip the parent-directory pre-flight when the provider declares is_object_store(). Validated end-to-end on Backblaze and Storj.
  • ls of a missing FTPS path returned exit=0 with (empty directory) — the unhappy path was indistinguishable from an actually empty directory, so scripts piping the output couldn't tell. Fix: return exit=2 "Path not found" when the upstream LIST rejects the resolved path. The empty-directory case (path exists, no entries) still exits 0.
  • Dropbox get of a missing file took 3.5s + 3 retries before failing — the path/lookup/not_found JSON error was being classified as transient and retried, instead of as a permanent client error. Fix: detect path/not_found upstream and return exit=2 immediately. Drops the failure latency from ~3.5 s to ~150 ms.
  • Koofr WebDAV default Remote Path was /dav/Koofr/ (issue #126) — the discovery preset paired server: https://app.koofr.net/dav/Koofr with basePath: /dav/Koofr/, so the joined request URL doubled to /dav/Koofr/dav/Koofr/..., which Koofr rejected with Invalid credentials. Fix: default basePath is now /. Existing saved profiles need to be edited once.

Added

  • Inline rename for saved servers (issue #127) - In addition to unlocking rename via the Edit dialog, My Servers tiles now support a faster path: right-click → "Rename (F2)" turns the tile name into an editable input with a green check / grey X for confirm / cancel, plus an F2 hotkey that renames the currently hovered tile. Enter and blur commit, Escape cancels. Works in both grid and list view, and applies to every provider class (OAuth included). The right-click variant lives next to the existing Edit voice in the context menu.
  • Protocol class label on My Servers tiles (issue #127 bonus) - Tiles now show a second small chip next to the protocol brand badge: OAuth (indigo), API (sky), WebDAV (purple), E2E (emerald), S3 (orange), Azure (blue), AeroCloud (cyan). FTP / FTPS / SFTP keep only the existing protocol badge to avoid duplication. The new label makes the WebDAV-Koofr vs API-Koofr distinction (and similar pairs across cloud providers) visible at a glance, parity with the Discover Services page.

Changed

  • Throughput parity with rclone on FTP / SFTP / WebDAV - Head-to-head profiling on Docker loopback exposed three avoidable bottlenecks in the upload path. (1) The plain-FTP uploader was paying a 100ms-2s "TLS drain" sleep before finalize_put_stream on every transfer; the sleep is a real workaround for a TLS/TCP close_notify race but has no effect on plain FTP. Gating it behind tls_active cuts a single-100MB upload on Docker FTP from 2.74s to 0.72s (37 MB/s -> 138 MB/s, -74%) and a 500x10K bulk-of-small-files run from 14.68s to 2.72s (-82%, now ahead of rclone with --transfers 4). (2) The SFTP default buffer was 32 KiB - the conservative protocol minimum - which caps loopback throughput at ~35 MB/s. Raised to 256 KiB (OpenSSH 8+ and russh-sftp negotiate larger packets in practice anyway, and 1 MiB only adds another ~5 MB/s on top). 100MB Docker SFTP: 3.71s -> 2.37s (-36%). --chunk-size / --buffer-size still override. (3) The WebDAV streaming PUT body was built with the default ReaderStream capacity of 8 KiB, churning syscalls. Bumped to 256 KiB. 100MB Docker WebDAV: 1.26s -> 0.84s (-33%). No protocol-level changes; tested in CI with the existing FTP/SFTP/WebDAV smoke jobs.
  • aeroftp-cli polish pass for the AI-agent demo path:
    • pget is now a real subcommand (alias for get --segments 4); the banner cell stops being a lie.
    • Banner protocol count and the connect "Unsupported protocol" error now derive from a single SUPPORTED_URL_SCHEMES constant, so they cannot drift apart again.
    • Subcommand help screens no longer reprint the ASCII banner. Banner is suppressed when stderr isn't a TTY, when AEROFTP_NO_BANNER is set, or when --no-banner is on the command line. Top-level --help still shows it once.
    • 11 connection globals (--bucket, --region, --container, --token, --tls, --key, --key-passphrase, --password-stdin, --insecure, --trust-host-key, --two-factor) move under a Connection options heading so per-subcommand help is no longer 30 random flags. 6 output flags move under Output options.
    • 39 sentinel default_value = "_" arguments now carry hide_default_value = true so subcommand help stops rendering [default: _] for every URL/path positional.
    • cleanup description rewritten to a single accurate sentence (was a confusing two-line run-on suggesting it handled duplicates, which it doesn't — that lives in dedupe).
    • dedupe gained the missing description.
    • tree --depth help no longer leaks the internal "TypeId mismatch" implementation note.
    • Invalid URL error now suggests --profile <name> when the input has no scheme, instead of leaking the raw url::ParseError message.

Skipped — flagged for follow-up

  • --exclude rename: sync already has a local --exclude (with -e short) whose Vec<String> semantics differ from the global --exclude-global; unifying them would force a refactor of the per-command merge loop. Out of scope for a polish pass; tracked for a dedicated PR with proper test coverage.

Downloads:

  • Windows: .msi installer, .exe, or .zip portable (no installation required)
  • macOS: .dmg disk image
  • Linux: .deb, .rpm, .snap, or .AppImage

Download AeroFTP

Don't miss a new aeroftp release

NewReleases is sending notifications on new releases.