Thalix VaultDocumentation
Browse documentation
Users
publishedimplementedEdit this page

Working with files

This page is the task-oriented guide to the file commands. The Command reference is the authoritative syntax, permission, and exit/error source for every command below; this page explains the "why" behind their less-obvious behavior. File lifecycle covers the underlying upload/version/revision design if you want the implementation detail.

Upload (tv file put)

tv file put ./report.pdf --folder "Finance" --meta reviewed=false --description "Q3 draft"

Bytes stream straight to private object storage through a short-lived signed URL computed server-side; the CLI computes SHA-256 and byte count as bytes pass through, and the backend independently re-measures the committed object from storage — a mismatch fails the commit with FILE_VERIFICATION_INVALID rather than silently accepting whatever the client claimed. This means arbitrarily large files never buffer fully in memory on either side. --meta key=value accepts one pair per active custom metadata field defined by tv metadata list; an unknown key or wrong-typed value fails locally with FILE_METADATA_INVALID before any upload begins.

Inspect (tv file info / tv file get)

tv file info <file-id> --revisions
tv file get <file-id> --version 2 --output ./report-v2.pdf

tv file info returns the current version, metadata, owner, and lifecycle status; --revisions additionally attaches the file's full immutable metadata-change history, most recent first, so you can see exactly who changed what and when without a separate command. tv file get --version <n> downloads a specific historical version instead of the current one; the download re-verifies the streamed bytes' size and SHA-256 against what the backend declares, and removes any partial output file on a mismatch rather than leaving a corrupt file on disk.

Find (tv file find)

tv file find --folder "Finance" --meta reviewed=true --created-after 2026-01-01
tv file find --archived --folder "Finance"
tv file find --within 11111111-1111-4111-8111-111111111111 --name invoice
tv file find --tag priority=urgent --tag customer

Filters compose: --folder, --within, --name, --owner, --mime-type, the --created-*/--updated-* date-range pairs, --status active|archived, repeatable --meta <key><op><value> (operators =, !=, >, >=, <, <=), and repeatable --tag <name>[=<value>] all apply together. Authorization is part of the query itself — the backend never returns a row you cannot see for the CLI to filter out afterward, so a search never leaks a file's existence, name, owner, or metadata to someone without at least read access. --archived (or --status archived) requires manage access to see a given archived file, unlike an active file's read-level visibility — see Access and delegation.

