4.9 KiB
FTP Plugin Walkthrough
This walkthrough covers the bundled ftp plugin. It lets users:
- run
search-file -plugin ftp -instance <name> ... - browse remote folders as result tables
- select file rows to
download-file - pipe selected file rows into
add-file - upload local files with
add-file -plugin ftp -instance <name>
The implementation lives in plugins/ftp/__init__.py.
What the plugin does
The FTP plugin demonstrates the main plugin hooks that matter for a storage-style integration:
config_schema()exposes host, credentials, base path, TLS, and search depth.extract_query_arguments()supports inline query fields likepath:anddepth:.search()walks an FTP directory tree and returnsSearchResultrows.selector()turns folder rows into a follow-up table when the user runs@N.download()anddownload_url()fetch FTP files intodownload-fileoutput paths.resolve_pipe_result_download()lets@N | add-file -instance ...materialize a remote FTP file first.upload()letsadd-file -plugin ftp -instance <name> -path ...push a local file to the configured FTP server.
Example config
Add one or more named FTP plugin instances to your config. The current stored
key path remains provider.ftp.<instance> for legacy compatibility:
[provider.ftp.work]
host = "ftp.example.com"
port = 21
username = "demo"
password = "secret"
base_path = "/incoming"
tls = false
passive = true
timeout = 20
search_depth = 1
[provider.ftp.archive]
host = "archive.example.com"
port = 2121
username = "archive-bot"
password = "secret"
base_path = "/dropbox"
tls = true
Notes:
workandarchiveare instance names.hostis the only required field for each instance to validate.usernamedefaults toanonymousandpassworddefaults toanonymous@.base_pathis both the default search root and the upload target directory.search_depthcontrols how many folder levelssearch-file -plugin ftpscans by default.- You can browse configured instances from
.config pluginsin the CLI.
Search flow
search-file -plugin ftp -instance work "*"
search-file -plugin ftp -instance work "invoice"
search-file -plugin ftp -instance work "path:/pub depth:2 invoice"
search-file -plugin ftp -instance work "path:/pub type:folder *"
The plugin returns rows with explicit columns for name, type, directory, size, and modification time.
Selection flow
Folder rows are navigation rows:
search-file -plugin ftp -instance work "*"
@2
File rows carry an explicit row action equivalent to:
download-file -plugin ftp -instance work -url ftp://ftp.example.com/incoming/report.pdf
So plain @N on a file row downloads it immediately:
search-file -plugin ftp -instance work "report"
@1
Download and add-file flow
search-file -plugin ftp -instance work "report"
@1 | download-file -path C:\Downloads
search-file -plugin ftp -instance work "report"
@1 | add-file -instance tutorial
Why this works:
- the file row advertises a
download-filerow action - the pipeline auto-inserts that download before
add-file - the FTP plugin also implements
resolve_pipe_result_download()so plugin-owned FTP rows can be materialized for ingestion - file rows carry the chosen
instance, so selection replay and@N | add-file ...keep the same FTP target
Upload flow
Uploading uses the same plugin, through add-file -plugin ftp -instance <name>:
add-file -plugin ftp -instance archive -path C:\Media\report.pdf
That sends the file to the selected instance's FTP base_path and returns the
FTP URL as the uploaded result.
Why the row metadata matters
The critical part of this plugin is the file-row metadata:
- file rows emit
_selection_argsas['-instance', '<name>', '-url', '<ftp-url>'] - file rows emit
_selection_actionas['download-file', '-plugin', 'ftp', '-instance', '<name>', '-url', '<ftp-url>'] - folder rows do not emit a download action, so
selector()owns drill-in behavior instead
That keeps these flows compatible:
@Non a folder opens a new table@Non a file downloads the file@N | add-file -instance ...first downloads, then ingests
Implementation notes
The plugin prefers MLSD for directory listings and falls back to NLST plus
directory probes when the server does not support machine-readable listings.
The code intentionally stays small and uses only Python stdlib pieces:
ftplibfor FTP and FTPSfnmatchfor wildcard-style search tokenstempfileforadd-filehandoff downloads
Recommended demo commands
search-file -plugin ftp -instance work "*"
search-file -plugin ftp -instance work "path:/incoming depth:2 *.pdf"
@1
@1 | download-file -path C:\Downloads
@1 | add-file -instance tutorial
add-file -plugin ftp -instance archive -path C:\Media\report.pdf