apiweiser-cli watches a repo's dependencies for you: it finds where each one is actually used in your code, tracks version updates as they're proposed, and has a coding agent write, test, and open a PR for each upgrade — migrating your call sites when the changelog says they need it.
--path/--repo) — reads your repo's SBOM, records every dependency, and finds every place in your source each one is actually called.docs/upgrade-grouping.md).Steps 2 and 3 each run once a day, in that order, for as long as the process keeps
running — or, with --once, right after the scan, and then the process exits.
There's no breaking-change filter: every group gets a change request, minor and patch
updates included. Groups are skipped only when the scan found no call sites for any of
their packages or none of their changelogs could be summarized. devDependencies are never
scanned for call sites, so upgrades of dev tooling alone (eslint, typescript, @types/*, ...) are always skipped. A devDependency only gets bumped as a peer of a
runtime package that's being upgraded.
Everything is persisted to a local sqlite database, separate from any repo you point it at.
.ts files directly, no build stepPATH--path at — a fine-grained token scoped to just that repo is the safer choice.Installs the CLI globally and sets up the codemod skill for whichever coding
agent is on your PATH, in one step:
curl -fsSL https://apiweiser-cli-fe.vercel.app/install.sh | sh Or do both manually:
npm install -g apiweiser-cli npx codemod ai --harness claude --user --no-interactive # swap claude for codex if that's what you use
Or run it without installing:
npx apiweiser-cli --path <path-to-repo>
Or clone the repo and run it from source (no build step needed):
git clone https://github.com/enismustafaj/apiweiser-scanner.git cd apiweiser-scanner npm install node src/main.ts --path <path-to-repo>
Every run needs ~/.apiweiser-cli/config.json filled in —
even a plain scan, since every run goes on to the changelog-lookup and suggestion jobs
(see Usage). Run the CLI once with no config and it creates a template for you there,
then exits with an error telling you to fill it in:
{
"llm": {
"apiKey": "sk-...",
"url": "https://api.openai.com/v1",
"model": "gpt-5"
},
"codingAgent": {
"command": "claude",
"args": ["-p"]
},
"github": {
"token": "ghp_..."
}
} | Field | What it's for |
|---|---|
| llm.apiKey | Your API key for whichever provider llm.url points at |
| llm.url | An OpenAI-compatible baseURL — api.openai.com/v1, or a compatible provider like Groq |
| llm.model | Model name at that endpoint — used to summarize changelogs for the coding agent |
| codingAgent.command | "claude" for Claude Code, "codex" for Codex — whichever you have installed |
| codingAgent.args | Flags that put that CLI into non-interactive mode — ["-p"] for Claude Code, ["exec"] for Codex |
| github.token | The PAT from Prerequisites above |
Optionally, codemodRegistry ({ "mode": "local" } by default) controls where
generated codemods are shared: "local" keeps them under ~/.apiweiser-cli/codemods/, "remote" also searches and publishes to the Codemod
registry and needs scope and apiKey — see docs/change-requests.md § Remote registry.
codingAgent also needs the codemod skill installed for it
(the one-line installer above does this automatically) — without it, your coding agent
won't know how to build a codemod package.
scan is the default subcommand — point it at exactly
one of a local path or a git URL:
apiweiser-cli --path <path-to-repo> apiweiser-cli scan --repo <git-url> # the first is short for: apiweiser-cli scan --path <path-to-repo>
--repo clones the URL (or pulls latest, if a previous run
already cloned it) into ~/.apiweiser-cli/repos/<owner>-<repo>/ and scans that instead — no need to git clone a repo
yourself first just to point apiweiser-cli at it. Everything after this point behaves
identically either way.
Scans the repo once — generates its SBOM, records its dependencies, finds every call site, and queues anything brand new for a changelog-source lookup — then settles into a long-running process running two daily schedulers (at midnight), in order: draining the changelog-lookup queue, then checking Renovate for version update suggestions. Each upgrade group Renovate's suggestions produce goes straight to a coding agent, which builds and tests a codemod for the migration; the versions are bumped, the codemod applied, and a PR opened — no further action needed from you beyond reviewing it. Safe to re-run the scan part — unchanged dependencies are skipped entirely, so it's fast even on a large repo. Leave the process running.
To run everything once instead — scan, changelog lookups, suggestions and any PRs, right
away — and exit, add --once:
apiweiser-cli scan --repo <git-url> --once
Handy for trying it out, or for driving it from your own cron/CI instead of leaving a process up.
All state lives in ~/.apiweiser-cli/ — the sqlite db, the
config file, SBOM/Renovate caches, clones made via --repo,
and generated codemod packages — separate from whatever repo is actually being scanned.
A local, read-only web view over that same sqlite db — every package it's found, every suggestion Renovate has raised, and every PR it's opened so far:
apiweiser-cli dashboard --port 3000 # default 3000, --port is optional
A foreground command, not a scheduler — it binds the port and runs until you Ctrl+C it, same as any local dev server. It only reads ~/.apiweiser-cli/db.sqlite, so it reflects whatever the scan process has already written and never scans or
writes anything itself — run it alongside a running scan,
or on its own against state from an earlier run.
package.json/lockfile bump. That's expected, not a bug.new expressions count as call sites. A package your code only uses as JSX components (<Combobox.Root>) or types records none, so its upgrade is skipped — unless it's grouped with a package that does have call sites.--path or cloned via --repo — not a fork, from a branch like apiweiser-cli/base-ui/react-1.9.0 (or apiweiser-cli/base-ui/react-1.9.0+date-fns-4.4.0 for a group — see docs/github.md). Point this at a repo you (or your token) actually have write access to.docker build -t apiweiser-cli . docker run --rm \ -v "$(pwd)":/repo:ro \ -v apiweiser-cli-state:/root/.apiweiser-cli \ apiweiser-cli --path /repo
The repo being scanned is mounted read-only at /repo. The
named volume persists ~/.apiweiser-cli/ (db, config,
caches) across runs — drop it and you lose scan history/config. A run is long-running by
default (see Usage), so add -d to run detached rather
than blocking the terminal — or append --once to run
everything once and exit.