Installation
cants ships as a self-contained native binary — no Bun or Node at runtime. Install the prebuilt binary for your platform with pip, Homebrew, or a one-line shell script; building from source is only needed if you’re hacking on the analyzer itself.
Install
Section titled “Install”The wheel bundles the prebuilt binary for your platform:
pip install codeanalyzer-typescriptcants --helpThis is also the package CLDK’s Python SDK depends on to locate the backend — it exposes codeanalyzer_typescript.bin_path() and schema_path() so the SDK can find the binary and the schema contract.
brew install codellm-devkit/homebrew-tap/codeanalyzer-typescriptcants --helpDownload and install the latest release for your platform:
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-typescript/releases/latest/download/cants-installer.sh | shThe installer drops cants into ~/.local/bin. Override the location with CANTS_INSTALL_DIR and pin a release with CANTS_VERSION:
CANTS_INSTALL_DIR=/usr/local/bin CANTS_VERSION=v0.3.0 \ curl --proto '=https' --tlsv1.2 -LsSf https://github.com/codellm-devkit/codeanalyzer-typescript/releases/latest/download/cants-installer.sh | shSupports macOS (arm64/x86_64) and Linux (x86_64/aarch64).
Running a prebuilt cants requires nothing else — the runtime is bundled, and the Jelly flow analyzer and the neo4j-driver used for the live Bolt push are already inside the binary. There is no extra pip or npm step to push to a graph.
Running Neo4j
Section titled “Running Neo4j”cants --emit neo4j projects the analysis into a Neo4j property graph — either a self-contained graph.cypher snapshot, or an incremental push to a live database over Bolt. The snapshot path needs no running database; the live push needs a Neo4j you can reach over Bolt.
A throwaway single instance for local work:
docker run --rm \ -p 7474:7474 -p 7687:7687 \ -e NEO4J_AUTH=neo4j/secret \ neo4j:5Bolt is on 7687; the browser UI is on 7474.
For shared, governed deployments use Neo4j Aura (managed) or Neo4j Enterprise with clustering for HA. Both speak Bolt; point --neo4j-uri at the cluster’s Bolt endpoint (e.g. neo4j+s://<id>.databases.neo4j.io for Aura) and use the same flags. Hand the analyzer write credentials; hand consumers read-only credentials.
cants reads the standard Neo4j connection variables when the matching flag is omitted (an explicit flag wins). Prefer the password env var — a flag value is visible in shell history and the process list.
export NEO4J_URI=bolt://localhost:7687export NEO4J_USERNAME=neo4jexport NEO4J_PASSWORD=secretexport NEO4J_DATABASE=neo4j # optional; omit to use the server's default DB
cants --input ./my-ts-project --emit neo4j --app-name my-ts-appThe --app-name you push with is the scoping key for the whole graph: it names the :TSApplication anchor node, the snapshot’s scoped wipe matches on it, and the CLDK Python SDK reads the graph back by it. Keep it stable across runs of the same application — and identical to the application_name the SDK reads with. See the Neo4j output guide for the full producer/consumer story.
Kubernetes / CI
Section titled “Kubernetes / CI”The producer and consumer scale separately. Run cants --emit neo4j out of band — as a CI step or a Kubernetes Job/CronJob that pushes app-scoped subgraphs into a shared, managed Neo4j over Bolt. Consumers (agents, the CLDK SDK, dashboards) are lightweight read-only clients that fan out from the same cluster and never run the analyzer.
In that setup, supply the connection details as environment variables — from a Secret for the credentials — so nothing lands in shell history or the pod spec:
export NEO4J_URI="$NEO4J_URI" # from the clusterexport NEO4J_USERNAME="$NEO4J_USERNAME"export NEO4J_PASSWORD="$NEO4J_PASSWORD" # from a Secretcants --input "$WORKSPACE" --emit neo4j --app-name "$APP_NAME"Each application is anchored at its own :TSApplication node, so many analyzer jobs can write into one database without clobbering each other. The graph is stamped with a schema_version (currently 2.0.0) on that node; publish the matching contract with cants --emit schema (it needs no project) so consumers can detect a producer/consumer mismatch before they query.
Why your project needs its dependencies
Section titled “Why your project needs its dependencies”To resolve types and call targets the way the project actually compiles, cants drives the TypeScript compiler against the project’s own node_modules. By default it materializes those dependencies before parsing — running the project’s package manager with --ignore-scripts so packages’ .d.ts/JS files are present without compiling native addons.
This means the project you analyze should be a normal Node/TypeScript project (a package.json, ideally a tsconfig.json). A few notes:
node_modulesis installed in place. Node’s module resolution requires it to live in the project tree, so the analyzer installs there and reuses it on later runs.- Already have
node_modules? Pass--no-buildto skip materialization and reuse the prepared tree. --eagerreinstalls dependencies from scratch, mirroring a clean rebuild.- It degrades, never crashes. If the install fails (offline, a broken dependency), the analyzer logs a warning and continues with partial type information rather than aborting.
Optional: CodeQL (level 2)
Section titled “Optional: CodeQL (level 2)”Level-2 analysis (--analysis-level 2) is designed to enrich the call graph with CodeQL. You do not need to install anything for it today — it is an experimental, not-yet-implemented stub that currently falls back to the level-1 graph.
Build from source
Section titled “Build from source”You only need this to develop the analyzer; for normal use, prefer a prebuilt install above. Building from source requires Bun 1.0+.
curl -fsSL https://bun.sh/install | bashbrew install oven-sh/bun/bunnpm install -g bunClone, install dependencies, and compile the binary:
git clone https://github.com/codellm-devkit/codeanalyzer-typescriptcd codeanalyzer-typescriptbun installbun run build # -> dist/cants (standalone native binary)./dist/cants --helpbun run build produces a standalone native binary at dist/cants — it bundles the runtime, so neither Bun nor Node is needed to run it afterward.
You can also run the analyzer straight from source without compiling:
bun installbun run start -- --input /path/to/typescript/project # run from sourcebun run typecheck # tsc --noEmitThe -- separates Bun’s arguments from the analyzer’s.