{"id":"f846e722-029e-4422-bc72-15e292dc3dc2","entityType":"agent","slug":"clawhub-olares-olares-files","name":"Olares Files (olares-cli files)","canonicalUrl":"https://www.xpersona.co/agent/clawhub-olares-olares-files","canonicalPath":"/agent/clawhub-olares-olares-files","generatedAt":"2026-10-11T20:59:04.029Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":null},"description":"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,...","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-files","sourceUrl":"https://clawhub.ai/olares/olares-files","homepage":"https://clawhub.ai/olares/skills/olares-files","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/olares/olares-files","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/olares/skills/olares-files","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Olares Files (olares-cli files) technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":null},"stars":null,"forks":null,"downloads":1021,"packageName":null,"latestVersion":"4.0.1","tractionLabel":"1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T17:09:02.239Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T17:09:02.307Z","lastCrawledAt":"2026-10-11T17:09:02.239Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T17:09:02.239Z","lastVerifiedAt":null,"highlights":[{"version":"4.0.1","createdAt":"2026-05-29T05:54:34.377Z","changelog":"Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0)","fileCount":14,"zipByteSize":28303},{"version":"4.0.0","createdAt":"2026-05-29T04:53:40.737Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":14,"zipByteSize":28182},{"version":"1.19.0","createdAt":"2026-05-28T09:28:00.178Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":3,"zipByteSize":62841},{"version":"1.4.0","createdAt":"2026-04-30T13:23:08.628Z","changelog":"Automated publish from cli/skills/publish.sh","fileCount":2,"zipByteSize":18805}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-files","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s1718vftp5x2gwbtzenpt3kxch87k6j0:olares-files` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/olares/olares-files before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T20:59:04.026Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-olares-olares-files/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":null},"readme":"Skill: Olares Files (olares-cli files)\n\nOwner: olares\n\nSummary: 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,...\n\nTags: latest:4.0.1\n\nVersion history:\n\nv4.0.1 | 2026-05-29T05:54:34.377Z | user\n\nMigrate ownership from @pengpeng to @olares (no content change vs 4.0.0)\n\nv4.0.0 | 2026-05-29T04:53:40.737Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv1.19.0 | 2026-05-28T09:28:00.178Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nv1.4.0 | 2026-04-30T13:23:08.628Z | user\n\nAutomated publish from cli/skills/publish.sh\n\nArchive index:\n\nArchive v4.0.1: 14 files, 28303 bytes\n\nFiles: references/olares-files-chown.md (2974b), references/olares-files-cp-mv.md (4001b), references/olares-files-download.md (2595b), references/olares-files-edit.md (3556b), references/olares-files-ls.md (2560b), references/olares-files-mkdir.md (2987b), references/olares-files-rename.md (2265b), references/olares-files-rm.md (2995b), references/olares-files-share.md (6733b), references/olares-files-smb.md (5021b), references/olares-files-upload.md (6634b), skill-card.md (3033b), SKILL.md (13108b), _meta.json (131b)\n\nFile v4.0.1:SKILL.md\n\n---\r\nname: olares-files\r\nversion: 4.0.1\r\ndescription: \"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, cat, edit (open in $EDITOR), mkdir, rm, cp, mv, rename, chown (POSIX owner uid get/set), folder share (internal cross-Olares-ID, public link with password + expiration, SMB Samba), mount / unmount / favorite external SMB servers, and Sync (Seafile) repo CRUD — all against the per-Olares-ID files-backend on Olares (drive/Home, drive/Data, sync, cache, external, awss3, dropbox, google, tencent, share). Use when the user mentions Olares, Olares ID, Olares Files, olares-cli files, LarePass Files on Olares, drive, Home, Data, sync, cache, uploading / downloading / listing / editing remote files on Olares, in-place rename, POSIX file ownership, sharing a folder with another Olares user (by Olares ID), public link with password / expiration, SMB / Samba network shares, the LarePass 'Connect to Server' dialog, or Sync (Seafile) libraries.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli files --help\"\r\n---\r\n\r\n# files (per-user files-backend)\r\n\r\n**CRITICAL — before running any verb here, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, and 401/403 recovery rules.**\r\n\r\n> **Source of truth for flags & wire shapes is always `olares-cli files <verb> --help`.** This file only carries what `--help` cannot give: the cross-cutting frontend-path concept, the trailing-slash convention, the five client-side hard constraints, and the verb index.\r\n\r\n## Core concept: the 3-segment frontend path\r\n\r\nEvery resource on the per-user files-backend is addressed by:\r\n\r\n```\r\n<fileType>/<extend>[/<subPath>]\r\n```\r\n\r\n| Segment | Meaning |\r\n|---------|---------|\r\n| `fileType` | Storage class (lowercase, case-sensitive): `drive`, `cache`, `sync`, `external`, `awss3`, `dropbox`, `google`, `tencent`, `share`, `internal` |\r\n| `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 |\r\n| `subPath` | Path inside `extend` (root if omitted). Leading `/` is implicit |\r\n\r\nExamples: `drive/Home/`, `drive/Home/Documents/report.pdf`, `sync/<repo_id>/notes/`, `awss3/<account>/<bucket>/key.txt`.\r\n\r\n> Drive's `extend` MUST be `Home` or `Data` exactly — `home` is rejected with `invalid drive type`.\r\n\r\n### Per-verb namespace support\r\n\r\n| Verb | Supported namespaces |\r\n|------|----------------------|\r\n| `ls` / `cat` / `download` / `rm` / `rename` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `edit` | `drive`, `sync`, `cache`, `external` only (cloud / tencent / share / internal refused) |\r\n| `mkdir` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `cp` / `mv` | same as `mkdir` (PATCH `/api/paste/<node>/`) |\r\n| `upload` | `drive/Home`, `drive/Data`, `sync/<repo_id>`, `cache/<node>`, `external/<node>/<volume>`, `awss3`, `google`, `dropbox` — **`tencent` rejected** (different upload protocol) |\r\n| `chown` | `drive/Home`, `drive/Data`, `cache/<node>` only (cloud, sync, external all refused) |\r\n| `share internal` | `drive`, `sync`, `external`, `cache` (cloud refused) |\r\n| `share smb` | `drive`, `external`, `cache` (sync + cloud refused) |\r\n| `share public` | `drive` only |\r\n| `smb mount` / `unmount` / `history` | keyed by `<node>` + `<smb-url>`, not frontend paths |\r\n| `repos` | operates on the Sync (Seafile) library catalog, not frontend paths |\r\n\r\n## Trailing-slash convention (critical)\r\n\r\nWhether a path ends with `/` is meaningful:\r\n\r\n| Form | Meaning |\r\n|------|---------|\r\n| `drive/Home/Foo/` | Directory intent |\r\n| `drive/Home/Foo` | File intent |\r\n\r\nIt shows up here:\r\n\r\n- `files rm drive/Home/Foo/` requires `-r` — the trailing `/` declares \"this is a directory\".\r\n- `files upload <local> drive/Home/Documents/` → upload INTO Documents; `files upload <local> drive/Home/Documents/2026-Q1.pdf` → upload AS that exact path.\r\n- `files cp <src> <dst>/` and `files mv <src> <dst>/` — `<dst>` MUST end with `/` (drop-into-directory mode). Renaming via `cp`/`mv` is not supported; use `files rename` for in-place basename changes.\r\n- `files cp -r drive/Home/old/` (trailing `/` on a source) requires `-r`.\r\n- `files ls drive/Home/` lists the volume root; both `drive/Home` and `drive/Home/` are accepted but the slash is recommended.\r\n\r\n## Client-side hard constraints (5 quirks — never work around)\r\n\r\nThese five rules are enforced client-side and reflect real backend / GUI invariants. Teach yourself AND the user to respect them — do not suggest curl / API workarounds.\r\n\r\n### 1. POST `/api/resources/<dir>/` auto-renames on collision\r\n\r\nHitting the directory-create endpoint against an existing directory does NOT return 409 — it silently creates `<dir> (1)` instead. Therefore: `files upload` does NOT pre-create the destination directory; use `files mkdir [-p]` first if the parent doesn't exist yet.\r\n\r\n### 2. GET `/api/resources/<file>` (no trailing slash) returns HTTP 500\r\n\r\nThe backend's single-file `List` handler tries to slurp file bytes into a JSON envelope and chokes on most files. Workaround baked into the CLI: `Stat` always lists the PARENT directory and finds the leaf in the items array. If the user reports `HTTP 500` on a direct file resource path, the answer is \"use `files cat` / `files download`\", never \"retry the raw URL\".\r\n\r\n### 3. `external/<node>/` is a virtual volume-listing layer (read-only)\r\n\r\nThis level has no backing filesystem — it just enumerates attached volumes (`hdd1`, `usb1`, `smb-...`). Writes against it either fail server-side or trip quirk #1.\r\n\r\nCLI client-side guards: `mkdir`, `cp` destination, `mv` destination, `upload`, AND `share` (all flavors) reject `external/<node>/` (and one level deeper for `mkdir`). Errors point at the corrected shape `external/<node>/<volume>/<sub>/`. Pure reads (`ls`, `cat`, `rm`, `rename`) DO work — that's how the user discovers what volumes are attached. Mount new volumes via LarePass, not via files-backend mkdir.\r\n\r\n### 4. `drive/Home/{Pictures, Music, Movies, Downloads, Documents, Code, Cache, Data, Home, Ollama, Huggingface}` are system-managed\r\n\r\nThese eleven names under `drive/Home/` are LarePass bootstrap directories that user apps look up by exact name (e.g. the model-runtime app's `Ollama` cache, the LarePass UI's \"Pictures\" sidebar tile). The LarePass GUI greys out cut / copy / paste / delete / rename for them, and so does the CLI:\r\n\r\n- `rename`, `rm`, and `mv source` REFUSE these names at the **first level under `drive/Home/` only**.\r\n- `cp` (copy) is intentionally NOT gated — duplicating bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is fine.\r\n- Content nested inside (`drive/Home/Pictures/Trip2024/`) is fully editable.\r\n- Other namespaces (`drive/Data/Pictures`, `sync/<repo>/Pictures`, `external/...`) are unaffected.\r\n\r\nNote LarePass casing: `Huggingface` is one word (not `HuggingFace`). Names are case-sensitive.\r\n\r\n### 5. `cache/<node>/` is a node-picker for share-create only\r\n\r\n`cache/<node>/` IS a real per-node directory on the wire, so `ls` / `cp` / `mkdir` / `upload` / `rm` / `rename` work fine. BUT the share-create flavors (`share internal` / `share public` / `share smb`) reject the bare node root because a share record on the node-picker layer points at no concrete dataset. Use `cache/<node>/<sub>/` for shares; `files ls cache/<node>/` for discovery.\r\n\r\n## Authentication transport\r\n\r\nEvery files API call carries `X-Authorization: <access_token>` (NOT `Authorization: Bearer ...`). The transport auto-refreshes expired tokens transparently — reactive on 401/403 for replayable requests (every verb except `upload`), pro-active JWT-exp pre-flight for streaming `upload` chunks (because once an `*os.File` chunk is consumed it can't be replayed). Concurrent goroutines and processes serialize on a single `/api/refresh`.\r\n\r\n**On `*ErrTokenInvalidated` / `*ErrNotLoggedIn`, do not retry — only `profile login` / `profile import` will help.** See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for the full recovery table.\r\n\r\n## Verb index\r\n\r\nFor flags, examples, and wire shapes, **always start with `olares-cli files <verb> --help`**. The references below add only what `--help` cannot give — agent-facing safety constraints, multi-step orchestrations, and common-error → fix maps.\r\n\r\n| Verb | `--help` first, then... | Notes |\r\n|---|---|---|\r\n| `ls` | [references/olares-files-ls.md](references/olares-files-ls.md) | Drive vs. cloud envelope shapes; `--json` semantics |\r\n| `cat` | `olares-cli files cat --help` | Trivial GET to stdout; binary-safe |\r\n| `download` | [references/olares-files-download.md](references/olares-files-download.md) | `--resume` / `--overwrite` semantics; directory parallel fetch |\r\n| `upload` | [references/olares-files-upload.md](references/olares-files-upload.md) | Two-stage cloud upload (stage 1 chunks → stage 2 server-side transfer task); `--parallel` semantics; tencent rejection |\r\n| `edit` | [references/olares-files-edit.md](references/olares-files-edit.md) | Editor cascade; three-tier size cap; text-only guard; concurrent-delete detection; cloud writeback gap |\r\n| `mkdir` | [references/olares-files-mkdir.md](references/olares-files-mkdir.md) | `-p` skips existing prefixes; auto-rename quirk on the leaf; `external/<node>/<X>/` depth-1 guard |\r\n| `rm` | [references/olares-files-rm.md](references/olares-files-rm.md) | Preflight existence check before prompt; trailing-slash signals dir; protected-names list |\r\n| `rename` | [references/olares-files-rename.md](references/olares-files-rename.md) | In-place only (synchronous PATCH); protected-names list; bare basename only |\r\n| `cp` / `mv` | [references/olares-files-cp-mv.md](references/olares-files-cp-mv.md) | Drop-into-dir semantics; `mv` source rejects protected names; preflight Stat of every src + dst dir |\r\n| `chown` | [references/olares-files-chown.md](references/olares-files-chown.md) | UID 0 / 1000 conventions; namespace allow-list; volume-root refusal |\r\n| `share` | [references/olares-files-share.md](references/olares-files-share.md) | Three flavors (internal / public / smb); directory-only; per-flavor namespace allow-list; update verbs (`set-members` / `set-password` / `set-smb`) |\r\n| `smb` | [references/olares-files-smb.md](references/olares-files-smb.md) | Mount → `external/<node>/<entry>/`; host-only address triggers share discovery; favorites history |\r\n| `repos` | `olares-cli files repos --help` | List / create / rename / rm Seafile libraries; repo_id is the `<extend>` segment |\r\n\r\n## Common errors (cross-verb)\r\n\r\n| Error fragment | Meaning | Fix |\r\n|---|---|---|\r\n| `is the volume listing layer (read-only); point at a real volume, e.g. external/<node>/<volume>/<sub>/` | Quirk #3 — bare `external/<node>/` write attempt | Add the `<volume>` segment |\r\n| `refusing to mkdir external/<node>/<X>/: depth-1 entries under external/<node>/ are mounted volumes` | Quirk #3 depth-1 — would create a phantom volume | Mount the volume via LarePass; target an existing one |\r\n| `refusing to {rename\\|delete\\|mv source} drive/Home/<name>: this is a system-managed Home folder` | Quirk #4 — protected name | Pick a different name, or operate on a nested path |\r\n| `refusing to share cache/<node>/: this is the node-picker layer (no concrete dataset to share)` | Quirk #5 — bare cache node-root share | Use `cache/<node>/<sub>/` |\r\n| `file disappeared between stat and fetch` | Concurrent-delete race on `edit` | Re-pull the parent directory and decide |\r\n| `tencent upload is not supported` (or similar) | Tencent's octet protocol is not implemented | Use the LarePass web app for tencent uploads |\r\n| `<src> does not exist on the server` (from `cp`/`mv`/`rm`) | Preflight Stat failed | `files ls` the parent and confirm the path |\r\n| `HTTP 500` from `/api/resources/<file>` | Quirk #2 — backend tried to embed file bytes | Use `files cat` / `files download` instead |\r\n\r\nFor auth-related errors (`server rejected the access token`, `refresh token for X became invalid`, …) see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\r\n\r\n## Safety contract\r\n\r\n- **Write & delete verbs** (`rm`, `rename`, `cp`, `mv`, `chown --uid`, `share rm`, `repos rm`, `smb unmount`) — confirm intent with the user FIRST. Several verbs preflight against the server before any state change; do not bypass that by retry-on-404.\r\n- **`rm -f` skips the y/N prompt but NOT the preflight existence check** — a missing path still aborts.\r\n- **Never echo `access_token` / `refresh_token` to the terminal.** Use `--password-stdin` (where supported) for SMB passwords too.\r\n- **Confirm destination paths** before any `upload --overwrite`, `cp` to an existing file, or any operation that could clobber bytes.\n\nFile v4.0.1:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-files\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034074377\n}\n\nFile v4.0.1:references/olares-files-chown.md\n\n# files chown\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files chown --help`.\r\n\r\nGet or set the POSIX owner UID of a file / directory. CLI counterpart of the LarePass web app's \"Permission\" tab.\r\n\r\n## Modes\r\n\r\n| Form | What it does |\r\n|---|---|\r\n| `files chown <path>` | GET — print the current uid |\r\n| `files chown <path> --uid <int>` | PUT — replace the uid |\r\n| `files chown <path> --uid <int> -r` | PUT — recurse into children |\r\n\r\n## UID conventions (LarePass presets)\r\n\r\n| UID | Meaning |\r\n|---|---|\r\n| `0` | Root (system; only set this if you know why) |\r\n| `1000` | User (the default LarePass user; matches the GUI's \"User\" preset) |\r\n\r\nAny integer is accepted, but these are the values the GUI surfaces.\r\n\r\n## Supported namespaces (allow-list)\r\n\r\n`drive/Home/<sub>`, `drive/Data/<sub>`, `cache/<node>/<sub>` only.\r\n\r\nRefused namespaces:\r\n\r\n| Namespace | Why |\r\n|---|---|\r\n| `sync/<repo_id>/...` | Seafile permissions live on the library itself — use `files repos` |\r\n| `external/<node>/<volume>/...` | LarePass GUI hides the Permission tab for external mounts |\r\n| `awss3` / `dropbox` / `google` / `tencent` | Object stores have no POSIX uid concept |\r\n\r\n## Safety constraints\r\n\r\n- **Destructive when `--uid` is provided — confirm intent with the user.** UID changes affect every app that reads the directory.\r\n- **Volume roots are refused** (`drive/Home/`, `drive/Data/`, `cache/<node>/`) — chowning an entire namespace root has too much blast radius. Pick a one-level-deeper path with `-r` if you need to fan out.\r\n- **`-r` recurses** — every descendant gets the new uid. Confirm directory contents with `files ls` first.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Inspect.\r\nolares-cli files chown drive/Home/Documents/foo.pdf\r\n\r\n# Hand a file to root.\r\nolares-cli files chown drive/Home/Documents/foo.pdf --uid 0\r\n\r\n# Hand an entire directory tree to the default user.\r\nolares-cli files chown drive/Home/Pictures/Trip2024/ --uid 1000 -r\r\n\r\n# Cache namespace.\r\nolares-cli files chown cache/<node>/scratch/build/ --uid 1000 -r\r\n```\r\n\r\n## Agent notes\r\n\r\n- The GET form is cheap — use it before any PUT to show the user the current uid and confirm the change.\r\n- Inside `-r`, partial-failure behavior is per-server — on an error the server may have already changed some descendants. **Do NOT retry blindly**; re-run the GET form on a few sampled paths to see what landed.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `chown is not supported for this namespace` | sync / external / cloud target | Use `files repos` (sync) or LarePass GUI (external) |\r\n| `refusing to chown a volume root` | `drive/Home/` / `drive/Data/` / `cache/<node>/` | Pick a sub-path |\r\n| 403 from server | Server-side ACL rejection | Confirm via `files ls -ld` (when available) or LarePass that the active user has permission |\n\nFile v4.0.1:references/olares-files-cp-mv.md\n\n# files cp / files mv\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files cp --help` and `olares-cli files mv --help`.\r\n\r\nCopy / move one or more entries between locations. Same wire endpoint (`PATCH /api/paste/<node>/`), different `action`. Cross-volume (drive ↔ sync ↔ external) is supported.\r\n\r\n## Safety constraints\r\n\r\n- **Destructive (mutates the server) — confirm intent with the user.** `mv` even more so since the source is removed.\r\n- **`<dst> MUST end with `/` (drop-into-directory mode).** Each `<src>`'s basename is appended; preserves the dir / file marker.\r\n- **Renaming via `cp` / `mv` is not supported** — use `files rename` for in-place basename changes, or rename first and then `mv`.\r\n- **Directory sources require `-r`** (Unix-style refusal otherwise).\r\n- **`mv` source rejects protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)): `mv drive/Home/Pictures/ ...` is refused because moving would unlink a dir that apps depend on. **`cp` (copy) is intentionally NOT gated** — duplicating bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is fine.\r\n- **`external/<node>/` destinations are rejected** ([quirk #3](../SKILL.md#3-externalnode-is-a-virtual-volume-listing-layer-read-only)) — point at `external/<node>/<volume>/<sub>/`.\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file → directory.\r\nolares-cli files cp drive/Home/notes.md drive/Home/Documents/\r\n\r\n# Recursive directory copy.\r\nolares-cli files cp -r drive/Home/Photos/ drive/Home/Backups/\r\n\r\n# Multiple sources into a directory.\r\nolares-cli files cp drive/Home/a.pdf drive/Home/b.pdf drive/Home/Archive/\r\n\r\n# Cross-volume (drive → sync repo).\r\nolares-cli files cp drive/Home/notes.md sync/<repo_id>/inbox/\r\n\r\n# Move (mv replaces cp where source removal is intended).\r\nolares-cli files mv drive/Home/notes.md drive/Home/Archive/\r\nolares-cli files mv -r drive/Home/Photos/ drive/Home/Backups/\r\n```\r\n\r\n## Preflight existence check\r\n\r\nRuns BEFORE any PATCH is sent:\r\n\r\n- Each `<src>` MUST exist on the server, AND its trailing-slash form must match the actual file/dir kind.\r\n- `<dst>` MUST exist as a directory on the server. **Create it first with `files mkdir -p` if needed** — `cp`/`mv` does NOT auto-create the destination (the auto-rename quirk #1 would land you in `<dst> (1)`).\r\n\r\nA typo on either side aborts before the server's task queue sees it.\r\n\r\n## Node selection (`--node`)\r\n\r\nEach PATCH carries a `{node}` URL segment. Default cascade:\r\n\r\n1. `--node` override (per invocation)\r\n2. External / Cache `<extend>` (when the destination is `external/<node>/` or `cache/<node>/` — the GUI's `dst_node || src_node || default` cascade)\r\n3. First entry from `/api/nodes/`\r\n\r\nUse `--node` only when you have a specific multi-node deployment with a non-default node hosting the paste task.\r\n\r\n## Agent notes\r\n\r\n- **For renames, ALWAYS use `files rename`** — it's synchronous (no task queue) and works in place. Don't try to fake a rename with `cp`/`mv`.\r\n- **`mv` is async** — the response is \"task accepted\", not \"move completed\". For most paths this is fast, but on huge directory trees consider running an `ls` afterward to confirm.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<dst> must end with /` | Drop-into-dir mode requires trailing slash | Add `/` |\r\n| `<dst> does not exist on the server` | Destination dir not pre-created | `files mkdir -p <dst>` first |\r\n| `<src> is a directory; pass -r` | Directory source without recursion | Add `-r` |\r\n| `refusing to mv drive/Home/<protected-name>` | Quirk #4 mv-source guard | Use `cp -r` (preserves the original) if the goal is duplication |\r\n| `is the volume listing layer (read-only)` | `external/<node>/` destination | Add `<volume>` segment |\n\nFile v4.0.1:references/olares-files-download.md\n\n# files download\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files download --help`.\r\n\r\nDownload a file or directory tree from the per-user files-backend.\r\n\r\n## Safety constraints\r\n\r\n- **Without `--resume` or `--overwrite`, the command refuses to clobber an existing local file** — confirm intent with the user before suggesting `--overwrite`.\r\n- `--overwrite` writes to `<dst>.tmp` then renames, so the previous version stays intact until the new bytes land — safe to suggest after the user confirms.\r\n- Directory mode mirrors the remote tree under the local destination; the remote root's own basename becomes the top-level directory (matches the LarePass folder-download UX).\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file into the current directory.\r\nolares-cli files download drive/Home/Documents/report.pdf\r\n\r\n# Same, but pick a different local name.\r\nolares-cli files download drive/Home/Documents/report.pdf ./Q1.pdf\r\n\r\n# Resume an interrupted download (server-driven Range; O_APPEND on the local file).\r\nolares-cli files download drive/Home/Backups/big.tar ./big.tar --resume\r\n\r\n# Recursively pull a folder, 4 files at a time (default).\r\nolares-cli files download drive/Home/Documents/ ./out/ --parallel 4\r\n```\r\n\r\n## Agent notes\r\n\r\n- **`Stat` always lists the parent directory** and finds the leaf in the items array — this is a workaround for [quirk #2](../SKILL.md#2-get-apiresourcesfile-no-trailing-slash-returns-http-500). You never need to suggest \"just GET the file URL\"; the CLI already handles it.\r\n- **Single-file resume** uses server-driven `Range: bytes=<localSize>-` — there is no sidecar progress file. A Ctrl-C + re-run keeps making forward progress as long as the local file is preserved.\r\n- **Directory downloads parallelize FILES, not chunks** — each file's bytes still stream sequentially. `--parallel N` bounds concurrent file fetches.\r\n- Empty subdirectories are mirrored locally so the tree matches even when a directory has no files.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<dst> exists; pass --overwrite or --resume` | Local target already on disk | Confirm with user, then `--overwrite` (replace) or `--resume` (continue) |\r\n| `HTTP 500` from a raw resource URL | Quirk #2 — the bare file URL embeds bytes in JSON | Use this verb (which Stats via parent), not a manual `curl` |\r\n| 401/403 | Token rotation or invalidation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.1:references/olares-files-edit.md\n\n# files edit\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files edit --help`.\r\n\r\nEdit a single existing file in-place by opening it in `$EDITOR`. **UPDATE-only verb** — the server-side `PUT /api/resources/<encPath>` handler is wired as \"replace bytes of an existing file\"; it does NOT create new files. To materialize a new file, use `files upload` from a local source.\r\n\r\n## Safety constraints\r\n\r\n- **Interactive TTY required.** `edit` spawns `$EDITOR` foreground; CI / pipes / heredocs are refused cleanly with a `download` + `upload` recovery hint.\r\n- **Three-tier size cap** (default 1 MiB, configurable via `--max-size`): pre-fetch Stat, during-fetch `io.LimitReader` defense, post-edit local file size.\r\n- **Text-only guard by default**: an extension deny-list (jpg/png/gif/heic/pdf/docx/mp4/mp3/zip/tar.gz/exe/so/sqlite/ttf/...) plus a NUL-byte sniff over the first 8 KiB. Pass `--allow-binary` to disable both.\r\n- **No ETag / If-Match support on the wire** — concurrent edits from two clients follow last-writer-wins. Same as the LarePass GUI.\r\n\r\n## Supported namespaces\r\n\r\n`drive/Home/<sub>/<file>`, `drive/Data/<sub>/<file>`, `sync/<repo_id>/<sub>/<file>`, `cache/<node>/<sub>/<file>`, `external/<node>/<volume>/<sub>/<file>`.\r\n\r\n**Cloud drives (awss3 / google / dropbox / tencent) are refused** — the PUT writeback shape is not wire-verified per cloud driver. Use this workflow instead:\r\n\r\n```bash\r\nolares-cli files download <cloud-path> <local>\r\n$EDITOR <local>\r\nolares-cli files upload <local> <cloud-path>\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files edit drive/Home/Documents/notes.md\r\nolares-cli files edit drive/Home/.config/app.yaml --editor nano\r\nolares-cli files edit sync/<repo_id>/Notes/draft.md\r\nolares-cli files edit drive/Home/Logs/today.log --max-size 5242880  # 5 MiB\r\nolares-cli files edit external/<node>/usb1/config.json --keep-temp\r\n```\r\n\r\n## Editor cascade\r\n\r\nMatches `git commit` / `crontab -e`:\r\n\r\n```\r\n--editor flag  →  $VISUAL  →  $EDITOR  →  vi (POSIX) / notepad (Windows)\r\n```\r\n\r\nThe binary is resolved up-front BEFORE the CLI dials the server. A missing / mistyped editor fails fast without pulling the remote file.\r\n\r\n## Agent notes\r\n\r\n- If the user exits the editor without changes, **no PUT is issued** (byte-for-byte comparison; robust against editors that always rewrite). No warning needed.\r\n- **Concurrent-delete detection**: if Stat says the file exists but the subsequent GET returns 404, the verb refuses with `file disappeared between stat and fetch`. Do NOT retry — re-pull the parent and ask the user what to do.\r\n- `--keep-temp` is the right escape hatch when an unexpected size-cap rejection or NUL-byte sniff blocks the writeback — point the user at the temp path so they can recover bytes manually.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `stdin/stdout is not a terminal` | Non-TTY context | Use `download` + local edit + `upload` |\r\n| `file too large: <N> bytes > max-size <M>` | Three-tier cap pre-fetch | Pass `--max-size 0` (unbounded) or `--max-size <bigger>` |\r\n| `binary content detected (extension/NUL)` | Text-only guard | Confirm intent, then `--allow-binary` |\r\n| `file disappeared between stat and fetch` | Someone else deleted the file between probes | Re-pull parent, ask user |\r\n| `cloud drive edit is not supported` | awss3/google/dropbox/tencent target | Use download → edit → upload workflow |\n\nFile v4.0.1:references/olares-files-ls.md\n\n# files ls\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first for the profile model, the 3-segment frontend path, and the 5 client-side quirks.\r\n> **Flags & wire shape:** `olares-cli files ls --help` (single source of truth).\r\n\r\nList a directory on the per-user files-backend. Uniform across all 10 namespaces.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files ls drive/Home/\r\nolares-cli files ls drive/Home/Documents\r\nolares-cli files ls sync/<repo_id>/\r\nolares-cli files ls awss3/<account>/<bucket>\r\nolares-cli files ls cache/<node>/\r\nolares-cli files ls external/<node>/           # virtual volume-listing layer (see SKILL.md quirk #3)\r\nolares-cli files ls drive/Home/Documents --json  # raw envelope, pretty-printed\r\n```\r\n\r\n## Output shape\r\n\r\nDefault table: `MODE  SIZE  TYPE  MODIFIED  NAME`. Directories sort before files; directory names get a trailing `/`. Empty directories print `(empty)`.\r\n\r\n`--json` prints the raw JSON envelope, useful for scripting.\r\n\r\n## Envelope shapes (transparent to the user, matters when reading `--json`)\r\n\r\n| Namespace | Children field | Per-item size | `mode` / `modified` |\r\n|---|---|---|---|\r\n| `drive` / `sync` / `cache` / `external` / `share` | `items` | `size` (number) | numeric `mode`, RFC3339 `modified` |\r\n| `awss3` / `google` / `dropbox` / `tencent` | `data` | `fileSize` | empty strings; the table renders `d---------` / `----------` and `-` in MODE / MODIFIED |\r\n\r\nThe cloud envelope ALSO omits the parent-level `numDirs` / `numFiles` / `modified` summary; the table header falls back to counting items so it stays informative.\r\n\r\n## Agent notes\r\n\r\n- `ls` is the canonical discovery verb. Use it before any write to confirm parent existence and the exact basename casing.\r\n- `ls external/<node>/` is the right way to discover attached volumes (`hdd1`, `usb1`, `smb-...`) before targeting `external/<node>/<volume>/<sub>/`.\r\n- `ls cache/<node>/` is the right way to discover what's under a node before sharing — share-create rejects bare `cache/<node>/`.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `invalid drive type` | `drive/home/...` instead of `drive/Home/...` | Use exact casing: `Home` or `Data` |\r\n| Empty `items` / `data` array on a known-non-empty dir | Wrong identity — the active profile can't see this scope | `olares-cli profile list` and switch with `profile use` |\r\n| 401/403 | Token rotation or invalidation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.1:references/olares-files-mkdir.md\n\n# files mkdir\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files mkdir --help`.\r\n\r\nCreate a directory on the per-user files-backend. Uniform across all namespaces (`POST /api/resources/<path>/`).\r\n\r\n## Critical caveat: auto-rename quirk\r\n\r\n`POST /api/resources/<dir>/` against an existing directory does NOT return 409 — the server silently creates `<dir> (1)` instead (see [quirk #1](../SKILL.md#1-post-apiresourcesdir-auto-renames-on-collision)).\r\n\r\n- `-p` mode side-steps this for parents (it lists each prefix's parent and skips when the basename already exists).\r\n- For the LEAF, the CLI prints a hint after the call so the user can `files ls` and confirm.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files mkdir drive/Home/Documents/Backups\r\nolares-cli files mkdir -p drive/Home/A/B/C/\r\nolares-cli files mkdir -p sync/<repo_id>/notes/2026/Q2\r\nolares-cli files mkdir -p awss3/<account>/Backups/2026\r\nolares-cli files mkdir -p google/<account>/Drafts\r\n```\r\n\r\n## Refusals (client-side)\r\n\r\n- **Volume roots** (`drive/Home/`, `drive/Data/`, `sync/<repo_id>/`, etc.) — they always exist, so the call would be a no-op or trip the auto-rename quirk on the extend folder.\r\n- **`.` or `..` segments ANYWHERE in the path** — path-traversal blacklist on raw input (before normalization), so `drive/Home/foo/../bar` errors out instead of being rewritten to `drive/Home/bar`.\r\n- **`external/<node>/` (quirk #3 bare root)** — virtual layer with no backing filesystem.\r\n- **`external/<node>/<single-segment>/` (depth-1 under external)** — depth-1 entries ARE the mounted volumes (USB-0, SMB-..., per-disk mount-points). Creating a new depth-1 entry would land as a phantom volume or collide with an existing mount. **Mount new volumes via LarePass first.** `-p` mode also refuses to auto-create a missing depth-1 intermediate.\r\n\r\n## Agent notes\r\n\r\n- **Always `-p` when chaining mkdir → upload.** `upload` does NOT pre-create directories (because of quirk #1), so the cheapest pattern is `mkdir -p <dest-dir> && upload <local> <dest-dir>`.\r\n- After a non-`-p` mkdir, if the user wonders why they see `Foo (1)`, the answer is \"you ran mkdir on an existing dir\". The fix: `files rm -r drive/Home/Foo (1)/` (the bytes inside `Foo` are untouched).\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `Foo (1)` appeared instead of `Foo` | Auto-rename quirk on existing leaf | Delete the dup; in future check with `files ls` first or use `-p` |\r\n| `refusing to mkdir external/<node>/<X>/: depth-1 entries under external/<node>/ are mounted volumes` | Quirk #3 depth-1 guard | Mount the volume via LarePass; target an existing volume's sub-path |\r\n| `is the volume listing layer (read-only)` | Quirk #3 bare-root | Add the `<volume>` segment |\r\n| `path contains . or .. segments` | Path-traversal blacklist | Use absolute / canonical paths only |\n\nFile v4.0.1:references/olares-files-rename.md\n\n# files rename\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files rename --help`.\r\n\r\nRename a remote entry in place — same parent directory, new basename. Synchronous PATCH (no `task_id` polling).\r\n\r\n## When to use this vs. `mv`\r\n\r\n| Want to ... | Use |\r\n|---|---|\r\n| Change basename, same parent | `rename` (synchronous, no node) |\r\n| Move to a different directory or volume | `mv` (async via paste queue) |\r\n\r\n## Safety constraints\r\n\r\n- **Destructive (mutates the server) — confirm intent with the user.**\r\n- **`<new-name>` is a BARE basename** — no `/` or `\\`. Empty, `.`, `..` are rejected.\r\n- **Protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)) refuse to be renamed at the first level under `drive/Home/`; deeper paths are fine.\r\n- **Volume roots** (`drive/Home/`, `sync/<repo>/`, ...) are refused.\r\n- **`.` or `..` segments ANYWHERE in `<remote-path>`** are rejected (path-traversal blacklist on raw input).\r\n\r\n## Examples\r\n\r\n```bash\r\n# Rename a file.\r\nolares-cli files rename drive/Home/Documents/foo.pdf foo-final.pdf\r\n\r\n# Rename a directory (trailing slash on source signals dir).\r\nolares-cli files rename drive/Home/Documents/old/ new-name\r\n\r\n# Sync repo.\r\nolares-cli files rename sync/<repo_id>/notes/draft.md final.md\r\n```\r\n\r\n## Agent notes\r\n\r\n- Trailing slash on `<remote-path>` is preserved on the wire so the backend routes through its directory handler — keep it when the source is a dir.\r\n- `rename` cannot be combined with a `mv`-style drop-into-directory; if the user wants both (move + rename), do `rename` first, then `mv` the result.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `refusing to rename drive/Home/<protected-name>` | Quirk #4 | Pick a different target |\r\n| `new name must be a bare basename` | `<new-name>` contains `/` | Drop the slash; use `mv` if you actually want to move |\r\n| `new name is empty` / `cannot rename to . or ..` | Invalid basename | Provide a real name |\r\n| `path contains . or .. segments` | Path-traversal blacklist | Use a clean path |\n\nFile v4.0.1:references/olares-files-rm.md\n\n# files rm\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files rm --help`.\r\n\r\nDelete one or more files / directories. Batch-aware: multiple targets that share a parent collapse into one DELETE request.\r\n\r\n## Safety constraints\r\n\r\n- **Destructive verb — confirm intent with the user before invocation.** The CLI prompts y/N by default; `-f` skips the prompt.\r\n- **`-f` does NOT bypass the preflight existence check** — a missing path still aborts (safer-than-Unix `rm -f`). This means a typo cannot half-delete a batch.\r\n- **Trailing slash signals directory intent.** Without `-r`, `rm drive/Home/Foo/` errors with \"<Foo> is a folder, pass -r\". Without trailing slash AND target is actually a dir, errors with the same CTA.\r\n- **Protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)) are refused at the first level under `drive/Home/` only; deeper paths (`drive/Home/Pictures/Trip2024/`) are fully deletable.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files rm drive/Home/Documents/old.pdf\r\nolares-cli files rm -r drive/Home/Backups/2024\r\nolares-cli files rm -r drive/Home/Backups/2024/\r\nolares-cli files rm -rf drive/Home/junk drive/Home/scratch/\r\n\r\n# Batch: two siblings + one cross-parent — collapses into 2 DELETE calls.\r\nolares-cli files rm drive/Home/a.pdf drive/Home/b.pdf sync/<repo>/old.md\r\n```\r\n\r\n## Preflight existence check\r\n\r\nRuns BEFORE the confirmation prompt. Aborts (with no \"will delete N entries\" line printed) if:\r\n\r\n- A target path doesn't exist on the server (typo / stale path)\r\n- The user typed `<target>/` or passed `--recursive`, but the entry is actually a FILE\r\n- The user typed `<target>` (no slash) without `--recursive`, but the entry is actually a DIRECTORY\r\n\r\nVolume roots are rejected upstream by the planner; the preflight only sees real entries.\r\n\r\n## Agent notes\r\n\r\n- **Always `files ls` the parent before suggesting `rm`** so the user sees what's actually there. Confirms the basename and gives them a chance to abort.\r\n- **Mixing files and folders in one `-r` invocation is unusual.** If a target list has both, split into two `rm` calls (one with `-r`, one without).\r\n- The CLI sorts requests by `fileType + extend + parent` — output ordering is stable and useful in scripts.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<path> is a folder; pass -r` | Trailing `/` or actual-dir without `-r` | Add `-r` |\r\n| `<path> is a file; remove the trailing slash` | Wrong intent | Drop the `/` |\r\n| `refusing to delete drive/Home/<protected-name>` | Quirk #4 | Pick a different target, or operate on a nested path |\r\n| `<path> does not exist on the server` | Stale / typo | `files ls` the parent first |\r\n| 401/403 | Token rotation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.1:references/olares-files-share.md\n\n# files share\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli files share --help` (parent), `olares-cli files share <flavor> --help` (each leaf), `olares-cli files share set-members --help`, …\r\n\r\nCreate and manage shares for directories. **All three flavors are directory-only** — the CLI Stats the target before posting and refuses files / non-existent paths up front. To share a single file, place it in a dedicated directory and share that.\r\n\r\n## Three flavors at a glance\r\n\r\n| Flavor | Audience | Recipient model | Update verb |\r\n|---|---|---|---|\r\n| `internal` | Other Olares users on the same node | Olares user names → per-user permission | `set-members` |\r\n| `public` | Anyone with the link + password | Opaque link, recipients open it at `<host>/sharable-link/<id>/` | `set-password` |\r\n| `smb` | Local network (Finder / Explorer / etc.) | SMB-account IDs (managed via `smb-users`), or `--public` for \"anyone on the LAN\" | `set-smb` |\r\n\r\n## Per-flavor namespace allow-list\r\n\r\n| Flavor | Allowed | Notes |\r\n|---|---|---|\r\n| `internal` | `drive`, `sync`, `external`, `cache` | Cloud rejected — cross-cloud-account share doesn't work. `external/<node>/` bare-root and `cache/<node>/` bare-root rejected (quirks #3, #5) — point at a sub-path |\r\n| `smb` | `drive`, `external`, `cache` | Sync rejected (Seafile has its own mount story, not Samba) + cloud rejected. Same bare-root guards as internal |\r\n| `public` | `drive` ONLY | Tightest of the three. Sync / external / cache / cloud all refused (the GUI restricts Public to drive only). Error messages route the user to `share internal` (sync) or `share internal` / `share smb` (external / cache) |\r\n\r\n## `--users` format (internal / smb / set-members)\r\n\r\n```\r\nname1:perm1,name2:perm2,name3   (perm defaults to \"view\" if omitted)\r\n```\r\n\r\nPermissions: `view` / `upload` / `edit` / `admin` (or `0..4`). Empty perm falls back to `view`.\r\n\r\nFor SMB shares, \"name\" is an **SMB-account ID** (see `share smb-users list`), not an Olares user name.\r\n\r\n## Wire shape (all create flavors)\r\n\r\n```\r\nPOST /api/share/share_path/<fileType>/<extend><subPath>/\r\nbody: {name, share_type, permission, password, ...}\r\n```\r\n\r\nResponse carries the new `share id`, plus per-flavor extras (`smb_link` / `smb_user` / `smb_password` for SMB; the Public-link URL is constructed by the LarePass app's `shareBaseUrl + /sharable-link/<id>/` pattern).\r\n\r\nManagement verbs (`list` / `get` / `rm`) take the share id and are share-type-agnostic.\r\n\r\n## Update verbs (REPLACES, not appends)\r\n\r\n| Verb | What it changes | Important semantic |\r\n|---|---|---|\r\n| `set-password` | Public-link password | One field; rejects non-Public shares up front |\r\n| `set-members` | Internal share member list | **Drops every member not listed in `--users`.** Pass `--clear` to drop them all. Wire has no \"add member\"; for additive updates, list every existing member + the new one |\r\n| `set-smb` | SMB account list OR public-SMB toggle | Same replace semantics as `set-members`. `--public` flips to \"anyone on the LAN\" mode |\r\n\r\n## Examples\r\n\r\n```bash\r\n# Internal share, two members (alice can edit, bob can view).\r\nolares-cli files share internal drive/Home/Backups/ \\\r\n    --users alice:edit,bob:view\r\n\r\n# Public link valid 7 days, password auto-generated and printed.\r\nolares-cli files share public drive/Home/Photos/ --expire-days 7\r\n\r\n# Public upload-only inbox with explicit password + size cap.\r\nolares-cli files share public drive/Home/Inbox/ --upload-only \\\r\n    --password drop --expire-days 30 --upload-size-limit 100M\r\n\r\n# SMB share for two SMB users.\r\nolares-cli files share smb drive/Home/Movies/ \\\r\n    --users smb-uid-1:edit,smb-uid-2:edit\r\n\r\n# Roll a Public link's password.\r\nolares-cli files share set-password <share-id>\r\n\r\n# Promote bob from view to admin on an Internal share (carry alice through unchanged!).\r\nolares-cli files share set-members <share-id> \\\r\n    --users alice:edit,bob:admin\r\n\r\n# Drop every member (share stays, becomes private to its owner).\r\nolares-cli files share set-members <share-id> --clear\r\n\r\n# Switch an SMB share to public-SMB.\r\nolares-cli files share set-smb <share-id> --public\r\n\r\n# List, inspect, remove.\r\nolares-cli files share list --shared-by-me\r\nolares-cli files share get <share-id>\r\nolares-cli files share rm <share-id>\r\n```\r\n\r\n## Public-link specifics\r\n\r\n- **Password is required** — either pass `--password <pw>` or let the CLI auto-generate an 8-char random password (which it then prints).\r\n- **Expiration is required** — pass exactly one of `--expire-days N` or `--expire-time <RFC3339>`. Public links without an expiration are not supported by the backend.\r\n- **`--upload-only`** locks recipients out of listing / download — they can only drop files in.\r\n- **`--upload-size-limit`** accepts human-readable sizes: `100M`, `1G`, `500K`, `512` (raw bytes). `0` / omitted = no per-upload cap.\r\n\r\n## Agent flows\r\n\r\n### Create a Public share and reply with the URL\r\n\r\n```bash\r\nSHARE_ID=$(olares-cli files share public drive/Home/Photos/ --expire-days 7 --password \"$PW\" --json | jq -r '.id')\r\necho \"Share link: https://<your-host>/sharable-link/$SHARE_ID/\"\r\n```\r\n\r\nThe `<your-host>` part is read from the LarePass app's `shareBaseUrl`; if the user doesn't already know it, point them at LarePass settings.\r\n\r\n### Add a member to an existing Internal share without dropping existing ones\r\n\r\n```bash\r\n# First fetch the current members.\r\nCURRENT=$(olares-cli files share get <share-id> --json | jq -r '.share_members | map(.share_member + \":\" + .permission_label) | join(\",\")')\r\n# Then re-list them PLUS the new member.\r\nolares-cli files share set-members <share-id> --users \"$CURRENT,carol:view\"\r\n```\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `share target is a file, not a directory` | Tried to share a single file | Wrap it in a directory and share that |\r\n| `Public only supports the drive namespace` | `share public sync/...` / `share public external/...` / etc. | Use `share internal` instead (or `share smb` for external/cache) |\r\n| `cloud namespaces are not supported` | `share <flavor> awss3/...` etc. | Move the data into drive first, then share |\r\n| `refusing to share external/<node>/` | Quirk #3 bare root | Point at `external/<node>/<volume>/<sub>/` |\r\n| `refusing to share cache/<node>/` | Quirk #5 node-picker layer | Point at `cache/<node>/<sub>/` |\r\n| `--users and --clear are mutually exclusive` | Both passed to `set-members` | Pick one |\r\n| `share-type mismatch` (e.g. set-password on Internal) | Wrong update verb for the flavor | Use the matching verb from the flavor table |\n\nFile v4.0.1:references/olares-files-smb.md\n\n# files smb\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli files smb --help` (parent), `olares-cli files smb mount --help`, `olares-cli files smb history --help`.\r\n\r\nMount **external** SMB shares into the per-user files-backend's `external/<node>/...` namespace. CLI counterpart of the LarePass \"Connect to Server\" modal.\r\n\r\n> **Don't confuse with `files share smb`** — that creates an OUTBOUND share (expose a directory over Samba). `files smb` consumes INBOUND shares (mount a network share into Olares).\r\n\r\n## Sub-commands\r\n\r\n| Sub-command | Purpose |\r\n|---|---|\r\n| `smb mount <smb-url>` | Mount a remote SMB share, materializes at `external/<node>/<entry>/` |\r\n| `smb unmount <name>` | Unmount a previously-mounted entry |\r\n| `smb history list` | List the per-node \"Favorite Servers\" |\r\n| `smb history add <smb-url>` | Stash a favorite for later (optional credentials) |\r\n| `smb history rm <smb-url>...` | Drop favorites by URL |\r\n\r\n## Safety constraints\r\n\r\n- **Mount / unmount mutate the per-node state — confirm intent with the user.**\r\n- **Credentials in `-p / --password` end up in shell history.** For scripts use `--password-stdin`; for interactive use, omit both and the CLI prompts without echo.\r\n- **History entries can carry credentials.** Treat them as sensitive — adding credentials to history is convenient but the entries are stored server-side.\r\n\r\n## Mount flow with host-only address (discovery)\r\n\r\n```\r\nPOST /api/mount/[<node>/]?external_type=smb\r\nbody: {smbPath, user, password}\r\nreply:\r\n  code 200 → mounted; visible at external/<node>/<entry>/\r\n  code 300 → smbPath was host-only; data is the list of discovered shares\r\n```\r\n\r\nWhen the user passes a HOST-only URL (e.g. `//host.local`), the server returns `code 300` with the discovered shares. The CLI prints the list and asks the user to re-run with one of them:\r\n\r\n```bash\r\n# Step 1 — host-only triggers discovery.\r\nolares-cli files smb mount //host.local\r\n# → server returned 3 shares: //host.local/Public, //host.local/Movies, //host.local/Backups\r\n\r\n# Step 2 — re-run with the chosen share path.\r\nolares-cli files smb mount //host.local/Public -u alice -p s3cret\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\n# Mount with credentials.\r\nolares-cli files smb mount //host.local/Public -u alice -p s3cret\r\n\r\n# Mount via stdin password (script-friendly).\r\nprintf '%s' \"$SMB_PASSWORD\" | olares-cli files smb mount //host.local/Public -u alice --password-stdin\r\n\r\n# Stash a favorite (credentials optional; prompted at mount time if omitted).\r\nolares-cli files smb history add //host.local/Public\r\n\r\n# List favorites for the current node.\r\nolares-cli files smb history list\r\n\r\n# Inspect the mounted entries (every external mount is just a child of external/<node>/).\r\nolares-cli files ls external/<node>/\r\n\r\n# Unmount when done.\r\nolares-cli files smb unmount <entry-name>\r\n\r\n# Remove a favorite by URL.\r\nolares-cli files smb history rm //host.local/Public\r\n```\r\n\r\n## Wire shape\r\n\r\n```\r\nPOST   /api/mount/[<node>/]?external_type=smb            (mount)\r\nPOST   /api/unmount/external/<node>/<name>/?external_type=smb  (unmount)\r\nGET    /api/smb_history/<node>/                          (history list)\r\nPUT    /api/smb_history/<node>/  body: array             (history upsert)\r\nDELETE /api/smb_history/<node>/  body: array of {url}    (history rm)\r\n```\r\n\r\n## Agent notes\r\n\r\n- **After a successful mount, the entry lives at `external/<node>/<entry>/`** and is consumed by every other `files` verb the same way as any other namespace. Use `files ls external/<node>/` to confirm the new entry name (it's usually a sanitized version of the SMB path).\r\n- **`--node` is rarely needed** — defaults to the active node from `/api/nodes/`. Pass it explicitly only if the user has a multi-node Olares and wants the mount to land on a specific node.\r\n- **Mount failures with code 300 are not errors** — they're discovery responses. Surface the share list to the user verbatim and ask them which one to mount.\r\n- **Don't try to mkdir under `external/<node>/`** — that's quirk #3 (virtual layer). New volumes come from mount, not mkdir.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `code 300` returned from mount | host-only URL; server returned discovered shares | Pick one from the list, re-run mount with full `//host/share` path |\r\n| `authentication failed` | Wrong username / password | Check credentials; ensure the SMB server actually accepts them |\r\n| `host not reachable` | Network / DNS issue | Verify the host is on the same network; check `ping <host>` |\r\n| Mount succeeded but the entry isn't at `external/<node>/<expected-name>/` | Backend sanitized the name | `files ls external/<node>/` to find the actual entry name |\r\n| `name already mounted` | The same share was mounted before (possibly under a different node) | `files smb history list`, then `files smb unmount` the stale one |\n\nArchive v4.0.0: 14 files, 28182 bytes\n\nFiles: references/olares-files-chown.md (2974b), references/olares-files-cp-mv.md (4001b), references/olares-files-download.md (2595b), references/olares-files-edit.md (3556b), references/olares-files-ls.md (2560b), references/olares-files-mkdir.md (2987b), references/olares-files-rename.md (2265b), references/olares-files-rm.md (2995b), references/olares-files-share.md (6733b), references/olares-files-smb.md (5021b), references/olares-files-upload.md (6634b), skill-card.md (2905b), SKILL.md (13108b), _meta.json (131b)\n\nFile v4.0.0:SKILL.md\n\n---\r\nname: olares-files\r\nversion: 4.0.0\r\ndescription: \"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, cat, edit (open in $EDITOR), mkdir, rm, cp, mv, rename, chown (POSIX owner uid get/set), folder share (internal cross-Olares-ID, public link with password + expiration, SMB Samba), mount / unmount / favorite external SMB servers, and Sync (Seafile) repo CRUD — all against the per-Olares-ID files-backend on Olares (drive/Home, drive/Data, sync, cache, external, awss3, dropbox, google, tencent, share). Use when the user mentions Olares, Olares ID, Olares Files, olares-cli files, LarePass Files on Olares, drive, Home, Data, sync, cache, uploading / downloading / listing / editing remote files on Olares, in-place rename, POSIX file ownership, sharing a folder with another Olares user (by Olares ID), public link with password / expiration, SMB / Samba network shares, the LarePass 'Connect to Server' dialog, or Sync (Seafile) libraries.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli files --help\"\r\n---\r\n\r\n# files (per-user files-backend)\r\n\r\n**CRITICAL — before running any verb here, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, and 401/403 recovery rules.**\r\n\r\n> **Source of truth for flags & wire shapes is always `olares-cli files <verb> --help`.** This file only carries what `--help` cannot give: the cross-cutting frontend-path concept, the trailing-slash convention, the five client-side hard constraints, and the verb index.\r\n\r\n## Core concept: the 3-segment frontend path\r\n\r\nEvery resource on the per-user files-backend is addressed by:\r\n\r\n```\r\n<fileType>/<extend>[/<subPath>]\r\n```\r\n\r\n| Segment | Meaning |\r\n|---------|---------|\r\n| `fileType` | Storage class (lowercase, case-sensitive): `drive`, `cache`, `sync`, `external`, `awss3`, `dropbox`, `google`, `tencent`, `share`, `internal` |\r\n| `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 |\r\n| `subPath` | Path inside `extend` (root if omitted). Leading `/` is implicit |\r\n\r\nExamples: `drive/Home/`, `drive/Home/Documents/report.pdf`, `sync/<repo_id>/notes/`, `awss3/<account>/<bucket>/key.txt`.\r\n\r\n> Drive's `extend` MUST be `Home` or `Data` exactly — `home` is rejected with `invalid drive type`.\r\n\r\n### Per-verb namespace support\r\n\r\n| Verb | Supported namespaces |\r\n|------|----------------------|\r\n| `ls` / `cat` / `download` / `rm` / `rename` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `edit` | `drive`, `sync`, `cache`, `external` only (cloud / tencent / share / internal refused) |\r\n| `mkdir` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `cp` / `mv` | same as `mkdir` (PATCH `/api/paste/<node>/`) |\r\n| `upload` | `drive/Home`, `drive/Data`, `sync/<repo_id>`, `cache/<node>`, `external/<node>/<volume>`, `awss3`, `google`, `dropbox` — **`tencent` rejected** (different upload protocol) |\r\n| `chown` | `drive/Home`, `drive/Data`, `cache/<node>` only (cloud, sync, external all refused) |\r\n| `share internal` | `drive`, `sync`, `external`, `cache` (cloud refused) |\r\n| `share smb` | `drive`, `external`, `cache` (sync + cloud refused) |\r\n| `share public` | `drive` only |\r\n| `smb mount` / `unmount` / `history` | keyed by `<node>` + `<smb-url>`, not frontend paths |\r\n| `repos` | operates on the Sync (Seafile) library catalog, not frontend paths |\r\n\r\n## Trailing-slash convention (critical)\r\n\r\nWhether a path ends with `/` is meaningful:\r\n\r\n| Form | Meaning |\r\n|------|---------|\r\n| `drive/Home/Foo/` | Directory intent |\r\n| `drive/Home/Foo` | File intent |\r\n\r\nIt shows up here:\r\n\r\n- `files rm drive/Home/Foo/` requires `-r` — the trailing `/` declares \"this is a directory\".\r\n- `files upload <local> drive/Home/Documents/` → upload INTO Documents; `files upload <local> drive/Home/Documents/2026-Q1.pdf` → upload AS that exact path.\r\n- `files cp <src> <dst>/` and `files mv <src> <dst>/` — `<dst>` MUST end with `/` (drop-into-directory mode). Renaming via `cp`/`mv` is not supported; use `files rename` for in-place basename changes.\r\n- `files cp -r drive/Home/old/` (trailing `/` on a source) requires `-r`.\r\n- `files ls drive/Home/` lists the volume root; both `drive/Home` and `drive/Home/` are accepted but the slash is recommended.\r\n\r\n## Client-side hard constraints (5 quirks — never work around)\r\n\r\nThese five rules are enforced client-side and reflect real backend / GUI invariants. Teach yourself AND the user to respect them — do not suggest curl / API workarounds.\r\n\r\n### 1. POST `/api/resources/<dir>/` auto-renames on collision\r\n\r\nHitting the directory-create endpoint against an existing directory does NOT return 409 — it silently creates `<dir> (1)` instead. Therefore: `files upload` does NOT pre-create the destination directory; use `files mkdir [-p]` first if the parent doesn't exist yet.\r\n\r\n### 2. GET `/api/resources/<file>` (no trailing slash) returns HTTP 500\r\n\r\nThe backend's single-file `List` handler tries to slurp file bytes into a JSON envelope and chokes on most files. Workaround baked into the CLI: `Stat` always lists the PARENT directory and finds the leaf in the items array. If the user reports `HTTP 500` on a direct file resource path, the answer is \"use `files cat` / `files download`\", never \"retry the raw URL\".\r\n\r\n### 3. `external/<node>/` is a virtual volume-listing layer (read-only)\r\n\r\nThis level has no backing filesystem — it just enumerates attached volumes (`hdd1`, `usb1`, `smb-...`). Writes against it either fail server-side or trip quirk #1.\r\n\r\nCLI client-side guards: `mkdir`, `cp` destination, `mv` destination, `upload`, AND `share` (all flavors) reject `external/<node>/` (and one level deeper for `mkdir`). Errors point at the corrected shape `external/<node>/<volume>/<sub>/`. Pure reads (`ls`, `cat`, `rm`, `rename`) DO work — that's how the user discovers what volumes are attached. Mount new volumes via LarePass, not via files-backend mkdir.\r\n\r\n### 4. `drive/Home/{Pictures, Music, Movies, Downloads, Documents, Code, Cache, Data, Home, Ollama, Huggingface}` are system-managed\r\n\r\nThese eleven names under `drive/Home/` are LarePass bootstrap directories that user apps look up by exact name (e.g. the model-runtime app's `Ollama` cache, the LarePass UI's \"Pictures\" sidebar tile). The LarePass GUI greys out cut / copy / paste / delete / rename for them, and so does the CLI:\r\n\r\n- `rename`, `rm`, and `mv source` REFUSE these names at the **first level under `drive/Home/` only**.\r\n- `cp` (copy) is intentionally NOT gated — duplicating bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is fine.\r\n- Content nested inside (`drive/Home/Pictures/Trip2024/`) is fully editable.\r\n- Other namespaces (`drive/Data/Pictures`, `sync/<repo>/Pictures`, `external/...`) are unaffected.\r\n\r\nNote LarePass casing: `Huggingface` is one word (not `HuggingFace`). Names are case-sensitive.\r\n\r\n### 5. `cache/<node>/` is a node-picker for share-create only\r\n\r\n`cache/<node>/` IS a real per-node directory on the wire, so `ls` / `cp` / `mkdir` / `upload` / `rm` / `rename` work fine. BUT the share-create flavors (`share internal` / `share public` / `share smb`) reject the bare node root because a share record on the node-picker layer points at no concrete dataset. Use `cache/<node>/<sub>/` for shares; `files ls cache/<node>/` for discovery.\r\n\r\n## Authentication transport\r\n\r\nEvery files API call carries `X-Authorization: <access_token>` (NOT `Authorization: Bearer ...`). The transport auto-refreshes expired tokens transparently — reactive on 401/403 for replayable requests (every verb except `upload`), pro-active JWT-exp pre-flight for streaming `upload` chunks (because once an `*os.File` chunk is consumed it can't be replayed). Concurrent goroutines and processes serialize on a single `/api/refresh`.\r\n\r\n**On `*ErrTokenInvalidated` / `*ErrNotLoggedIn`, do not retry — only `profile login` / `profile import` will help.** See [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for the full recovery table.\r\n\r\n## Verb index\r\n\r\nFor flags, examples, and wire shapes, **always start with `olares-cli files <verb> --help`**. The references below add only what `--help` cannot give — agent-facing safety constraints, multi-step orchestrations, and common-error → fix maps.\r\n\r\n| Verb | `--help` first, then... | Notes |\r\n|---|---|---|\r\n| `ls` | [references/olares-files-ls.md](references/olares-files-ls.md) | Drive vs. cloud envelope shapes; `--json` semantics |\r\n| `cat` | `olares-cli files cat --help` | Trivial GET to stdout; binary-safe |\r\n| `download` | [references/olares-files-download.md](references/olares-files-download.md) | `--resume` / `--overwrite` semantics; directory parallel fetch |\r\n| `upload` | [references/olares-files-upload.md](references/olares-files-upload.md) | Two-stage cloud upload (stage 1 chunks → stage 2 server-side transfer task); `--parallel` semantics; tencent rejection |\r\n| `edit` | [references/olares-files-edit.md](references/olares-files-edit.md) | Editor cascade; three-tier size cap; text-only guard; concurrent-delete detection; cloud writeback gap |\r\n| `mkdir` | [references/olares-files-mkdir.md](references/olares-files-mkdir.md) | `-p` skips existing prefixes; auto-rename quirk on the leaf; `external/<node>/<X>/` depth-1 guard |\r\n| `rm` | [references/olares-files-rm.md](references/olares-files-rm.md) | Preflight existence check before prompt; trailing-slash signals dir; protected-names list |\r\n| `rename` | [references/olares-files-rename.md](references/olares-files-rename.md) | In-place only (synchronous PATCH); protected-names list; bare basename only |\r\n| `cp` / `mv` | [references/olares-files-cp-mv.md](references/olares-files-cp-mv.md) | Drop-into-dir semantics; `mv` source rejects protected names; preflight Stat of every src + dst dir |\r\n| `chown` | [references/olares-files-chown.md](references/olares-files-chown.md) | UID 0 / 1000 conventions; namespace allow-list; volume-root refusal |\r\n| `share` | [references/olares-files-share.md](references/olares-files-share.md) | Three flavors (internal / public / smb); directory-only; per-flavor namespace allow-list; update verbs (`set-members` / `set-password` / `set-smb`) |\r\n| `smb` | [references/olares-files-smb.md](references/olares-files-smb.md) | Mount → `external/<node>/<entry>/`; host-only address triggers share discovery; favorites history |\r\n| `repos` | `olares-cli files repos --help` | List / create / rename / rm Seafile libraries; repo_id is the `<extend>` segment |\r\n\r\n## Common errors (cross-verb)\r\n\r\n| Error fragment | Meaning | Fix |\r\n|---|---|---|\r\n| `is the volume listing layer (read-only); point at a real volume, e.g. external/<node>/<volume>/<sub>/` | Quirk #3 — bare `external/<node>/` write attempt | Add the `<volume>` segment |\r\n| `refusing to mkdir external/<node>/<X>/: depth-1 entries under external/<node>/ are mounted volumes` | Quirk #3 depth-1 — would create a phantom volume | Mount the volume via LarePass; target an existing one |\r\n| `refusing to {rename\\|delete\\|mv source} drive/Home/<name>: this is a system-managed Home folder` | Quirk #4 — protected name | Pick a different name, or operate on a nested path |\r\n| `refusing to share cache/<node>/: this is the node-picker layer (no concrete dataset to share)` | Quirk #5 — bare cache node-root share | Use `cache/<node>/<sub>/` |\r\n| `file disappeared between stat and fetch` | Concurrent-delete race on `edit` | Re-pull the parent directory and decide |\r\n| `tencent upload is not supported` (or similar) | Tencent's octet protocol is not implemented | Use the LarePass web app for tencent uploads |\r\n| `<src> does not exist on the server` (from `cp`/`mv`/`rm`) | Preflight Stat failed | `files ls` the parent and confirm the path |\r\n| `HTTP 500` from `/api/resources/<file>` | Quirk #2 — backend tried to embed file bytes | Use `files cat` / `files download` instead |\r\n\r\nFor auth-related errors (`server rejected the access token`, `refresh token for X became invalid`, …) see [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md).\r\n\r\n## Safety contract\r\n\r\n- **Write & delete verbs** (`rm`, `rename`, `cp`, `mv`, `chown --uid`, `share rm`, `repos rm`, `smb unmount`) — confirm intent with the user FIRST. Several verbs preflight against the server before any state change; do not bypass that by retry-on-404.\r\n- **`rm -f` skips the y/N prompt but NOT the preflight existence check** — a missing path still aborts.\r\n- **Never echo `access_token` / `refresh_token` to the terminal.** Use `--password-stdin` (where supported) for SMB passwords too.\r\n- **Confirm destination paths** before any `upload --overwrite`, `cp` to an existing file, or any operation that could clobber bytes.\n\nFile v4.0.0:_meta.json\n\n{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-files\",\n  \"version\": \"4.0.0\",\n  \"publishedAt\": 1780030420737\n}\n\nFile v4.0.0:references/olares-files-chown.md\n\n# files chown\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files chown --help`.\r\n\r\nGet or set the POSIX owner UID of a file / directory. CLI counterpart of the LarePass web app's \"Permission\" tab.\r\n\r\n## Modes\r\n\r\n| Form | What it does |\r\n|---|---|\r\n| `files chown <path>` | GET — print the current uid |\r\n| `files chown <path> --uid <int>` | PUT — replace the uid |\r\n| `files chown <path> --uid <int> -r` | PUT — recurse into children |\r\n\r\n## UID conventions (LarePass presets)\r\n\r\n| UID | Meaning |\r\n|---|---|\r\n| `0` | Root (system; only set this if you know why) |\r\n| `1000` | User (the default LarePass user; matches the GUI's \"User\" preset) |\r\n\r\nAny integer is accepted, but these are the values the GUI surfaces.\r\n\r\n## Supported namespaces (allow-list)\r\n\r\n`drive/Home/<sub>`, `drive/Data/<sub>`, `cache/<node>/<sub>` only.\r\n\r\nRefused namespaces:\r\n\r\n| Namespace | Why |\r\n|---|---|\r\n| `sync/<repo_id>/...` | Seafile permissions live on the library itself — use `files repos` |\r\n| `external/<node>/<volume>/...` | LarePass GUI hides the Permission tab for external mounts |\r\n| `awss3` / `dropbox` / `google` / `tencent` | Object stores have no POSIX uid concept |\r\n\r\n## Safety constraints\r\n\r\n- **Destructive when `--uid` is provided — confirm intent with the user.** UID changes affect every app that reads the directory.\r\n- **Volume roots are refused** (`drive/Home/`, `drive/Data/`, `cache/<node>/`) — chowning an entire namespace root has too much blast radius. Pick a one-level-deeper path with `-r` if you need to fan out.\r\n- **`-r` recurses** — every descendant gets the new uid. Confirm directory contents with `files ls` first.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Inspect.\r\nolares-cli files chown drive/Home/Documents/foo.pdf\r\n\r\n# Hand a file to root.\r\nolares-cli files chown drive/Home/Documents/foo.pdf --uid 0\r\n\r\n# Hand an entire directory tree to the default user.\r\nolares-cli files chown drive/Home/Pictures/Trip2024/ --uid 1000 -r\r\n\r\n# Cache namespace.\r\nolares-cli files chown cache/<node>/scratch/build/ --uid 1000 -r\r\n```\r\n\r\n## Agent notes\r\n\r\n- The GET form is cheap — use it before any PUT to show the user the current uid and confirm the change.\r\n- Inside `-r`, partial-failure behavior is per-server — on an error the server may have already changed some descendants. **Do NOT retry blindly**; re-run the GET form on a few sampled paths to see what landed.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `chown is not supported for this namespace` | sync / external / cloud target | Use `files repos` (sync) or LarePass GUI (external) |\r\n| `refusing to chown a volume root` | `drive/Home/` / `drive/Data/` / `cache/<node>/` | Pick a sub-path |\r\n| 403 from server | Server-side ACL rejection | Confirm via `files ls -ld` (when available) or LarePass that the active user has permission |\n\nFile v4.0.0:references/olares-files-cp-mv.md\n\n# files cp / files mv\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files cp --help` and `olares-cli files mv --help`.\r\n\r\nCopy / move one or more entries between locations. Same wire endpoint (`PATCH /api/paste/<node>/`), different `action`. Cross-volume (drive ↔ sync ↔ external) is supported.\r\n\r\n## Safety constraints\r\n\r\n- **Destructive (mutates the server) — confirm intent with the user.** `mv` even more so since the source is removed.\r\n- **`<dst> MUST end with `/` (drop-into-directory mode).** Each `<src>`'s basename is appended; preserves the dir / file marker.\r\n- **Renaming via `cp` / `mv` is not supported** — use `files rename` for in-place basename changes, or rename first and then `mv`.\r\n- **Directory sources require `-r`** (Unix-style refusal otherwise).\r\n- **`mv` source rejects protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)): `mv drive/Home/Pictures/ ...` is refused because moving would unlink a dir that apps depend on. **`cp` (copy) is intentionally NOT gated** — duplicating bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is fine.\r\n- **`external/<node>/` destinations are rejected** ([quirk #3](../SKILL.md#3-externalnode-is-a-virtual-volume-listing-layer-read-only)) — point at `external/<node>/<volume>/<sub>/`.\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file → directory.\r\nolares-cli files cp drive/Home/notes.md drive/Home/Documents/\r\n\r\n# Recursive directory copy.\r\nolares-cli files cp -r drive/Home/Photos/ drive/Home/Backups/\r\n\r\n# Multiple sources into a directory.\r\nolares-cli files cp drive/Home/a.pdf drive/Home/b.pdf drive/Home/Archive/\r\n\r\n# Cross-volume (drive → sync repo).\r\nolares-cli files cp drive/Home/notes.md sync/<repo_id>/inbox/\r\n\r\n# Move (mv replaces cp where source removal is intended).\r\nolares-cli files mv drive/Home/notes.md drive/Home/Archive/\r\nolares-cli files mv -r drive/Home/Photos/ drive/Home/Backups/\r\n```\r\n\r\n## Preflight existence check\r\n\r\nRuns BEFORE any PATCH is sent:\r\n\r\n- Each `<src>` MUST exist on the server, AND its trailing-slash form must match the actual file/dir kind.\r\n- `<dst>` MUST exist as a directory on the server. **Create it first with `files mkdir -p` if needed** — `cp`/`mv` does NOT auto-create the destination (the auto-rename quirk #1 would land you in `<dst> (1)`).\r\n\r\nA typo on either side aborts before the server's task queue sees it.\r\n\r\n## Node selection (`--node`)\r\n\r\nEach PATCH carries a `{node}` URL segment. Default cascade:\r\n\r\n1. `--node` override (per invocation)\r\n2. External / Cache `<extend>` (when the destination is `external/<node>/` or `cache/<node>/` — the GUI's `dst_node || src_node || default` cascade)\r\n3. First entry from `/api/nodes/`\r\n\r\nUse `--node` only when you have a specific multi-node deployment with a non-default node hosting the paste task.\r\n\r\n## Agent notes\r\n\r\n- **For renames, ALWAYS use `files rename`** — it's synchronous (no task queue) and works in place. Don't try to fake a rename with `cp`/`mv`.\r\n- **`mv` is async** — the response is \"task accepted\", not \"move completed\". For most paths this is fast, but on huge directory trees consider running an `ls` afterward to confirm.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<dst> must end with /` | Drop-into-dir mode requires trailing slash | Add `/` |\r\n| `<dst> does not exist on the server` | Destination dir not pre-created | `files mkdir -p <dst>` first |\r\n| `<src> is a directory; pass -r` | Directory source without recursion | Add `-r` |\r\n| `refusing to mv drive/Home/<protected-name>` | Quirk #4 mv-source guard | Use `cp -r` (preserves the original) if the goal is duplication |\r\n| `is the volume listing layer (read-only)` | `external/<node>/` destination | Add `<volume>` segment |\n\nFile v4.0.0:references/olares-files-download.md\n\n# files download\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files download --help`.\r\n\r\nDownload a file or directory tree from the per-user files-backend.\r\n\r\n## Safety constraints\r\n\r\n- **Without `--resume` or `--overwrite`, the command refuses to clobber an existing local file** — confirm intent with the user before suggesting `--overwrite`.\r\n- `--overwrite` writes to `<dst>.tmp` then renames, so the previous version stays intact until the new bytes land — safe to suggest after the user confirms.\r\n- Directory mode mirrors the remote tree under the local destination; the remote root's own basename becomes the top-level directory (matches the LarePass folder-download UX).\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file into the current directory.\r\nolares-cli files download drive/Home/Documents/report.pdf\r\n\r\n# Same, but pick a different local name.\r\nolares-cli files download drive/Home/Documents/report.pdf ./Q1.pdf\r\n\r\n# Resume an interrupted download (server-driven Range; O_APPEND on the local file).\r\nolares-cli files download drive/Home/Backups/big.tar ./big.tar --resume\r\n\r\n# Recursively pull a folder, 4 files at a time (default).\r\nolares-cli files download drive/Home/Documents/ ./out/ --parallel 4\r\n```\r\n\r\n## Agent notes\r\n\r\n- **`Stat` always lists the parent directory** and finds the leaf in the items array — this is a workaround for [quirk #2](../SKILL.md#2-get-apiresourcesfile-no-trailing-slash-returns-http-500). You never need to suggest \"just GET the file URL\"; the CLI already handles it.\r\n- **Single-file resume** uses server-driven `Range: bytes=<localSize>-` — there is no sidecar progress file. A Ctrl-C + re-run keeps making forward progress as long as the local file is preserved.\r\n- **Directory downloads parallelize FILES, not chunks** — each file's bytes still stream sequentially. `--parallel N` bounds concurrent file fetches.\r\n- Empty subdirectories are mirrored locally so the tree matches even when a directory has no files.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<dst> exists; pass --overwrite or --resume` | Local target already on disk | Confirm with user, then `--overwrite` (replace) or `--resume` (continue) |\r\n| `HTTP 500` from a raw resource URL | Quirk #2 — the bare file URL embeds bytes in JSON | Use this verb (which Stats via parent), not a manual `curl` |\r\n| 401/403 | Token rotation or invalidation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.0:references/olares-files-edit.md\n\n# files edit\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files edit --help`.\r\n\r\nEdit a single existing file in-place by opening it in `$EDITOR`. **UPDATE-only verb** — the server-side `PUT /api/resources/<encPath>` handler is wired as \"replace bytes of an existing file\"; it does NOT create new files. To materialize a new file, use `files upload` from a local source.\r\n\r\n## Safety constraints\r\n\r\n- **Interactive TTY required.** `edit` spawns `$EDITOR` foreground; CI / pipes / heredocs are refused cleanly with a `download` + `upload` recovery hint.\r\n- **Three-tier size cap** (default 1 MiB, configurable via `--max-size`): pre-fetch Stat, during-fetch `io.LimitReader` defense, post-edit local file size.\r\n- **Text-only guard by default**: an extension deny-list (jpg/png/gif/heic/pdf/docx/mp4/mp3/zip/tar.gz/exe/so/sqlite/ttf/...) plus a NUL-byte sniff over the first 8 KiB. Pass `--allow-binary` to disable both.\r\n- **No ETag / If-Match support on the wire** — concurrent edits from two clients follow last-writer-wins. Same as the LarePass GUI.\r\n\r\n## Supported namespaces\r\n\r\n`drive/Home/<sub>/<file>`, `drive/Data/<sub>/<file>`, `sync/<repo_id>/<sub>/<file>`, `cache/<node>/<sub>/<file>`, `external/<node>/<volume>/<sub>/<file>`.\r\n\r\n**Cloud drives (awss3 / google / dropbox / tencent) are refused** — the PUT writeback shape is not wire-verified per cloud driver. Use this workflow instead:\r\n\r\n```bash\r\nolares-cli files download <cloud-path> <local>\r\n$EDITOR <local>\r\nolares-cli files upload <local> <cloud-path>\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files edit drive/Home/Documents/notes.md\r\nolares-cli files edit drive/Home/.config/app.yaml --editor nano\r\nolares-cli files edit sync/<repo_id>/Notes/draft.md\r\nolares-cli files edit drive/Home/Logs/today.log --max-size 5242880  # 5 MiB\r\nolares-cli files edit external/<node>/usb1/config.json --keep-temp\r\n```\r\n\r\n## Editor cascade\r\n\r\nMatches `git commit` / `crontab -e`:\r\n\r\n```\r\n--editor flag  →  $VISUAL  →  $EDITOR  →  vi (POSIX) / notepad (Windows)\r\n```\r\n\r\nThe binary is resolved up-front BEFORE the CLI dials the server. A missing / mistyped editor fails fast without pulling the remote file.\r\n\r\n## Agent notes\r\n\r\n- If the user exits the editor without changes, **no PUT is issued** (byte-for-byte comparison; robust against editors that always rewrite). No warning needed.\r\n- **Concurrent-delete detection**: if Stat says the file exists but the subsequent GET returns 404, the verb refuses with `file disappeared between stat and fetch`. Do NOT retry — re-pull the parent and ask the user what to do.\r\n- `--keep-temp` is the right escape hatch when an unexpected size-cap rejection or NUL-byte sniff blocks the writeback — point the user at the temp path so they can recover bytes manually.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `stdin/stdout is not a terminal` | Non-TTY context | Use `download` + local edit + `upload` |\r\n| `file too large: <N> bytes > max-size <M>` | Three-tier cap pre-fetch | Pass `--max-size 0` (unbounded) or `--max-size <bigger>` |\r\n| `binary content detected (extension/NUL)` | Text-only guard | Confirm intent, then `--allow-binary` |\r\n| `file disappeared between stat and fetch` | Someone else deleted the file between probes | Re-pull parent, ask user |\r\n| `cloud drive edit is not supported` | awss3/google/dropbox/tencent target | Use download → edit → upload workflow |\n\nFile v4.0.0:references/olares-files-ls.md\n\n# files ls\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first for the profile model, the 3-segment frontend path, and the 5 client-side quirks.\r\n> **Flags & wire shape:** `olares-cli files ls --help` (single source of truth).\r\n\r\nList a directory on the per-user files-backend. Uniform across all 10 namespaces.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files ls drive/Home/\r\nolares-cli files ls drive/Home/Documents\r\nolares-cli files ls sync/<repo_id>/\r\nolares-cli files ls awss3/<account>/<bucket>\r\nolares-cli files ls cache/<node>/\r\nolares-cli files ls external/<node>/           # virtual volume-listing layer (see SKILL.md quirk #3)\r\nolares-cli files ls drive/Home/Documents --json  # raw envelope, pretty-printed\r\n```\r\n\r\n## Output shape\r\n\r\nDefault table: `MODE  SIZE  TYPE  MODIFIED  NAME`. Directories sort before files; directory names get a trailing `/`. Empty directories print `(empty)`.\r\n\r\n`--json` prints the raw JSON envelope, useful for scripting.\r\n\r\n## Envelope shapes (transparent to the user, matters when reading `--json`)\r\n\r\n| Namespace | Children field | Per-item size | `mode` / `modified` |\r\n|---|---|---|---|\r\n| `drive` / `sync` / `cache` / `external` / `share` | `items` | `size` (number) | numeric `mode`, RFC3339 `modified` |\r\n| `awss3` / `google` / `dropbox` / `tencent` | `data` | `fileSize` | empty strings; the table renders `d---------` / `----------` and `-` in MODE / MODIFIED |\r\n\r\nThe cloud envelope ALSO omits the parent-level `numDirs` / `numFiles` / `modified` summary; the table header falls back to counting items so it stays informative.\r\n\r\n## Agent notes\r\n\r\n- `ls` is the canonical discovery verb. Use it before any write to confirm parent existence and the exact basename casing.\r\n- `ls external/<node>/` is the right way to discover attached volumes (`hdd1`, `usb1`, `smb-...`) before targeting `external/<node>/<volume>/<sub>/`.\r\n- `ls cache/<node>/` is the right way to discover what's under a node before sharing — share-create rejects bare `cache/<node>/`.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `invalid drive type` | `drive/home/...` instead of `drive/Home/...` | Use exact casing: `Home` or `Data` |\r\n| Empty `items` / `data` array on a known-non-empty dir | Wrong identity — the active profile can't see this scope | `olares-cli profile list` and switch with `profile use` |\r\n| 401/403 | Token rotation or invalidation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.0:references/olares-files-mkdir.md\n\n# files mkdir\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files mkdir --help`.\r\n\r\nCreate a directory on the per-user files-backend. Uniform across all namespaces (`POST /api/resources/<path>/`).\r\n\r\n## Critical caveat: auto-rename quirk\r\n\r\n`POST /api/resources/<dir>/` against an existing directory does NOT return 409 — the server silently creates `<dir> (1)` instead (see [quirk #1](../SKILL.md#1-post-apiresourcesdir-auto-renames-on-collision)).\r\n\r\n- `-p` mode side-steps this for parents (it lists each prefix's parent and skips when the basename already exists).\r\n- For the LEAF, the CLI prints a hint after the call so the user can `files ls` and confirm.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files mkdir drive/Home/Documents/Backups\r\nolares-cli files mkdir -p drive/Home/A/B/C/\r\nolares-cli files mkdir -p sync/<repo_id>/notes/2026/Q2\r\nolares-cli files mkdir -p awss3/<account>/Backups/2026\r\nolares-cli files mkdir -p google/<account>/Drafts\r\n```\r\n\r\n## Refusals (client-side)\r\n\r\n- **Volume roots** (`drive/Home/`, `drive/Data/`, `sync/<repo_id>/`, etc.) — they always exist, so the call would be a no-op or trip the auto-rename quirk on the extend folder.\r\n- **`.` or `..` segments ANYWHERE in the path** — path-traversal blacklist on raw input (before normalization), so `drive/Home/foo/../bar` errors out instead of being rewritten to `drive/Home/bar`.\r\n- **`external/<node>/` (quirk #3 bare root)** — virtual layer with no backing filesystem.\r\n- **`external/<node>/<single-segment>/` (depth-1 under external)** — depth-1 entries ARE the mounted volumes (USB-0, SMB-..., per-disk mount-points). Creating a new depth-1 entry would land as a phantom volume or collide with an existing mount. **Mount new volumes via LarePass first.** `-p` mode also refuses to auto-create a missing depth-1 intermediate.\r\n\r\n## Agent notes\r\n\r\n- **Always `-p` when chaining mkdir → upload.** `upload` does NOT pre-create directories (because of quirk #1), so the cheapest pattern is `mkdir -p <dest-dir> && upload <local> <dest-dir>`.\r\n- After a non-`-p` mkdir, if the user wonders why they see `Foo (1)`, the answer is \"you ran mkdir on an existing dir\". The fix: `files rm -r drive/Home/Foo (1)/` (the bytes inside `Foo` are untouched).\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `Foo (1)` appeared instead of `Foo` | Auto-rename quirk on existing leaf | Delete the dup; in future check with `files ls` first or use `-p` |\r\n| `refusing to mkdir external/<node>/<X>/: depth-1 entries under external/<node>/ are mounted volumes` | Quirk #3 depth-1 guard | Mount the volume via LarePass; target an existing volume's sub-path |\r\n| `is the volume listing layer (read-only)` | Quirk #3 bare-root | Add the `<volume>` segment |\r\n| `path contains . or .. segments` | Path-traversal blacklist | Use absolute / canonical paths only |\n\nFile v4.0.0:references/olares-files-rename.md\n\n# files rename\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files rename --help`.\r\n\r\nRename a remote entry in place — same parent directory, new basename. Synchronous PATCH (no `task_id` polling).\r\n\r\n## When to use this vs. `mv`\r\n\r\n| Want to ... | Use |\r\n|---|---|\r\n| Change basename, same parent | `rename` (synchronous, no node) |\r\n| Move to a different directory or volume | `mv` (async via paste queue) |\r\n\r\n## Safety constraints\r\n\r\n- **Destructive (mutates the server) — confirm intent with the user.**\r\n- **`<new-name>` is a BARE basename** — no `/` or `\\`. Empty, `.`, `..` are rejected.\r\n- **Protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)) refuse to be renamed at the first level under `drive/Home/`; deeper paths are fine.\r\n- **Volume roots** (`drive/Home/`, `sync/<repo>/`, ...) are refused.\r\n- **`.` or `..` segments ANYWHERE in `<remote-path>`** are rejected (path-traversal blacklist on raw input).\r\n\r\n## Examples\r\n\r\n```bash\r\n# Rename a file.\r\nolares-cli files rename drive/Home/Documents/foo.pdf foo-final.pdf\r\n\r\n# Rename a directory (trailing slash on source signals dir).\r\nolares-cli files rename drive/Home/Documents/old/ new-name\r\n\r\n# Sync repo.\r\nolares-cli files rename sync/<repo_id>/notes/draft.md final.md\r\n```\r\n\r\n## Agent notes\r\n\r\n- Trailing slash on `<remote-path>` is preserved on the wire so the backend routes through its directory handler — keep it when the source is a dir.\r\n- `rename` cannot be combined with a `mv`-style drop-into-directory; if the user wants both (move + rename), do `rename` first, then `mv` the result.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `refusing to rename drive/Home/<protected-name>` | Quirk #4 | Pick a different target |\r\n| `new name must be a bare basename` | `<new-name>` contains `/` | Drop the slash; use `mv` if you actually want to move |\r\n| `new name is empty` / `cannot rename to . or ..` | Invalid basename | Provide a real name |\r\n| `path contains . or .. segments` | Path-traversal blacklist | Use a clean path |\n\nFile v4.0.0:references/olares-files-rm.md\n\n# files rm\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files rm --help`.\r\n\r\nDelete one or more files / directories. Batch-aware: multiple targets that share a parent collapse into one DELETE request.\r\n\r\n## Safety constraints\r\n\r\n- **Destructive verb — confirm intent with the user before invocation.** The CLI prompts y/N by default; `-f` skips the prompt.\r\n- **`-f` does NOT bypass the preflight existence check** — a missing path still aborts (safer-than-Unix `rm -f`). This means a typo cannot half-delete a batch.\r\n- **Trailing slash signals directory intent.** Without `-r`, `rm drive/Home/Foo/` errors with \"<Foo> is a folder, pass -r\". Without trailing slash AND target is actually a dir, errors with the same CTA.\r\n- **Protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)) are refused at the first level under `drive/Home/` only; deeper paths (`drive/Home/Pictures/Trip2024/`) are fully deletable.\r\n\r\n## Examples\r\n\r\n```bash\r\nolares-cli files rm drive/Home/Documents/old.pdf\r\nolares-cli files rm -r drive/Home/Backups/2024\r\nolares-cli files rm -r drive/Home/Backups/2024/\r\nolares-cli files rm -rf drive/Home/junk drive/Home/scratch/\r\n\r\n# Batch: two siblings + one cross-parent — collapses into 2 DELETE calls.\r\nolares-cli files rm drive/Home/a.pdf drive/Home/b.pdf sync/<repo>/old.md\r\n```\r\n\r\n## Preflight existence check\r\n\r\nRuns BEFORE the confirmation prompt. Aborts (with no \"will delete N entries\" line printed) if:\r\n\r\n- A target path doesn't exist on the server (typo / stale path)\r\n- The user typed `<target>/` or passed `--recursive`, but the entry is actually a FILE\r\n- The user typed `<target>` (no slash) without `--recursive`, but the entry is actually a DIRECTORY\r\n\r\nVolume roots are rejected upstream by the planner; the preflight only sees real entries.\r\n\r\n## Agent notes\r\n\r\n- **Always `files ls` the parent before suggesting `rm`** so the user sees what's actually there. Confirms the basename and gives them a chance to abort.\r\n- **Mixing files and folders in one `-r` invocation is unusual.** If a target list has both, split into two `rm` calls (one with `-r`, one without).\r\n- The CLI sorts requests by `fileType + extend + parent` — output ordering is stable and useful in scripts.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<path> is a folder; pass -r` | Trailing `/` or actual-dir without `-r` | Add `-r` |\r\n| `<path> is a file; remove the trailing slash` | Wrong intent | Drop the `/` |\r\n| `refusing to delete drive/Home/<protected-name>` | Quirk #4 | Pick a different target, or operate on a nested path |\r\n| `<path> does not exist on the server` | Stale / typo | `files ls` the parent first |\r\n| 401/403 | Token rotation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |\n\nFile v4.0.0:references/olares-files-share.md\n\n# files share\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli files share --help` (parent), `olares-cli files share <flavor> --help` (each leaf), `olares-cli files share set-members --help`, …\r\n\r\nCreate and manage shares for directories. **All three flavors are directory-only** — the CLI Stats the target before posting and refuses files / non-existent paths up front. To share a single file, place it in a dedicated directory and share that.\r\n\r\n## Three flavors at a glance\r\n\r\n| Flavor | Audience | Recipient model | Update verb |\r\n|---|---|---|---|\r\n| `internal` | Other Olares users on the same node | Olares user names → per-user permission | `set-members` |\r\n| `public` | Anyone with the link + password | Opaque link, recipients open it at `<host>/sharable-link/<id>/` | `set-password` |\r\n| `smb` | Local network (Finder / Explorer / etc.) | SMB-account IDs (managed via `smb-users`), or `--public` for \"anyone on the LAN\" | `set-smb` |\r\n\r\n## Per-flavor namespace allow-list\r\n\r\n| Flavor | Allowed | Notes |\r\n|---|---|---|\r\n| `internal` | `drive`, `sync`, `external`, `cache` | Cloud rejected — cross-cloud-account share doesn't work. `external/<node>/` bare-root and `cache/<node>/` bare-root rejected (quirks #3, #5) — point at a sub-path |\r\n| `smb` | `drive`, `external`, `cache` | Sync rejected (Seafile has its own mount story, not Samba) + cloud rejected. Same bare-root guards as internal |\r\n| `public` | `drive` ONLY | Tightest of the three. Sync / external / cache / cloud all refused (the GUI restricts Public to drive only). Error messages route the user to `share internal` (sync) or `share internal` / `share smb` (external / cache) |\r\n\r\n## `--users` format (internal / smb / set-members)\r\n\r\n```\r\nname1:perm1,name2:perm2,name3   (perm defaults to \"view\" if omitted)\r\n```\r\n\r\nPermissions: `view` / `upload` / `edit` / `admin` (or `0..4`). Empty perm falls back to `view`.\r\n\r\nFor SMB shares, \"name\" is an **SMB-account ID** (see `share smb-users list`), not an Olares user name.\r\n\r\n## Wire shape (all create flavors)\r\n\r\n```\r\nPOST /api/share/share_path/<fileType>/<extend><subPath>/\r\nbody: {name, share_type, permission, password, ...}\r\n```\r\n\r\nResponse carries the new `share id`, plus per-flavor extras (`smb_link` / `smb_user` / `smb_password` for SMB; the Public-link URL is constructed by the LarePass app's `shareBaseUrl + /sharable-link/<id>/` pattern).\r\n\r\nManagement verbs (`list` / `get` / `rm`) take the share id and are share-type-agnostic.\r\n\r\n## Update verbs (REPLACES, not appends)\r\n\r\n| Verb | What it changes | Important semantic |\r\n|---|---|---|\r\n| `set-password` | Public-link password | One field; rejects non-Public shares up front |\r\n| `set-members` | Internal share member list | **Drops every member not listed in `--users`.** Pass `--clear` to drop them all. Wire has no \"add member\"; for additive updates, list every existing member + the new one |\r\n| `set-smb` | SMB account list OR public-SMB toggle | Same replace semantics as `set-members`. `--public` flips to \"anyone on the LAN\" mode |\r\n\r\n## Examples\r\n\r\n```bash\r\n# Internal share, two members (alice can edit, bob can view).\r\nolares-cli files share internal drive/Home/Backups/ \\\r\n    --users alice:edit,bob:view\r\n\r\n# Public link valid 7 days, password auto-generated and printed.\r\nolares-cli files share public drive/Home/Photos/ --expire-days 7\r\n\r\n# Public upload-only inbox with explicit password + size cap.\r\nolares-cli files share public drive/Home/Inbox/ --upload-only \\\r\n    --password drop --expire-days 30 --upload-size-limit 100M\r\n\r\n# SMB share for two SMB users.\r\nolares-cli files share smb drive/Home/Movies/ \\\r\n    --users smb-uid-1:edit,smb-uid-2:edit\r\n\r\n# Roll a Public link's password.\r\nolares-cli files share set-password <share-id>\r\n\r\n# Promote bob from view to admin on an Internal share (carry alice through unchanged!).\r\nolares-cli files share set-members <share-id> \\\r\n    --users alice:edit,bob:admin\r\n\r\n# Drop every member (share stays, becomes private to its owner).\r\nolares-cli files share set-members <share-id> --clear\r\n\r\n# Switch an SMB share to public-SMB.\r\nolares-cli files share set-smb <share-id> --public\r\n\r\n# List, inspect, remove.\r\nolares-cli files share list --shared-by-me\r\nolares-cli files share get <share-id>\r\nolares-cli files share rm <share-id>\r\n```\r\n\r\n## Public-link specifics\r\n\r\n- **Password is required** — either pass `--password <pw>` or let the CLI auto-generate an 8-char random password (which it then prints).\r\n- **Expiration is required** — pass exactly one of `--expire-days N` or `--expire-time <RFC3339>`. Public links without an expiration are not supported by the backend.\r\n- **`--upload-only`** locks recipients out of listing / download — they can only drop files in.\r\n- **`--upload-size-limit`** accepts human-readable sizes: `100M`, `1G`, `500K`, `512` (raw bytes). `0` / omitted = no per-upload cap.\r\n\r\n## Agent flows\r\n\r\n### Create a Public share and reply with the URL\r\n\r\n```bash\r\nSHARE_ID=$(olares-cli files share public drive/Home/Photos/ --expire-days 7 --password \"$PW\" --json | jq -r '.id')\r\necho \"Share link: https://<your-host>/sharable-link/$SHARE_ID/\"\r\n```\r\n\r\nThe `<your-host>` part is read from the LarePass app's `shareBaseUrl`; if the user doesn't already know it, point them at LarePass settings.\r\n\r\n### Add a member to an existing Internal share without dropping existing ones\r\n\r\n```bash\r\n# First fetch the current members.\r\nCURRENT=$(olares-cli files share get <share-id> --json | jq -r '.share_members | map(.share_member + \":\" + .permission_label) | join(\",\")')\r\n# Then re-list them PLUS the new member.\r\nolares-cli files share set-members <share-id> --users \"$CURRENT,carol:view\"\r\n```\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `share target is a file, not a directory` | Tried to share a single file | Wrap it in a directory and share that |\r\n| `Public only supports the drive namespace` | `share public sync/...` / `share public external/...` / etc. | Use `share internal` instead (or `share smb` for external/cache) |\r\n| `cloud namespaces are not supported` | `share <flavor> awss3/...` etc. | Move the data into drive first, then share |\r\n| `refusing to share external/<node>/` | Quirk #3 bare root | Point at `external/<node>/<volume>/<sub>/` |\r\n| `refusing to share cache/<node>/` | Quirk #5 node-picker layer | Point at `cache/<node>/<sub>/` |\r\n| `--users and --clear are mutually exclusive` | Both passed to `set-members` | Pick one |\r\n| `share-type mismatch` (e.g. set-password on Internal) | Wrong update verb for the flavor | Use the matching verb from the flavor table |\n\nFile v4.0.0:references/olares-files-smb.md\n\n# files smb\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shapes:** `olares-cli files smb --help` (parent), `olares-cli files smb mount --help`, `olares-cli files smb history --help`.\r\n\r\nMount **external** SMB shares into the per-user files-backend's `external/<node>/...` namespace. CLI counterpart of the LarePass \"Connect to Server\" modal.\r\n\r\n> **Don't confuse with `files share smb`** — that creates an OUTBOUND share (expose a directory over Samba). `files smb` consumes INBOUND shares (mount a network share into Olares).\r\n\r\n## Sub-commands\r\n\r\n| Sub-command | Purpose |\r\n|---|---|\r\n| `smb mount <smb-url>` | Mount a remote SMB share, materializes at `external/<node>/<entry>/` |\r\n| `smb unmount <name>` | Unmount a previously-mounted entry |\r\n| `smb history list` | List the per-node \"Favorite Servers\" |\r\n| `smb history add <smb-url>` | Stash a favorite for later (optional credentials) |\r\n| `smb history rm <smb-url>...` | Drop favorites by URL |\r\n\r\n## Safety constraints\r\n\r\n- **Mount / unmount mutate the per-node state — confirm intent with the user.**\r\n- **Credentials in `-p / --password` end up in shell history.** For scripts use `--password-stdin`; for interactive use, omit both and the CLI prompts without echo.\r\n- **History entries can carry credentials.** Treat them as sensitive — adding credentials to history is convenient but the entries are stored server-side.\r\n\r\n## Mount flow with host-only address (discovery)\r\n\r\n```\r\nPOST /api/mount/[<node>/]?external_type=smb\r\nbody: {smbPath, user, password}\r\nreply:\r\n  code 200 → mounted; visible at external/<node>/<entry>/\r\n  code 300 → smbPath was host-only; data is the list of discovered shares\r\n```\r\n\r\nWhen the user passes a HOST-only URL (e.g. `//host.local`), the server returns `code 300` with the discovered shares. The CLI prints the list and asks the user to re-run with one of them:\r\n\r\n```bash\r\n# Step 1 — host-only triggers discovery.\r\nolares-cli files smb mount //host.local\r\n# → server returned 3 shares: //host.local/Public, //host.local/Movies, //host.local/Backups\r\n\r\n# Step 2 — re-run with the chosen share path.\r\nolares-cli files smb mount //host.local/Public -u alice -p s3cret\r\n```\r\n\r\n## Examples\r\n\r\n```bash\r\n# Mount with credentials.\r\nolares-cli files smb mount //host.local/Public -u alice -p s3cret\r\n\r\n# Mount via stdin password (script-friendly).\r\nprintf '%s' \"$SMB_PASSWORD\" | olares-cli files smb mount //host.local/Public -u alice --password-stdin\r\n\r\n# Stash a favorite (credentials optional; prompted at mount time if omitted).\r\nolares-cli files smb history add //host.local/Public\r\n\r\n# List favorites for the current node.\r\nolares-cli files smb history list\r\n\r\n# Inspect the mounted entries (every external mount is just a child of external/<node>/).\r\nolares-cli files ls external/<node>/\r\n\r\n# Unmount when done.\r\nolares-cli files smb unmount <entry-name>\r\n\r\n# Remove a favorite by URL.\r\nolares-cli files smb history rm //host.local/Public\r\n```\r\n\r\n## Wire shape\r\n\r\n```\r\nPOST   /api/mount/[<node>/]?external_type=smb            (mount)\r\nPOST   /api/unmount/external/<node>/<name>/?external_type=smb  (unmount)\r\nGET    /api/smb_history/<node>/                          (history list)\r\nPUT    /api/smb_history/<node>/  body: array             (history upsert)\r\nDELETE /api/smb_history/<node>/  body: array of {url}    (history rm)\r\n```\r\n\r\n## Agent notes\r\n\r\n- **After a successful mount, the entry lives at `external/<node>/<entry>/`** and is consumed by every other `files` verb the same way as any other namespace. Use `files ls external/<node>/` to confirm the new entry name (it's usually a sanitized version of the SMB path).\r\n- **`--node` is rarely needed** — defaults to the active node from `/api/nodes/`. Pass it explicitly only if the user has a multi-node Olares and wants the mount to land on a specific node.\r\n- **Mount failures with code 300 are not errors** — they're discovery responses. Surface the share list to the user verbatim and ask them which one to mount.\r\n- **Don't try to mkdir under `external/<node>/`** — that's quirk #3 (virtual layer). New volumes come from mount, not mkdir.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `code 300` returned from mount | host-only URL; server returned discovered shares | Pick one from the list, re-run mount with full `//host/share` path |\r\n| `authentication failed` | Wrong username / password | Check credentials; ensure the SMB server actually accepts them |\r\n| `host not reachable` | Network / DNS issue | Verify the host is on the same network; check `ping <host>` |\r\n| Mount succeeded but the entry isn't at `external/<node>/<expected-name>/` | Backend sanitized the name | `files ls external/<node>/` to find the actual entry name |\r\n| `name already mounted` | The same share was mounted before (possibly under a different node) | `files smb history list`, then `files smb unmount` the stale one |\n\nArchive v1.19.0: 3 files, 62841 bytes\n\nFiles: skill-card.md (2490b), SKILL.md (178593b), _meta.json (132b)\n\nFile v1.19.0:SKILL.md\n\n---\r\nname: olares-files\r\nversion: 1.19.0\r\ndescription: \"Manage files on an Olares system from the command line via olares-cli files. Covers list (ls), upload, download, cat, edit (open in $EDITOR), mkdir, rm, cp, mv, rename, chown (POSIX owner uid get/set), folder share (internal cross-user, public link with password + expiration, SMB Samba), mount / unmount / favorite external SMB servers, and Sync (Seafile) repo CRUD — all against the per-user Olares files-backend (drive/Home, drive/Data, sync, cache, external, awss3, dropbox, google, tencent, share). Use when the user mentions Olares files, olares-cli files, LarePass Files, drive, Home, Data, sync, cache, uploading / downloading / listing / editing remote files, in-place rename, POSIX file ownership, sharing a folder with other Olares users, public link with password / expiration, SMB / Samba network shares, the LarePass 'Connect to Server' dialog, or Sync (Seafile) libraries.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli files --help\"\r\n---\r\n\r\n# files (Drive v2 + per-user files-backend)\r\n\r\n**CRITICAL — before doing anything, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for the profile selection, login, and HTTP 401/403 recovery rules that every command here depends on.**\r\n\r\n## Core concept: the 3-segment frontend path\r\n\r\nEvery resource on the per-user files-backend is addressed by a 3-segment \"frontend path\" (see [`cli/cmd/ctl/files/path.go`](cli/cmd/ctl/files/path.go)):\r\n\r\n```\r\n<fileType>/<extend>[/<subPath>]\r\n```\r\n\r\n| Segment | Meaning |\r\n|---------|---------|\r\n| `fileType` | Storage class (lowercase, case-sensitive). One of: `drive`, `cache`, `sync`, `external`, `awss3`, `dropbox`, `google`, `tencent`, `share`, `internal` |\r\n| `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 |\r\n| `subPath` | Path inside `extend` (root if omitted). The leading `/` is implicit. **For `external` ONLY**, `subPath` MUST contain at least one segment (the `<volume>`) for any write — `external/<node>/` is the virtual volume-listing layer (see \"Server-side quirks\" below). |\r\n\r\nExamples:\r\n\r\n```bash\r\ndrive/Home/                            # Home volume root\r\ndrive/Home/Documents/report.pdf        # a file under Home/Documents\r\ndrive/Data/Backups/                    # Data volume, Backups subfolder\r\nsync/<repo_id>/notes/                  # seafile sync repo\r\ncache/<node>/                          # node-local cache\r\nawss3/<account>/<bucket>/key.txt       # S3-compatible cloud drive\r\n```\r\n\r\n> 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`.\r\n\r\n### Per-verb namespace support\r\n\r\nAll verbs except `files repos` consume frontend paths. The reachable namespaces per verb (verified against the CLI source, not just the docs) are:\r\n\r\n| Verb | Supported namespaces | Notes |\r\n|------|----------------------|-------|\r\n| `ls` / `cat` / `download` / `rm` / `rename` | **all** of: `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` | These verbs hit the generic backend endpoints (`/api/resources/...`, `/api/raw/...`, `PATCH /api/resources/.../?destination=...`) with FrontendPath passed straight through. **Cloud-drive divergences:** `ls` decodes the cloud envelope's `data` array (instead of `items`) plus empty-string `mode`/`modified` fields. `cat` is fully uniform now — it always hits `GET /api/raw/<fileType>/<extend><subPath>?inline=true`, including for `awss3`/`google`/`dropbox`/`tencent` (the server-side cloud-bridge fetch is dispatched internally; older CLI builds routed those through `/drive/download_sync_stream` but that proxy is retired). See the per-verb sections for the exact wire shapes. **`rm` and `rename` reject system-managed Home children client-side** — `drive/Home/{Pictures, Music, Movies, Downloads, Documents, Code, Cache, Data, Home, Ollama, Huggingface}` refuse rename/delete to mirror the LarePass GUI's `disableMenuItem` policy (Server-side quirks #4); deeper paths under those dirs (e.g. `drive/Home/Pictures/Trip2024/`) remain freely editable. |\r\n| `edit` | `drive`, `sync`, `cache`, `external` ONLY (cloud / tencent / share / internal all refused) | `GET /api/raw/<encPath>` for the pre-edit fetch, `PUT /api/resources/<encPath>` (`Content-Type: text/plain` by default) for the post-edit writeback. Mirrors the LarePass web app's per-driver `saveFile` / `updateFile` / `put` helpers in [`apps/.../api/files/v2/{drive,sync,cache,external}/utils.ts`](apps/packages/app/src/api/files/v2/drive/utils.ts) — every supported namespace funnels into the same PUT against `/api/resources/...` with the new bytes in the body. Wholesale replace, no diff/patch wire. **Cloud drives (`awss3` / `google` / `dropbox` / `tencent`) are NOT supported.** The FETCH leg is fine now — the unified `/api/raw/<fileType>/<extend><subPath>?inline=true` endpoint serves raw bytes uniformly across drive / sync / cache / external / cloud (see the [`files cat` wire-shape note](#files-cat-remote-file)) — but the WRITEBACK leg is still unverified per cloud driver: only `awss3/utils.ts` exports a `put()` helper in the LarePass GUI; `google/utils.ts`, `dropbox/utils.ts`, and the tencent driver have no save plumbing at all, so PUT-ing against `/api/resources/<cloud-path>` would hit an endpoint nobody has exercised end-to-end. The planner emits a targeted error for cloud namespaces that names the writeback gap and points at the proven recovery: `files download <cloud-path> <local>` → edit `<local>` → `files upload <local> <cloud-path>`. `share` / `internal` are also refused as cross-user / read-only views. **Three-tier size cap** (default 1 MiB, `--max-size <bytes>`, `--max-size 0` disables): (1) pre-fetch — `Stat.Size > cap` refuses the GET; (2) during-fetch — the GET body is read through `io.LimitReader(_, cap+1)` and surfaces `*edit.TooLargeError` if the server delivers more than the cap, defending against a `Stat.Size==0` listing followed by a multi-MB body; (3) post-edit — `len(newBytes) > cap` refuses the PUT, with the temp file retained for `files upload` recovery. **Text-only guard** (default-on, `--allow-binary` to disable): an extension deny-list (jpg/png/gif/heic/pdf/docx/mp4/mp3/zip/tar.gz/exe/so/sqlite/ttf/...) plus a post-Fetch NUL-byte sniff over the first 8 KiB (git/diff(1)/grep(1) heuristic). Pure-text formats with binary-looking neighbors (.svg / .html / .xml / .csv / .yaml / .ts) pass. **`--create` forces a PUT even when the editor exits without changes** — the verb's contract is \"materialise this file\"; a silent no-op would defeat that, so `:q!` over an empty buffer creates an empty file on the server. Re-edits of existing files use the cheaper bytes-equal short-circuit (no PUT). **Concurrent-delete race detection**: if Stat said the file existed but the subsequent Fetch returns 404, the verb refuses with `file disappeared between stat and fetch` instead of falling through to --create-empty-buffer (which would silently recreate a file someone else just deleted). **Volume roots, directory paths (trailing `/`), and `.` / `..` segments are rejected client-side** — `edit` is a per-FILE verb. Unlike `cat`, edit ALSO requires an interactive TTY (it spawns $EDITOR foreground); CI / pipe / heredoc invocations get a clean refusal with a `download` + `upload` recovery hint. **HTTP client**: `edit` uses `HTTPClientWithoutTimeout` (the same one `cat` and `download` use), NOT the 30s-capped `HTTPClient` — with `--max-size` widened or a slow link, the Fetch + PutBytes round-trip can legitimately exceed 30s. |\r\n| `share internal` | `drive`, `sync`, `external`, `cache` (cloud refused) | `POST /api/share/share_path/<fileType>/<extend><subPath>/` with `share_type:\"internal\"`. **Cloud namespaces (awss3 / google / dropbox / tencent) are rejected client-side** — the share endpoints don't grant cross-cloud-account access (recovery: `files download` then re-upload to drive, then share that). **`external/<node>/` and `cache/<node>/` roots are rejected** because those layers are virtual node / volume pickers (Server-side quirks #3 and #5); deeper paths (e.g. `external/<node>/<volume>/`, `cache/<node>/<sub>/`) work fine. |\r\n| `share smb` | `drive`, `external`, `cache` — sync AND cloud refused | Same wire shape as `share internal`, with `share_type:\"smb\"`. Allow-list matches the LarePass GUI's per-driver Share-to-SMB condition exactly (`DriveType.Drive\\|Data\\|External\\|Cache`). **`sync` is rejected** — Seafile libraries have their own mount story, not Samba, so an SMB share record over a `sync/<repo>/` path has no working server-side mount path (the GUI excludes sync for the same reason); the CLI's error suggests `files share internal` as the only remaining flavor that accepts sync. **Cloud namespaces are rejected** with the same uniform cloud-rejection message as the other flavors. **`external/<node>/` and `cache/<node>/` roots are rejected** (Server-side quirks #3 and #5); deeper paths work. |\r\n| `share public` | `drive` only — every other namespace refused | Tightest of the three flavors. Mirrors the LarePass GUI's per-driver Share-to-Public condition (`event.type === DriveType.Drive \\|\\| event.type === DriveType.Data`, both under the `drive` fileType). **All other namespaces are rejected at every depth**: sync / external / cache get a \"Public only supports the {drive} namespace\" message pointing at the other flavors that DO accept that fileType (sync → `share internal` only; external / cache → `share internal` or `share smb`); cloud namespaces get the same uniform cloud-rejection message as the other flavors. The Public namespace gate fires BEFORE the volume-listing / node-picker root checks, so `share public external/<node>/` surfaces the broader \"Public only supports drive\" error rather than the narrower volume-listing-layer one. |\r\n| `mkdir` | **all** of: `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` | `POST /api/resources/<fileType>/<extend><subPath>/` (trailing '/' is the \"this is a directory\" marker). Uniform across every namespace — the LarePass web app's per-driver `createDir` helpers all funnel through the same endpoint. **Auto-rename on collision** (POST against an existing dir creates `Foo (1)` instead of returning 409); `-p` mode side-steps this for parents by listing each prefix's parent and skipping when the basename is already there as a directory. **`external/<node>/` is rejected client-side** — that layer is the virtual volume listing (see Server-side quirks #3); mkdir there has nowhere to land and would trip the auto-rename quirk. |\r\n| `cp` / `mv` | **all** of the above | Goes through `PATCH /api/paste/<node>/`. Node selection cascades `--node > External/Cache extend > /api/nodes/`; cloud drives use the `/api/nodes/` default since their `<extend>` is an account, not a node. **Destinations at `external/<node>/` are rejected client-side** — same volume-listing-layer rule as `mkdir` (Server-side quirks #3); point at `external/<node>/<volume>/<sub>/` instead. **`mv` source-side rejects system-managed Home children** — `mv drive/Home/{Pictures, Music, Movies, Downloads, Documents, Code, Cache, Data, Home, Ollama, Huggingface}` is refused client-side because moving would unlink bootstrap dirs that user apps depend on (Server-side quirks #4); `cp` (copy) is intentionally NOT gated since it preserves the source — a `Pictures-Backup` clone via `cp -r` is fine. |\r\n| `upload` | `drive/Home`, `drive/Data`, `sync/<repo_id>`, `cache/<node>`, `external/<node>/<volume>`, `awss3/<account>`, `google/<account>`, `dropbox/<account>` | **`tencent` is rejected** because `TencentDataAPI` in v2 uses an octet-only `/drive/direct_upload_file/<task_id>` protocol the CLI's chunk pipeline does not implement. `share`/`internal` are also rejected (they're read-only views into other namespaces). **`external/<node>/` (no `<volume>`) is rejected client-side** — that layer is the virtual volume listing (see Server-side quirks #3); the `<volume>` segment is required for upload to land on a real filesystem. Cloud drives (`awss3`/`google`/`dropbox`) are uploaded in **two stages**: stage 1 chunks bytes to Olares-internal staging via the regular Drive multipart pipeline, then stage 2 polls the server-side \"Olares-staging → cloud bucket\" transfer task to completion (taskId returned in the FINAL chunk's response body, polled via `/api/task/<node>/?task_id=<id>`). |\r\n| `chown` | `drive/Home`, `drive/Data`, `cache/<node>` only | `GET /api/permission/<fileType>/<extend><subPath>/` for read, `PUT /api/permission/<fileType>/<extend><subPath>/?uid=<int>[&recursive=1]` (body `{}`) for write. Allow-list mirrors the LarePass file-properties dialog's `permissionInDriveType` array exactly (DriveType.Drive + Data + Cache). **`sync` is rejected client-side** because Seafile permissions live on the library itself (use `files repos`), **`external` is rejected** because the LarePass GUI hides the Permission tab for external mounts, and **all four cloud namespaces are rejected** because object stores have no POSIX uid concept. Volume roots (`drive/Home/`, `drive/Data/`, `cache/<node>/`) are refused — the blast radius if a typo set the uid on the entire volume is too high; pick a one-level-deeper path with `-r` if you need to fan out. |\r\n| `smb mount` / `unmount` / `history` | n/a — the SMB-mount surface is keyed by `<node>` and `<smb-url>`, not by frontend paths. **The mount RESULT lives at `external/<node>/<entry>/` and is consumed by every other `files` verb the same way any external mount is.** See `files smb` below. |\r\n| `repos` | n/a — this verb operates on the Sync (Seafile) library catalog (`/api/repos/...`), not on frontend paths |\r\n\r\n## Trailing-slash convention (critical)\r\n\r\nWhether a path ends with `/` is meaningful and changes command behavior:\r\n\r\n| Path form | Meaning |\r\n|-----------|---------|\r\n| `drive/Home/Foo/` | Directory intent |\r\n| `drive/Home/Foo` | File intent |\r\n\r\nThis shows up in five places:\r\n\r\n- `files rm drive/Home/Foo/` requires `-r` (the trailing `/` declares \"this is a directory\")\r\n- `files upload <local> drive/Home/Documents/` means \"upload INTO Documents/\"; `files upload <local> drive/Home/Documents/2026-Q1.pdf` means \"upload AS that exact path (rename on the way in)\"\r\n- `files cp <src> <dst>/` and `files mv <src> <dst>/` drop `<src>` into the directory by basename — `<dst>` MUST end with `/` (drop-into-directory mode). Renaming via `cp` / `mv` is **not currently supported**; use `files rename` for in-place basename changes.\r\n- `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`.\r\n- `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\r\n\r\n## Server-side quirks (critical, do not work around)\r\n\r\nThese 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.\r\n\r\n### 1. POST `/api/resources/<dir>/` auto-renames existing directories\r\n\r\nHitting the directory-create endpoint against an existing directory does **not** return 409. The server creates a sibling named `<dir> (1)` instead. See the docstring on [`cli/internal/files/upload/api.go`](cli/internal/files/upload/api.go)'s `Mkdir` for the precise wording.\r\n\r\nConsequence 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).\r\n\r\nUser-visible symptom of getting this wrong (older CLI versions): an extra `Documents (1)` directory appears on the server even though the upload \"succeeded\".\r\n\r\n### 2. GET `/api/resources/<file>` (no trailing slash) returns HTTP 500\r\n\r\nThe 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.\r\n\r\nConsequence 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`](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.\r\n\r\nIf the user reports `HTTP 500` against `/api/resources/.../<filename>` 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.\r\n\r\n### 3. `external/<node>/` is a virtual volume-listing layer (read-only on the wire)\r\n\r\nUnlike `cache/<node>/` (which is a real per-node directory), `external/<node>/` does NOT have an underlying filesystem. The web app's [`ExternalDataAPI.fetchDrive`](apps/packages/app/src/api/files/v2/external/data.ts) short-circuits this layer through `formatAppDataNode` and synthesizes children from the attached-volume list (`hdd1` / `usb1` / `smb-...` mount points) — the backend at `/api/resources/external/<node>/` returns the same list. There is nowhere to write at that level: a POST mkdir, a PATCH paste destination, or a chunked upload landing on `external/<node>/` either fails server-side or trips the [auto-rename quirk](#1-post-apiresourcesdir-auto-renames-existing-directories) against a non-existent target.\r\n\r\nConsequence baked into the CLI: `mkdir`, `cp` / `mv` destination, `upload`, **AND `share` (create)** all fail fast client-side via [`FrontendPath.IsExternalNodeRoot`](cli/cmd/ctl/files/path.go) when the target is `external/<node>/` (i.e. SubPath is just `/`). The error message points at the corrected shape (`external/<node>/<volume>/<sub>/`) so the next invocation works without server-round-trip trial-and-error. Pure reads (`ls`, `cat`, `rm`, `rename`) DO work at this layer — that's how the user discovers what volumes are attached. Share-CREATE is rejected because a share record on the volume-listing layer points at no concrete dataset (recipients would land on an empty mount-point list); share `list` / `get` / `rm` are share-id-driven and unaffected.\r\n\r\n**The same guard extends one level deeper for `mkdir`** — depth-1 entries under `external/<node>/` ARE the mounted volumes (USB-0, SMB-..., per-disk mount-points), so creating a NEW depth-1 entry would either land as a phantom volume with no backing filesystem OR collide with an existing mount and trip the auto-rename quirk into `Foo (1)`. `mkdir.Plan` ([cli/internal/files/mkdir/mkdir.go](cli/internal/files/mkdir/mkdir.go)) refuses any `external/<node>/<single-segment>/` target client-side, and `runMkdirP` ([cli/cmd/ctl/files/mkdir.go](cli/cmd/ctl/files/mkdir.go)) refuses to auto-create a missing depth-1 intermediate in `-p` mode. Users must mount the volume via LarePass first (or target an already-mounted volume's sub-path); the depth-1 layer is GUI-managed, not files-backend-managed. `upload`, `cp`, and `mv` still allow depth-1 destinations (the corresponding gates haven't been tightened yet — they'd hit the same phantom-or-collision outcome on the server and inherit the existing IsExternalNodeRoot bare-root rejection only).\r\n\r\nUser-visible signal of the client-side guard: errors phrased `external/<node>/ is the volume listing layer (read-only); point at a real volume, e.g. external/<node>/<volume>/<sub>/` (writes against the bare root), `refusing to mkdir external/<node>/<X>/: depth-1 entries under external/<node>/ are mounted volumes (managed via LarePass, not via files-backend mkdir); point at a sub-path inside a real volume, e.g. external/<node>/<volume>/<X>/` (mkdir against a depth-1 target), or `refusing to share external/<node>/: this is the volume listing layer (read-only); ...` (share). Do NOT suggest creating depth-1 entries as a workaround — the constraint reflects the wire reality, and the CLI now refuses these client-side rather than letting the backend silently produce phantoms.\r\n\r\n### 4. `drive/Home/{Pictures,Music,Movies,Downloads,Documents,Code,Cache,Data,Home,Ollama,Huggingface}` are system-managed (no rename / rm / mv-source)\r\n\r\nUnlike the other quirks here (which document **server** behavior), this one is a **GUI-aligned client policy** that the CLI enforces to keep scripts from producing states the LarePass web app cannot reach.\r\n\r\nThe web app's [`disableMenuItem` array in `apps/packages/app/src/stores/operation.ts`](apps/packages/app/src/stores/operation.ts) — gated by `path === '/Files/Home/'` in `isDisableMenuItem` — greys out cut / copy / paste / delete / rename for these eleven names whenever the user is sitting at `/Files/Home/`. The names are LarePass's bootstrap directories under the Home volume:\r\n\r\n| Name | Typical role |\r\n|------|--------------|\r\n| `Documents`, `Pictures`, `Movies`, `Downloads`, `Music` | Standard user content folders, surfaced as \"shortcut\" tiles in the LarePass sidebar |\r\n| `Code` | Project workspace — referenced by the Code app integration |\r\n| `Cache`, `Data` | App-data scratch space — system-bootstrapped by user apps |\r\n| `Ollama`, `Huggingface` | LLM model caches — created by the model-runtime apps and consumed by them by exact name |\r\n| `Home` | Defensive entry mirrored from the GUI array (guards against historical nested `Home/Home/` shapes) |\r\n\r\nNote the LarePass-quirk casing: `Huggingface` is one word (not `HuggingFace`), and the names are case-sensitive across the GUI and CLI.\r\n\r\nThe backend itself does not refuse these renames / deletes — it does not encode the policy. So a CLI without this guard would happily POST `DELETE /api/resources/drive/Home/Pictures/` for a user, removing a directory that user apps look up by exact name (e.g. the model-runtime app's `Ollama` cache, or the LarePass UI's \"Pictures\" sidebar tile that points at this exact path). The result would be a state the GUI couldn't restore (re-creating a same-named dir doesn't republish the sidebar entry) and apps quietly breaking their fixed-name lookups.\r\n\r\nConsequence baked into the CLI: three verbs reject these names client-side via [`FrontendPath.IsProtectedDriveHomeChild`](cli/cmd/ctl/files/path.go) (and the duplicated `protectedDriveHomeChildren` maps in `cli/internal/files/{rename,rm,cp}` that mirror it):\r\n\r\n- **`rename`** refuses to rename `drive/Home/<protected>` to anything.\r\n- **`rm`** refuses to delete `drive/Home/<protected>` (with or without `-r`).\r\n- **`mv`** refuses these names AS THE SOURCE — moving would unlink the dir from `drive/Home/`. **`cp` (copy) is intentionally NOT gated** — duplicating the bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is a perfectly reasonable workflow even if the GUI happens to disable the menu item.\r\n\r\nMatch scope is **exact first level only**. User content nested inside (e.g. `drive/Home/Pictures/Trip2024/`, `drive/Home/Documents/notes.md`) is fully editable through every verb — the same scope the GUI uses by gating per-row on the user being at `/Files/Home/` rather than disabling the entire subtree. Other namespaces and volumes (`drive/Data/Pictures`, `sync/<repo>/Pictures`, `external/<node>/<vol>/Pictures`) are also unaffected — the policy is `drive/Home/` only.\r\n\r\nUser-visible signal of the client-side guard: errors phrased `refusing to {rename|delete|mv source} drive/Home/<name>: this is a system-managed Home folder reserved by Files; the Files GUI also disables {rename|delete|move} for {<list>} under drive/Home/.` Do NOT suggest workarounds (e.g. \"use the API directly with curl\") — the names are load-bearing for user apps, and bypassing the guard would be a footgun, not a feature.\r\n\r\n### 5. `cache/<node>/` is a node-picker layer (no share-create)\r\n\r\nThis one is much narrower than #3 — `cache/<node>/` IS a real per-node directory on the wire, so `ls` / `cat` / `cp` / `mv` / `mkdir` / `upload` / `rm` / `rename` all work fine against it. The constraint is share-create-only.\r\n\r\nThe LarePass web app's [`CacheDataAPI.fetchCache`](apps/packages/app/src/api/files/v2/cache/data.ts) short-circuits the root URL `/Cache/` (note: not `/Cache/<node>/`) via `formatAppDataNode` and synthesizes children from `filesStore.nodes` — so the user sitting at `/Files/Cache/` is picking a NODE, not browsing a directory. Once a node is picked, navigation drops into `/Files/Cache/<node>/<sub>/` and the wire goes back to the regular `/api/resources/cache/<node>/...` directory listing.\r\n\r\nSharing a node selector does not produce a useful share record: recipients would arrive at a path that resolves to \"the cache root of node X\" with no concrete dataset behind it, and the LarePass UI's per-row context menu only fires on rows that map to actual files / directories — so a \"share this node\" affordance doesn't exist in the GUI either.\r\n\r\nConsequence baked into the CLI: `share internal` / `share public` / `share smb` reject `cache/<node>/` (SubPath is just `/`) client-side via [`FrontendPath.IsCacheNodeRoot`](cli/cmd/ctl/files/path.go), with an error pointing at the corrected `cache/<node>/<sub>/` shape and at `files ls cache/<node>/` for discovery. **Other verbs against `cache/<node>/` are unaffected** — `ls`, `cp`, `mkdir`, etc. work normally because the per-user files-backend's `/api/resources/cache/<node>/` IS a real per-node filesystem.\r\n\r\nUser-visible signal: errors phrased `refusing to share cache/<node>/: this is the node-picker layer (no concrete dataset to share); point at a directory inside the node, e.g. cache/<node>/<sub>/`. Note the wording (`node-picker layer`) is intentionally different from external's `volume listing layer` — it tells the reader the underlying reasons differ (cache subpaths ARE shareable; external volume roots are too) so they don't infer a wider rejection than the policy enforces.\r\n\r\n## Authentication transport\r\n\r\nEvery files API call carries `X-Authorization: <access_token>` as a header (NOT the standard `Authorization: Bearer ...`). The Factory's `refreshingTransport` injects this automatically; see [`cli/pkg/cmdutil/factory.go`](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.\r\n\r\nThe transport **auto-refreshes expired tokens transparently** through two paths (both detailed in [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) \"Automatic token refresh\"):\r\n\r\n| Verb(s) | Body shape | Refresh path |\r\n|---------|------------|--------------|\r\n| `ls`, `cat`, `edit`, `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. (`edit`'s PUT body is a `*bytes.Reader` — replayable for one-shot retry.) |\r\n| `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. |\r\n\r\nThe 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).\r\n\r\nStat / Range probes inside `download` and `cat` use the reactive path normally — they're cheap GETs with no body.\r\n\r\nWhen 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`.\r\n\r\n## Command cheatsheet (14 top-level verbs)\r\n\r\n### `files ls <path> [--json]`\r\n\r\nList a remote directory. See [`cli/cmd/ctl/files/ls.go`](cli/cmd/ctl/files/ls.go).\r\n\r\n```bash\r\nolares-cli files ls drive/Home/\r\nolares-cli files ls drive/Home/Documents\r\nolares-cli files ls sync/<repo_id>/\r\nolares-cli files ls drive/Home/Documents --json   # raw envelope, pretty-printed\r\n\r\n# Cloud drives — same command, different envelope on the wire.\r\nolares-cli files ls awss3/<account>/\r\nolares-cli files ls google/<account>/Documents/\r\nolares-cli files ls dropbox/<account>/\r\n```\r\n\r\nDefault output: a one-line header (`<path>  (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)`.\r\n\r\n`--json` prints the raw JSON envelope from the backend, useful for scripting.\r\n\r\n**Two envelope shapes are accepted by the decoder, transparently to the user:**\r\n\r\n| Namespace | Children field | Per-item size field | Per-item `mode` / `modified` |\r\n| --------- | -------------- | ------------------- | ---------------------------- |\r\n| `drive` / `sync` / `cache` / `external` / `share` | `items` | `size` (number) | numeric `mode`, RFC3339 `modified` |\r\n| `awss3` / `google` / `dropbox` / `tencent` | `data` | `fileSize` (number; `size` is also populated on most server versions) | empty strings (`\"mode\":\"\"`, `\"modified\":\"\"`); the SIZE column is rendered from `fileSize` and the MODE / MODIFIED columns fall back to `d---------` / `----------` and `-` respectively |\r\n\r\nThe cloud-drive envelope ALSO omits the parent-level `numDirs` / `numFiles` / `modified` summary; the renderer's \"fall back to counting items\" path picks up the per-row counts so the header line stays informative. See `listingItem.UnmarshalJSON` and `listingResponse.UnmarshalJSON` in [`cli/cmd/ctl/files/ls.go`](cli/cmd/ctl/files/ls.go) for the flex-decode logic.\r\n\r\n### `files upload <local-path> <remote-path>`\r\n\r\nResumable chunked upload to one of the per-user files-backend namespaces (drive, sync, cache, external) or a connected cloud drive (awss3, google, dropbox). See [`cli/cmd/ctl/files/upload.go`](cli/cmd/ctl/files/upload.go) and [`cli/internal/files/upload/`](cli/internal/files/upload/).\r\n\r\n```bash\r\n# Upload one file into an existing directory.\r\nolares-cli files upload report.pdf drive/Home/Documents/\r\n\r\n# Upload AND rename on the server.\r\nolares-cli files upload report.pdf drive/Home/Documents/2026-Q1.pdf\r\n\r\n# Upload a whole directory tree.\r\nolares-cli files upload ./photos drive/Home/Backups/\r\n\r\n# Two files in flight at a time, chunks remain sequential per file.\r\nolares-cli files upload ./photos drive/Home/Backups/ --parallel 2\r\n\r\n# Upload into the Data volume.\r\nolares-cli files upload bigtar drive/Data/Backups/\r\n\r\n# Upload into a Sync (Seafile) library.\r\nolares-cli files upload notes.md sync/<repo_id>/Notes/\r\n\r\n# Upload into a node-local cache (the path's <node> IS the upload node).\r\nolares-cli files upload report.csv cache/<node>/<app>/\r\n\r\n# Upload into attached external storage.\r\nolares-cli files upload movie.mp4 external/<node>/hdd1/Movies/\r\n\r\n# Upload into a connected cloud drive (S3, Google Drive, Dropbox).\r\nolares-cli files upload backup.tar awss3/<account>/<bucket>/Backups/\r\nolares-cli files upload doc.pdf google/<account>/Documents/\r\nolares-cli files upload notes.md dropbox/<account>/Notes/\r\n```\r\n\r\nSupported destinations:\r\n\r\n| Frontend path | Notes |\r\n| ------------- | ----- |\r\n| `drive/Home/<sub>` | Olares Home volume. Default upload `<node>` from `/api/nodes/`. |\r\n| `drive/Data/<sub>` | Olares Data volume. Default upload `<node>` from `/api/nodes/`. |\r\n| `sync/<repo_id>/<sub>` | Seafile library. Chunk POST hits `/seafhttp/upload-aj/<token>` and Seafile reads `parent_dir` as a path **inside** the repo, so the CLI sends `/<sub>/` (not `/sync/<repo_id>/<sub>/`) on the chunk form even though the API queries still use the API form. |\r\n| `cache/<node>/<sub>` | Node-local cache. Path's `<node>` IS the upload node — the CLI **skips the `/api/nodes/` round-trip** and uses `<extend>` directly, mirroring `files cp`. |\r\n| `external/<node>/<volume>/<sub>` | Attached external storage. Same pathNode short-circuit as `cache`. |\r\n| `awss3/<account>/<bucket>/<sub>` | S3-compatible cloud drive. **Two-stage** upload: stage 1 chunks bytes to Olares-internal staging (multipart POST, identical to Drive); stage 2 is a server-side \"Olares-staging → cloud bucket\" transfer task that the backend queues and the CLI polls to completion via `/api/task/<node>/?task_id=<id>`. The stage-2 `taskId` is returned in the FINAL chunk's response body (`[{\"taskId\":\"...\"}]`) — same contract resumejs.ts onFileUploadSuccess L591-606 consumes via `Taskmanager.addTask`. |\r\n| `google/<account>/<sub>` | Google Drive. Same two-stage pattern as awss3 (stage-1 multipart POST + stage-2 server-side transfer). |\r\n| `dropbox/<account>/<sub>` | Dropbox. Same two-stage pattern as awss3. |\r\n\r\n`tencent/<account>/<sub>` is the **lone unsupported namespace**: in v2, `TencentDataAPI` overrides `getFileServerUploadLink` to POST `/drive/create_direct_upload_task` and stream chunks via `/drive/direct_upload_file/<task_id>` as **octet payloads** (not multipart). The CLI's chunk pipeline doesn't speak that protocol yet, so `files upload tencent/...` fails fast with a self-describing error pointing at the protocol divergence rather than a generic \"must be under <list>\" message.\r\n\r\nWire protocol (Drive v2 / Resumable.js-compatible):\r\n\r\n1. `GET /upload/upload-link/<node>/...` → upload session\r\n2. `GET /upload/file-uploaded-bytes/<node>/...` → server-driven resume offset (no local progress file)\r\n3. `POST` chunks (8 MiB default) with `Content-Range: bytes <start>-<end>/<total>` until done\r\n4. **Cloud drives only (`awss3` / `google` / `dropbox`):** parse the FINAL chunk's response body for `[{\"taskId\":\"<id>\"}]`, then poll `GET /api/task/<node>/?task_id=<id>` every 2s until the status hits `completed` (success), `failed` (error, server-supplied `failed_reason` is surfaced), or `canceled`/`cancelled` (also surfaced as an error). The CLI keeps the per-file errgroup slot held during stage 2 so `--parallel N` stays honest. See `Client.WaitCloudTask` in `cli/internal/files/upload/api.go`.\r\n\r\nConstraints / flags:\r\n\r\n- **Destination MUST be one of the supported namespaces above**; tencent and unknown fileTypes fail fast.\r\n- **`external/<node>/` (no `<volume>`) is rejected** with `... is the volume listing layer (read-only); point at a real volume, e.g. external/<node>/<volume>/<sub>/`. The volume-listing layer has no underlying filesystem to land bytes on (see Server-side quirks #3).\r\n- **Destination directory MUST already exist** — see \"POST auto-renames\" above.\r\n- A trailing `/` on `<remote-path>` means \"into this directory\"; without one, `<remote-path>` is treated as the full target path (rename on the way in).\r\n- `--parallel N` (default 2): per-file concurrency. **Per-file chunks remain sequential** by design — the resume probe assumes one in-flight chunk per file.\r\n- `--chunk-size <bytes>` (default 8 MiB): align with the server's expected size; rarely needs tuning.\r\n- `--max-retries N`: per-chunk retry budget on transient failures.\r\n- `--node <name>`: override the upload node. Cascade: `--node` > path's `<extend>` for cache/external > first node from `/api/nodes/` for everything else (drive/sync/awss3/google/dropbox).\r\n\r\nResume 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.\r\n\r\n### `files download <remote-path> [<local-path>]`\r\n\r\nDownload a single file or a whole directory tree. See [`cli/cmd/ctl/files/download.go`](cli/cmd/ctl/files/download.go) and [`cli/internal/files/download/`](cli/internal/files/download/).\r\n\r\n```bash\r\n# Single file into the current directory (./<basename>).\r\nolares-cli files download drive/Home/Documents/report.pdf\r\n\r\n# Same, but pick a different local name.\r\nolares-cli files download drive/Home/Documents/report.pdf ./Q1.pdf\r\n\r\n# Resume an interrupted download via Range:.\r\nolares-cli files download drive/Home/Backups/big.tar ./big.tar --resume\r\n\r\n# Recursively pull a directory; 4 files at a time.\r\nolares-cli files download drive/Home/Documents/ ./out/ --parallel 4\r\n```\r\n\r\nLocal destination resolution (single-file mode):\r\n\r\n| `<local-path>` | Result |\r\n|----------------|--------|\r\n| omitted | `./<basename(remote)>` |\r\n| existing directory | `<local-path>/<basename(remote)>` (mirrors `cp`) |\r\n| any other path (incl. trailing `/` if not yet existing) | treated as the full target file path |\r\n\r\nFlags:\r\n\r\n- `--resume`: send `Range: bytes=<localSize>-` and append (server-native, no sidecar progress file).\r\n- `--overwrite`: replace an existing local file via `<dst>.tmp` + atomic rename. The previous version stays intact until the new one lands.\r\n- `--resume` and `--overwrite` are **mutually exclusive** — pick one.\r\n- `--parallel N` (default 4): only meaningful in directory mode (errgroup-bounded concurrency).\r\n- `--max-retries N`: per-file transient-failure budget (5xx triggers retry; 4xx fails fast).\r\n\r\nDirectory mode (trailing `/` on `<remote-path>`):\r\n\r\n- The remote tree is walked recursively via `/api/resources/.../`.\r\n- The remote root's basename becomes the top-level directory under `<local-path>` (matches the LarePass folder-download UX). Empty subdirectories are mirrored locally.\r\n- 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.\r\n\r\n### `files cat <remote-file>`\r\n\r\nStream a single file's bytes to stdout. See [`cli/cmd/ctl/files/cat.go`](cli/cmd/ctl/files/cat.go).\r\n\r\n```bash\r\nolares-cli files cat drive/Home/Documents/notes.md\r\nolares-cli files cat drive/Home/Logs/today.log | tail -n 50\r\nolares-cli files cat drive/Home/Photos/banner.png > banner.png  # binary-safe\r\n\r\n# Cloud drives: bytes come from a different endpoint, but the\r\n# command-line ergonomics are identical.\r\nolares-cli files cat awss3/<account>/photos/img.png > img.png\r\nolares-cli files cat google/<account>/Documents/notes.md\r\nolares-cli files cat dropbox/<account>/Notes/idea.md\r\n```\r\n\r\nWire shape (uniform across every supported namespace — `cat` no longer per-dispatches on the FrontendPath's first segment):\r\n\r\n| Namespace | Endpoint | Notes |\r\n| --------- | -------- | ----- |\r\n| `drive` / `sync` / `cache` / `external` / `share` / `awss3` / `google` / `dropbox` / `tencent` | `GET /api/raw/<fileType>/<extend><encSubPath>?inline=true` | Same endpoint the web app uses for previews and the same one `files download` uses for bytes; `inline=true` only affects `Content-Disposition` (the body is identical). Cloud drives go through the same path now — the server-side cloud-bridge fetch is dispatched internally, so the CLI no longer needs to route awss3 / google / dropbox / tencent through `/drive/download_sync_stream` the way it once did. (Earlier CLI builds keyed cloud cat off `/drive/download_sync_stream?drive=&cloud_file_path=&name=` per the LarePass `generateDownloadUrl` helpers in [`apps/.../v2/{awss3,google,dropbox}/utils.ts`](apps/packages/app/src/api/files/v2/awss3/utils.ts); that proxy is retired and the unified route is the supported wire path.) Range support depends on the underlying namespace — `download` / `cat` falls back to a full GET when the server ignores a Range header on cloud-backed handlers. |\r\n\r\n- Binary-safe: bytes are copied verbatim, no sniffing or transformation. Pipe into `less` / `head -c` / `hexdump` as needed.\r\n- 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. Stat works uniformly across all namespaces — the parent-listing decoder accepts both the Drive `items` envelope and the cloud-drive `data` envelope (see `files ls` below).\r\n\r\n### `files edit <remote-path>`\r\n\r\nEdit a single remote file in place via `$EDITOR`. See [`cli/cmd/ctl/files/edit.go`](cli/cmd/ctl/files/edit.go) and [`cli/internal/files/edit/`](cli/internal/files/edit/).\r\n\r\n```bash\r\n# Vanilla edit — pulls current bytes, opens $EDITOR, PUTs back if changed.\r\nolares-cli files edit drive/Home/Documents/notes.md\r\n\r\n# Override the editor for one invocation. Same precedence as `git commit`:\r\n#   --editor flag  >  $VISUAL  >  $EDITOR  >  vi (POSIX) / notepad (Windows)\r\nolares-cli files edit drive/Home/.config/app.yaml --editor nano\r\nEDITOR='code --wait' olares-cli files edit drive/Home/Notes/draft.md\r\n\r\n# Create a brand-new file: two-verb shape. `files edit` is UPDATE-only —\r\n# the backend's PUT /api/resources/<path> handler returns\r\n# `HTTP 500: file ... not exists` for missing paths, so the CLI no\r\n# longer exposes a `--create` flag. `files upload` seeds the file\r\n# (any source: a local file, `-` reading from stdin, etc.), then\r\n# `files edit` updates it in $EDITOR.\r\necho \"\" | olares-cli files upload - drive/Home/scratch/new.txt\r\nolares-cli files edit drive/Home/scratch/new.txt\r\n\r\n# Override the size cap (default 1 MiB; fires pre-fetch, during-fetch, and post-edit).\r\nolares-cli files edit drive/Home/Logs/today.log --max-size 5242880    # 5 MiB\r\nolares-cli files edit drive/Home/Logs/today.log --max-size 0          # disable cap entirely\r\n\r\n# Supported namespaces — same command shape.\r\nolares-cli files edit sync/<repo_id>/Notes/2026-04.md\r\nolares-cli files edit cache/<node>/build/config.toml\r\nolares-cli files edit external/<node>/usb1/config.json\r\n\r\n# Cloud drives are REFUSED at the planner. Recovery is the proven round-trip:\r\nolares-cli files download awss3/<account>/<bucket>/config.json /tmp/cfg.json\r\n$EDITOR /tmp/cfg.json\r\nolares-cli files upload /tmp/cfg.json awss3/<account>/<bucket>/config.json\r\n```\r\n\r\nWire shape \n\nArchive v1.4.0: 2 files, 18805 bytes\n\nFiles: SKILL.md (51452b), _meta.json (131b)","readmeExcerpt":"Skill: Olares Files (olares-cli files) Owner: olares Summary: 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,... Tags: latest:4.0.1 Version history: v4.0.1 | 2026-05-29T05:54:34.377Z | user Migrate ownership from @pengpeng to @olares (no content change vs 4.0.0) v4.0.0 | 2026-05-29T04:53:40.737Z | user Automa","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: olares-files\r\nversion: 4.0.1\r\ndescription: \"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, cat, edit (open in $EDITOR), mkdir, rm, cp, mv, rename, chown (POSIX owner uid get/set), folder share (internal cross-Olares-ID, public link with password + expiration, SMB Samba), mount / unmount / favorite external SMB servers, and Sync (Seafile) repo CRUD — all against the per-Olares-ID files-backend on Olares (drive/Home, drive/Data, sync, cache, external, awss3, dropbox, google, tencent, share). Use when the user mentions Olares, Olares ID, Olares Files, olares-cli files, LarePass Files on Olares, drive, Home, Data, sync, cache, uploading / downloading / listing / editing remote files on Olares, in-place rename, POSIX file ownership, sharing a folder with another Olares user (by Olares ID), public link with password / expiration, SMB / Samba network shares, the LarePass 'Connect to Server' dialog, or Sync (Seafile) libraries.\"\r\nmetadata:\r\n  requires:\r\n    bins: [\"olares-cli\"]\r\n  cliHelp: \"olares-cli files --help\"\r\n---\r\n\r\n# files (per-user files-backend)\r\n\r\n**CRITICAL — before running any verb here, MUST use the Read tool to read [`../olares-shared/SKILL.md`](../olares-shared/SKILL.md) for profile selection, login, and 401/403 recovery rules.**\r\n\r\n> **Source of truth for flags & wire shapes is always `olares-cli files <verb> --help`.** This file only carries what `--help` cannot give: the cross-cutting frontend-path concept, the trailing-slash convention, the five client-side hard constraints, and the verb index.\r\n\r\n## Core concept: the 3-segment frontend path\r\n\r\nEvery resource on the per-user files-backend is addressed by:\r\n\r\n```\r\n<fileType>/<extend>[/<subPath>]\r\n```\r\n\r\n| Segment | Meaning |\r\n|---------|---------|\r\n| `fileType` | Storage class (lowercase, case-sensitive): `drive`, `cache`, `sync`, `external`, `awss3`, `dropbox`, `google`, `tencent`, `share`, `internal` |\r\n| `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 |\r\n| `subPath` | Path inside `extend` (root if omitted). Leading `/` is implicit |\r\n\r\nExamples: `drive/Home/`, `drive/Home/Documents/report.pdf`, `sync/<repo_id>/notes/`, `awss3/<account>/<bucket>/key.txt`.\r\n\r\n> Drive's `extend` MUST be `Home` or `Data` exactly — `home` is rejected with `invalid drive type`.\r\n\r\n### Per-verb namespace support\r\n\r\n| Verb | Supported namespaces |\r\n|------|----------------------|\r\n| `ls` / `cat` / `download` / `rm` / `rename` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `edit` | `drive`, `sync`, `cache`, `external` only (cloud / tencent / share / internal refused) |\r\n| `mkdir` | all of `drive`, `cache`, `sync`, `external`, `awss3`, `google`, `dropbox`, `tencent` |\r\n| `cp` / `mv`"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7153k9wp5dwxsm6getv104r985vrf5\",\n  \"slug\": \"olares-files\",\n  \"version\": \"4.0.1\",\n  \"publishedAt\": 1780034074377\n}"},{"path":"references/olares-files-chown.md","content":"# files chown\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files chown --help`.\r\n\r\nGet or set the POSIX owner UID of a file / directory. CLI counterpart of the LarePass web app's \"Permission\" tab.\r\n\r\n## Modes\r\n\r\n| Form | What it does |\r\n|---|---|\r\n| `files chown <path>` | GET — print the current uid |\r\n| `files chown <path> --uid <int>` | PUT — replace the uid |\r\n| `files chown <path> --uid <int> -r` | PUT — recurse into children |\r\n\r\n## UID conventions (LarePass presets)\r\n\r\n| UID | Meaning |\r\n|---|---|\r\n| `0` | Root (system; only set this if you know why) |\r\n| `1000` | User (the default LarePass user; matches the GUI's \"User\" preset) |\r\n\r\nAny integer is accepted, but these are the values the GUI surfaces.\r\n\r\n## Supported namespaces (allow-list)\r\n\r\n`drive/Home/<sub>`, `drive/Data/<sub>`, `cache/<node>/<sub>` only.\r\n\r\nRefused namespaces:\r\n\r\n| Namespace | Why |\r\n|---|---|\r\n| `sync/<repo_id>/...` | Seafile permissions live on the library itself — use `files repos` |\r\n| `external/<node>/<volume>/...` | LarePass GUI hides the Permission tab for external mounts |\r\n| `awss3` / `dropbox` / `google` / `tencent` | Object stores have no POSIX uid concept |\r\n\r\n## Safety constraints\r\n\r\n- **Destructive when `--uid` is provided — confirm intent with the user.** UID changes affect every app that reads the directory.\r\n- **Volume roots are refused** (`drive/Home/`, `drive/Data/`, `cache/<node>/`) — chowning an entire namespace root has too much blast radius. Pick a one-level-deeper path with `-r` if you need to fan out.\r\n- **`-r` recurses** — every descendant gets the new uid. Confirm directory contents with `files ls` first.\r\n\r\n## Examples\r\n\r\n```bash\r\n# Inspect.\r\nolares-cli files chown drive/Home/Documents/foo.pdf\r\n\r\n# Hand a file to root.\r\nolares-cli files chown drive/Home/Documents/foo.pdf --uid 0\r\n\r\n# Hand an entire directory tree to the default user.\r\nolares-cli files chown drive/Home/Pictures/Trip2024/ --uid 1000 -r\r\n\r\n# Cache namespace.\r\nolares-cli files chown cache/<node>/scratch/build/ --uid 1000 -r\r\n```\r\n\r\n## Agent notes\r\n\r\n- The GET form is cheap — use it before any PUT to show the user the current uid and confirm the change.\r\n- Inside `-r`, partial-failure behavior is per-server — on an error the server may have already changed some descendants. **Do NOT retry blindly**; re-run the GET form on a few sampled paths to see what landed.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `chown is not supported for this namespace` | sync / external / cloud target | Use `files repos` (sync) or LarePass GUI (external) |\r\n| `refusing to chown a volume root` | `drive/Home/` / `drive/Data/` / `cache/<node>/` | Pick a sub-path |\r\n| 403 from server | Server-side ACL rejection | Confirm via `files ls -ld` (when available) or LarePass that the active user has permission |"},{"path":"references/olares-files-cp-mv.md","content":"# files cp / files mv\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files cp --help` and `olares-cli files mv --help`.\r\n\r\nCopy / move one or more entries between locations. Same wire endpoint (`PATCH /api/paste/<node>/`), different `action`. Cross-volume (drive ↔ sync ↔ external) is supported.\r\n\r\n## Safety constraints\r\n\r\n- **Destructive (mutates the server) — confirm intent with the user.** `mv` even more so since the source is removed.\r\n- **`<dst> MUST end with `/` (drop-into-directory mode).** Each `<src>`'s basename is appended; preserves the dir / file marker.\r\n- **Renaming via `cp` / `mv` is not supported** — use `files rename` for in-place basename changes, or rename first and then `mv`.\r\n- **Directory sources require `-r`** (Unix-style refusal otherwise).\r\n- **`mv` source rejects protected names** ([quirk #4](../SKILL.md#4-drivehomepictures-music-movies-downloads-documents-code-cache-data-home-ollama-huggingface-are-system-managed)): `mv drive/Home/Pictures/ ...` is refused because moving would unlink a dir that apps depend on. **`cp` (copy) is intentionally NOT gated** — duplicating bytes (e.g. `cp -r drive/Home/Pictures/ drive/Home/Pictures-Backup/`) preserves the original and is fine.\r\n- **`external/<node>/` destinations are rejected** ([quirk #3](../SKILL.md#3-externalnode-is-a-virtual-volume-listing-layer-read-only)) — point at `external/<node>/<volume>/<sub>/`.\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file → directory.\r\nolares-cli files cp drive/Home/notes.md drive/Home/Documents/\r\n\r\n# Recursive directory copy.\r\nolares-cli files cp -r drive/Home/Photos/ drive/Home/Backups/\r\n\r\n# Multiple sources into a directory.\r\nolares-cli files cp drive/Home/a.pdf drive/Home/b.pdf drive/Home/Archive/\r\n\r\n# Cross-volume (drive → sync repo).\r\nolares-cli files cp drive/Home/notes.md sync/<repo_id>/inbox/\r\n\r\n# Move (mv replaces cp where source removal is intended).\r\nolares-cli files mv drive/Home/notes.md drive/Home/Archive/\r\nolares-cli files mv -r drive/Home/Photos/ drive/Home/Backups/\r\n```\r\n\r\n## Preflight existence check\r\n\r\nRuns BEFORE any PATCH is sent:\r\n\r\n- Each `<src>` MUST exist on the server, AND its trailing-slash form must match the actual file/dir kind.\r\n- `<dst>` MUST exist as a directory on the server. **Create it first with `files mkdir -p` if needed** — `cp`/`mv` does NOT auto-create the destination (the auto-rename quirk #1 would land you in `<dst> (1)`).\r\n\r\nA typo on either side aborts before the server's task queue sees it.\r\n\r\n## Node selection (`--node`)\r\n\r\nEach PATCH carries a `{node}` URL segment. Default cascade:\r\n\r\n1. `--node` override (per invocation)\r\n2. External / Cache `<extend>` (when the destination is `external/<node>/` or `cache/<node>/` — the GUI's `dst_node || src_node || default` cascade)\r\n3. First entry from `/api/nodes/`\r\n\r\nUse `--node` only when you have a specific multi-node deployment with a"},{"path":"references/olares-files-download.md","content":"# files download\r\n\r\n> **Prerequisite:** Read [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) and the parent [`../SKILL.md`](../SKILL.md) first.\r\n> **Flags & wire shape:** `olares-cli files download --help`.\r\n\r\nDownload a file or directory tree from the per-user files-backend.\r\n\r\n## Safety constraints\r\n\r\n- **Without `--resume` or `--overwrite`, the command refuses to clobber an existing local file** — confirm intent with the user before suggesting `--overwrite`.\r\n- `--overwrite` writes to `<dst>.tmp` then renames, so the previous version stays intact until the new bytes land — safe to suggest after the user confirms.\r\n- Directory mode mirrors the remote tree under the local destination; the remote root's own basename becomes the top-level directory (matches the LarePass folder-download UX).\r\n\r\n## Examples\r\n\r\n```bash\r\n# One file into the current directory.\r\nolares-cli files download drive/Home/Documents/report.pdf\r\n\r\n# Same, but pick a different local name.\r\nolares-cli files download drive/Home/Documents/report.pdf ./Q1.pdf\r\n\r\n# Resume an interrupted download (server-driven Range; O_APPEND on the local file).\r\nolares-cli files download drive/Home/Backups/big.tar ./big.tar --resume\r\n\r\n# Recursively pull a folder, 4 files at a time (default).\r\nolares-cli files download drive/Home/Documents/ ./out/ --parallel 4\r\n```\r\n\r\n## Agent notes\r\n\r\n- **`Stat` always lists the parent directory** and finds the leaf in the items array — this is a workaround for [quirk #2](../SKILL.md#2-get-apiresourcesfile-no-trailing-slash-returns-http-500). You never need to suggest \"just GET the file URL\"; the CLI already handles it.\r\n- **Single-file resume** uses server-driven `Range: bytes=<localSize>-` — there is no sidecar progress file. A Ctrl-C + re-run keeps making forward progress as long as the local file is preserved.\r\n- **Directory downloads parallelize FILES, not chunks** — each file's bytes still stream sequentially. `--parallel N` bounds concurrent file fetches.\r\n- Empty subdirectories are mirrored locally so the tree matches even when a directory has no files.\r\n\r\n## Common errors\r\n\r\n| Symptom | Cause | Fix |\r\n|---|---|---|\r\n| `<dst> exists; pass --overwrite or --resume` | Local target already on disk | Confirm with user, then `--overwrite` (replace) or `--resume` (continue) |\r\n| `HTTP 500` from a raw resource URL | Quirk #2 — the bare file URL embeds bytes in JSON | Use this verb (which Stats via parent), not a manual `curl` |\r\n| 401/403 | Token rotation or invalidation | See [`../../olares-shared/SKILL.md`](../../olares-shared/SKILL.md) |"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":1778,"uniquenessScore":42,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T17:09:02.307Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T20:59:04.029Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}