lurq

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 typeorm

Not 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:239

It 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:

SeverityWhat changedReal example
blockinga symbol your code uses is gonecookie.parse, removed in cookie 2
blockinga deep import is no longer offeredrequire('uuid/v4') on uuid 8
blockingthe package went ESM-only, and your code calls what require() returned or reads a property it no longer exportsfetch(url) on node-fetch 3, chalk.red on chalk 5
warninga call passes an argument count the new version no longer accepts. Calls that still fit are not reportedsrc/log.ts:31 passes 1
warningthe type check finds a new compiler errorTS2554 Expected 2-3 arguments on zod 4
warningrequire() of a now-ESM package, which throws on Node before 20.19 / 22.12. Skipped when your engines.node rules those outexeca 9
warninga new engines.node or peer range this project does not meetreact-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.sarif

With 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 attemptedWhy
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-code

check-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 reportedWhy
Platform variables — NODE_ENV, CI, PORT, the GITHUB_*, npm_*, VERCEL_* families, XDG and Windows pathsSupplied by the runtime or the runner; they belong in no .env.example
Reads inside tests, specs and fixturesThe 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/weather

mcp-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 measures

Both 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 30

push --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, read lurq 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 pr

For 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 --local

Output format

The query, check and scan commands support --json:

lurq evaluate zod --json

This emits structured output suitable for piping into other tools, the same shape the MCP layer serializes.

On this page