Lynk CLI
lynk is the command-line interface for the Lynk semantic layer platform. Log in once
with your browser, then build your semantic layer and query it over SQL — all from your
terminal.
Install
The CLI is a standalone Python package. Install it as an isolated tool:
# with uv
uv tool install lynk-cli
# or with pipx
pipx install lynk-cli
Quickstart
lynk auth login # opens your browser to authenticate
lynk auth whoami # confirm who you are and your active tenant
lynk build run # build / validate your semantic layer
lynk setup --harness claude # install the Lynk plugins into this project (Claude Code)
Commands
-h works anywhere --help does, on the root app and on every group and command.
-f json always writes exactly one valid JSON document to stdout and nothing else, so it
pipes into jq unconditionally. Everything the human report shows as context is a field
in that document — build local carries its archive summary, and a branch with no
builds is null rather than empty output. Progress and warnings go to stderr.
lynk auth login
Authenticates via your browser using OAuth (Authorization Code + PKCE) and stores your tokens securely in the OS keyring. The active tenant is chosen during the browser login flow, so login is single-tenant.
lynk auth logout
Removes your stored credentials and revokes the session.
lynk auth whoami
Shows the authenticated user, active tenant, and token expiry.
-f json prints the same fields as a JSON document for scripting.
lynk build
A command group for building and inspecting the semantic layer.
lynk build run builds (validates) the semantic layer for a branch and prints a report —
a summary on success, a grouped issue list on failure. Exits non-zero when the build is
invalid (CI-friendly). A commit that was already built reuses its build.
lynk build run # build the current git branch
lynk build run -b main # build a specific branch
lynk build latest # show the report for the latest build (any status)
lynk build status # show just the latest build's status / commit / time
lynk build run -f json # raw JSON instead of the report (any build command)
For run / latest / status the branch defaults to your current git branch. build latest / build status report the latest remote build — builds created by build local are excluded from those lookups by the API.
lynk build local zips your .lynk/ directory at HEAD and uploads it, so it needs no
git integration on the server side. It always builds the branch you have checked out, and
refuses to run while .lynk/ has uncommitted changes — the build is recorded against a
commit sha, so the upload has to be what that sha names. Changes under .lynk/evals/ are
the exception: a build never reads your test cases, so editing them doesn't block one.
Changes elsewhere in the repo don't matter either. --dry-run prints the archive summary
without uploading.
lynk build local # zip and upload .lynk/ at HEAD
lynk build local --dry-run # build the archive but don't send it
lynk build local -f json # raw JSON instead of the report
lynk evals
A command group for running the agent pipeline (explore -> text-to-sql) against a dataset
of test cases and grading the results, so you can catch regressions before they reach
customers. Requires an existing build for the checked-out commit — run lynk build local
or lynk build run first.
Test cases are authored as a flat YAML list under .lynk/evals/: .lynk/evals/dataset.yml
is the recommended single file, but every *.yml/*.yaml in that directory is read and
merged. Each case needs at minimum a name and a domain; everything else (the fields
that describe the question, the expected entities, labels, etc.) is forwarded to the API
as authored — run lynk evals case-schema to see the full field list the backend expects.
# .lynk/evals/dataset.yml
- name: total-revenue-last-quarter
domain: sales
labels: [smoke]
entities: [order]
request: "What was our total revenue last quarter?"
Only name and domain are interpreted by the CLI; the remaining field names are the
backend's, so take them from lynk evals case-schema rather than from this example.
lynk evals case-schema # print the case field schema (authoring reference)
lynk evals validate # check every case: locally, then against the API
lynk evals run # run every case through the pipeline
lynk evals run --dry-run # show which cases would run, without sending anything
lynk evals run -F smoke # only cases labeled "smoke"
lynk evals run -F smoke -F entity:team # only cases labeled BOTH (labels are AND-ed)
lynk evals run -d core -d finance # cases in either domain (domains are OR-ed)
lynk evals run -F smoke -d core # "smoke" cases in the core domain
lynk evals run --max-concurrency 10 # default 5, capped at 20
--filter/-F and --domain/-d are both repeatable: multiple --filter values must
all be present on a case's labels (AND), while multiple --domain values match a case
in any of them (OR). validate also accepts -t/--test-case NAME to check a single case.
run goes further: it validates every selected case against the API before starting, and
refuses to run at all if any case fails — a broken expected_sql would otherwise cost two
agent runs and a judge call, then be scored against SQL that was never valid. A rejected run
creates no run directory. That costs one cheap validation request per case up front.
Both commands check your dataset locally first — case names unique across files, and each
case's domain and entities actually present in your .lynk source — but they react
differently. validate reports every case: a case with a typo fails on its own (and is
not sent), while the rest are still validated against the API, so one mistake never hides
the state of the whole dataset. It exits non-zero if any case failed. run stops before
sending anything if any case has a problem, because it is about to spend real agent time.
The local check is a typo catcher, not a guarantee: the agents see the compiled layer, so a name that exists in your source can still be missing from a build.
Each run writes its artifacts under .lynk/evals/runs/{timestamp}/: run.json (run
metadata and counts) and raw.jsonl (one line per case, with every stage's raw response).
Add .lynk/evals/runs/ to your .gitignore.
A run reports each case as it finishes, with a spinner for the ones still going, so a long run shows progress instead of sitting silent. It ends with a table of the scores each case got, one column per evaluator.
Cases that errored have no scores, so they appear in the list above the table but not in it
— an infra failure is not a low score. run never exits non-zero for low scores or case
errors; those are results, not failures. It exits non-zero only when a run could not start:
no git repo, no dataset, a bad --max-concurrency, a pre-flight problem, or a case that
failed validation. Aggregation across a run is not implemented, so each score is a single
trial — small differences between cases are noise.
lynk marketplace
A command group for the Lynk plugins, distributed as a plugin marketplace. Every command
names its coding harness explicitly — --harness claude is required, and claude is the
only value today — and that harness's own CLI has to be on your PATH, since these
commands drive it.
lynk marketplace install --harness claude (alias: lynk setup --harness claude) sets up
the current project: it downloads the latest marketplace release to
$HOME/.lynk/marketplace, registers it with Claude Code, installs the plugins it declares,
and wires a SessionStart hook into the project's .claude/settings.json so the plugins
stay current automatically. Start a new Claude Code session afterwards to load them.
lynk marketplace install --harness claude # set up this project (same as `lynk setup`)
lynk marketplace update --harness claude # refresh without waiting for a session start
lynk marketplace update --harness claude fetches the marketplace only when it has changed
(conditional request), updates the installed plugins, and tells you to run
/reload-plugins or relaunch when a new version lands. It prints nothing when everything
is already current, and always exits 0 — a failed refresh (offline, expired login,
missing claude) reports a warning and leaves the working installation in place, so it can
never stop a session from starting. --hook is what the hook itself passes: it emits
Claude Code SessionStart JSON on stdout, which Claude Code reads to tell you the plugins
moved.
The marketplace lives at $HOME/.lynk/marketplace — machine-wide, not per project,
because Claude Code keeps a single global marketplace-name → path mapping. The extracted
release version is recorded in $HOME/.lynk/marketplace.release.
Commit .claude/settings.json (the hook plus your enabledPlugins) so everyone on the
repo gets the same setup; keep the generated, machine-specific
.claude/settings.local.json out of git.
Configuration
API token (non-interactive auth)
For CI and automation where a browser login isn't possible, authenticate with an API
token instead of lynk auth login. Provide it with --api-token or the
LYNK_API_TOKEN environment variable — it works on every command:
LYNK_API_TOKEN=lynk_… lynk build run # via env var (best for CI)
lynk --api-token lynk_… build run # global flag (before the command)
lynk build run --api-token lynk_… # per-command flag (after the command)
When set, the token is sent as the x-api-key header on API requests, bypassing the
OAuth session entirely. An API token is a secret, so it is read only from
--api-token / LYNK_API_TOKEN — never from ~/.lynk/config.toml. It takes precedence
over any stored browser login.
Tracing
The CLI reports a trace of each invocation to Lynk, and API calls carry the trace context so server-side spans join the same trace.
Set TRACEPARENT (a standard W3C traceparent) to have the CLI join a caller's trace
instead of starting its own — useful when a CI job or wrapper script invokes lynk and
wants its trace to include the invocation.
Troubleshooting
| Symptom | Fix |
|---|---|
Not logged in / Session expired |
Run lynk auth login (or pass --api-token / set LYNK_API_TOKEN). |
Authentication failed with an API token |
The --api-token / LYNK_API_TOKEN value is invalid or revoked — check it. |
Could not determine the branch |
Pass -b <branch> or run from a git repo. |
evals run/validate fails because no build exists for this commit |
Run lynk build local (or lynk build run) first. |
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file lynk_cli-0.1.3.tar.gz.
File metadata
- Download URL: lynk_cli-0.1.3.tar.gz
- Upload date:
- Size: 121.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e72377b0405c2922265e7c0902e9228ee3bfed6e24d037bb9e7999efb28b52aa
|
|
| MD5 |
24201263487f513094b11ee979466790
|
|
| BLAKE2b-256 |
97647ad77c7c8e8a5119a1cb2d6088c744045f5bc629f054e06c45e14832a4b9
|
File details
Details for the file lynk_cli-0.1.3-py3-none-any.whl.
File metadata
- Download URL: lynk_cli-0.1.3-py3-none-any.whl
- Upload date:
- Size: 66.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
uv/0.12.5 {"installer":{"name":"uv","version":"0.12.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7d8ddcbe702e029a95135e9ca037b9aecc32b9351fb6bb7d7453158054acc1e
|
|
| MD5 |
78f46b110f137951fe5c47660f3e908f
|
|
| BLAKE2b-256 |
a1099e8d964208ee0eb004a212e157bf6ac233a24afa7231cebc62c6f88f153b
|