lokictl Reference

Command-line client for the Lowkey Media Server HTTP API.

Ships with the server on Windows, macOS, and Linux

Version 2.31.0

Synopsis

lokictl [global flags] <command> [subcommand] [arguments] [command flags]

lokictl is a client for the Lowkey Media Server HTTP API. Every command maps to one or more API calls. Results are written to standard output as JSON. Errors are written to standard error as a single JSON object. Exit codes are stable. The tool is designed to be driven by scripts and AI agents as well as by people.

Global flags must appear before the command name. Flags that follow the command belong to that command. Run lokictl help to print the command table for the installed version.

Installation

The binary ships alongside the server in every release package.

PlatformLocation after install
Windows (installer)lokictl.exe in the server's install directory, next to media-server.exe
Windows (zip)lokictl.exe at the archive root
Linux (tar.gz)lokictl at the archive root, next to lowkeymediaserver
macOS (dmg)lokictl inside the application bundle, next to the lowkeymediaserver binary
Docker imageNot included. Download a release archive for the host platform and point it at the container with --server.

The binary has no runtime dependencies. Copy it anywhere on the PATH. To build from source, run the following from the repository root:

npm run build:cli
# equivalent:
cd media-server && go build -o lokictl ./cmd/lokictl

Quick Start

  1. Confirm the server is reachable. No credential is required.
    lokictl health
  2. Authorize the CLI. This opens a browser page on the server, signs you in if needed, and stores a long-lived API key in the CLI config file.
    lokictl login
    On a machine without a browser, use a password instead:
    lokictl login --password <password> --username <user>
  3. Verify the stored credential.
    lokictl whoami
  4. List the tasks the server can run, then run one and wait for it.
    lokictl task list -o table
    lokictl task show describe
    lokictl job run describe --apply missing "D:/photos/2024" --wait --timeout 30m
  5. Query the library.
    lokictl media query --tag sunset --exclude-tag blurry
    lokictl media visual "a red car in the snow" --limit 20
    lokictl db query "SELECT COUNT(*) FROM media"

To use a remote server, set LOKICTL_SERVER or pass --server before login. The stored config then carries both the URL and the key.

lokictl --server http://nas.local:10111 login

Connection

Server URL Resolution

The base URL is resolved from the first source that supplies a value, in this order:

  1. --server flag
  2. LOKICTL_SERVER environment variable
  3. server field in the config file
  4. Auto-detected default: http://localhost:<port>

The auto-detected port is LOWKEY_PORT if set, otherwise the port field of the local server's own config.json (in the server data directory), otherwise 10111. A CLI on the same machine as the server therefore follows the server's port without configuration.

The base URL is used verbatim as a prefix. Include the scheme. Do not include a trailing path. A reverse proxy that terminates TLS is supported with an https:// URL.

Config File

lokictl login and lokictl key create --save write a JSON file with two optional fields. The file is created with mode 0600 and its directory with 0700.

PlatformPath
Windows%APPDATA%\lokictl\config.json
Linux$XDG_CONFIG_HOME/lokictl/config.json (default ~/.config/lokictl/config.json)
macOS~/Library/Application Support/lokictl/config.json

Set LOKICTL_CONFIG_DIR to relocate the directory.

{
"server": "http://nas.local:10111",
"token": "lk_..."
}

The file may be written by hand. Either field may be omitted. A missing file is not an error.

Authentication

Every endpoint except /health requires a credential unless the server has Allow Public Access enabled, in which case read-only endpoints accept anonymous requests. The CLI sends its token as Authorization: Bearer <token> on every request. Two kinds of token are accepted:

