🎁 Get the FREE AI Skills Starter GuideSubscribe →
BytesAgainBytesAgain
🦀 ClawHub

Olares Files (olares-cli files)

by @olares

Olares Files (olares-cli files) — manage files on an Olares system from the command line, scoped to the active Olares ID. Covers list (ls), upload, download,...

Versionv4.0.1
Downloads426
TERMINAL
clawhub install olares-files

📖 About This Skill


name: olares-files version: 1.4.0 description: "olares-cli files command tree: list (ls), upload, download, cat, rm, cp, mv, rename (rn), share (internal / public / smb), and Sync-repo CRUD (repos list / get / create / rename / rm) against the per-user files-backend (drive/Home, drive/Data, sync, cache, external, awss3, dropbox, google, tencent, share). Covers the 3-segment frontend path schema (//), resumable chunked upload (Drive v2 protocol), Range-based resumable download, recursive directory transfer with errgroup parallelism, batch DELETE wire shape, server-side copy/move via PATCH /api/paste// (async task_id queue, cross-volume supported), synchronous in-place rename via PATCH /api/resources/.../?destination=..., folder-share creation across the three flavors (Internal cross-user, Public link with password+expiration, SMB Samba mount) via POST /api/share/share_path/<...>/, share management (list / get / rm) plus SMB-account roster (smb-users list/create), Sync (Seafile) library catalog management via /api/repos/ (GET list with type=mine|share_to_me|shared, POST create with ?repoName=, PATCH rename with ?destination=&repoId=, DELETE with ?repoId=), and two server-side quirks the user MUST know about (POST mkdir auto-renames existing dirs to 'Foo (1)'; GET single-file resource returns HTTP 500). Use whenever the user mentions files / drive / Home / Data / sync / cache, uploading or downloading files, listing a remote directory, deleting remote files, cat-ting a remote file, copying or moving (renaming) remote files / directories, in-place renaming, sharing a folder with other users, public links with password / expiration, SMB / Samba network shares, listing / creating / renaming / deleting Sync (Seafile) libraries, repo_id discovery, /api/resources, /api/raw, /api/paste, /api/share, /api/repos, frontend path, or sees errors like 'Documents (1)' appearing on the server." metadata: requires: bins: ["olares-cli"] cliHelp: "olares-cli files --help"

files (Drive v2 + per-user files-backend)

CRITICAL — before doing anything, MUST use the Read tool to read ../olares-shared/SKILL.md for the profile selection, login, and HTTP 401/403 recovery rules that every command here depends on.

Core concept: the 3-segment frontend path

Every resource on the per-user files-backend is addressed by a 3-segment "frontend path" (see cli/cmd/ctl/files/path.go):

/[/]

| Segment | Meaning | |---------|---------| | fileType | Storage class (lowercase, case-sensitive). One of: drive, cache, sync, external, awss3, dropbox, google, tencent, share, internal | | extend | Volume / repo / account inside that class. Case-sensitive. Drive: only Home or Data. Cache / external: node name. Sync: seafile repo id. Cloud (awss3/dropbox/google/tencent): account key | | subPath | Path inside extend (root if omitted). The leading / is implicit |

Examples:

drive/Home/                            # Home volume root
drive/Home/Documents/report.pdf        # a file under Home/Documents
drive/Data/Backups/                    # Data volume, Backups subfolder
sync//notes/                  # seafile sync repo
cache//                          # node-local cache
awss3///key.txt       # S3-compatible cloud drive

> The first segment is normalized to lowercase by the backend; the CLI accepts only the canonical lowercase form on input. Drive's extend MUST be Home or Data exactly — home will be rejected with invalid drive type.

Trailing-slash convention (critical)

Whether a path ends with / is meaningful and changes command behavior:

| Path form | Meaning | |-----------|---------| | drive/Home/Foo/ | Directory intent | | drive/Home/Foo | File intent |