--within <folder-id> (issue #57) narrows a search to one folder plus every folder nested beneath it, computed from the current hierarchy every time you run the command — moving a folder in or out of that subtree changes what a --within search returns on your very next call, never a stale snapshot. Omit it and search stays organization-wide, exactly as before. A folder ID you cannot see — because it does not exist, belongs to another organization, or was never shared with you — fails with the same NOT_FOUND a nonexistent folder would, so you can never use --within to discover folder structure you are not authorized to see. Every --json response echoes the requested folder ID back as withinFolderId (null when you omit --within) so a script can confirm which subtree a page of results came from, without exposing any parent chain or descendant list. Because results are re-evaluated live on every page, a script paginating with --cursor must resend --within (and every other filter) on each request — nothing is remembered from the previous page.

--tag <name>[=<value>] (issue #59) repeatably filters by the tags Tags below describes: a bare <name> matches any file with an active value for that normalized name; <name>=<value> requires an exact, case-preserving value match — --tag priority= (nothing after the =) matches only an explicit empty-value tag, never treated as existence-only. Repeat --tag to AND multiple filters together (never OR); it composes with --within, --meta, and every other filter above, and a file matching more than one requested tag still returns exactly once. Tag name comparison is case-insensitive, identical to tv file tag set/unset; value comparison is exact, case-preserving, and untrimmed. A tag set or unset on a file changes search results on your very next call, with no caching. Every --json response echoes back exactly the validated --tag filters this page was matched against as tagFilters — never a matched file's complete tag list (run tv file tag list <file-id> for that); human output shows the same as a Tags: ... summary line.

Update metadata (tv file update)

tv file update <file-id> --set reviewed=true --unset draft-notes --reason "Sign-off complete"

--reason is required on every metadata change, and every change — set or unset — produces one more immutable revision row (visible later via tv file info --revisions), never an in-place overwrite. A metadata update on an archived file is refused with FILE_ARCHIVED. Restore the file first (tv file restore) if you need to reclassify it. --set and --unset cannot name the same key in one call.

Tags (tv file tag set / unset / list)

tv file tag set <file-id> priority=urgent --reason "flagging for review"
tv file tag unset <file-id> priority --reason "resolved"
tv file tag list <file-id>

A tag is a permanent, free-form name=value pair you attach directly to a file -- no administrator setup, no schema, no type. This is deliberately separate from the typed metadata above (see Glossary): tags never appear in tv file info's metadataValues, and typed metadata never appears in tv file tag list. A file has at most one active value per normalized tag name -- names are case-insensitive for identity (Priority and priority are the same tag) but case-preserving for display (whichever casing you last set wins), 1-64 characters, and restricted to a render-safe grammar (letters, digits, spaces, and _.:-, starting with a letter or digit). Values are trimmed, case-preserving text, 0-500 characters, and control-character-free.

tv file tag set <id> name= (nothing after the =) is an explicit empty-value set -- the tag exists with an empty string value. It is not the same as removing the tag: only tv file tag unset <id> name does that. Setting the exact same name and value again is a no-op (no new revision); a display-casing-only change (Priority re-set as priority with the identical value) still counts as a real change. Setting or unsetting requires only write access or higher -- one level below the manage access typed-metadata changes require (see Access and delegation); viewing with tv file tag list requires only read.

Every set or unset writes its own immutable revision, visible through the same tv file info --revisions/tv file revisions history as typed-metadata changes -- look for changeType: "tag" entries alongside metadata_values/folder/lifecycle ones. There is no separate tv file tag revisions command. A tag change on an archived file is refused with FILE_ARCHIVED, the same rule metadata updates follow; tv file tag list still works on an archived file.

Filter tv file find by tag with --tag <name>[=<value>] (see Find above) — no separate search command exists for tags.

Move between Root Folders (tv file move)

tv file move <file-id> --folder "Legal" --acknowledge-access-change

Moving a file to a different Root Folder immediately recalculates which Root Folder-scoped grants apply to it — access can both appear and disappear as a side effect, since Root Folder grants are owner-bounded (see Access and delegation). tv file move warns and requires --yes or --acknowledge-access-change before proceeding whenever the move would actually change someone's effective access; a move that changes nothing about access proceeds without confirmation.

Content versions (tv file version add / tv file versions)

tv file version add <file-id> ./report-v2.pdf
tv file versions <file-id>

version add appends a new immutable, sequentially-numbered content version without touching metadata; versions lists every version's number, size, checksum, uploader, and timestamp. Adding a version requires only write access (lower than the manage access metadata changes require) — see Access and delegation. Adding a version to an archived file is refused with FILE_ARCHIVED, the same rule as metadata updates.

Archive and restore

tv file archive <file-id> --reason "Superseded by 2026 Q3 report"
tv file restore <file-id> --reason "Reopened for audit"

Archiving hides a file from default tv file find results without deleting any history; restoring reverses that. Both require manage access and are idempotent — archiving an already-archived file (or restoring an already-active one) succeeds with no new revision and no audit event, rather than erroring. There is no permanent-delete command anywhere in tv: the database itself rejects any attempt to delete a file or a committed version row, for every role including the service role (see Trust boundaries).

Validation you'll actually hit

  • --meta/--set values are checked against the field's declared type (text, number, boolean, date, or choice) and, for choice, against its current allowed values — FILE_METADATA_INVALID names the offending key. A required field left unset on put fails with FILE_METADATA_REQUIRED.
  • A metadata value set before a choice field's allowed values were narrowed keeps failing re-validation as FILE_METADATA_INCOMPATIBLE until you set it to a currently-allowed value.
  • --folder must name a currently active Root Folder; a deactivated or misspelled name fails with FOLDER_NOT_FOUND/FOLDER_INACTIVE before any upload or move happens.

Continue with Access and delegation to share a file, or JSON automation for scripting every command above.