CLI options
cants [OPTIONS]CLDK TypeScript analyzer — builds one canonical model (symbol table, call graph, external symbols) with the TypeScript compiler via ts-morph, then emits it to the target you pick: the analysis.json artifact (default), a Neo4j property graph, or the machine-readable schema contract. The only required option is --input (except for --emit schema, which needs no input). With --emit json and no --output, the artifact is printed to stdout as compact JSON.
Options
Section titled “Options”| Option | Alias | Type | Default | Description |
|---|---|---|---|---|
--input | -i | PATH | required | Project root to analyze. Not required for --emit schema. |
--output | -o | DIR | (stdout / cwd) | Output directory. Holds analysis.json (--emit json), graph.cypher (--emit neo4j snapshot), or schema.json (--emit schema). Ignored for a live Bolt push. |
--emit | json | neo4j | schema | json | Output target. json writes the analysis.json artifact; neo4j projects the model into a labeled property graph; schema writes the versioned graph schema contract. | |
--app-name | NAME | (input dir basename) | Logical application name — the name of the graph’s :TSApplication anchor node, and the scoping key for every node, the snapshot wipe, and SDK read-back. | |
--neo4j-uri | URI | (none → snapshot) | Bolt URI of a live Neo4j. When set, the graph is pushed incrementally over Bolt instead of written as graph.cypher. Env: NEO4J_URI. | |
--neo4j-user | STRING | neo4j | Neo4j username for the Bolt push. Env: NEO4J_USERNAME. | |
--neo4j-password | STRING | neo4j | Neo4j password. Env: NEO4J_PASSWORD — prefer the env var (a flag value leaks into shell history and the process list). | |
--neo4j-database | STRING | (driver default) | Target database name (multi-db). Env: NEO4J_DATABASE. | |
--call-graph-provider | tsc | jelly | both | tsc | Call-graph backend. tsc is the TypeScript-checker resolver; jelly is the embedded cs-au-dk/jelly flow analyzer (shipped inside the cants binary — no extra install); both merges them. | |
--analysis-level | -a | 1 | 2 | 1 | 1 = tsc resolver call graph + RTA; 2 = + CodeQL enrichment (experimental). |
--target-files | -t | PATHS… | (whole project) | Restrict analysis to specific files (incremental); the rest is served from cache. On a Bolt push this also makes the run targeted — orphan pruning of vanished modules is skipped. |
--skip-tests | flag | --skip-tests | Skip test trees (the default). | |
--include-tests | flag | Include test trees in the analysis. | ||
--eager | flag | Force a clean rebuild — reinstall dependencies, rebuild the analysis. | ||
--lazy | flag | --lazy | Reuse the cache (the default). | |
--no-build | flag | Skip dependency materialization; reuse a prepared node_modules. | ||
--no-phantoms | flag | Disable phantom (external) nodes for imported/required library calls. | ||
--cache-dir | -c | DIR | <input>/.codeanalyzer | Cache/intermediate directory. |
--verbose | -v | count | 0 | Increase verbosity (repeatable): -v (info), -vv (debug). |
--help | -h | Show the help message and exit. |
Notes on defaults
Section titled “Notes on defaults”- JSON by default.
--emit jsonbuilds theanalysis.jsonartifact (and prints it to stdout when--outputis omitted).--emit neo4jand--emit schemaare alternative targets — they replace the JSON output, they do not add to it. - Level 1 by default. The TypeScript checker plus RTA resolve the call graph; pass
--analysis-level 2for (experimental) CodeQL enrichment. tsccall graph by default. The TypeScript checker resolves calls; pass--call-graph-provider jellyfor the embedded flow analyzer orbothto merge.- Lazy by default. Analysis reuses the cached symbol table and call graph for unchanged files. Use
--eagerto force a full rebuild. - Builds by default. Dependencies are materialized into
node_modulesso types resolve. Use--no-buildto reuse a prepared tree. - Phantoms on by default. Calls into imported libraries become
external_symbols(and:TSExternalnodes in the graph). Use--no-phantomsto restrict the graph to in-project targets. - Tests excluded by default. Test trees are skipped unless you pass
--include-tests. - Cache kept by default. The cache under
.codeanalyzer/survives between runs;--eagerrebuilds it.
Neo4j connection
Section titled “Neo4j connection”The four --neo4j-* options each have a matching environment variable. A flag wins when it is set; otherwise the env var is used. Pass secrets through the environment so they never land in shell history or the process list.
| Flag | Env var | Default |
|---|---|---|
--neo4j-uri | NEO4J_URI | (none → write graph.cypher snapshot) |
--neo4j-user | NEO4J_USERNAME | neo4j |
--neo4j-password | NEO4J_PASSWORD | neo4j |
--neo4j-database | NEO4J_DATABASE | (driver default DB) |
The presence of --neo4j-uri is the switch between the two --emit neo4j writers: omit it for a self-contained graph.cypher snapshot, set it to push incrementally to a live database over Bolt. Both write into an application-scoped subgraph anchored on (:TSApplication {name: <app-name>}), so many applications can share one database without clobbering each other.
Output files
Section titled “Output files”--emit | --neo4j-uri | Goes to | Notes |
|---|---|---|---|
json | — | <output>/analysis.json, or stdout | Compact JSON. The default target; stdout when --output is omitted. |
neo4j | (unset) | <output>/graph.cypher (else cwd) | Self-contained snapshot: DDL constraints + indexes, a scoped wipe of this app’s prior subgraph, then batched MERGE. Load with cypher-shell < graph.cypher. Not incremental by design. |
neo4j | (set) | the live database, over Bolt | Incremental push: diffs each module by content_hash and re-pushes only what changed. Shared externals, packages, and decorators are MERGE-only and never deleted. |
schema | — | <output>/schema.json, or stdout | The versioned Neo4j schema contract (schema_version 2.0.0). Needs no --input. |
The json and neo4j targets are two projections of the same in-memory model — see Output schema for the TSApplication artifact and the Neo4j graph schema for the property-graph labels and relationships.
Examples
Section titled “Examples”Emit JSON
Section titled “Emit JSON”# Symbol table + call graph to stdoutcants --input ./proj
# Write JSON to a directorycants --input ./proj --output ./out
# Eager rebuild, merge both call-graph providerscants --input ./proj --output ./out --call-graph-provider both --eager
# Incremental: only two files, rest from cachecants --input ./proj --target-files src/a.ts src/b.ts
# Reuse a prepared node_modules, custom cache, debug loggingcants --input ./proj --no-build --cache-dir /tmp/ca -vvEmit a Neo4j graph
Section titled “Emit a Neo4j graph”# Self-contained snapshot — no live database neededcants --input ./proj --emit neo4j --app-name my-app --output ./outcypher-shell -u neo4j -p "$NEO4J_PASSWORD" < ./out/graph.cypher
# Incremental live push over Bolt (password from the environment)export NEO4J_PASSWORD=...cants --input ./proj --emit neo4j --app-name my-app \ --neo4j-uri bolt://localhost:7687 --neo4j-user neo4j --neo4j-database neo4j
# Re-push only two changed files into the same application subgraphcants --input ./proj --emit neo4j --app-name my-app \ --neo4j-uri bolt://localhost:7687 --target-files src/a.ts src/b.tsThe --app-name you load with is the handle every consumer reads back by — keep it stable for an application across runs. The Python SDK’s application_name must equal this --app-name:
from cldk import CLDKfrom cldk.analysis.commons.backend_config import Neo4jConnectionConfig
analysis = CLDK.typescript( backend=Neo4jConnectionConfig( uri="bolt://localhost:7687", username="neo4j", password="neo4j", # read-only credentials are sufficient application_name="my-app", ),)classes = analysis.get_classes() # Dict[str, TSClass]Emit the schema contract
Section titled “Emit the schema contract”# Print the versioned Neo4j schema contract (no --input needed)cants --emit schema
# Write it to a file for your tooling to pin againstcants --emit schema --output ./out # → ./out/schema.json