This shows up in five places:

  • files rm drive/Home/Foo/ requires -r (the trailing / declares "this is a directory")
  • files upload drive/Home/Documents/ means "upload INTO Documents/"; files upload drive/Home/Documents/2026-Q1.pdf means "upload AS that exact path (rename on the way in)"
  • files cp / drops into the directory by basename; files cp (no trailing slash, single source only) treats as the full target path (rename / exact-target mode). Same for files mv.
  • files cp -r drive/Home/old/ (trailing / on a source) requires -r — Unix-style refusal to operate on directories without recursion. Same for files mv.
  • files ls drive/Home/ lists the volume root; the parser tolerates both drive/Home and drive/Home/ for ls but the trailing slash is recommended for clarity
  • Server-side quirks (critical, do not work around)

    These are real backend behaviors that have already cost us debugging time. Teach yourself and the user to respect them; do not suggest "workarounds" that bypass the CLI's existing handling.

    1. POST /api/resources// auto-renames existing directories

    Hitting the directory-create endpoint against an existing directory does not return 409. The server creates a sibling named

    (1) instead. See the docstring on cli/internal/files/upload/api.go's Mkdir for the precise wording.

    Consequence baked into the CLI: files upload does not pre-create the destination directory. It relies on the chunk POST to implicitly materialize parents. The destination directory MUST already exist on the server — if you need a fresh directory, create it through the LarePass web app first (a future files mkdir verb may cover this).

    User-visible symptom of getting this wrong (older CLI versions): an extra Documents (1) directory appears on the server even though the upload "succeeded".

    2. GET /api/resources/ (no trailing slash) returns HTTP 500

    The backend's single-file List handler hard-codes Content: true (files/pkg/drivers/posix/posix/posix.go getFiles) and tries to slurp the file's bytes into the response envelope. For json / binary / large files, this just 500s.

    Consequence baked into the CLI: Stat always lists the parent directory and looks up the leaf in its items array (see cli/internal/files/download/stat.go). This matches what the LarePass web app does — it never probes a single-file resource directly. Both download and cat use this code path.

    If the user reports HTTP 500 against /api/resources/.../ with no trailing slash, do NOT suggest "just retry". The right answer is: use the CLI command (files cat / files download), or list the parent and look at items.

    Authentication transport

    Every files API call carries X-Authorization: as a header (NOT the standard Authorization: Bearer ...). The Factory's refreshingTransport injects this automatically; see cli/pkg/cmdutil/factory.go. Do not try to call the backend via curl with a Bearer token — that header shape is not what the per-user files-backend expects and the request will fail.

    The transport auto-refreshes expired tokens transparently through two paths (both detailed in ../olares-shared/SKILL.md "Automatic token refresh"):

    | Verb(s) | Body shape | Refresh path | |---------|------------|--------------| | ls, cat, download, rm, cp, mv, rename, share (all subcommands), repos (all subcommands) | No body or *bytes.Reader/*bytes.Buffer (replayable) | Reactive — send with current token; on 401/403 call /api/refresh and retry once with the new token. | | upload (chunk POST) | *os.File slice (non-replayable streaming body) | Pro-active — decode the JWT's exp before each chunk; if within 60s of expiry, refresh BEFORE handing the body to the transport. |

    The pro-active path on upload exists because once a *os.File chunk is consumed by the first send, we can't replay it on a 401 — the resume probe would re-pull from the server-known offset on the next run, but the in-flight chunk would already have failed the user's command. Pre-flight rotation collapses that into a silent rotate-and-continue, even when --parallel N>1 has multiple chunks racing the same expiry window (the Refresher's in-process mutex + cross-process flock guarantee a single /api/refresh hit per stale token).

    Stat / Range probes inside download and cat use the reactive path normally — they're cheap GETs with no body.

    When the refresh leg itself fails (/api/refresh rejects the refresh_token), the typed *credential.ErrTokenInvalidated propagates through reformatHTTPErr / reformatRmHTTPErr so the user sees the canonical "run profile login" CTA directly, without a Get "https://...": URL prefix. Recovery rules live in olares-shared.

    Command cheatsheet (10 top-level verbs)

    files ls [--json]

    List a remote directory. See cli/cmd/ctl/files/ls.go.

    olares-cli files ls drive/Home/
    olares-cli files ls drive/Home/Documents
    olares-cli files ls sync//
    olares-cli files ls drive/Home/Documents --json   # raw envelope, pretty-printed
    

    Default output: a one-line header ( (N dirs, M files, modified ...)) followed by a 5-column table MODE SIZE TYPE MODIFIED NAME. Directories sort before files; directory names get a trailing /. Empty directories print (empty).

    --json prints the raw JSON envelope from the backend, useful for scripting.

    files upload

    Resumable chunked upload to drive/Home/<...>. See cli/cmd/ctl/files/upload.go and cli/internal/files/upload/.

    # Upload one file into an existing directory.
    olares-cli files upload report.pdf drive/Home/Documents/

    Upload AND rename on the server.

    olares-cli files upload report.pdf drive/Home/Documents/2026-Q1.pdf

    Upload a whole directory tree.

    olares-cli files upload ./photos drive/Home/Backups/

    Two files in flight at a time, chunks remain sequential per file.

    olares-cli files upload ./photos drive/Home/Backups/ --parallel 2

    Wire protocol (Drive v2 / Resumable.js-compatible):

    1. GET /upload/upload-link//... → upload session 2. GET /upload/file-uploaded-bytes//... → server-driven resume offset (no local progress file) 3. POST chunks (8 MiB default) with Content-Range: bytes -/ until done

    Constraints / flags:

  • Destination MUST be under drive/Home (drive/Data is read-only on the wire); the CLI rejects anything else with upload destination must be under drive/Home.
  • Destination directory MUST already exist — see "POST auto-renames" above.
  • A trailing / on means "into this directory"; without one, is treated as the full target path (rename on the way in).
  • --parallel N (default 2): per-file concurrency. Per-file chunks remain sequential by design — the resume probe assumes one in-flight chunk per file.
  • --chunk-size (default 8 MiB): align with the server's expected size; rarely needs tuning.
  • --max-retries N: per-chunk retry budget on transient failures.
  • --node : override the upload node; default is the first node from /api/nodes/.
  • Resume is automatic and server-driven: re-running the same command after a Ctrl-C / network drop just re-asks the server how many bytes it already has, floors to a chunk boundary, and continues.

    files download []

    Download a single file or a whole directory tree. See cli/cmd/ctl/files/download.go and cli/internal/files/download/.

    # Single file into the current directory (./).
    olares-cli files download drive/Home/Documents/report.pdf

    Same, but pick a different local name.

    olares-cli files download drive/Home/Documents/report.pdf ./Q1.pdf

    Resume an interrupted download via Range:.

    olares-cli files download drive/Home/Backups/big.tar ./big.tar --resume

    Recursively pull a directory; 4 files at a time.

    olares-cli files download drive/Home/Documents/ ./out/ --parallel 4

    Local destination resolution (single-file mode):

    | | Result | |----------------|--------| | omitted | ./ | | existing directory | / (mirrors cp) | | any other path (incl. trailing / if not yet existing) | treated as the full target file path |

    Flags:

  • --resume: send Range: bytes=- and append (server-native, no sidecar progress file).
  • --overwrite: replace an existing local file via .tmp + atomic rename. The previous version stays intact until the new one lands.
  • --resume and --overwrite are mutually exclusive — pick one.
  • --parallel N (default 4): only meaningful in directory mode (errgroup-bounded concurrency).
  • --max-retries N: per-file transient-failure budget (5xx triggers retry; 4xx fails fast).
  • Directory mode (trailing / on ):

  • The remote tree is walked recursively via /api/resources/.../.
  • The remote root's basename becomes the top-level directory under (matches the LarePass folder-download UX). Empty subdirectories are mirrored locally.
  • Single Stat lookup at the start to confirm the path is actually a directory; then BuildPlan materializes the file list before any byte is written.
  • files cat

    Stream a single file's bytes to stdout. See cli/cmd/ctl/files/cat.go.

    olares-cli files cat drive/Home/Documents/notes.md
    olares-cli files cat drive/Home/Logs/today.log | tail -n 50
    olares-cli files cat drive/Home/Photos/banner.png > banner.png  # binary-safe
    

    Wire shape: GET /api/raw/?inline=true (the same endpoint LarePass uses for text previews; inline=true only affects Content-Disposition, the body is identical).

  • Binary-safe: bytes are copied verbatim, no sniffing or transformation. Pipe into less / head -c / hexdump as needed.
  • Pre-flight Stat (parent listing) refuses directories early with a clear error, instead of letting the server return its terser 400. Use files download for directories.
  • files rm [-r] [-f] ...

    Delete one or more remote files / directories. See cli/cmd/ctl/files/rm.go and cli/internal/files/rm/.

    # Delete one file.
    olares-cli files rm drive/Home/Documents/old.pdf

    Recursively remove a directory.

    olares-cli files rm -r drive/Home/Backups/2024/

    Multiple targets, no prompt (scripts).

    olares-cli files rm -rf drive/Home/junk drive/Home/scratch/

    Wire shape (one batch DELETE per parent dir):

    DELETE /api/resources//   body: {"dirents": ["", "", ...]}
    

    Targets sharing a parent collapse into a single request (matches the LarePass web app's batchDeleteFileItems). Targets across different parents send one request each, sorted by fileType + extend + parent for stable output.

    Flags / rules:

  • -r / -R / --recursive: required for directories. A trailing / on a target IS a directory-intent signal and triggers the same check (so files rm drive/Home/Foo/ errors without -r even if Foo is technically empty).
  • -f / --force: skip the y/N prompt. In a non-TTY context (CI, piped stdin) the command refuses without --force rather than guessing.
  • Without -f: prints "will delete N entries in M batches" with the full list, then prompts [y/N].
  • Removing the root of a volume (drive/Home/, sync//, ...) is rejected by the planner: refusing to delete the root of /.
  • Aliases: olares-cli files remove ..., olares-cli files delete ... are the same command.

    files cp [-r] ...

    Server-side copy across remote paths via the per-user files-backend's paste endpoint. See cli/cmd/ctl/files/cp.go and cli/internal/files/cp/.

    # Copy one file into a directory.
    olares-cli files cp drive/Home/notes.md drive/Home/Documents/

    Copy with a new name on the destination side.

    olares-cli files cp drive/Home/notes.md drive/Home/notes-2026.md

    Recursive directory copy.

    olares-cli files cp -r drive/Home/Photos/ drive/Home/Backups/

    Multiple sources into a directory.

    olares-cli files cp drive/Home/a.pdf drive/Home/b.pdf drive/Home/Archive/

    Cross-volume copy (drive → sync repo).

    olares-cli files cp drive/Home/notes.md sync//inbox/

    Wire shape (one PATCH per source — the endpoint does not batch like rm):

    PATCH /api/paste//   body: {"action": "copy", "source": "//", "destination": "//"}
    

    Source / destination are plain UTF-8 paths (not percent-encoded) — the LarePass web app decodeURIComponents before serializing, and the CLI builds them directly in that shape. Cross-volume / cross-storage-class is fully supported because the endpoint takes raw string paths and the backend dispatches by .

    Destination semantics (Unix-style):

    | Form of | Meaning | |-----------------|---------| | ends with / | Drop-into-directory mode. Each 's basename is appended; the dir / file marker on the source is preserved on the destination. | | no trailing / (single source only) | Rename / exact-target mode. is used verbatim as the full target path. | | no trailing / with multi-source | Rejected: target ... must end with '/' when more than one source is given. |

    Recursion + safety rules:

  • -r / -R / --recursive is required for directory sources. A trailing / on a IS a directory-intent signal — without -r you get ... is a directory: pass -r/-R to copy it recursively.
  • Volume-root sources are rejected: cp drive/Home/ ... errors with refusing to copy the root of drive/Home. Same rule that protects rm.
  • src == dst is rejected — almost always a typo.
  • Cycle detection: copying drive/Home/a/ into drive/Home/a/sub/ errors with destination ... is inside source ... (would create a cycle).
  • Async / task semantics — important:

  • The PATCH returns a task_id after the server enqueues the copy task. The actual byte movement happens asynchronously on the files-backend's task queue; the CLI does NOT block until completion. The summary line prints queued N copy task(s): , , ....
  • For multi-source cp src1 src2 src3 dst/, the CLI sends N PATCH requests serially (no parallelism) so a per-call failure aborts the rest cleanly — paste tasks have no transactional rollback, and serial execution lets the user see exactly which call failed and re-run from there.
  • There is currently no built-in completion polling. If the user needs "fail / succeed in foreground", they have to monitor server-side task progress through the LarePass web app for now.
  • Node selection (--node):

  • Each PATCH carries a URL segment. Default is the first entry from /api/nodes/ (same default files upload uses) — the CLI fetches /api/nodes/ once per invocation, but skips the round-trip when both source and destination already supply a node hint.
  • For external and cache fileTypes the path's IS the node name, and the CLI follows the LarePass web app's dst_node || src_node || default cascade automatically. Drive-↔-Drive copies and use the default.
  • --node forces a specific node for every PATCH in the batch and overrides the cascade. Useful when copying across multi-master clusters where you need to pin the operation to a particular node.
  • Server-side rejection signal (code: -1):

  • The endpoint returns HTTP 200 with {"code": -1, "message": "..."} for malformed paths (most commonly a literal backslash anywhere in or — same failure mode the LarePass web app surfaces as the files.backslash_upload notification). The CLI surfaces this as paste : . Do not retry blindly on this — the path itself needs to be fixed.
  • files mv [-r] ...

    Same wire endpoint as files cp (PATCH /api/paste//) but with action: "move" instead of "copy" — the server moves the source instead of duplicating it. Every flag, rule, and failure mode from files cp applies verbatim; the only difference is the verb.

    # Rename a file in place.
    olares-cli files mv drive/Home/notes.md drive/Home/notes-2026.md

    Move several files into a directory.

    olares-cli files mv drive/Home/a.pdf drive/Home/b.pdf drive/Home/Archive/

    Recursive directory move.

    olares-cli files mv -r drive/Home/Photos/ drive/Home/Backups/

    mv is a separate cobra command (not a cp --move alias) so the help text stays honest about what each verb does and so olares-cli files mv reads the way users expect.

    > mv is a single-step destructive operation from the user's POV. Even though it's just a flag flip on the wire, treat it the way you would a mv on local disk: confirm with the user before running it on directories, and prefer cp then rm when you want a "verify then delete" workflow.

    files rename (alias files rn)

    Synchronous in-place rename of a single file or directory. Different wire endpoint and semantics from mv — pick rename whenever you literally just want to change the basename. See cli/cmd/ctl/files/rename.go and cli/internal/files/rename/.

    # Rename a file in place (notes.md → 2026-Q1-notes.md, same parent dir).
    olares-cli files rename drive/Home/Documents/notes.md 2026-Q1-notes.md

    Rename a directory; the trailing '/' on the source confirms directory intent.

    olares-cli files rename drive/Home/Photos/ Photos-old/

    Short alias.

    olares-cli files rn drive/Home/draft.txt final.txt

    Wire shape:

    PATCH /api/resources//[/]?destination=
    

    Key differences vs. mv:

    | Aspect | mv | rename | |--------|------|----------| | Wire endpoint | PATCH /api/paste// | PATCH /api/resources/.../?destination=... | | Body | {action:"move", source, destination} | None (new name is a query param) | | Async / sync | Async (returns task_id, processed by task queue) | Synchronous (returns immediately, change is visible right away) | | Cross-directory | Yes (move within or across volumes) | No — basename change only, same parent dir | | Multi-source / batch | Yes (one PATCH per source) | No — exactly one path + one new name per call | | URL segment | Yes (per-node routing) | No |

    Validation rules (rejected client-side, before any HTTP call):

  • MUST NOT contain / or \ — those are not basename characters; use mv for cross-directory moves.
  • MUST NOT be empty, ., or ...
  • The source MUST NOT be the volume root (drive/Home/, sync//, ...).
  • MUST differ from the source's current basename — same-name rename is a no-op the server would silently accept; we reject it client-side so a typo doesn't go unnoticed.
  • If the server replies HTTP 409, that's typically a basename collision (a sibling under the same parent already has ). The CLI surfaces this as ... server reported a conflict (HTTP 409); .... Pick a different name or rm the existing sibling first.

    > Use rename for in-place basename changes; use mv for moves between directories or volumes. Picking the right verb keeps the wire shape simple and makes the user's intent legible in shell history.

    files share

    Create / list / remove shares for files-backend resources. Three creation flavors (Internal cross-user, Public link, SMB Samba) plus management verbs (list / get / rm) plus an SMB-account roster (smb-users). All flavors converge on the same wire endpoint and disambiguate via the share_type field in the JSON body.

    See cli/cmd/ctl/files/share.go, cli/cmd/ctl/files/share_create.go, and cli/internal/files/share/.

    #### Three share flavors

    | Flavor | Audience | Auth model | Lifetime | Output the user needs | |--------|----------|------------|----------|-----------------------| | internal | Other Olares users on the same node | Olares user identity | Persistent until share rm | Share id, member list | | public | Anyone who has the link + password | Per-share password (auto-gen 8-char default) | Required: --expire-days N OR --expire-time RFC3339 | Share id, password (printed once, NOT echoed back later), link template /sharable-link// | | smb | LAN clients via SMB protocol | Per-share smb_user / smb_password issued by the server, OR public-SMB ("anyone on the local network") | Persistent until share rm | UNC smb_link, smb_user, smb_password |

    #### Common wire shape

    POST /api/share/share_path///
    body: {name, share_type, permission, password, expire_in?, expire_time?,
           users?, public_smb?, upload_size_limit?}
    

    Permission integers (SharePermission in the LarePass app):

    | Value | Label | Meaning | |-------|-------|---------| | 0 | none | filter sentinel only — not a sensible create-time value | | 1 | view | read-only | | 2 | upload | upload-only (Public-link "drop-box" mode) | | 3 | edit | read + write (default Public-link recipient perm; default SMB read-write) | | 4 | admin | full control (default Internal-share owner perm) |

    The CLI accepts canonical labels OR the numeric form: --permission edit, --permission 3, and view / read / ro / read-only are all aliases for 1. See share.ParsePermission in cli/internal/files/share/share.go for the full alias list.

    #### files share internal [--users name:perm,...] [--permission admin]

    # Bare share record, no members yet.
    olares-cli files share internal drive/Home/Backups/

    Share a single file with two members.

    olares-cli files share internal drive/Home/Reports/Q1.pdf \ --users alice:edit,bob:view

    Two-call sequence on the wire:

    POST /api/share/share_path/<...>/      → creates the share record (returns id)
    POST /api/share/share_member/          → adds the listed users (only when --users is given)
    

    If the second call fails, the share record is already on the server — the CLI surfaces the share id in the error message so the user can recover by calling member-add directly (or re-running share internal with the same --users).

    --permission controls the OWNER's permission on the share record; default admin matches the LarePass web app and the only other sensible value is edit. Per-user permission lives in --users's name:perm syntax (default view if omitted).

    #### files share public [--password] [--expire-days N | --expire-time RFC3339] [--upload-only] [--upload-size-limit 100M]

    # 7-day expiration, auto-generated 8-char URL-safe password.
    olares-cli files share public drive/Home/Photos/ --expire-days 7

    Explicit password, 30 days, 100 MiB upload cap.

    olares-cli files share public drive/Home/Photos/ \ --password "s3cret-pw-1" --expire-days 30 \ --upload-size-limit 100M

    Upload-only "drop box" with explicit expiration time.

    olares-cli files share public drive/Home/Inbox/ --upload-only \ --password drop --expire-time 2026-12-31T23:59:00Z

    Required flags:

  • An expiration is mandatory. Pass exactly one of --expire-days N or --expire-time RFC3339. The web app forces this choice; the CLI mirrors it client-side. Public links without an expiration are not supported by the backend.
  • --password is technically optional — when omitted, the CLI generates an 8-byte URL-safe random password (crypto/randbase64.RawURLEncoding, ≈11 chars) and prints it ONCE. The server does not echo the password back on subsequent reads (share get / share list show only the share record, not the cleartext password), so capture it from the create-output the first time.
  • Minimum password length is 6 chars (matches the web app's passwordLimitRule).
  • Optional flags:

  • --upload-only flips recipient permission from edit (read+write, default) to upload (drop-only — recipients can POST files but can't list / download). Useful for inbox-style intake links.
  • --upload-size-limit accepts 100M / 1G / 500K / 512 (bytes). Suffixes are 1024-based (KiB/MiB/GiB/TiB) to match the web app's fileLimitSize(). Omitted / zero means no cap.
  • The CLI prints the share id, password, expiration, and a link template of the form /sharable-link//. The full link is built by the LarePass app from the user's hostname (shareBaseUrl() in apps/.../stores/files.ts); the CLI deliberately does NOT guess this prefix because the right value depends on subdomain layout the CLI can't observe — copy the id and let the LarePass app or the share-recipient's browser resolve the prefix.

    #### files share smb [--public | --users id:perm,...] [--read-only]

    # Public-SMB: anyone on the local network can mount.
    olares-cli files share smb drive/Home/Movies/ --public

    Per-user SMB: must list SMB-account IDs (NOT Olares user names).

    olares-cli files share smb drive/Home/Backups/ \ --users smb-uid-1:edit,smb-uid-2:view

    Read-only override for every member.

    olares-cli files share smb drive/Home/Reports/ \ --users smb-uid-1 --read-only

    Mutually exclusive recipient model:

    | Mode | Flag | Body shape | |------|------|-----------| | Public-SMB | --public | public_smb: true, no users array | | Per-user | --users id:perm,... | public_smb: false, users: [{id, permission}, ...] |

    Exactly one of these flags MUST be passed — the CLI rejects an empty SMB share at parse time because a share visible to no one is rarely intentional.

    Permission shape:

  • SMB shares accept only view (1) or edit (3) per recipient. The CLI rejects upload / admin in --users parsing with a clean message.
  • --read-only overrides every recipient's perm to view regardless of --users's :perm annotations.
  • Public-SMB mode forces permission: edit at the share-record level (matches the web app).
  • The IDs in --users are SMB-account IDs, NOT Olares user names. Discover available IDs via:

    olares-cli files share smb-users list
    

    Create new SMB accounts via:

    olares-cli files share smb-users create  
    

    The output of share smb includes the smb_link (UNC path), smb_user, and smb_password — these are what a Finder / Explorer / mount.cifs client needs to mount the share.

    #### Management verbs

    # List all shares (default: both shared-by-me and shared-to-me).
    olares-cli files share list

    Filter by direction / type / owner.

    olares-cli files share list --shared-by-me olares-cli files share list --shared-by-me=false # only "shared with me" olares-cli files share list --type smb,external olares-cli files share list --owner alice,bob

    Inspect one share by id (returns shareable details for SMB shares).

    olares-cli files share get

    Remove one or more shares (single batched DELETE on the wire).

    olares-cli files share rm [...]

    list columns: ID TYPE NAME OWNER PATH PERMISSION EXPIRE. Output is sorted by (type, id) for stable repeated invocations — the server doesn't guarantee any ordering.

    get renders the full share record key:value-style; SMB shares' smb_link / smb_user / smb_password print on their own lines so they're easy to copy.

    rm accepts multiple IDs and joins them into a single DELETE /api/share/share_path/?path_ids= call (atomic from the CLI's perspective).

    files repos

    CRUD verbs for the per-user files-backend's catalog of Sync (Seafile) libraries. A "repo" (the Seafile name; LarePass UI calls them "libraries") is the unit of storage that backs every sync// frontend path. Each repo has a stable UUID (repo_id) that becomes the segment, and a mutable display name. The CLI exposes the same five operations LarePass surfaces in its left-nav, plus --json for scripting. See cli/cmd/ctl/files/repos.go and cli/internal/files/repos/.

    > Why this is its own verb. sync//... is the only fileType whose segment is a server-assigned UUID rather than a user-typed name. Without files repos, the repo_id had to be copied out of the LarePass web app every time. files repos list keeps the discovery loop in-CLI; the rest of the verbs let the CLI also create / rename / tear down libraries without leaving the terminal.

    #### Wire shape (one endpoint, four methods)

    | Verb | HTTP method + URL | Source | |------|-------------------|--------| | list (mine) | GET /api/repos/ (no type param) | mirrors fetchMineRepo in apps/.../api/files/v2/sync/utils.ts | | list --type share-to-me | GET /api/repos/?type=share_to_me | fetchtosharedRepo | | list --type shared | GET /api/repos/?type=shared | fetchsharedRepo | | create | POST /api/repos/?repoName= (no body) | createLibrary | | rename | PATCH /api/repos/?destination=&repoId= (no body) | renameRepo | | rm | DELETE /api/repos/?repoId= | deleteRepo |

    All write verbs return the standard {code, message, ...} envelope; a non-zero code is promoted to a Go error and surfaced verbatim, matching the LarePass response interceptor at apps/packages/app/src/api/files/fetch.ts L118-133.

    #### files repos list [--type mine|share-to-me|shared|all] [--json]

    # Default: repos you own (matches LarePass's "My Libraries" group).
    olares-cli files repos list

    Repos others have shared with you, or repos you've shared out.

    olares-cli files repos list --type share-to-me olares-cli files repos list --type shared

    Concatenated fan-out across all three flavors (for one-shot

    discovery in fresh terminals). Adds a TYPE column.

    olares-cli files repos list --type all

    Raw JSON for jq pipelines.

    olares-cli files repos list --json

    Default columns: REPO_ID NAME PERMISSION OWNER SIZE MODIFIED ENC. Rows are sorted (type group → name → id) so repeated invocations diff cleanly.

    PERMISSION is rw / r / -. For shared variants the permission lives in the share_permission field on the wire — the CLI normalizes both into one column. OWNER falls back to the share counterparty when the row was reached via share-to-me / shared. ENC flags client-side encrypted libraries (not unlock-able from the CLI — see below).

    #### files repos get

    olares-cli files repos get 
    

    Fans out across the three flavors (mineshare-to-meshared) and returns the first match. Exits non-zero with repo not found if the id isn't in any list — useful for scripts that branch on absence without parsing list output. Output is key:value-style with Repo ID, Name, Owner, Permission, Encrypted, Size, Last modified, plus a trailing use with: olares-cli files ls sync// hint.

    #### files repos create [--json]

    # Provision a new (unencrypted) library.
    olares-cli files repos create "Project Alpha"
    

    Output:

    created repo: Project Alpha (id: )

    use with: olares-cli files ls sync//

    Capture the id for scripting.

    REPO_ID=$(olares-cli files repos create "Project Alpha" --json | jq -r .repo_id) olares-cli files ls sync/$REPO_ID/

    > Encryption is NOT exposed. The per-user files-backend's createLibrary endpoint accepts no password / encryption flags, and the LarePass UI has no equivalent option either. If the user needs an encrypted library they must create it from the LarePass app or directly via Seahub; once it exists the CLI can list it (ENC=yes), but every upload / download / cat / ls against it will fail until the user unlocks the repo via the web app.

    --json prints the full repo record (mirroring repos list's row shape) so jq pipelines can extract any field, not just repo_id.

    #### files repos rename

    olares-cli files repos rename abc-123 "Project Alpha (archived)"
    

    Output (when the old name is fetchable):

    renamed repo abc-123: "Project Alpha" -> "Project Alpha (archived)"

    The repo's UUID is stable across renames — already-cached sync//... frontend paths keep working. The CLI does a best-effort Get first to fetch the old name for the audit line; if that lookup fails, the rename still proceeds and the output simplifies to renamed repo -> "".

    #### files repos rm ... [--yes|-y] [--force|-f]

    # In a real terminal: lists targets (id + name) and asks y/N first.
    olares-cli files repos rm abc-123

    Opt out of the prompt (required when stdin is not a TTY — scripts, CI, pipes).

    olares-cli files repos rm abc-123 --yes olares-cli files repos rm abc-123 -y olares-cli files repos rm abc-123 -f olares-cli files repos rm abc-123 def-456 ghi-789 -y

    The confirmation model matches files rm / cp / mv safety: default is interactive on a TTY (proceed with repo deletion? [y/N]:); in a non-interactive context the command refuses until you add -y / --yes (or --force / -f as a files rm -f-style alias). Both -y and -f bind the same bool — either alone skips the prompt.

    Destructive: removes the repo and all of its contents. The Seafile deployment may keep the data in a server-side trash window, but the CLI does not expose a restore verb — recovery requires the LarePass app or direct Seahub access.

    Multiple ids are deleted in turn. Per-id failures are printed as failed: (), the loop continues on, and the command exits non-zero if any deletion failed (with all per-id errors joined via errors.Join).

    Common errors → fixes

    | Error message (excerpt) | Likely cause | Fix | |-------------------------|--------------|-----| | server rejected the access token (HTTP 401) / (HTTP 403) | Token expired / revoked | Follow olares-shared's recovery: olares-cli profile login --olares-id | | HTTP 404 ... not found on the server | Path typo or wrong case (Home vs home, Data vs data) | files ls the parent directory to confirm spelling | | invalid drive type: | Drive's extend isn't Home or Data | Use exactly Home or Data | | upload destination must be under drive/Home | Tried to upload to drive/Data/... or another fileType | Move the target under drive/Home/... | | Documents (1) (or similar) appearing on the server after upload | Older CLI version triggered the POST-mkdir auto-rename quirk | Upgrade to a CLI version that has the pre-mkdir removal fix | | --overwrite and --resume are mutually exclusive | Passed both download flags | Pick one | | refusing to delete without --force in a non-interactive context (no TTY) | files rm from a script with no TTY | Add -f after explicitly listing the targets to the user first | | refusing to delete the root of / | Tried files rm -r drive/Home/ (or another volume root) | The CLI does not support volume-root deletion; remove children individually | | cat ... is a directory | files cat on a path that resolves to a directory | Use files download / instead | | HTTP 500 against /api/resources/.../ (no trailing slash) | Hit the backend's single-file List quirk directly (e.g. via curl) | Don't bypass the CLI; the CLI uses parent-listing Stat for a reason | | ... is a directory: pass -r/-R to copy/move it recursively | cp / mv on a path with trailing / (or any directory) without -r | Add -r after confirming the user wants the whole tree | | target ... must end with '/' when more than one source is given | cp src1 src2 dst with dst not ending in / | Add a trailing / to (drop-into-dir) or split into separate single-source cp calls | | source and destination are the same | cp foo foo (typo) | Pick a real destination | | destination ... is inside source ... (would create a cycle) | cp -r drive/Home/a/ drive/Home/a/sub/ | Pick a destination outside the source tree | | refusing to copy/move the root of / | cp drive/Home/ ... | Volume-root copy/move is unsupported; specify a child path | | paste : (HTTP 200, code -1) | Server-side rejection — typically a literal backslash in the path | Fix the path; don't retry verbatim | | cannot resolve {node} URL segment | Neither side has an External/Cache hint and /api/nodes/ returned no usable default | Pass --node explicitly | | queued N copy/move task(s): ... but the file isn't visible yet | Task is queued; backend hasn't processed it | Wait briefly and files ls the destination; the CLI does not currently poll task completion | | contains '/' / ... contains '\\' | rename was given a path-like new name | rename only changes the basename; use mv for cross-directory moves | | is "." / is ".." / is empty | rename was given a sentinel basename | Pick a real basename | | equals the current basename | rename was a no-op | If the user intended a cross-directory move, use mv | | refusing to rename the root of / | rename drive/Home/ ... | Volume roots can't be renamed; pick a child path | | ... server reported a conflict (HTTP 409) (rename) | A sibling with already exists under the same parent | Pick a different name or rm the existing sibling first | | Public shares require an expiration | share public without --expire-days or --expire-time | Pass exactly one expiration flag (Public-link shares need a TTL) | | --expire-days and --expire-time are mutually exclusive | Both flags passed together | Pick one | | --password must be at least 6 characters | share public --password shorter than 6 chars | Use a longer password (or omit --password to auto-generate one) | | --public and --users are mutually exclusive | share smb --public --users ... | Pick one recipient model — Public-SMB OR a specific account list | | share smb requires either --public OR --users | Neither flag set on share smb | Add --public or --users id:perm,... | | entry "...": SMB shares accept only view or edit | share smb --users id:upload or :admin | SMB perm is binary; use view or edit | | share created, but adding members failed: ... | share internal --users post-create call failed | The share record IS on the server (id is in the message); fix the underlying error and re-run share internal with the same --users, or call member-add directly through the LarePass app | | share ... not found on the server (share get) | Share id is wrong / already removed | List shares to confirm: files share list | | share ... server reported a conflict (HTTP 409) (share) | Resource is already shared, or the share id is in use | Use share list to find the existing share id; share rm it first if you intended to recreate | | unknown repos type "..." (files repos list --type ...) | Misspelled --type value | Use mine, share-to-me, shared, or all | | repos Create: empty repo name | files repos create "" (or whitespace-only) | Pass a non-empty argument | | repos Create: server accepted the request but did not return a repo_id | Rare Seahub path that 200s but elides the response payload | Run files repos list to discover the new repo; the create likely succeeded | | repos Create: server rejected (code N): ... | Server-side validation failed (duplicate name, encryption-only deployment, etc.) | Surface the message verbatim — it's the Seahub-side reason | | repos Rename ...: server rejected (code N): ... | Permission denied / target name conflict on rename | Read the embedded message; if it's a name conflict, pick a different | | repo : not found in any of mine / share-to-me / shared (repos get) | Wrong / removed repo id, or it's an encrypted repo the caller has no access to | Run files repos list --type all to confirm | | repos rm: refusing to delete without -y / --yes (no TTY) | Running in CI / pipe / heredoc without a confirmation opt-out | Add -y, --yes, -f, or --force (or run from a real TTY to get the y/N prompt) | | repos rm: N of M failed: ... | One or more ids hit per-id errors mid-batch | Each failed: () line above identifies which one; fix or retry that subset |

    Typical workflow

    # 1. Explore
    olares-cli files ls drive/Home/
    olares-cli files ls drive/Home/Documents/

    2. Push a local tree up (target dir must already exist).

    olares-cli files upload ~/local-dir drive/Home/Documents/

    3. Pull a tree down with parallelism + resume.

    olares-cli files download drive/Home/Documents/ ./out --parallel 4 --resume

    4. Quick peek at a file.

    olares-cli files cat drive/Home/Notes/today.md | tail -n 50

    5. Reorganize on the server side (no local round-trip).

    olares-cli files cp drive/Home/Documents/2026-Q1.pdf drive/Home/Archive/ olares-cli files mv drive/Home/Inbox/draft.md drive/Home/Notes/2026-04-draft.md olares-cli files mv -r drive/Home/scratch/ drive/Home/Old/scratch/

    5b. In-place basename change (synchronous, no task queue).

    olares-cli files rename drive/Home/Documents/notes.md 2026-Q1-notes.md

    6. Clean up after confirming with ls first.

    olares-cli files ls drive/Home/Old/ olares-cli files rm -r drive/Home/Old/

    7. Sharing workflows (orthogonal to the upload / move / cleanup loop).

    olares-cli files share internal drive/Home/Reports/Q1.pdf \ --users alice:edit,bob:view olares-cli files share public drive/Home/Photos/ --expire-days 7 olares-cli files share smb drive/Home/Movies/ --public

    Inspect / clean up.

    olares-cli files share list --shared-by-me olares-cli files share rm

    8. Sync (Seafile) library lifecycle (orthogonal to drive/Home).

    olares-cli files repos list # discover repo_ids REPO_ID=$(olares-cli files repos create "Project Alpha" --json | jq -r .repo_id) olares-cli files upload ~/local-tree sync/$REPO_ID/Backups/ # use it like any other path olares-cli files repos rename $REPO_ID "Project Alpha (archived)" olares-cli files repos rm $REPO_ID --yes

    When operating across multiple Olares instances, prefix each command with --profile (see olares-shared for the global flag) instead of flipping the persistent current pointer.

    Security rules

  • Always preview destructive operations. Before passing -f to rm in a script, list the exact paths to the user and get explicit confirmation. The interactive [y/N] prompt is a safety net, not a substitute for thoughtful intent.
  • mv is destructive too. It has no [y/N] prompt (the wire endpoint is async / fire-and-forget), so always show the user the exact plan before running it on directories or in scripts. When in doubt, prefer cp + ls + rm so the user can verify the new copy before deleting the original.
  • Local files are never overwritten implicitly. files download refuses to clobber unless --overwrite (atomic via .tmp+rename) or --resume (append) is passed. Never recommend --overwrite without checking with the user.
  • Server-side overwrite on cp / mv is the backend's call, not the CLI's. The PATCH /api/paste endpoint does not expose an "overwrite vs auto-rename" flag the way the LarePass web app's modal dialog does; the server picks its own collision behavior, which historically matches the POST-mkdir auto-rename quirk (creates (1) instead of overwriting). Treat cp dst/ and mv dst/ as non-idempotent and confirm the destination is empty / non-conflicting before running.
  • Do not echo to the terminal. The token lives in the OS keychain for a reason; pulling it out into a shell variable for curl defeats that. Use the CLI commands.
  • files upload does NOT delete the local source — it's a copy, not a move. If a user wants delete-after-upload semantics, they have to do it explicitly and after verifying the upload succeeded.
  • files share public passwords are surfaced ONCE. The CLI prints the (auto- or user-supplied) password in the create-output; the server does NOT echo it back on share get / share list. When you script share public, capture the create-output before piping anywhere it might be lost — the only recovery path is share rm + recreate, which gives the recipient a new id.
  • SMB-share secrets (smb_user, smb_password) are returned by the create call too — same capture-once discipline as Public-link passwords. They are queryable later via share get , but treat the create-time output as the canonical record.
  • share rm removes the share record only, not the underlying resource. A user who expects "stop sharing AND delete this folder" needs share rm followed by files rm -r . Confirm intent before chaining the two.
  • share public --password "" is rejected (the CLI minimum is 6 chars). Don't try to bypass it by passing a single character — the wire-side check exists, you'll get an opaque server rejection a few seconds later.
  • files repos rm is irreversible from the CLI. There is no restore verb; the Seafile-side trash window (if the deployment has one) is reachable only through the LarePass app or direct Seahub access. On a TTY the command lists id + name and asks y/N; in scripts, require -y after explicit user intent. Prefer repos rename " (archived)" for "soft delete" workflows.
  • files repos create cannot make encrypted libraries. Don't suggest a password flag — the CLI will refuse, and the per-user files-backend has no endpoint for it. Encrypted libraries must be created from the LarePass app; the CLI can only enumerate / rename / delete them after the fact.