TokenPrefixLifetimeRevocableObtained by
API keylk_Until revokedYes (lokictl key revoke, or Config → API Keys in the web UI)lokictl login (browser flow), lokictl key create, web UI
JWTnone1 yearNo (rotate the server's JWT secret to invalidate all)lokictl login --password, POST /auth/login

API keys are the recommended credential for the CLI. They are tied to a user, named, listable, and individually revocable.

Browser Authorization

lokictl login
lokictl login --no-browser

The default login is a loopback authorization flow (RFC 8252 with PKCE). The sequence is:

  1. The CLI opens a listener on an ephemeral 127.0.0.1 port and generates a random state value and a PKCE code verifier.
  2. The CLI opens the system browser at <server>/auth/cli/authorize with the port, state, code challenge, and a key name (lokictl@<hostname>). With --no-browser, the URL is printed instead.
  3. If the browser has no server session, the server redirects to /login and back.
  4. The server shows an approval page naming the user, the key name, and the loopback port. Approving redirects the browser to http://127.0.0.1:<port>/callback with a one-time code. Denying redirects with error=access_denied.
  5. The CLI exchanges the code and its verifier at POST /auth/cli/token. The server mints a new lk_ API key owned by the approving user and returns it.
  6. The CLI writes the key and the server URL to the config file and prints the key metadata.

Constraints:

  • The browser must run on the same machine as the CLI. The callback is a loopback address. Over SSH, use password login.
  • The one-time code expires two minutes after approval. The CLI stops waiting for the browser after three minutes and exits 1 with a hint to use --password.
  • The server refuses to authorize the CLI while the temporary admin/admin account is the only user. Complete first-run setup first.
  • Each successful login creates a new key. Old keys remain valid until revoked. Use lokictl key list to audit them.

Password Login

lokictl login --password <password> [--username <user>]
FlagDefaultDescription
--password PPassword. Presence of this flag selects the password flow.
--username UadminUsername.

Calls POST /auth/login and stores the returned JWT in the config file. JWTs issued to API clients are valid for one year and cannot be revoked individually. A warning is printed if the login succeeded as the temporary default admin, since most endpoints return 403 until a real user exists.

API Keys

lokictl key create --name <name> [--username <user>] [--save]
lokictl key list
lokictl key revoke --id <id>
FlagDescription
--name NRequired. Display name for the key.
--username UOwner. Defaults to the user the current credential authenticates as.
--saveWrite the new key to the config file as this CLI's token.
--id NNumeric key id from key list.

The plaintext key is returned once, in the key field of the create response. The server stores only a hash. The list output shows id, owner, name, prefix, creation time, and last-used time.

A key may also be presented in an X-API-Key header. The CLI always uses the Authorization header.

Token Precedence

  1. --token flag
  2. LOKICTL_TOKEN environment variable
  3. token field in the config file
  4. No credential. Only /health and, with public access enabled, read-only endpoints succeed.

Authentication Errors

HTTP statusMeaningAction
401No credential, an expired JWT, or a revoked key.Run lokictl login again, or create a new key.
403 with setup_requiredAuthenticated as the temporary default admin.Complete first-run setup in a browser.
403 (other)Cross-origin or same-origin check failed on the authorize page.Retry the login from a browser on the same machine.

Global Flags

Global flags are recognized only before the command name.

FlagDefaultDescription
--server URLsee resolutionServer base URL.
--token TOKENsee precedenceAPI key or JWT.
-o, --output json|tablejsonOutput format. table renders arrays of flat objects as tab-separated rows with a header line. Values that are not arrays of objects fall back to JSON.
--timeout DUR30sHTTP request timeout as a Go duration (90s, 10m). Streaming commands (--follow, job logs, cleanup log streams) ignore it. Commands with their own --timeout flag use that flag for the wait, not the request.
-q, --quietoffSuppress progress lines on stderr. Result JSON is still printed.
-h, --helpPrint the command table and exit 0.

Flags accept both --flag value and --flag=value.

Output Contract

Standard Output

On success, stdout carries exactly one JSON document, pretty-printed with two-space indentation and without HTML escaping. The document is the server response for the primary API call, or a CLI-composed object for commands that make several calls. Commands that await a job print the final job object.

With -o table, an array of objects is printed as TSV: a header row of the first element's keys in order, followed by any additional keys found in later elements sorted alphabetically, then one row per element. Nested values are JSON-encoded inside the cell.

Standard Error

On failure, stderr carries exactly one JSON object on one line:

{"error": "<message>", "status": 404, "hint": "<suggestion>", "detail": <parsed server body>}
FieldPresent whenDescription
erroralwaysHuman-readable message.
statusHTTP errorHTTP status code returned by the server.
hintknown conditionSuggested next action (for example, re-run login on 401).
detailserver body is JSONThe server's error body, parsed.

Usage errors print {"error": "usage: ..."} followed by the command's flag list. Progress output from --follow, --wait, and streamed task logs is also written to stderr as plain lines, so stdout remains parseable.

Exit Codes

CodeMeaning
0Success.
1Server returned an error status, or a network failure occurred.
2Usage error: unknown command, missing argument, invalid flag, or a destructive command without --yes.
3An awaited job or workflow ended in error or cancelled, or the wait timed out.

Destructive Commands

The following commands exit 2 without contacting the server unless --yes is present:

job clear, media delete, media forget, tag delete, tag unassign-bulk, category delete, workflow delete, deps delete, index delete, index prune.

media cleanup and media thumbnail-cleanup delete data without --yes. Use --dry-run first.

Commands

Each entry lists the command form, its flags, the API endpoint it calls, and the shape of its output. Angle brackets mark required positional arguments; square brackets mark optional ones. Media paths are the absolute paths stored in the library (or s3://bucket/key for object storage roots).

Discovery

CommandEndpointDescription
healthGET /healthServer status, SSE connection stats, and job counts by state. No credential required.
statsGET /api/statsLibrary statistics and metadata coverage. Returns {"ready": false} until the first snapshot has been computed after startup.
whoamiGET /auth/statusThe user the current token authenticates as, plus publicAccess and defaultStartPath.
helpPrint the command table.

login, whoami

See Authentication.

key

CommandEndpointOutput
key create --name N [--username U] [--save]POST /auth/keys{status, key, id, name, username, prefix}. key is the plaintext, shown once.
key listGET /auth/keys{keys: [{id, username, name, prefix, created_at, last_used_at}]}
key revoke --id NDELETE /auth/keys?id=N{status: "deleted"}

job

CommandEndpointDescription
job run <task> [args...] [--field k=v]... [--wait] [--follow] [--timeout D]POST /createCreate a job. See Job Input Syntax.
job list [--state S]GET /jobs/listAll jobs. --state filters client-side: pending, in_progress, paused, completed, cancelled, error.
job get <id>GET /jobs/listOne job object.
job wait <id> [--timeout D]GET /jobs/list (polled)Block until the job is terminal. Prints the final job. Exit 3 on error, cancel, or timeout. --timeout 0 (default) waits forever.
job logs <id>GET /streamStream a running job's stdout lines. Output is not persisted by the server; this works only while the job runs.
job cancel <id>POST /job/{id}/cancelCancel a pending or running job.
job pause <id>POST /job/{id}/pausePause at the next item boundary. Completed items are kept.
job resume <id>POST /job/{id}/resumeResume a paused job.
job copy <id>POST /job/{id}/copyClone into a new pending job. Prints {id, message}.
job remove <id>POST /job/{id}/removeDelete the job record.
job clear --yesPOST /jobs/clearDelete every finished job. Running, pending, and paused jobs are kept. Prints {cleared_count, message}.

Flags for job run:

FlagDescription
--field k=vSend an option value verbatim as --k v, bypassing the server's token splitter. Repeatable. Use this for values containing spaces, quotes, or newlines.
--waitPoll until the job is terminal and print the final job object. Exit 3 on error, cancel, or timeout.
--followSubscribe to the job's stdout over SSE and print lines to stderr while waiting. Implies --wait.
--timeout DMaximum wait. 0 waits forever.

Job object fields: id, command, arguments, input, original_input, state (integer: 0 pending, 1 in progress, 2 completed, 3 cancelled, 4 error, 5 paused), dependencies, workflow_id, created_at, claimed_at, completed_at, errored_at, progress_done, progress_total, output_files, source_files.

task

CommandEndpointDescription
task listGET /tasksEvery registered task with its option schema.
task show <id>GET /tasksOne task's {id, name, options}.

Each option has name, label, type (string, bool, enum, multi-enum, number), optional choices, default, required, and description. An option named x is passed to job run as --x value, or --x alone for booleans.

workflow

CommandEndpointDescription
workflow listGET /workflowsSaved workflows.
workflow get <id>GET /workflows/{id}One saved workflow with its DAG.
workflow create --name N --dag FILE|-POST /workflows/createSave a workflow. See DAG Format.
workflow update <id> [--name N] [--dag FILE|-]PUT /workflows/{id}Replace the name, the DAG, or both. Omitted fields keep their current value.
workflow delete <id> --yesDELETE /workflows/{id}Delete a saved workflow.
workflow run <id> [--input S] [--wait] [--timeout D]POST /workflows/{id}/runInstantiate the DAG as jobs. --input is injected into the root tasks. Prints {ids}, or with --wait, the final job objects. --timeout is per job.
workflow run-adhoc --dag FILE|- [--wait] [--timeout D]POST /workflowRun a DAG without saving it.

media

CommandEndpointDescription
media query [flags]POST /api/media/queryComposable predicate query. See flags below.
media search <text>POST /api/media/searchSubstring match on descriptions.
media similar <path> [--limit N]GET /api/media/similarItems visually similar to a library item, ranked. Default limit 50.
media visual <text> [--limit N]GET /api/media/search/visualText-to-image search over the embedding index. Default limit 50.
media image-search <image-file>POST /api/media/search/imageReverse image search with a local file (uploaded as the request body, 32 MB cap).
media metadata <path>POST /api/media/metadataWidth, height, size, description, transcript, hash, and (video) duration.
media tags <path>POST /api/media/tagsTags on one item with category, weight, and timestamp.
media describe <path> (--text D | --clear)POST /api/media/descriptionSet or clear the description.
media transcript <path> [--text T | --clear]POST /api/media/transcriptRead (no flags), set, or clear the transcript.
media rate <path> [--elo E] [--views N] [--wins N] [--losses N]POST /api/media/ratingRead (no flags) or set Battle Mode rating fields. Only supplied fields change.
media thumbs <path> [--regenerate] [--cache C] [--timestamp S]POST /api/thumbnails, POST /api/thumbnails/regenerateList cached thumbnails, or regenerate one. --cache is thumbnail_path_100, thumbnail_path_600 (default), or thumbnail_path_1200. --timestamp selects the video frame.
media generate <path> --type T [--field k=v]... [--wait] [--follow] [--timeout D]POST /createRun the metadata task for one item. --type is description, transcript, hash, dimensions, or a comma-separated list.
media move <from> <to> [--prefix] [--dry-run]POST /api/media/moveRe-point every database reference after a file or folder was moved on disk. Nothing on disk is touched.
media forget <path> --yesPOST /api/media/forgetDelete every database row that names the path. The file is kept.
media delete <path> --yesPOST /api/media/deleteDelete the item from the library. Same database effect as forget.

Flags for media query:

FlagPredicateDescription
--tag TtagRequire this tag. Repeatable.
--exclude-tag Ttag, excludeExclude this tag. Repeatable.
--path PpathPath substring.
--description DdescriptionDescription substring.
--hash HhashContent hash.
--similar PATHsimilarRank by visual similarity to a library item.
--visual TEXTvisualRank by text-to-image similarity.
--mode AND|ORHow predicates combine. Default AND.
--predicates FILE|-Raw predicate JSON array from a file or stdin. Overrides all predicate flags. Use this for category, orientation, faces, clip, face, per-predicate join, and blended nodes. The schema is documented in the API reference.
--limit NClient-side cap on returned items. Default 50.

Query results are objects with path, width, height, elo, battles, and, when a tag predicate drives the query, tagLabel, weight, and timeStamp. When a visual predicate is present the array is ordered by descending score.

Flags for media move:

FlagDescription
--prefixTreat both arguments as folders and move every item underneath. Matching is segment-aligned: moving 2023 does not affect 2023extra.
--dry-runPerform the move in a transaction and roll it back. Reported counts are exactly what a real run would change.

The move re-points the media row, tag assignments, embeddings, faces, scan markers, and battle log in one transaction. A destination that already belongs to another item returns 409 with the conflicting paths; a move never merges two items.

media cleanup

lokictl media cleanup [--dir D] [--dry-run] [--max-missing-percent N] [--skip-orphans] [--detach] [--timeout D]

Runs the cleanup task as a job and streams its log to stderr. The task forgets media whose files no longer exist, removing every tag, embedding, face, and battle row that names them, then sweeps dangling sidecar rows. Files are scanned, then re-verified before deletion. Offline volumes are skipped. The final job object is printed to stdout with the full log in stdout.

FlagDefaultDescription
--dir Dwhole libraryRestrict the scan to items under this directory.
--dry-runoffReport what would be forgotten without writing.
--max-missing-percent N50Hold back any volume with more than N% of its items missing. Set to 100 to purge a volume that is gone for good.
--skip-orphansoffSkip the final sweep of dangling rows.
--detachoffCreate the job and return its id without waiting.
--timeout D0Maximum wait.

media thumbnail-cleanup

lokictl media thumbnail-cleanup [--dir D] [--dry-run] [--db PATH]... [--no-discover] [--manifest FILE] [--detach] [--timeout D]

Runs the thumbnail-cleanup task as a job. It deletes cached thumbnails that no library still references. Every SQLite database found beside the configured one is treated as a library sharing the cache, so thumbnails owned by a sibling library are kept.

FlagDescription
--dir DScope to thumbnails whose media path is under this folder (recursive). The log names the media path of every removed file.
--dry-runReport only.
--db PATHAdd another library database to the ownership check. Repeatable.
--no-discoverDo not auto-discover sibling databases; use only the configured one and any --db values.
--manifest FILEWrite a TSV of every cached file and which library owns it.
--detachCreate the job and return without waiting.
--timeout DMaximum wait.

index

CommandEndpointDescription
index statusGET /api/index/statusIn-memory index state, active model, media total, count missing for the active model, orphaned vectors, and per-model stored-vector stats.
index modelsGET /api/index/modelsEmbedding model registry with active and indexed flags.
index missing [--model M] [--limit N]GET /api/index/missingPaths without an embedding. --limit 0 returns the count only. Default 100, max 10000.
index get <path> [--model M] [--vector]GET /api/embeddingsStored embedding rows for one item. --vector includes the float array.
index delete <path> [--model M] --yesDELETE /api/embeddingsDelete one item's vectors (all models unless --model).
index prune --yesPOST /api/embeddings/pruneDelete vectors whose media row no longer exists.
index rebuildPOST /api/index/rebuildRebuild the in-memory index for the active model. Large libraries need a longer global --timeout.
index embed [paths...] [--field k=v]... [--wait] [--follow] [--timeout D]POST /createAlias for job run embed.

db

CommandEndpointDescription
db query "SQL" [--arg V]... [--limit N] [--timeout-ms N]POST /api/db/queryRun one read-only statement.
db tablesPOST /api/db/queryTables and views.
db schema [table]POST /api/db/queryCREATE statements for one table or all.
FlagDefaultDescription
--arg VBind value for the next ? placeholder. Repeatable, positional. Sent as a string; SQLite type affinity coerces it.
--limit N1000Row cap. Maximum 10000.
--timeout-ms N5000Server-side statement timeout. Maximum 30000.

The server accepts a single SELECT or WITH statement and opens the connection in query_only mode. The response is {columns, rows, row_count, truncated, elapsed_ms} where rows is an array of arrays.

taxonomy, tag, category

CommandEndpointDescription
taxonomyGET /api/taxonomyEvery category with its tags.
taxonomy --category CGET /api/taxonomy/tagsOne category's tags.
taxonomy categoriesGET /api/taxonomy/categoriesCategory list only.
tag list [--category C]GET /api/tags/listTags with media_count.
tag create <label> --category C [--weight W]POST /api/tagsCreate a tag.
tag delete <label> --category C --yesDELETE /api/tagsDelete a tag and all its assignments.
tag rename <old> <new>POST /api/tags/renameRename a tag.
tag move <label> --category CPOST /api/tags/moveMove a tag to another category.
tag weight <label> --weight WPOST /api/tags/weightSet a tag's sort weight.
tag count <label>POST /api/tags/countDistinct media count for a tag.
tag has <media-path> <label> --category CGET /media/has-tag{has_tag: bool}
tag assign <media-path> <label> --category C [--timestamp S]POST /api/assignmentsTag one item. --timestamp attaches the tag to a video moment.
tag assign-bulk <label> --category C [--timestamp S] [paths... | --stdin]POST /api/assignmentsTag many items in one request.
tag unassign <media-path> <label>DELETE /api/assignmentsRemove a tag from one item.
tag unassign-bulk <label> [paths... | --stdin] --yesDELETE /api/assignmentsRemove a tag from many items.
tag assignment-weight <media-path> <label> --weight W [--timestamp S]POST /api/assignments/weightSet the weight of one assignment.
tag timestamp <media-path> <label> (--from S --to S | --remove --at S)PUT/DELETE /api/tags/timestampMove or remove a video tag timestamp.
category create <label>POST /api/categoriesCreate a category.
category delete <label> --yesDELETE /api/categoriesDelete a category and its tags.
category rename <old> <new>POST /api/categories/renameRename a category.
category count <label>GET /api/taxonomy/category-countDistinct media count for a category.

--stdin reads one path per line. Blank lines are ignored.

deps

CommandEndpointDescription
deps statusGET /api/deps/statusState of every bundled binary, optional tool, and downloadable model.
deps download <model-id> [--wait] [--timeout D]POST /api/deps/models/{id}/downloadStart a download. --wait polls status until installed. Default wait timeout 30m.
deps verify <model-id>POST /api/deps/models/{id}/verifyChecksum every file. Returns {id, files: {relPath: "ok" | error}}.
deps delete <model-id> --yesDELETE /api/deps/models/{id}Remove a downloaded model.

Model ids: wd-eva02-large-tagger-v3, siglip2-base-patch16-224, dinov2-base, yunet, sface, anime-head, ccip, faster-whisper.

config, fs, upload

CommandEndpointDescription
config getGET /api/configActive configuration. Secrets are redacted.
config set --json '{...}'|@file|-GET /api/config then POST /configMerge fields over the current config and save. Redacted secrets and storage roots are never echoed back, so edit credentials in the web Config page.
fs list [path]POST /api/fs/listBrowse a storage root. Empty path lists the roots. Returns {entries, parent, roots}.
fs scan <path> [--recursive]POST /api/fs/scanList media files under a directory. Returns {library, cursor}.
upload <file>... [--dest DIR] [--no-ingest]POST /api/uploadMultipart upload. Files land under uploads/ in the default root, or in --dest (must be inside a root). An ingest job is queued unless --no-ingest.

api

lokictl api <METHOD> <path> [--body JSON|@file|-]

Calls any endpoint with the resolved server URL and credential attached. path is appended to the base URL and may include a query string. --body accepts literal JSON, @file, or - for stdin, and is sent with Content-Type: application/json. A JSON response is pretty-printed; any other body is printed as-is. A non-2xx status exits 1 with the standard error object.

lokictl api GET "/api/people"
lokictl api POST /api/faces/tuning --body '{"thresholdOffset": 0.02}'
lokictl api DELETE "/api/faces/all?confirm=true"

Job Input Syntax

job run joins the task id and every positional argument into one string and sends it as the input field of POST /create. The server splits that string on spaces, honouring double quotes, and interprets it as:

<task-id> [option tokens...] <input>
  • The first token is the task id.
  • The last token is the job's input (a path, URL, or query, depending on the task).
  • Everything between is passed to the task as its argument list. Options are --name value or --flag.
  • A task with no input takes exactly one token.

The splitter has no escape syntax. A token containing a double quote is rejected. Values with spaces must be quoted at the shell level so they reach the CLI as one argument; the CLI re-quotes them. For values containing quotes or newlines, use --field name=value, which is sent out of band and appended as --name value without passing through the splitter.

lokictl job run ffmpeg-scale --width 1280 "C:/vids/in.mp4" --follow
lokictl job run describe --apply all --field prompt="Describe the scene. Include \"mood\"." "D:/photos" --wait

Per-item tasks (describe, transcribe, hash, dimensions, autotag, embed, faces, process) accept a directory, a single file, or a SQL query returning paths as the input. Inspect the task's options with task show; the --apply option (missing or all) controls whether items that already have the result are reprocessed.

Workflow DAG Format

workflow create, workflow update, and workflow run-adhoc read a JSON array of task nodes. Each node has the following fields:

FieldTypeDescription
idstringNode id, unique within the DAG. Used by dependencies.
commandstringTask id.
argumentsstring[]Option tokens, already split.
inputstringJob input. Root nodes receive the workflow's --input when it is given.
dependenciesstring[]Ids of nodes that must complete first. A failed dependency cancels this node.
pos_x, pos_ynumberOptional editor layout. Ignored at run time.
[
{"id": "ingest", "command": "ingest", "arguments": ["--recursive"], "input": ""},
{"id": "hash", "command": "hash", "arguments": ["--apply", "missing"], "input": "", "dependencies": ["ingest"]},
{"id": "embed", "command": "embed", "arguments": [], "input": "", "dependencies": ["hash"]},
{"id": "describe", "command": "describe", "arguments": [], "input": "", "dependencies": ["hash"]}
]

A file whose top-level value is an object with a dag key is also accepted, so the output of workflow get can be fed back to workflow create.

Recipes

Run a task and wait for the result

lokictl task show metadata
lokictl job run metadata --type description --apply all "C:/pics/x.jpg" --wait --timeout 10m
# exit 0 completed; exit 3 error, cancelled, or timeout

Watch a long job's output live

lokictl job run ffmpeg-scale --width 1280 "C:/vids/in.mp4" --follow
# stdout lines stream to stderr; the final job JSON lands on stdout

Forget media that no longer exists

lokictl media cleanup --dry-run
lokictl media cleanup --dir "D:/photos/2019" --dry-run
lokictl media cleanup
lokictl media cleanup --max-missing-percent 100 # a volume that is gone for good

Reclaim thumbnail cache space

lokictl media thumbnail-cleanup --dry-run --manifest C:/tmp/thumbs.tsv
lokictl media thumbnail-cleanup --dir "D:/photos/2019"
lokictl media thumbnail-cleanup

Ask the database anything

lokictl db tables
lokictl db schema media_tag_by_category
lokictl db query "SELECT hash, COUNT(*) n FROM media WHERE hash IS NOT NULL GROUP BY hash HAVING n > 1 ORDER BY n DESC" --limit 50
lokictl db query "SELECT path FROM media WHERE path LIKE ?" --arg "%/vacation/%"

Search the library four ways

lokictl media query --tag sunset --exclude-tag blurry --mode AND
lokictl media visual "a red car in the snow" --limit 20
lokictl media similar "C:/pics/x.jpg"
lokictl media image-search "C:/downloads/some.jpg"

Blended query through raw predicates

echo '[{"type":"similar","value":"C:/pics/x.jpg","nodes":[{"kind":"text","value":"at night","weight":0.5},{"kind":"text","value":"blurry","weight":0.3,"negative":true}]},{"type":"tag","value":"portrait"}]' \
| lokictl media query --predicates - --limit 30

Keep the embedding index healthy

lokictl index status
lokictl index missing --limit 0
lokictl index embed --query "SELECT path FROM media" --wait
lokictl index prune --yes
lokictl --timeout 10m index rebuild

Curate tags in bulk

lokictl tag list -o table
lokictl db query "SELECT path FROM media WHERE path LIKE '%/vacation/%'" -q \
| jq -r '.rows[][]' | lokictl tag assign-bulk vacation --category trips --stdin
lokictl tag unassign-bulk blurry --stdin --yes < paths.txt

Write metadata directly, or have AI generate it

lokictl media describe "C:/pics/x.jpg" --text "Two dogs on a beach"
lokictl media transcript "C:/vids/talk.mp4"
lokictl media generate "C:/vids/talk.mp4" --type transcript --wait
lokictl media rate "C:/pics/x.jpg" --elo 1600

Files moved on disk

lokictl media move "C:/pics/old.jpg" "D:/archive/old.jpg"
lokictl media move "C:/pics/2023" "D:/archive/2023" --prefix --dry-run
lokictl media move "C:/pics/2023" "D:/archive/2023" --prefix
lokictl media forget "C:/pics/gone.jpg" --yes

Run a saved workflow

lokictl workflow list
lokictl workflow run <id> --input "C:/pics/new" --wait

Install a model

lokictl deps status
lokictl deps download siglip2-base-patch16-224 --wait

CI or automation with a scoped key

lokictl key create --name ci
# store the printed key as a secret, then:
LOKICTL_SERVER=https://media.example.com LOKICTL_TOKEN=lk_... lokictl health

Environment Variables

VariableDescription
LOKICTL_SERVERServer base URL. Overrides the config file; overridden by --server.
LOKICTL_TOKENAPI key or JWT. Overrides the config file; overridden by --token.
LOKICTL_CONFIG_DIRDirectory holding config.json.
LOWKEY_PORTRead only for the auto-detected default URL when no server is configured.

Notes and Limits

  • job logs and --follow work only while a job is running. The server streams stdout over SSE and does not persist it, except for the cleanup tasks, whose final job object carries the log.
  • db query accepts one SELECT or WITH statement. Rows are capped at 1000 by default and 10000 at most; truncated is true when the cap was hit.
  • --arg values are strings. SQLite affinity coerces them for comparisons against numeric columns.
  • Job input tokens containing a double quote are rejected. Use --field.
  • stats returns {"ready": false} until the server has finished its first library count after startup.
  • config set cannot write secrets or storage roots, because config get redacts them and the merge would clear them.
  • The CLI sends Accept: application/json so that authentication failures return a 401 JSON body instead of a redirect to the login page.
  • The api command does not add a trailing newline to non-JSON bodies.

See Also