Skip to content

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.

OptionAliasTypeDefaultDescription
--input-iPATHrequiredProject root to analyze. Not required for --emit schema.
--output-oDIR(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.
--emitjson | neo4j | schemajsonOutput target. json writes the analysis.json artifact; neo4j projects the model into a labeled property graph; schema writes the versioned graph schema contract.
--app-nameNAME(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-uriURI(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-userSTRINGneo4jNeo4j username for the Bolt push. Env: NEO4J_USERNAME.
--neo4j-passwordSTRINGneo4jNeo4j password. Env: NEO4J_PASSWORDprefer the env var (a flag value leaks into shell history and the process list).
--neo4j-databaseSTRING(driver default)Target database name (multi-db). Env: NEO4J_DATABASE.
--call-graph-providertsc | jelly | bothtscCall-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-a1 | 211 = tsc resolver call graph + RTA; 2 = + CodeQL enrichment (experimental).
--target-files-tPATHS…(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-testsflag--skip-testsSkip test trees (the default).
--include-testsflagInclude test trees in the analysis.
--eagerflagForce a clean rebuild — reinstall dependencies, rebuild the analysis.
--lazyflag--lazyReuse the cache (the default).
--no-buildflagSkip dependency materialization; reuse a prepared node_modules.
--no-phantomsflagDisable phantom (external) nodes for imported/required library calls.
--cache-dir-cDIR<input>/.codeanalyzerCache/intermediate directory.
--verbose-vcount0Increase verbosity (repeatable): -v (info), -vv (debug).
--help-hShow the help message and exit.
  • JSON by default. --emit json builds the analysis.json artifact (and prints it to stdout when --output is omitted). --emit neo4j and --emit schema are 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 2 for (experimental) CodeQL enrichment.
  • tsc call graph by default. The TypeScript checker resolves calls; pass --call-graph-provider jelly for the embedded flow analyzer or both to merge.
  • Lazy by default. Analysis reuses the cached symbol table and call graph for unchanged files. Use --eager to force a full rebuild.
  • Builds by default. Dependencies are materialized into node_modules so types resolve. Use --no-build to reuse a prepared tree.
  • Phantoms on by default. Calls into imported libraries become external_symbols (and :TSExternal nodes in the graph). Use --no-phantoms to 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; --eager rebuilds it.

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.

FlagEnv varDefault
--neo4j-uriNEO4J_URI(none → write graph.cypher snapshot)
--neo4j-userNEO4J_USERNAMEneo4j
--neo4j-passwordNEO4J_PASSWORDneo4j
--neo4j-databaseNEO4J_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.

--emit--neo4j-uriGoes toNotes
json<output>/analysis.json, or stdoutCompact 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 BoltIncremental 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 stdoutThe 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.

Terminal window
# Symbol table + call graph to stdout
cants --input ./proj
# Write JSON to a directory
cants --input ./proj --output ./out
# Eager rebuild, merge both call-graph providers
cants --input ./proj --output ./out --call-graph-provider both --eager
# Incremental: only two files, rest from cache
cants --input ./proj --target-files src/a.ts src/b.ts
# Reuse a prepared node_modules, custom cache, debug logging
cants --input ./proj --no-build --cache-dir /tmp/ca -vv
Terminal window
# Self-contained snapshot — no live database needed
cants --input ./proj --emit neo4j --app-name my-app --output ./out
cypher-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 subgraph
cants --input ./proj --emit neo4j --app-name my-app \
--neo4j-uri bolt://localhost:7687 --target-files src/a.ts src/b.ts

The --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 CLDK
from 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]
Terminal window
# Print the versioned Neo4j schema contract (no --input needed)
cants --emit schema
# Write it to a file for your tooling to pin against
cants --emit schema --output ./out # → ./out/schema.json