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.
| Platform | Location 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 image | Not 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
-
Confirm the server is reachable. No credential is required.
lokictl health -
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.
On a machine without a browser, use a password instead:
lokictl loginlokictl login --password <password> --username <user> -
Verify the stored credential.
lokictl whoami -
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 -
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:
--serverflagLOKICTL_SERVERenvironment variableserverfield in the config file- 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.
| Platform | Path |
|---|---|
| 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:
| Token | Prefix | Lifetime | Revocable | Obtained by |
|---|---|---|---|---|
| API key | lk_ | Until revoked | Yes (lokictl key revoke, or Config → API Keys in the web UI) | lokictl login (browser flow), lokictl key create, web UI |
| JWT | none | 1 year | No (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 loginlokictl login --no-browser
The default login is a loopback authorization flow (RFC 8252 with PKCE). The sequence is:
- The CLI opens a listener on an ephemeral
127.0.0.1port and generates a random state value and a PKCE code verifier. - The CLI opens the system browser at
<server>/auth/cli/authorizewith the port, state, code challenge, and a key name (lokictl@<hostname>). With--no-browser, the URL is printed instead. - If the browser has no server session, the server redirects to
/loginand back. - 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>/callbackwith a one-time code. Denying redirects witherror=access_denied. - The CLI exchanges the code and its verifier at
POST /auth/cli/token. The server mints a newlk_API key owned by the approving user and returns it. - 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/adminaccount 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 listto audit them.
Password Login
lokictl login --password <password> [--username <user>]
| Flag | Default | Description |
|---|---|---|
--password P | Password. Presence of this flag selects the password flow. | |
--username U | admin | Username. |
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 listlokictl key revoke --id <id>
| Flag | Description |
|---|---|
--name N | Required. Display name for the key. |
--username U | Owner. Defaults to the user the current credential authenticates as. |
--save | Write the new key to the config file as this CLI's token. |
--id N | Numeric 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
--tokenflagLOKICTL_TOKENenvironment variabletokenfield in the config file- No credential. Only
/healthand, with public access enabled, read-only endpoints succeed.
Authentication Errors
| HTTP status | Meaning | Action |
|---|---|---|
| 401 | No credential, an expired JWT, or a revoked key. | Run lokictl login again, or create a new key. |
403 with setup_required | Authenticated 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.
| Flag | Default | Description |
|---|---|---|
--server URL | see resolution | Server base URL. |
--token TOKEN | see precedence | API key or JWT. |
-o, --output json|table | json | Output 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 DUR | 30s | HTTP 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, --quiet | off | Suppress progress lines on stderr. Result JSON is still printed. |
-h, --help | Print 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>}
| Field | Present when | Description |
|---|---|---|
error | always | Human-readable message. |
status | HTTP error | HTTP status code returned by the server. |
hint | known condition | Suggested next action (for example, re-run login on 401). |
detail | server body is JSON | The 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
| Code | Meaning |
|---|---|
0 | Success. |
1 | Server returned an error status, or a network failure occurred. |
2 | Usage error: unknown command, missing argument, invalid flag, or a destructive command without --yes. |
3 | An 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
| Command | Endpoint | Description |
|---|---|---|
health | GET /health | Server status, SSE connection stats, and job counts by state. No credential required. |
stats | GET /api/stats | Library statistics and metadata coverage. Returns {"ready": false} until the first snapshot has been computed after startup. |
whoami | GET /auth/status | The user the current token authenticates as, plus publicAccess and defaultStartPath. |
help | Print the command table. |
login, whoami
See Authentication.
key
| Command | Endpoint | Output |
|---|---|---|
key create --name N [--username U] [--save] | POST /auth/keys | {status, key, id, name, username, prefix}. key is the plaintext, shown once. |
key list | GET /auth/keys | {keys: [{id, username, name, prefix, created_at, last_used_at}]} |
key revoke --id N | DELETE /auth/keys?id=N | {status: "deleted"} |
job
| Command | Endpoint | Description |
|---|---|---|
job run <task> [args...] [--field k=v]... [--wait] [--follow] [--timeout D] | POST /create | Create a job. See Job Input Syntax. |
job list [--state S] | GET /jobs/list | All jobs. --state filters client-side: pending, in_progress, paused, completed, cancelled, error. |
job get <id> | GET /jobs/list | One 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 /stream | Stream 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}/cancel | Cancel a pending or running job. |
job pause <id> | POST /job/{id}/pause | Pause at the next item boundary. Completed items are kept. |
job resume <id> | POST /job/{id}/resume | Resume a paused job. |
job copy <id> | POST /job/{id}/copy | Clone into a new pending job. Prints {id, message}. |
job remove <id> | POST /job/{id}/remove | Delete the job record. |
job clear --yes | POST /jobs/clear | Delete every finished job. Running, pending, and paused jobs are kept. Prints {cleared_count, message}. |
Flags for job run:
| Flag | Description |
|---|---|
--field k=v | Send an option value verbatim as --k v, bypassing the server's token splitter. Repeatable. Use this for values containing spaces, quotes, or newlines. |
--wait | Poll until the job is terminal and print the final job object. Exit 3 on error, cancel, or timeout. |
--follow | Subscribe to the job's stdout over SSE and print lines to stderr while waiting. Implies --wait. |
--timeout D | Maximum 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
| Command | Endpoint | Description |
|---|---|---|
task list | GET /tasks | Every registered task with its option schema. |
task show <id> | GET /tasks | One 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
| Command | Endpoint | Description |
|---|---|---|
workflow list | GET /workflows | Saved workflows. |
workflow get <id> | GET /workflows/{id} | One saved workflow with its DAG. |
workflow create --name N --dag FILE|- | POST /workflows/create | Save 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> --yes | DELETE /workflows/{id} | Delete a saved workflow. |
workflow run <id> [--input S] [--wait] [--timeout D] | POST /workflows/{id}/run | Instantiate 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 /workflow | Run a DAG without saving it. |
media
| Command | Endpoint | Description |
|---|---|---|
media query [flags] | POST /api/media/query | Composable predicate query. See flags below. |
media search <text> | POST /api/media/search | Substring match on descriptions. |
media similar <path> [--limit N] | GET /api/media/similar | Items visually similar to a library item, ranked. Default limit 50. |
media visual <text> [--limit N] | GET /api/media/search/visual | Text-to-image search over the embedding index. Default limit 50. |
media image-search <image-file> | POST /api/media/search/image | Reverse image search with a local file (uploaded as the request body, 32 MB cap). |
media metadata <path> | POST /api/media/metadata | Width, height, size, description, transcript, hash, and (video) duration. |
media tags <path> | POST /api/media/tags | Tags on one item with category, weight, and timestamp. |
media describe <path> (--text D | --clear) | POST /api/media/description | Set or clear the description. |
media transcript <path> [--text T | --clear] | POST /api/media/transcript | Read (no flags), set, or clear the transcript. |
media rate <path> [--elo E] [--views N] [--wins N] [--losses N] | POST /api/media/rating | Read (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/regenerate | List 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 /create | Run 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/move | Re-point every database reference after a file or folder was moved on disk. Nothing on disk is touched. |
media forget <path> --yes | POST /api/media/forget | Delete every database row that names the path. The file is kept. |
media delete <path> --yes | POST /api/media/delete | Delete the item from the library. Same database effect as forget. |
Flags for media query:
| Flag | Predicate | Description |
|---|---|---|
--tag T | tag | Require this tag. Repeatable. |
--exclude-tag T | tag, exclude | Exclude this tag. Repeatable. |
--path P | path | Path substring. |
--description D | description | Description substring. |
--hash H | hash | Content hash. |
--similar PATH | similar | Rank by visual similarity to a library item. |
--visual TEXT | visual | Rank by text-to-image similarity. |
--mode AND|OR | How 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 N | Client-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:
| Flag | Description |
|---|---|
--prefix | Treat both arguments as folders and move every item underneath. Matching is segment-aligned: moving 2023 does not affect 2023extra. |
--dry-run | Perform 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.
| Flag | Default | Description |
|---|---|---|
--dir D | whole library | Restrict the scan to items under this directory. |
--dry-run | off | Report what would be forgotten without writing. |
--max-missing-percent N | 50 | Hold back any volume with more than N% of its items missing. Set to 100 to purge a volume that is gone for good. |
--skip-orphans | off | Skip the final sweep of dangling rows. |
--detach | off | Create the job and return its id without waiting. |
--timeout D | 0 | Maximum 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.
| Flag | Description |
|---|---|
--dir D | Scope to thumbnails whose media path is under this folder (recursive). The log names the media path of every removed file. |
--dry-run | Report only. |
--db PATH | Add another library database to the ownership check. Repeatable. |
--no-discover | Do not auto-discover sibling databases; use only the configured one and any --db values. |
--manifest FILE | Write a TSV of every cached file and which library owns it. |
--detach | Create the job and return without waiting. |
--timeout D | Maximum wait. |
index
| Command | Endpoint | Description |
|---|---|---|
index status | GET /api/index/status | In-memory index state, active model, media total, count missing for the active model, orphaned vectors, and per-model stored-vector stats. |
index models | GET /api/index/models | Embedding model registry with active and indexed flags. |
index missing [--model M] [--limit N] | GET /api/index/missing | Paths without an embedding. --limit 0 returns the count only. Default 100, max 10000. |
index get <path> [--model M] [--vector] | GET /api/embeddings | Stored embedding rows for one item. --vector includes the float array. |
index delete <path> [--model M] --yes | DELETE /api/embeddings | Delete one item's vectors (all models unless --model). |
index prune --yes | POST /api/embeddings/prune | Delete vectors whose media row no longer exists. |
index rebuild | POST /api/index/rebuild | Rebuild 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 /create | Alias for job run embed. |
db
| Command | Endpoint | Description |
|---|---|---|
db query "SQL" [--arg V]... [--limit N] [--timeout-ms N] | POST /api/db/query | Run one read-only statement. |
db tables | POST /api/db/query | Tables and views. |
db schema [table] | POST /api/db/query | CREATE statements for one table or all. |
| Flag | Default | Description |
|---|---|---|
--arg V | Bind value for the next ? placeholder. Repeatable, positional. Sent as a string; SQLite type affinity coerces it. | |
--limit N | 1000 | Row cap. Maximum 10000. |
--timeout-ms N | 5000 | Server-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
| Command | Endpoint | Description |
|---|---|---|
taxonomy | GET /api/taxonomy | Every category with its tags. |
taxonomy --category C | GET /api/taxonomy/tags | One category's tags. |
taxonomy categories | GET /api/taxonomy/categories | Category list only. |
tag list [--category C] | GET /api/tags/list | Tags with media_count. |
tag create <label> --category C [--weight W] | POST /api/tags | Create a tag. |
tag delete <label> --category C --yes | DELETE /api/tags | Delete a tag and all its assignments. |
tag rename <old> <new> | POST /api/tags/rename | Rename a tag. |
tag move <label> --category C | POST /api/tags/move | Move a tag to another category. |
tag weight <label> --weight W | POST /api/tags/weight | Set a tag's sort weight. |
tag count <label> | POST /api/tags/count | Distinct media count for a tag. |
tag has <media-path> <label> --category C | GET /media/has-tag | {has_tag: bool} |
tag assign <media-path> <label> --category C [--timestamp S] | POST /api/assignments | Tag one item. --timestamp attaches the tag to a video moment. |
tag assign-bulk <label> --category C [--timestamp S] [paths... | --stdin] | POST /api/assignments | Tag many items in one request. |
tag unassign <media-path> <label> | DELETE /api/assignments | Remove a tag from one item. |
tag unassign-bulk <label> [paths... | --stdin] --yes | DELETE /api/assignments | Remove a tag from many items. |
tag assignment-weight <media-path> <label> --weight W [--timestamp S] | POST /api/assignments/weight | Set the weight of one assignment. |
tag timestamp <media-path> <label> (--from S --to S | --remove --at S) | PUT/DELETE /api/tags/timestamp | Move or remove a video tag timestamp. |
category create <label> | POST /api/categories | Create a category. |
category delete <label> --yes | DELETE /api/categories | Delete a category and its tags. |
category rename <old> <new> | POST /api/categories/rename | Rename a category. |
category count <label> | GET /api/taxonomy/category-count | Distinct media count for a category. |
--stdin reads one path per line. Blank lines are ignored.
deps
| Command | Endpoint | Description |
|---|---|---|
deps status | GET /api/deps/status | State of every bundled binary, optional tool, and downloadable model. |
deps download <model-id> [--wait] [--timeout D] | POST /api/deps/models/{id}/download | Start a download. --wait polls status until installed. Default wait timeout 30m. |
deps verify <model-id> | POST /api/deps/models/{id}/verify | Checksum every file. Returns {id, files: {relPath: "ok" | error}}. |
deps delete <model-id> --yes | DELETE /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
| Command | Endpoint | Description |
|---|---|---|
config get | GET /api/config | Active configuration. Secrets are redacted. |
config set --json '{...}'|@file|- | GET /api/config then POST /config | Merge 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/list | Browse a storage root. Empty path lists the roots. Returns {entries, parent, roots}. |
fs scan <path> [--recursive] | POST /api/fs/scan | List media files under a directory. Returns {library, cursor}. |
upload <file>... [--dest DIR] [--no-ingest] | POST /api/upload | Multipart 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 valueor--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" --followlokictl 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:
| Field | Type | Description |
|---|---|---|
id | string | Node id, unique within the DAG. Used by dependencies. |
command | string | Task id. |
arguments | string[] | Option tokens, already split. |
input | string | Job input. Root nodes receive the workflow's --input when it is given. |
dependencies | string[] | Ids of nodes that must complete first. A failed dependency cancels this node. |
pos_x, pos_y | number | Optional 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 metadatalokictl 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-runlokictl media cleanup --dir "D:/photos/2019" --dry-runlokictl media cleanuplokictl 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.tsvlokictl media thumbnail-cleanup --dir "D:/photos/2019"lokictl media thumbnail-cleanup
Ask the database anything
lokictl db tableslokictl db schema media_tag_by_categorylokictl db query "SELECT hash, COUNT(*) n FROM media WHERE hash IS NOT NULL GROUP BY hash HAVING n > 1 ORDER BY n DESC" --limit 50lokictl 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 ANDlokictl media visual "a red car in the snow" --limit 20lokictl 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 statuslokictl index missing --limit 0lokictl index embed --query "SELECT path FROM media" --waitlokictl index prune --yeslokictl --timeout 10m index rebuild
Curate tags in bulk
lokictl tag list -o tablelokictl db query "SELECT path FROM media WHERE path LIKE '%/vacation/%'" -q \ | jq -r '.rows[][]' | lokictl tag assign-bulk vacation --category trips --stdinlokictl 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 --waitlokictl 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-runlokictl media move "C:/pics/2023" "D:/archive/2023" --prefixlokictl media forget "C:/pics/gone.jpg" --yes
Run a saved workflow
lokictl workflow listlokictl workflow run <id> --input "C:/pics/new" --wait
Install a model
lokictl deps statuslokictl 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
| Variable | Description |
|---|---|
LOKICTL_SERVER | Server base URL. Overrides the config file; overridden by --server. |
LOKICTL_TOKEN | API key or JWT. Overrides the config file; overridden by --token. |
LOKICTL_CONFIG_DIR | Directory holding config.json. |
LOWKEY_PORT | Read only for the auto-detected default URL when no server is configured. |
Notes and Limits
job logsand--followwork 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 queryaccepts oneSELECTorWITHstatement. Rows are capped at 1000 by default and 10000 at most;truncatedistruewhen the cap was hit.--argvalues are strings. SQLite affinity coerces them for comparisons against numeric columns.- Job input tokens containing a double quote are rejected. Use
--field. statsreturns{"ready": false}until the server has finished its first library count after startup.config setcannot write secrets or storage roots, becauseconfig getredacts them and the merge would clear them.- The CLI sends
Accept: application/jsonso that authentication failures return a 401 JSON body instead of a redirect to the login page. - The
apicommand does not add a trailing newline to non-JSON bodies.