Thalix VaultDocumentation
Browse documentation
CLI Reference
publishedpartially implementedEdit this page

Command reference

This is the planned MVP command surface. Email-and-password tv login, scoped tv whoami, revoking tv logout, tv version with Git checkout and standalone origin reporting, and tv update [--rollback] for both private-Git checkout installs (#25) and standalone signed installs (#12) are implemented, each covered by real-executable tests. The development-only capabilities mount and CLI call are also covered by contract and local integration tests. tv folder create (any active member owns their own Root Folder, not only an administrator), tv folder list, tv metadata list, tv admin folder list|deactivate, and tv admin metadata create|list|show|update|deactivate are implemented (#5), each covered by real-executable tests. tv file find|update|move and tv file info --revisions are implemented (#7); tv file version add|versions, tv file get --version, tv file archive|restore, and tv file find --status archived|--archived are implemented (#8) -- each covered by real-executable tests. tv admin user list|deactivate|role, tv admin group create|list|add-member|remove-member, tv file access grant|revoke|list, and tv folder access grant|revoke|list are implemented (#9), each covered by real-executable tests. tv admin audit list and the tv file info|get --admin-reason <reason> exceptional-administrator-access override are implemented (#10), each covered by real-executable tests; tv admin user invite remains planned. tv skill install|doctor|update|uninstall|export are implemented (#11), each covered by real-executable tests; tv skill status remains planned. Card #24 and tv update check remain planned.

Every command below is also documented by tv's own built-in help, sourced from the same registry (packages/cli/src/help.ts) main.ts dispatches against: bare tv, tv help, and tv --help print this same command list grouped by section to standard output and exit 0; tv help <command> or tv <command> --help (for example tv help file put or tv file --help) prints that command's or command family's full flag syntax. A usage error prints only the offending command's one-line usage plus Run tv help for commands., never the full list.

Session

tv login [--user <email>] [--password-stdin] [--browser [--no-browser]] [--base-url <url>] [--json]
tv whoami [--json]
tv logout [--json]
tv version [--json]
tv update [--rollback] [--json]

tv login signs in with an invited email and password. An interactive session prompts for the email, then reads the password with echo disabled. --user <email> supplies the email directly; --password-stdin reads the password as exactly one line from standard input. A password is never accepted as a command-line argument, so it can never land in shell history or a process listing; --password-stdin requires --user. Unknown email, wrong password, and a passwordless account all return the identical INVALID_CREDENTIALS error so sign-in attempts cannot enumerate accounts.

With --user and --password-stdin, --json writes one newline-delimited authenticated event (expires-at, scopes, token prefix, correlation ID) and never echoes the password or token. Without them, --json is a usage error.

--browser selects the fallback device-approval flow instead: it creates a one-time device request and opens the browser approval page during an interactive human session. --browser --no-browser leaves the address in terminal output for manual opening; --no-browser requires --browser and cannot be combined with --user/--password-stdin. The production-origin activation address begins with https://vault.thalixinc.ai/cli/activate?code=. The signed-in browser user reviews the displayed code and approves or denies the request while the CLI polls for completion; with --json, this mode writes two newline-delimited events — a safe authorization_pending event with the user code and activation URL, followed by authenticated after approval. Neither event exposes the device code or access token. The device create, poll, and decision routes form an internal handshake, not a public API.

For development, the local Supabase stack and application server must already be running. Select the application origin with --base-url http://localhost:4174 or THALIX_BASE_URL=http://localhost:4174; the command-line option takes precedence. Production login defaults to https://vault.thalixinc.ai.

After authentication, tv login stores the opaque token without printing it. tv whoami resolves the active identity, membership, and role through the scoped token. tv logout revokes the current token and safely clears the local file; an uncertain failure preserves it. File and further token-management behavior for #24 remains planned. The CLI overview owns the behavior summary; Security and authorization and ADR-0005 own storage details; Audit and observability owns audit and correlation details.

tv version [--json] reports the CLI development version. Until it connects to a backend, its backend version is reported as not connected in human output and null in JSON output. When the running executable is linked from a Git checkout, origin additionally reports the checkout's commit, optional tag description, branch, and path ({ "source": "git-checkout", ... }); a standalone release binary instead reports its active binary path and whether a previous version is retained for rollback ({ "source": "standalone", ... }, #12); otherwise origin reports { "source": "unknown" }.

tv update [--rollback] [--json] fast-forwards a private-Git checkout from its configured Git upstream (git pull --ff-only), or fetches, verifies, and atomically activates a signed release for a standalone install, detecting which applies from the running executable itself; --rollback restores a standalone install's one retained previous version and does not apply to a checkout install. Each path refuses with its own stable error code on its own preconditions -- a dirty tree/detached HEAD/missing upstream for a checkout install, an invalid signature/checksum/platform/downgrade/failed health check for a standalone install. See Install and update from a private Git checkout for the checkout path's full refusal-code table and output shape, and Install/Releasing tv for the standalone path (#12).

Errors: AUTHENTICATION_REQUIRED, INVALID_CREDENTIALS, AUTHENTICATION_FAILED, IDENTITY_NOT_ALLOWED, ACCOUNT_DEACTIVATED, RATE_LIMITED, SESSION_STORE_UNAVAILABLE, AUTHORIZATION_PENDING, SLOW_DOWN, AUTHORIZATION_DENIED, DEVICE_CODE_EXPIRED, INVALID_DEVICE_CODE, SCOPE_DENIED, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. See Automation for the full meaning table.

Development diagnostics

tv dev capabilities [--base-url <url>] [--json]

This development-only command calls GET /api/dev/capabilities. The response contains non-sensitive service status, development version, capability names, and a validated correlation ID. Start the server on an explicitly selected development port, for example THALIX_ENABLE_DEV_CAPABILITIES=true bun run dev --port 4174 --strictPort, then pass the matching origin with --base-url http://localhost:4174 or THALIX_BASE_URL=http://localhost:4174. The route is disabled by default and returns 404 without that explicit non-secret flag. The CLI has no implicit backend origin and fails before making a request when neither origin setting is supplied.

Root Folders and metadata (create, browse, rename, move, archive/restore)

tv folder create <name> [--parent <folder-id>] [--json]
tv folder rename <folder-id> <name> --reason <text> [--json]
tv folder move <folder-id> --parent <folder-id> --reason <text> [--json]
tv folder archive <folder-id> --reason <text> [--json]
tv folder restore <folder-id> --reason <text> [--json]
tv folder list [folder-id] [--include-archived] [--limit <n>] [--cursor <token>] [--json]
tv metadata list [--json]

tv folder create <name> lets any active organization member -- not only an administrator -- create a Root Folder they own; the caller becomes that folder's immutable owner and immediately resolves manage access, with no separate grant row ever created for that ownership. A trimmed name only conflicts (FOLDER_NAME_CONFLICT) with another Root Folder the _same_ owner already has, active or previously deactivated; a different owner may freely reuse the exact same name.

tv folder list [<folder-id>] (issue #56) browses only the _immediate_ child folders and files of the selected folder -- never a recursively flattened subtree; descend by re-running it with a child folder's ID. Omitting <folder-id> browses the caller's own default Root Folder. Each entry is a minimal typed row -- kind (folder/file), ID, name, status, an owner-safe owner: { userId, email } projection, and timestamps -- never file bytes, signed URLs, hidden tags/typed metadata, grants, or full audit history. Entries order active folders first, then active files, each group by normalized name then ID; --include-archived also includes archived entries in the same ordering after their active counterparts. --limit/--cursor page with a bounded size and an opaque keyset cursor computed after authorization filtering -- never an offset. An ordinary caller sees only what they can already access; a hidden folder ID and a nonexistent one return the identical anti-enumerating NOT_FOUND. An administrator (admin/global_admin) instead browses any folder in the organization unconditionally, with every call recorded as its own audited admin.content.access event. This replaces the previous no-argument tv folder list behavior of listing every Root Folder the caller owned or was granted -- that whole-catalog discovery view is retired, not aliased; tv admin folder list remains the separate, administrator-only, organization-wide flat catalog.

tv folder create <name> --parent <folder-id> (FOLDERS-04) instead creates a subfolder beneath any root or existing subfolder the caller has live manage access to -- read/write do not qualify. The created record's parentFolderId, immutable rootFolderId (equal to its own ID for a root), and depth (0 for a root, up to 32 for the deepest allowed subfolder) are always present in --json and shown as Parent/Root/Depth in human output; a subfolder inherits its root's owner regardless of who ran the command. Nesting beyond depth 32 fails with FOLDER_DEPTH_EXCEEDED; a parent that is missing, cross-organization/cross-root, or simply not manageable by the caller all fail identically with the anti-enumerating FOLDER_PARENT_NOT_FOUND; a sibling n…

tv folder rename <folder-id> <name> --reason <text> (FOLDERS-05, #53) renames a root or subfolder addressed by ID, for a caller with manage on it; a Root Folder's name is no longer fixed for its lifetime. The new name is checked for sibling case-insensitive uniqueness the same way create is, and that reservation extends to archived siblings (FOLDER_NAME_CONFLICT), so an archived folder's name cannot be reused by an active sibling until the archived one is itself renamed. tv folder move <folder-id> --parent <folder-id> relocates a subfolder to a new parent under the same root only -- a root folder can never move (FOLDER_ROOT_IMMOVABLE), a target parent in a different root is rejected (FOLDER_CROSS_ROOT_MOVE), and self-parenting or parenting under one of the folder's own descendants is rejected (FOLDER_CYCLE_DETECTED); the move re-validates depth (FOLDER_DEPTH_EXCEEDED) and requires manage on the folder, its current parent, and its new parent, applied and audited atomically so concurrent moves serialize to one winning parent. tv folder archive <folder-id> reversibly hides a folder the caller can manage, but only when it is completely empty -- no active or archived child file and no active or archived child folder (FOLDER_NOT_EMPTY) -- and never for the caller's default Root Folder (DEFAULT_FOLDER_PROTECTED); there is still no hard-delete command or HTTP DELETE route for a folder. tv folder restore <folder-id> reverses an archive and requires every folder in the restored folder's parent chain to already be active (FOLDER_PARENT_INACTIVE). All four mutations require a genuine, bounded (500 characters), render-safe --reason (no control characters) -- a missing or unsafe reason is a usage error before the request is ever sent, and the backend independently rejects an unsafe one with FOLDER_REASON_INVALID. Each returns the resulting Root Folder projection and records a before/after audit event (folder.renamed/folder.moved/folder.archived/folder.restored) with the actor, reason, and correlation ID.

Errors: AUTHENTICATION_REQUIRED, ACCOUNT_DEACTIVATED, NOT_FOUND, FOLDER_NAME_INVALID, FOLDER_NAME_CONFLICT, FOLDER_PARENT_NOT_FOUND, FOLDER_DEPTH_EXCEEDED, DEFAULT_FOLDER_PROTECTED, FOLDER_ROOT_IMMOVABLE, FOLDER_CROSS_ROOT_MOVE, FOLDER_CYCLE_DETECTED, FOLDER_NOT_EMPTY, FOLDER_PARENT_INACTIVE, FOLDER_REASON_INVALID, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. See Automation for the full meaning table.

Files (put/info/get/find/update/move/version add/versions/archive/restore implemented, #6/#7/#8)

tv file put <path> [--folder <folder>] [--meta key=value ...] [--description <text>] [--mime-type <type>] [--json]
tv file info <file-id> [--revisions] [--admin-reason <reason>] [--json]
tv file get <file-id> [--version <number>] [--output <path>] [--admin-reason <reason>] [--json]
tv file find [--folder <folder>] [--within <folder-id>] [--name <text>] [--owner <email>] [--mime-type <type>] [--created-after <iso>] [--created-before <iso>] [--updated-after <iso>] [--updated-before <iso>] [--status active|archived] [--archived] [--meta <key><op><value> ...] [--tag <name>[=<value>] ...] [--limit <n>] [--cursor <token>] [--json]
tv file update <file-id> [--set key=value ...] [--unset key ...] --reason <text> [--json]
tv file move <file-id> --folder <folder> [--yes] [--acknowledge-access-change] [--json]
tv file version add <file-id> <path> [--mime-type <type>] [--json]
tv file versions <file-id> [--json]
tv file archive <file-id> [--reason <text>] [--json]
tv file restore <file-id> [--reason <text>] [--json]
tv file tag set <file-id> <name>=<value> --reason <text> [--admin-reason <reason>] [--json]
tv file tag unset <file-id> <name> --reason <text> [--admin-reason <reason>] [--json]
tv file tag list <file-id> [--admin-reason <reason>] [--json]

tv file put streams a local file straight to private object storage through a short-lived, server-minted signed URL (the CLI never sees a Supabase secret key), computing its SHA-256 and byte count as bytes pass through so arbitrarily large files never buffer in memory; the backend independently re-measures the uploaded object from storage and only commits if its own measurement matches what the CLI reported. --folder may be omitted, in which case the upload resolves to the caller's own default Root Folder ("My Files") -- never another member's, and never inferred for any other --folder-accepting command (tv file move/tv folder access grant still require it explicitly). tv file info returns a committed file's current version, metadata, ownership, and lifecycle status, and --revisions additionally attaches its full immutable metadata-change history (most recent first); tv file get downloads a version, verifying the streamed bytes against the backend's declared size and SHA-256 and removing any partial output file on mismatch.

tv file find --within <folder-id> (issue #57) scopes search to that folder plus every nested descendant, computed live from the current hierarchy on every call -- moving a folder changes which results a --within search returns immediately, with no snapshot. Omitting --within retains the existing organization-wide, access-filtered search. --within AND-composes with every other filter (--folder, --name, --owner, --mime-type, date ranges, --status, --meta, and --tag); results are access-filtered exactly as they already are, so a subtree file the caller cannot read is invisible and never consumes a page slot. A folder ID the caller cannot see -- hidden, nonexistent, or in another organization -- fails identically with NOT_FOUND, the same anti-enumerating pattern tv folder list <folder-id> uses; an administrator's subtree search is unconditional and, when it required the override, recorded as its own audited admin.content.access event. The JSON response's withinFolderId echoes back exactly the requested folder ID (null when omitted) and carries no ancestry or hierarchy data.

tv file find --tag <name>[=<value>] (issue #59) repeatably filters by the tags issue #58 introduces: a bare <name> matches any file with an active value for that normalized name; <name>=<value> requires an exact, case-preserving value match (name= with nothing after the = matches only an explicit empty-value tag, never treated as existence-only). Multiple --tag flags AND-compose -- never OR -- and combine with --within, --meta, and every other filter in the same query plan, index-backed against the same (file_id, normalized_name) index tag mutations already maintain (never a JSON or application-side scan); 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 and case-preserving, with no trimming. A tag set or unset takes effect on the very next search, with no caching. The JSON response's tagFilters echoes back exactly the validated --tag filters this page was matched against -- 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.

tv file tag set <file-id> <name>=<value> --reason <text> creates or overwrites the one active value for a normalized tag name (case-insensitive identity, case-preserving display); name= with nothing after the = is an explicit empty-value set, never a no-op. tv file tag unset <file-id> <name> --reason <text> removes the active value entirely -- the two are never conflated. tv file tag list <file-id> shows every active tag as name/value/createdAt/updatedAt. Tags are a permanent, schema-less name=value model, fully separate from the typed metadata above -- no admin-defined schema, no type coercion, and never shown by tv file info's metadataValues (only tv file tag list). Setting or unsetting requires only write access or higher (one level below typed metadata's manage requirement); listing requires only read. Every set/unset writes its own immutable file_tag_revisions row, surfaced through the existing tv file info --revisions/tv file revisions history as a changeType: "tag" entry alongside metadata_values/folder/lifecycle entries -- there is no separate tv file tag revisions command. An administrator with no grant on the file succeeds unconditionally on all three tag commands, exactly like the folder/file read override above; --admin-reason <reason> is accepted and recorded on the audit event when supplied, but is never required.

Errors: FORBIDDEN, ACCOUNT_DEACTIVATED, NOT_FOUND, FOLDER_NOT_FOUND, FOLDER_INACTIVE, FILE_NAME_INVALID, FILE_MIME_TYPE_INVALID, FILE_DESCRIPTION_INVALID, FILE_METADATA_INVALID, FILE_VERIFICATION_INVALID, FILE_ARCHIVED, FILE_COMMIT_CONFLICT, FILE_UPLOAD_FAILED, FILE_CHECKSUM_MISMATCH, FILE_SIZE_MISMATCH, FILE_SEARCH_FILTER_INVALID, FILE_UPDATE_REASON_REQUIRED, FILE_METADATA_REQUIRED, FILE_METADATA_INCOMPATIBLE, TAG_NAME_INVALID, TAG_VALUE_INVALID, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. An administrator without a grant on the file succeeds unconditionally (issue #55) -- --admin-reason <reason> is accepted and recorded on the audit event when supplied, but is never required and never blocks. See Automation for the full meaning table.

ICM templates

tv template list [--json]
tv template show <template-id|unambiguous-name> [--json]
tv template instantiate <template> --name <workspace-name> [--json]
tv template derive <template> --name <template-name> --description <text> [--json]
tv template import (<path|zip> | --starter) --name <template-name> --description <text> [--json]
tv template update <template> <path|zip> --reason <text> [--json]
tv template review <template> --approve --reason <text> [--json]

Templates are ordinary Root Folders classified by reserved tags on their root CLAUDE.md manifest. Import accepts a safe local folder, ZIP archive, or the bundled MIT starter and creates a draft. A distinct administrator must approve the current immutable manifest version before instantiate can create a workspace. derive requires manage access and creates a new draft whose immutable lineageFolderId points to the source template. Updating a template adds new versioned bytes and resets approval to draft; review must then approve that new manifest version. Drafts are addressed by UUID because name lookup lists only approved, visible templates.

Errors: AUTHENTICATION_REQUIRED, ACCOUNT_DEACTIVATED, TEMPLATE_NOT_FOUND, TEMPLATE_NOT_APPROVED, TEMPLATE_REVIEW_CONFLICT, TEMPLATE_INVALID_MANIFEST, TEMPLATE_WALK_FAILED, TEMPLATE_LINEAGE_INVALID, TEMPLATE_SOURCE_CHANGED, TEMPLATE_IMPORT_INVALID, TEMPLATE_GRAPH_TOO_LARGE, TEMPLATE_COPY_FAILED, TEMPLATE_VERSION_CONFLICT, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. See Templates for structure, permissions, safety bounds, and examples.

ICM workspaces and MCP

tv workspace init --name <workspace-name> [--json]
tv workspace import <path|zip> --name <workspace-name> [--json]
tv workspace checkout <workspace-id|unambiguous-name> --output <directory> [--json]
tv workspace commit <checkout-directory> --reason <text> [--json]
tv workspace walk-test <checkout-directory> [--json]
tv workspace lock <workspace-id> <folder-id> [--json]
tv workspace unlock <folder-id> [--json]
tv workspace locks <workspace-id> [--json]
tv mcp serve
tv mcp serve --workspace <root-id> --stage <stage-id>
tv mcp install [--target <claude-code|claude-desktop|cursor|all>] [--json]
tv mcp uninstall [--target <claude-code|claude-desktop|cursor|all>] [--json]
tv mcp doctor [--json]

Workspace checkout/commit is the bounded local edit seam; MCP is a local stdio wrapper around the same commands and stored user session. A stage-scoped MCP server exposes the exact tool allowlist in that stage's direct CONTEXT.md, narrowed by effective inherited folder access. See Workspaces and local MCP for the manifest, atomicity, configuration, audit, and security contracts.

tv mcp install [--target <claude-code|claude-desktop|cursor|all>] registers the local stdio server in each host's config file (~/.claude.json, Claude Desktop's claude_desktop_config.json, ~/.cursor/mcp.json). The entry is a command + args only -- never a credential -- because the server reads the signed-in session at runtime. tv mcp uninstall removes it; tv mcp doctor reports which hosts are registered and whether a session exists, without printing any secret.

File and Root Folder access (implemented, #9)

tv file access list <file-id> [--json]
tv file access grant <file-id> (--user <email> | --group <name>) --level <read|write|manage> [--reason <text>] [--json]
tv file access revoke <file-id> (--user <email> | --group <name>) [--reason <text>] [--json]

tv folder access list <folder> [--json]
tv folder access grant <folder> (--user <email> | --group <name>) --level <read|write|manage> [--reason <text>] [--json]
tv folder access revoke <folder> (--user <email> | --group <name>) [--reason <text>] [--json]

tv file access grant/revoke/list require manage on the file (ownership implies manage); a caller without it sees the same anti-enumeration not_found a missing file would produce. tv folder access grant/revoke/list require the actor to own the Root Folder itself or hold an active manage-level grant on it; a caller without that authority sees the same anti-enumeration NOT_FOUND a nonexistent folder would produce. The grant they create on a Root Folder is owner-bounded (PRD authorization rules) -- it only ever resolves access on files its own grantor owns or manages at the moment of resolution, never on an unrelated owner's files that merely share the Root Folder. See Groups below for how a --group <name> subject is created, owned, and managed -- group creation and membership are never administrator commands.

Errors: FORBIDDEN, NOT_FOUND, USER_ROLE_INVALID, FOLDER_NOT_FOUND, FOLDER_INACTIVE, GRANT_LEVEL_INVALID, GRANT_SUBJECT_NOT_FOUND, AUTHENTICATION_REQUIRED, ACCOUNT_DEACTIVATED, INVALID_REQUEST, INTERNAL_ERROR. See Automation for the full meaning table.

Groups (self-service, user-owned, #54)

tv group create <name> [--json]
tv group list [--json]
tv group invite <group> <email> [--role <manager|member>] [--json]
tv group invites [--json]
tv group accept <invite-id> [--json]
tv group decline <invite-id> [--json]
tv group members <group> [--json]
tv group remove-member <group> <email> [--json]
tv group role <group> <email> <owner|manager|member> [--json]
tv group transfer <group> <email> [--json]

Any active organization member -- not only an administrator -- creates a group with tv group create <name> and becomes its sole owner; a trimmed name only conflicts (GROUP_NAME_CONFLICT) with another group the _same_ owner already has, active or previously deactivated -- a different owner may freely reuse the exact same name. tv group list shows every group the caller belongs to with their own role in each (owner, manager, or member) and its member count.

tv group invite <group> <email> [--role manager|member] (default member; an owner role can never be granted through invite, only through tv group transfer or tv group role) requires owner or manager on the group and targets an existing active Vault member only. Inviting an email that is unknown, belongs to a different organization, or is deactivated returns the identical success envelope a genuine invitation would, but persists nothing -- no invitation row, no membership, and no distinguishing side channel. Resending to an email with an existing pending invitation refreshes that same row instead of creating a duplicate; a group holds at most one pending invitation per email at a time. tv group invites lists invitations addressed to the caller across every group; tv group accept <invite-id> and tv group decline <invite-id> are the only ways an invitation resolves to accepted/declined -- membership is never created any other way.

tv group members <group> works for any active member of that group or an administrator (the administrator path is separately audited, since an administrator is not necessarily a member). tv group remove-member <group> <email> (owner/manager only) immediately revokes the removed member's group-derived file access on the next access check; any grant made to them directly is untouched. tv group role <group> <email> <owner|manager|member> lets an owner set any role, including promoting to owner; a manager may set any role except promoting to owner, and neither an owner nor a manager can demote or remove the group's last remaining active owner (GROUP_LAST_OWNER). tv group transfer <group> <email> (owner-only) is the atomic one-step handoff of sole ownership -- the caller becomes manager and the target, who must already be an active member (GROUP_TRANSFER_TARGET_NOT_MEMBER otherwise), becomes owner -- a group is never observably ownerless mid-operation. Groups are flat (no nesting) and organization-local.

Errors: GROUP_NAME_INVALID, GROUP_NAME_CONFLICT, GROUP_NOT_FOUND, GROUP_MEMBER_NOT_FOUND, GROUP_ROLE_INVALID, GROUP_ALREADY_MEMBER, GROUP_INVITATION_NOT_FOUND, GROUP_INVITATION_ALREADY_RESOLVED, GROUP_LAST_OWNER, GROUP_TRANSFER_TARGET_NOT_MEMBER, GROUP_NAME_AMBIGUOUS, FORBIDDEN, NOT_FOUND, AUTHENTICATION_REQUIRED, ACCOUNT_DEACTIVATED, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. See Automation for the full meaning table.

Agent skills (implemented, #11)

tv skill install --target <claude-code|codex|all> [--scope <user|project>] [--backup-and-replace] [--json]
tv skill doctor [--target <claude-code|codex|all>] [--json]
tv skill status [--json]
tv skill update [--target <claude-code|codex|all>] [--backup-and-replace] [--json]
tv skill uninstall --target <claude-code|codex|all> [--scope <user|project>] [--json]
tv skill export --target chatgpt [--output <path>] [--json]

A portable, versioned thalix-vault Agent Skill ships inside the CLI's own Git checkout (packages/cli/skill/thalix-vault/) and is never downloaded separately. tv skill install --target <claude-code|codex|all> copies it into --scope user (default; $HOME/.claude/skills/ for Claude Code, $HOME/.agents/skills/ for Codex) or --scope project (the Git repository detected from the current directory; SKILL_NO_REPOSITORY if none is found) atomically -- every payload file and a new .tv-skill-manifest.json (skill version, target, scope, and a SHA-256 per file) are written to a hidden sibling staging directory first, and the install directory is only ever touched by the final rename. Re-running install on an already-current, unmodified install is a no-op (already-up-to-date); install onto a location holding files tv did not put there, or onto an install with a file changed outside tv, is refused (SKILL_MODIFIED_FILES) unless --backup-and-replace is given, in which case the previous directory contents are preserved as a timestamped sibling rather than deleted. tv skill update takes no --scope; it re-syncs every install it finds for the requested target(s) across both scopes and applies the identical modified-file refusal/--backup-and-replace rule to each. tv skill uninstall removes only the files its manifest proves it placed, then the manifest, then any directory left empty by that removal -- a file the user placed there themselves is never touched, and an install directory holding one survives uninstall. tv skill doctor reports, for every (target, scope) location it can resolve, whether tv is on PATH, whether a local session exists (existence only, never its contents), and each install's status (healthy, outdated, modified, not-installed, or corrupt) without ever reading or printing credentials. tv skill export --target chatgpt [--output <path>] writes a command-composition brief for a ChatGPT browser/app chat, which explicitly states it cannot execute tv itself. tv skill status is a lighter-weight alias for tv skill doctor: it reports only whether tv is on PATH, whether a local session exists, and the bundled skill version -- skipping the per-location install inspection.

Errors: SKILL_BUNDLE_NOT_FOUND, SKILL_NO_REPOSITORY, SKILL_NOT_INSTALLED, SKILL_MODIFIED_FILES, SKILL_MANIFEST_CORRUPT. See Automation for the full meaning table.

CLI updates

Implemented for both distribution paths -- private-Git checkout (#25) and standalone signed installer (#12):

tv update [--rollback] [--json]
tv update check [--json]

tv update detects which install produced the running executable and routes to the matching flow: a private-Git checkout fast-forwards from its configured upstream (git pull --ff-only, unchanged behavior, see Install and update from a private Git checkout); a standalone binary fetches, verifies, and atomically activates its channel's signed release, per Install and Releasing tv. tv update --rollback restores the one retained previous version for a standalone install (RELEASE_NO_PREVIOUS_VERSION if none is retained); a private-Git checkout has no retained version to restore and refuses --rollback with UPDATE_ROLLBACK_NOT_SUPPORTED. This is tv update --rollback, not the originally sketched tv update rollback subcommand -- kept consistent with the flag-based --channel/--version shape a standalone install's channel/version selection would also take. tv update check is a dry-run availability check: it reports whether a newer version is available without downloading or replacing any bytes (a standalone install fetches and verifies the manifest but never downloads the artifact; a Git checkout fetches but never pulls).

tv admin folder list, tv admin folder deactivate, plus the full tv admin metadata group, tv admin user list|deactivate|role|invite|invites|uninvite|reactivate, tv admin group list|members|remove-member|transfer, and tv admin audit list are implemented (#5, #9, #10, #45, #54; global_admin/admin only). Creating a Root Folder is no longer part of this administrator group -- see tv folder create above. Group creation and membership are never administrator commands -- see Groups.

tv admin user invite <email> [--role <admin|user>] [--expires-days <n>] [--json]
tv admin user invites [--json]
tv admin user uninvite <email> [--json]
tv admin user reactivate <email> [--json]
tv admin user list [--json]
tv admin user deactivate <email> [--json]
tv admin user role <email> <admin|user> [--json]

tv admin folder list [--folder <id>] [--json]
tv admin folder deactivate <name> [--json]

tv admin group list [--json]
tv admin group members <group> [--json]
tv admin group remove-member <group> <email> [--json]
tv admin group transfer <group> <email> [--json]

tv admin metadata create <key> --label <label> --type <text|number|boolean|date|choice> [--required] [--choice <value> ...] [--json]
tv admin metadata list [--json]
tv admin metadata show <key> [--json]
tv admin metadata update <key> [--label <label>] [--required <true|false>] [--add-choice <value> ...] [--remove-choice <value> ...] [--json]
tv admin metadata deactivate <key> [--json]

tv admin audit list [--actor <email>] [--action <prefix>] [--target-type <type>] [--target-id <id>] [--result success|denied] [--correlation <id>] [--after <iso>] [--before <iso>] [--limit <n>] [--cursor <token>] [--json]
tv admin reconcile [--stale-after <seconds>] [--json]

There is no tv admin folder update: member-facing lifecycle commands rename folders by ID and move subfolders within their root, while the administrator surface provides organization-wide listing and deactivation. tv admin folder deactivate <name> is idempotent and never deletes the row; a member's default Root Folder refuses both deactivation and archive (DEFAULT_FOLDER_PROTECTED).

tv admin metadata create <key> fixes the field's key and --type (text, number, boolean, date, or choice) permanently; only --label, --required, and -- for a choice field -- its --add-choice/--remove-choice set may change later through tv admin metadata update, which accepts no --type flag at all. --choice/--add-choice values are trimmed and de-duplicated. tv admin metadata deactivate <key> is idempotent and never deletes the definition. tv admin metadata list and tv admin metadata show <key> show every status to an administrator; the unprivileged tv metadata list above shows active definitions only.

tv admin user invite <email> [--role <admin|user>] [--expires-days <n>] sends an invitation email to a new or existing-but-unconsumed email address; by default it grants user role and expires in 7 days. Resending an invitation to an already-pending email reuses the same database row, refreshing its expiry and sending a fresh email. tv admin user invites lists all invitations (pending, accepted, revoked, expired) with their current status and expiry. tv admin user uninvite <email> revokes a pending invitation; revoking an already-accepted or already-revoked invitation is refused. tv admin user reactivate <email> restores a deactivated member's ability to sign in (reactivation does not restore sessions deleted during deactivation). Both invite and reactivate follow the identity module's hierarchy rule: a global_admin may act on anyone; an admin may only invite users or reactivate users (not admins).

tv admin group list shows every group in the organization regardless of who owns it, including each group's owner count and whether it needs recovery (needsOwnerRecovery, meaning zero active owners). tv admin group members <group> inspects any group's membership even when the administrator is not a member themselves -- always audited. tv admin group remove-member <group> <email> is a moderation action that bypasses the normal owner/manager requirement. tv admin group transfer <group> <email> is recovery-only: it promotes an existing active member to owner without demoting anyone, and only applies to a group with zero active owners; deactivating a group's sole remaining owner while other active members exist is itself refused with GROUP_OWNER_DEACTIVATION_BLOCKED until ownership is transferred through tv group transfer.

tv admin audit list is the unified retrieval surface across every audit domain -- identity, device/token authentication, Root Folder/metadata, files, access grants, search, cross-domain authorization denials, and exceptional administrator content access -- projected to one common envelope (id, occurredAt, actorUserId, organizationId, action, targetType, targetId, result, correlationId, details) and returned as a stable JSON page with --json, or a human table otherwise. --actor <email>, --action <prefix>, --target-type <type>, --target-id <id>, --result success|denied, --correlation <id>, and --after/--before <iso> compose (all supplied filters apply together); --limit <n> (1-100, default 25) and an opaque --cursor <token> from a prior page's nextCursor walk results newest-first with zero duplicates or gaps, never an OFFSET. details never contains a token, secret, signed URL, or file byte -- every value passes through a fixed allowlist before storage. Restricted to global_admin/admin; a plain member's attempt is itself denied and recorded (#10).

tv admin reconcile [--stale-after <seconds>] reports every pending upload older than the given threshold (default 3600 seconds) and, for each, whether an object actually exists in object storage -- a read-only report, never a mutation. It is global_admin/admin only; a user-role token is refused with FORBIDDEN. See Stale upload reconciliation for the cleanup runbook.

Errors (Root Folders/metadata/discovery): FORBIDDEN, ACCOUNT_DEACTIVATED, NOT_FOUND, FOLDER_NAME_INVALID, FOLDER_NAME_CONFLICT, FOLDER_PARENT_NOT_FOUND, FOLDER_DEPTH_EXCEEDED, DEFAULT_FOLDER_PROTECTED, METADATA_KEY_INVALID, METADATA_KEY_CONFLICT, METADATA_LABEL_INVALID, METADATA_TYPE_INVALID, METADATA_CHOICES_INVALID, METADATA_CHOICE_MUTATION_INVALID, METADATA_INACTIVE, AUTHENTICATION_REQUIRED, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. Errors (users and invitations): FORBIDDEN, ACCOUNT_DEACTIVATED, NOT_FOUND, ALREADY_MEMBER, INVITATION_EMAIL_INVALID, INVITATION_EXPIRY_INVALID, INVITATION_ALREADY_REVOKED, GROUP_OWNER_DEACTIVATION_BLOCKED, AUTHENTICATION_REQUIRED, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. Errors (group administration): GROUP_NOT_FOUND, GROUP_MEMBER_NOT_FOUND, GROUP_TRANSFER_TARGET_NOT_MEMBER, FORBIDDEN, ACCOUNT_DEACTIVATED, AUTHENTICATION_REQUIRED, INVALID_REQUEST, INVALID_BACKEND_RESPONSE, INTERNAL_ERROR. See Automation for the full meaning table.

Global conventions

  • Commands supporting structured output accept --json.
  • Commands requiring confirmation may accept --yes for explicit non-interactive use.
  • File and administrative errors include a correlation ID suitable for audit or support lookup.
  • Every command's JSON success and error documents carry schemaVersion: 1. See Automation for the envelope shape, correlation IDs, and the full error-code table.

Exit codes

Exit codeMeaning
0Success.
1Unexpected local/internal failure without a mapped CliError.
2Usage or INVALID_REQUEST error; malformed local input never reaches the network.
3Authentication and mapped backend/domain errors (NOT_FOUND, FORBIDDEN, conflicts, validation failures, and similar stable codes).
130The interactive tv login prompt was cancelled before completion.