Files
SaveAny-Bot/en/usage/cli/index.html
2026-06-28 10:00:28 +00:00

34 lines
13 KiB
HTML

<!doctype html><html lang=en dir=ltr><head><meta charset=UTF-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="
CLI Subcommands
#
Besides running the Telegram bot with ./saveany-bot (no subcommand), the binary exposes two helper subcommands for moving local files into a storage backend: upload (one-shot) and watch (continuous).
These subcommands load the same config.toml as the bot, initialize the database and caches, then perform their task. They do not start the Telegram bot itself, although storages of type telegram will spin up the bot client just for the upload."><meta name=theme-color media="(prefers-color-scheme: light)" content="#ffffff"><meta name=theme-color media="(prefers-color-scheme: dark)" content="#343a40"><meta name=color-scheme content="light dark"><meta property="og:url" content="https://sabot.unv.app/en/usage/cli/"><meta property="og:site_name" content="Save Any Bot"><meta property="og:title" content="CLI Subcommands"><meta property="og:description" content="CLI Subcommands # Besides running the Telegram bot with ./saveany-bot (no subcommand), the binary exposes two helper subcommands for moving local files into a storage backend: upload (one-shot) and watch (continuous).
These subcommands load the same config.toml as the bot, initialize the database and caches, then perform their task. They do not start the Telegram bot itself, although storages of type telegram will spin up the bot client just for the upload."><meta property="og:locale" content="en"><meta property="og:type" content="article"><meta property="article:section" content="usage"><meta property="article:modified_time" content="2026-06-28T18:00:02+08:00"><title>CLI Subcommands | Save Any Bot</title><link rel=icon href=/favicon.png><link rel=manifest href=/manifest.json><link rel=canonical href=https://sabot.unv.app/en/usage/cli/><link rel=alternate hreflang=zh href=https://sabot.unv.app/usage/cli/ title=命令行子命令><link rel=stylesheet href=/book.min.a22f4c7d8c2bdc5e3d6e34ba11cb59ab50ea5772594e71305bfd5a595dc78b7e.css integrity="sha256-oi9MfYwr3F49bjS6EctZq1DqV3JZTnEwW/1aWV3Hi34=" crossorigin=anonymous></head><body dir=ltr><input type=checkbox class="hidden toggle" id=menu-control>
<input type=checkbox class="hidden toggle" id=toc-control><main class="container flex"><aside class=book-menu><div class=book-menu-content><nav><h2 class=book-brand><a class="flex align-center" href=/en/><img src=/logo.png alt=Logo class=book-icon><span>Save Any Bot</span></a></h2><ul class=book-languages><li><input type=checkbox id=languages class=toggle>
<label for=languages class=flex><a role=button class="flex flex-auto"><img src=/svg/translate.svg class=book-icon alt=Languages>
English</a></label><ul><li><a href=/usage/cli/>简体中文</a></li></ul></li></ul><ul><li><a href=https://github.com/krau/SaveAny-Bot target=_blank rel=noopener>🔗 GitHub</a></li></ul><ul><li><span>Deployment Guide</span><ul><li><a href=/en/deployment/configuration/>Configuration Guide</a><ul><li><a href=/en/deployment/configuration/storages/>Storage Configuration</a></li></ul></li><li><a href=/en/deployment/installation/>Installation and Updates</a></li></ul></li><li><a href=/en/usage/>Usage</a><ul><li><a href=/en/usage/silent/>Silent Mode</a></li><li><a href=/en/usage/rules/>Storage Rules</a></li><li><a href=/en/usage/watch/>Watch Chats</a></li><li><a href=/en/usage/directlinks/>Direct Download Links</a></li><li><a href=/en/usage/aria2/>Aria2 Download</a></li><li><a href=/en/usage/ytdlp/>yt-dlp Video Download</a></li><li><a href=/en/usage/transfer/>Storage Transfer</a></li><li><a href=/en/usage/parsers/>Save Files Outside Telegram</a></li><li><a href=/en/usage/config/>File Naming & Conflict Strategies</a></li><li><a href=/en/usage/api/>HTTP API</a></li><li><a href=/en/usage/cli/ class=active>CLI Subcommands</a></li></ul></li><li><a href=/en/help/>Frequently Asked Questions</a><ul></ul></li><li><a href=/en/contribute/>Contributing</a><ul></ul></li></ul></nav><script>(function(){var e=document.querySelector("aside .book-menu-content");addEventListener("beforeunload",function(){localStorage.setItem("menu.scrollTop",e.scrollTop)}),e.scrollTop=localStorage.getItem("menu.scrollTop")})()</script></div></aside><div class=book-page><header class=book-header><div class="flex align-center justify-between"><label for=menu-control><img src=/svg/menu.svg class=book-icon alt=Menu></label><h3>CLI Subcommands</h3><label for=toc-control><img src=/svg/toc.svg class=book-icon alt="Table of Contents"></label></div><aside class="hidden clearfix"><nav id=TableOfContents><ul><li><a href=#upload--upload-a-single-file><code>upload</code> — Upload a Single File</a></li><li><a href=#watch--watch-a-directory-and-auto-upload><code>watch</code> — Watch a Directory and Auto-Upload</a><ul><li><a href=#behavior-notes>Behavior notes</a></li></ul></li></ul></nav></aside></header><article class="markdown book-article"><h1 id=cli-subcommands>CLI Subcommands
<a class=anchor href=#cli-subcommands>#</a></h1><p>Besides running the Telegram bot with <code>./saveany-bot</code> (no subcommand), the binary exposes two helper subcommands for moving local files into a storage backend: <code>upload</code> (one-shot) and <code>watch</code> (continuous).</p><p>These subcommands load the same <code>config.toml</code> as the bot, initialize the database and caches, then perform their task. They do <strong>not</strong> start the Telegram bot itself, although storages of type <code>telegram</code> will spin up the bot client just for the upload.</p><h2 id=upload--upload-a-single-file><code>upload</code> — Upload a Single File
<a class=anchor href=#upload--upload-a-single-file>#</a></h2><pre tabindex=0><code>saveany-bot upload -f &lt;file&gt; -s &lt;storage&gt; [-d &lt;dir&gt;] [--no-progress]
</code></pre><p>Flags:</p><table><thead><tr><th>Flag</th><th>Required</th><th>Description</th></tr></thead><tbody><tr><td><code>-f, --file</code></td><td>Yes</td><td>Path to the local file to upload</td></tr><tr><td><code>-s, --storage</code></td><td>Yes</td><td>Target storage name (must exist in <code>config.toml</code>)</td></tr><tr><td><code>-d, --dir</code></td><td>No</td><td>Destination directory within the storage. Defaults to the storage&rsquo;s <code>base_path</code></td></tr><tr><td><code>--no-progress</code></td><td>No</td><td>Disable the terminal progress bar</td></tr></tbody></table><p>Examples:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span><span style=color:#75715e># Upload a file to the default dir of storage &#34;MyAlist&#34;</span>
</span></span><span style=display:flex><span>./saveany-bot upload -f ./movie.mp4 -s MyAlist
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#75715e># Upload into a specific subdirectory</span>
</span></span><span style=display:flex><span>./saveany-bot upload -f ./movie.mp4 -s MyAlist -d movies/2026
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#75715e># Upload via Telegram storage without a progress bar</span>
</span></span><span style=display:flex><span>./saveany-bot upload -f ./photo.jpg -s MyChannel --no-progress
</span></span></code></pre></div><h2 id=watch--watch-a-directory-and-auto-upload><code>watch</code> — Watch a Directory and Auto-Upload
<a class=anchor href=#watch--watch-a-directory-and-auto-upload>#</a></h2><p>The <code>watch</code> subcommand continuously monitors a local directory and uploads created or modified files to a storage backend, preserving the relative directory structure from the watch root.</p><pre tabindex=0><code>saveany-bot watch -p &lt;path&gt; -s &lt;storage&gt; [-d &lt;dir&gt;] [options]
</code></pre><p>Flags:</p><table><thead><tr><th>Flag</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td><code>-p, --path</code></td><td><em>(required)</em></td><td>Local directory to watch</td></tr><tr><td><code>-s, --storage</code></td><td><em>(required)</em></td><td>Target storage name</td></tr><tr><td><code>-d, --dir</code></td><td>storage&rsquo;s <code>base_path</code></td><td>Destination directory within the storage</td></tr><tr><td><code>-r, --recursive</code></td><td><code>false</code></td><td>Watch subdirectories recursively</td></tr><tr><td><code>--overwrite</code></td><td><code>false</code></td><td>Overwrite existing files on the storage instead of skipping them</td></tr><tr><td><code>--initial-scan</code></td><td><code>false</code></td><td>Upload files already present in the directory on startup</td></tr><tr><td><code>--debounce</code></td><td><code>2s</code></td><td>How long to wait after the last write before uploading a file</td></tr><tr><td><code>--upload-workers</code></td><td><code>config.workers</code></td><td>Number of concurrent uploads</td></tr><tr><td><code>--retry-delay</code></td><td><code>3s</code></td><td>Delay between upload retries</td></tr></tbody></table><blockquote class="book-hint info">Write-completion detection: the watcher debounces per file and only uploads once the file size stays unchanged across the debounce window, so partial/write-in-progress files are not uploaded.<br>If a file changes while being uploaded, it is re-uploaded once after the current upload finishes (instead of being queued multiple times).</blockquote><p>Examples:</p><div class=highlight><pre tabindex=0 style=color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4><code class=language-bash data-lang=bash><span style=display:flex><span><span style=color:#75715e># Watch ./inbox and upload new files to &#34;MyAlist&#34; recursively</span>
</span></span><span style=display:flex><span>./saveany-bot watch -p ./inbox -s MyAlist -r
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#75715e># Watch with a custom destination dir and overwrite</span>
</span></span><span style=display:flex><span>./saveany-bot watch -p ./inbox -s MyAlist -d backup --overwrite
</span></span><span style=display:flex><span>
</span></span><span style=display:flex><span><span style=color:#75715e># On startup, also upload everything already in ./inbox</span>
</span></span><span style=display:flex><span>./saveany-bot watch -p ./inbox -s MyAlist --initial-scan
</span></span></code></pre></div><h3 id=behavior-notes>Behavior notes
<a class=anchor href=#behavior-notes>#</a></h3><ul><li>Relative directory structure is preserved under the destination directory. A file written to <code>./inbox/sub/file.txt</code> with <code>--path ./inbox</code> is uploaded to <code>&lt;dest_dir>/sub/file.txt</code>.</li><li><code>watch</code> runs until interrupted (e.g. <code>Ctrl-C</code> / <code>SIGINT</code>); in-flight uploads are drained before exit.</li><li>Retries follow the global <code>retry</code> value from <code>config.toml</code>, with <code>--retry-delay</code> between attempts.</li><li>Telegram-type storages will start the bot client automatically to perform uploads.</li></ul><blockquote class="book-hint warning">`watch` is unrelated to the in-bot `/watch` command (which watches Telegram chats). This subcommand watches a **local filesystem directory** and uploads to a storage backend, independent of Telegram.</blockquote></article><footer class=book-footer><div class="flex flex-wrap justify-between"><div><a class="flex align-center" href=https://github.com/krau/saveany-bot/commit/c9bb6c9e3c325c83ffef5bbe4fa54a58b82de2df title='Last modified by krau | 2026/06/28' target=_blank rel=noopener><img src=/svg/calendar.svg class=book-icon alt>
<span>2026/06/28</span></a></div><div><a class="flex align-center" href=https://github.com/krau/saveany-bot/edit/main/docs/content/en/usage/cli.md target=_blank rel=noopener><img src=/svg/edit.svg class=book-icon alt>
<span>Edit this page</span></a></div></div><script>(function(){function e(e){const t=window.getSelection(),n=document.createRange();n.selectNodeContents(e),t.removeAllRanges(),t.addRange(n)}document.querySelectorAll("pre code").forEach(t=>{t.addEventListener("click",function(){if(window.getSelection().toString())return;e(t.parentElement),navigator.clipboard&&navigator.clipboard.writeText(t.parentElement.textContent)})})})()</script></footer><div class=book-comments></div><label for=menu-control class="hidden book-menu-overlay"></label></div><aside class=book-toc><div class=book-toc-content><nav id=TableOfContents><ul><li><a href=#upload--upload-a-single-file><code>upload</code> — Upload a Single File</a></li><li><a href=#watch--watch-a-directory-and-auto-upload><code>watch</code> — Watch a Directory and Auto-Upload</a><ul><li><a href=#behavior-notes>Behavior notes</a></li></ul></li></ul></nav></div></aside></main></body></html>