CLI reference
Use the same lurq engine straight from your terminal.
The lurq engine is usable standalone from the terminal, with the same scoring and
retrieval the MCP tools expose. Add --json to the query and check commands for
machine-readable output.
Run lurq setup first. The query commands read whichever index is configured: the hosted service
via your stored API key, or your own Postgres if DATABASE_URL is set and no key is. A key wins
over DATABASE_URL: otherwise, running lurq inside your own Postgres-backed app would point
package lookups at that app's database. Operators holding both can force the local index with
LURQ_LOCAL=1. See Self-hosting.
Querying the index
lurq verify lodahs
lurq evaluate zod
lurq compare drizzle-orm prisma typeormNot sure which command answers your question? Ask in plain words:
lurq can "check a major upgrade"can searches the catalog that ships with the CLI, so it works offline and
before setup.
API surfaces
What a version actually exports, and what moved since the version you know:
lurq usage zod # the current export surface
lurq usage zod --known 3.22.4 # …plus the exact delta from the version you know
lurq usage react --target 19.0.0 # pin the target version
lurq versions react # the stored version timeline (local index only)--known is the one worth remembering: pass the version your model thinks it
knows and get the precise added / removed / renamed / changed list for the
version you're actually installing.
Whole stacks and projects
lurq compat next react react-dom # do these install together?
lurq compat next react --pin next=15.5.4 # check exact versions
lurq audit # everything this project depends on
lurq audit --project-only # …ignoring user-level agent configs--pin requires an exact published version. A range would silently resolve to
latest and report the wrong version as pinned, so it's rejected outright.
audit reads the project's package.json, lockfile, and the MCP servers
configured for it, and reports what is outdated, deprecated, carries an advisory
for the installed version, or has drifted. Anything it could not assess is
counted as such, never reported clean.
Upgrades
These run where your code is — a laptop or a CI runner:
# what's behind, and what each upgrade removes from its API (needs your lurq key)
lurq upgrade-plan . --json > lurq-plan.json
# do those upgrades remove symbols YOUR code references? (needs nothing)
lurq check-upgrade . --plan lurq-plan.json --exit-code
# or name the upgrades directly, no plan needed
lurq check-upgrade . --upgrade commander@11.1.0..12.1.0 --upgrade zod@3.23.8..4.0.0
# and write the changes that need no judgement
lurq fix .check-upgrade compares both versions' npm tarballs against the symbols your
codebase actually imports, and reports them at file:line. It needs no API key,
no network to lurq, and no test suite — which is the point: it catches
breakage in code no test covers. --exit-code exits 1 when the report is not
safe, for use as a CI gate. --upgrade takes pkg@from..to, and repeats.
When the package itself proves a rename (the old version exported both names from the same function), the replacement is printed next to the call site:
BLOCKING cookie 1.1.1 → 2.0.1
Removes 1 symbol(s) your code references:
· cookie.parse → parseCookie src/session.ts:239It also type-checks the files that import each package, twice: once against the
old version's type definitions and once against the new one's, straight from the
tarballs, with nothing installed. Only errors the upgrade introduces are
reported, so a renamed option or a narrowed parameter shows up at file:line
even though the runtime exports never changed. It uses your project's own
typescript when it has one, needs a tsconfig.json, and skips packages that
ship no type definitions of their own. --no-types turns it off, for a faster,
runtime-only check.
What it reports:
| Severity | What changed | Real example |
|---|---|---|
| blocking | a symbol your code uses is gone | cookie.parse, removed in cookie 2 |
| blocking | a deep import is no longer offered | require('uuid/v4') on uuid 8 |
| blocking | the package went ESM-only, and your code calls what require() returned or reads a property it no longer exports | fetch(url) on node-fetch 3, chalk.red on chalk 5 |
| warning | a call passes an argument count the new version no longer accepts. Calls that still fit are not reported | src/log.ts:31 passes 1 |
| warning | the type check finds a new compiler error | TS2554 Expected 2-3 arguments on zod 4 |
| warning | require() of a now-ESM package, which throws on Node before 20.19 / 22.12. Skipped when your engines.node rules those out | execa 9 |
| warning | a new engines.node or peer range this project does not meet | react-dom 19 with react 18 installed |
Downloads are retried on registry errors and checked against the integrity hash
the registry publishes. A version range (^2.0.0) is refused rather than
guessed at, and a scan that stops at its file limit says so.
An upgrade it could not establish is reported as unverified and is never
counted as safe.
--report sends the result to your lurq dashboard as well (it needs an API key,
from --api-key or LURQ_API_KEY). It's opt-in and never changes the outcome of
the check.
Writing the fix
check-upgrade tells you what breaks. lurq fix writes the part of it that
needs no judgement, and hands the rest to your agent with the facts it needs.
# work out what moved, and show the diff (one lookup needs your lurq key)
lurq fix .
# name the versions yourself, and it needs nothing
lurq fix . --upgrade cookie@1.1.1..2.0.1
# write the files
lurq fix . --apply
# file every finding as a GitHub code scanning alert
lurq fix . --sarif lurq.sarifWith neither --plan nor --upgrade it works out what moved by itself, so you
never have to look up a version to pass one in. That single lookup is the only
part that talks to lurq, and it applies your repo's selection policy — an
upgrade the policy holds is not attempted. Naming the versions yourself keeps
the entire run offline, exactly like check-upgrade.
Nothing is written until --apply; the default prints a unified diff, and
git apply accepts it:
Would write 2 file(s) (pass --apply):
rename parse to parseCookie — src/alias.ts, src/app.ts
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,4 +1,4 @@
-import { parse } from 'cookie';
+import { parseCookie } from 'cookie';It bumps the declared range in the same run. Rewriting the imports and leaving
"cookie": "^1.1.1" in package.json is a tree that does not build: the code
now calls an export the installed version lacks, and the next npm install
faithfully reinstalls the old one. Every manifest declaring the package is
bumped, across workspaces, keeping the operator you wrote (^, ~, or an exact
pin). A range that already admits the new version is left alone rather than
narrowed, and one whose intent is a judgement — >=1.0.0 <2, 1 || 2, 1.x —
is reported instead of guessed at.
Only a rename the package itself proves is ever written — the old version
exported both names from one declaration and the new one still exports the
survivor. That makes the rewrite mechanical rather than a guess. Whether a given
upgrade qualifies depends on the exact versions: cookie 1.1.1 → 2.0.1 does,
because 1.1.x shipped parseCookie alongside parse; 1.0.2 → 2.0.0 does not.
Everything else becomes a brief instead of an edit, carrying the exports the new version actually ships with their kinds and arities — the facts an agent doing the rewrite cannot look up, since it has no network and its training data is the stale source this is correcting. Two candidates is a choice, not a fix, so it is briefed too.
Refusals are per file. A file where the imported name is declared twice can't have its call sites attributed to the import, so that file is briefed while the rest are still rewritten:
1 change(s) lurq will not make for you:
parse was renamed to parseCookie, but 1 file(s) need a person:
src/shadowed.ts (parse is declared more than once)An aliased import is rewritten once, at the import, because every call site still reads the local name. Type-only references are skipped, subpath imports keep their own surface, and every edit records the bytes it was computed from — so if a file changes between the scan and the write, nothing is written at all rather than a rename landing in the wrong place.
Three kinds of upgrade it will not attempt at all — and it names each one rather than passing over it in silence:
| Not attempted | Why |
|---|---|
A multi-major upgrade (hops in the plan) | It has to be walked one major at a time. Jumping straight to the target skips whatever each intermediate release removed. |
One the plan could not sequence (sequenceNote) | That is a migration, not an upgrade, and nothing mechanical should attempt it. |
Anything past --max <n> | The blast radius. A deterministic edit is safer than a model's, but a diff touching forty packages is still a review nobody finishes, and the number is your call. |
check-upgrade still reports on all three: checking an upgrade is free and tells
you something, so only performing one is capped. If every upgrade in a plan
falls into these, fix says so and names them — it never reports "nothing to
fix" for work it declined to do.
--sarif writes SARIF 2.1.0 for github/codeql-action/upload-sarif, which
gives each finding dedup, assignment and closing itself when it stops
reproducing. Proven renames carry the replacement as a SARIF fix, so the
suggested change is visible in the alert; briefs carry the agent instruction
instead. --exit-code exits 1 when anything is left for a person or an agent to
do, and --json prints the whole result.
Before you publish
The same diff, pointed the other way: will your change break the people who depend on you?
# does the version in package.json match what this release did to the API?
lurq check-release --exit-code
lurq check-release --against 2.3.0 # compare with a specific published version
# does this change break callers of your own HTTP API?
lurq check-api openapi.yaml --against main --exit-codecheck-release compares the package in the directory with its latest published
version (or --against), and --exit-code exits 1 when the version bump
understates the change — a removal shipped as a minor, say. Use it in
prepublishOnly or CI. It needs no key and no network to lurq.
check-api compares an OpenAPI document with the same file at a git revision
(--against, default HEAD), and exits 1 on a breaking change with
--exit-code. With no argument it uses the first OpenAPI document it finds;
-C <dir> points at another repository. Nothing leaves the machine.
Environment
lurq check-env .Which variables does this project read that nothing declares — the "works on my
machine" gap. It reads your own source and your .env* files, needs no API key
and no network, and never looks at a value: a declared name is the text left of
the first =, and the rest is not parsed.
A commented declaration counts. .env.example files conventionally document
optional variables as # REDIS_URL=…, and that still tells a fresh clone the
variable exists.
Three things are deliberately never reported, because a noisy check gets switched off and a check nobody runs is worth less than no check:
| Not reported | Why |
|---|---|
Platform variables — NODE_ENV, CI, PORT, the GITHUB_*, npm_*, VERCEL_* families, XDG and Windows paths | Supplied by the runtime or the runner; they belong in no .env.example |
| Reads inside tests, specs and fixtures | The harness that runs that code sets them itself |
A read with its fallback beside it — process.env.CACHE_TTL ?? '60' | The code already states what happens without it |
A variable defaulted at every site is optional; one unguarded read anywhere
makes it required, and only those sites are cited. Reads are found through the
TypeScript AST, so a process.env.X inside a comment or a string is not a read,
and process.env[key] is skipped rather than guessed at.
--exit-code exits 1 when anything is undeclared or when the scan stopped
at its file limit — a file nobody opened is not a clean result. --sarif files
each variable as a GitHub code scanning alert; --json prints every finding
with all of its read sites.
MCP servers
lurq mcp-scan # read every configured server's real contract
lurq mcp-ci # a GitHub Actions workflow that rescans daily
lurq mcp-stack # do your configured servers collide?
lurq mcp-surface @modelcontextprotocol/server-filesystem
lurq mcp-drift @modelcontextprotocol/server-filesystem --from <version> --to <version>
lurq connect-check https://mcp.example.com/mcp --client chatgpt
lurq mcp-pin io.github.acme/weather # tell me when this server changes
lurq mcp-pins # what changed since you pinned it
lurq mcp-unpin io.github.acme/weathermcp-scan and mcp-ci have their own page: see
Scanning your MCP servers. mcp-surface returns a published
server's tool contract as lurq probed it (--version for an exact version);
mcp-drift needs both --from and --to, and reports what moved between them,
including silent schema changes and tools that stopped being read-only.
connect-check takes an endpoint URL, an official registry name or an npm
package name, and answers for every MCP client lurq has a profile for — or one
client with --client — whether the server works there, what setup it needs (a
key to send as a header, an OAuth client to pre-register and the redirect URIs
to allow), or why it is blocked. With --client it also prints the config to
paste in that client's own format. A URL lurq has not probed is read live and
not stored.
mcp-pin records a remote server as it is now, so lurq tells this account when
its tools, its sign-in path or its availability change; mcp-pins lists what
changed since, and re-running mcp-pin approves a change you reviewed. Servers
in your uploaded mcp-scan results are watched the same way without pinning.
These three need an API key, because a pin belongs to an account.
Scoring controls
lurq weights # show & explain the weight model
lurq edit-weights --set composite.lambda=0.5 # override a weight (or --reset)
lurq edit-weights --explain adoption # what a component measuresBoth work without an index: the weight model ships with the package. Overrides
are written to your user config, or to .lurq/weights.json in the project with
--project.
Selection policy
Keep the rules your agents are held to in a file, review changes in a pull request, and apply them from CI.
# download the current policy
lurq policy pull lurq.policy.json
# in a PR check: is the file a valid policy? sends nothing
lurq policy push lurq.policy.json --check
# after merge: replace the policy with the file
lurq policy push lurq.policy.json
# who changed the policy, from where, and what changed
lurq policy history
# what agents were refused, or warned about, in the last 30 days
lurq policy log --days 30push --check prints the change the file would make when an API key is
available, and every push prints the change it made. An invalid file is
rejected with the field that is wrong.
Two fields are worth knowing when you edit the file by hand:
"mode": "warn"reports what the rules would refuse without refusing it. Roll a new rule out in warn mode, readlurq policy log, then switch to"enforce".- An exception can carry a reason and an expiry:
{ "name": "moment", "reason": "legacy date code", "expires": "2027-01-01" }. On the expiry date it stops applying, and stays in the file until you remove it.
push replaces the whole policy. It needs an API key with the policy:write
scope, created in the dashboard on a Team or Business plan. The key lurq setup
stores for your agents can read policy but never change it, so an agent cannot
loosen its own rules. How far back policy log reaches depends on the plan (see
Plans & limits).
Arming a repository
One command does the whole repository setup: the secret, the workflow file, and the Anthropic credential when the mode needs one.
# in a checkout, with `gh` signed in
lurq autopilot init
# any number of repositories at once — no clone needed
lurq autopilot init --repo owner/api owner/web owner/worker
# review first: a pull request in each instead of a commit
lurq autopilot init --repo owner/api owner/web --pr
# fix (the default) needs no model; pr also runs an agent and sets its credential
lurq autopilot init --mode prFor each repository it sets LURQ_API_KEY, commits
.github/workflows/lurq-upgrade.yml to the default branch through the GitHub
API, and starts the first run. A protected default branch gets a pull request
instead. Every write is made by your gh login, never by lurq's GitHub App,
which stays read-only. The token needs the workflow scope
(gh auth refresh -s workflow); the command checks before touching anything. An
already-committed workflow is left alone unless you pass --force. Secrets go to
gh on stdin, never in arguments, so they stay out of ps and your shell
history.
In pr mode it runs claude setup-token and sets
CLAUDE_CODE_OAUTH_TOKEN for you. That token expires one year after
creation, with no warning — on a weekly job that means it works for a year
and then stops. If anyone else maintains the repository, use an
ANTHROPIC_API_KEY from the Anthropic console instead: it does not expire and
does not belong to one person. If claude is not installed, the command tells
you that and sets nothing.
Run it again any time: an existing workflow file is left alone unless you pass
--force.
Setup and credentials
# interactive: sign in in the browser, then wire up every assistant found
lurq setup
# scriptable: no prompts, no browser
lurq setup --yes --no-open --api-key <key> --agent all
# just one assistant
lurq setup --agent claude-code
# point at your own serve-http instead of api.lurq.run
lurq setup --url https://lurq.internal/mcp
# forget the stored key on this machine (agents keep theirs)
lurq logout
# remove lurq from every agent and this machine
lurq uninstall--agent takes claude-code, cursor, windsurf, copilot, codex,
gemini-cli, antigravity, kiro, or all. See
per-agent configuration for what each one
gets, and Uninstall for what uninstall removes.
The endpoint is stored with the key, so a later bare lurq setup stays on your
server. LURQ_ENDPOINT overrides it for a single command without changing what
is stored.
lurq install and lurq login are aliases for lurq setup.
install-skill is the older, narrower command: it writes MCP entries and skill
files but does not store the key for the CLI. Prefer setup.
# self-host: wire your agent to a local stdio server instead of the hosted API
lurq install-skill --localOutput format
The query, check and scan commands support --json:
lurq evaluate zod --jsonThis emits structured output suitable for piping into other tools, the same shape the MCP layer serializes.