jdx command reference
The user-facing catalogue. Every shipped flag must appear in jdx <command> --help,
here, and in docs/PROPOSAL.md Appendix B. (Moved out of README.md to keep it a
hook, demo, install, quickstart, and links.)
jdx <command> --help documents each command; jdx help --agent prints the compact
agent block. Every query command accepts --json (same information, structured envelope)
and --no-daemon (force in-process even when the daemon is up).
Read: declaration, members, structure#
| Command | IDE equivalent | Key flags |
|---|---|---|
jdx show <type> |
Go to declaration | classpath flags only |
jdx members <type> |
Code completion after . |
--declared / --inherited (default), --kind all|method|field|ctor|property, --access public|protected|package|private|all, --static / --instance, --from <supertype>, --grep <regex>, --include-synthetic, --limit (50), --with-doc, --brief, --max-lines, --sort kind|name|declaring, --view kotlin|jvm |
jdx outline <type> |
File structure (Ctrl+F12) |
Same filters as members --declared (never inherited): --kind, --access, --static / --instance, --grep, --include-synthetic, --limit, --with-doc, --brief, --max-lines, --sort, --view |
jdx signature <member> |
Parameter info (Ctrl+P) |
--include-synthetic, --view kotlin|jvm, --limit (50) |
jdx doc <symbol> |
Quick documentation (Ctrl+Q) |
--inherited (default) / --no-inherited, --raw, --max-lines (200). Undocumented members fall back to the nearest documenting supertype. |
jdx body <member> |
Open the method | --context N, --line-numbers, --max-lines (200), --engine vineflower|javap, --with-doc, --with-signature |
jdx source <type> |
Open the file / decompile | --lines A:B, --around '<type>#<member>' + --context N, --line-numbers, --max-lines (200), --engine vineflower|javap |
body/source ladder: paired sources → Vineflower reconstruction → javap disassembly →
signatures → not found. --engine vineflower forces reconstruction even when sources are
paired; --engine javap shows raw bytecode. Decompiled output is always labelled
⚠ reconstructed with the engine named.
Search and browse#
| Command | IDE equivalent | Key flags |
|---|---|---|
jdx search <pattern> |
Search everywhere (Shift Shift) |
--kind all|type|class|interface|enum|record|annotation|object|companion|method|field|package|module, --regex, --fuzzy (Levenshtein retry on miss), --in <artifact-glob>, --package <glob>, --limit (50). Bare words match case-insensitively or as camel humps (HMap finds HashMap). Default --kind all searches types, packages and modules only — pass --kind method|field to scan members; a miss under the default names that flag. |
jdx resolve <name> |
"What is this symbol?" | --limit (50). Several candidates are success (exit 0); none is exit 1 with did-you-mean. |
jdx ls [package-glob] |
External library browser (packages/types) | --limit (50). A glob lists packages with type counts; an exact package lists its types. |
jdx tree [artifact-glob] |
External library browser (artifacts) | --depth (8; 0 = top segments), --counts, --limit (50) |
Graph: usages, hierarchy, calls, samples#
| Command | IDE equivalent | Key flags |
|---|---|---|
jdx usages <symbol> |
Find usages (Alt+F7) |
--kind all|call|read|write|ref|new|throw|annotation (impl/override redirect to hierarchy/implementors), --where key=value (repeatable; requires --kind annotation — filter by annotation element values, issue #26; e.g. --where forRemoval=true; dotted paths into nested annotations --where nested.k=1; {…} arrays match order-insensitively; omitted elements filled from AnnotationDefault for matching only), annotation-use spelling 'Type(k=v)' with --kind annotation peels into the type + where clauses (omit leading @ on the CLI — Clikt @file; MCP accepts @Type(k=v); same issue), --context N (inline source window around each call site; 0 = file:line only; capped at 15 lines — issue #10), --in / --exclude <artifact-glob>, --limit (50), --src <dir> (repeatable source-dir roots). new = constructor call sites; throw = methods declaring the type in throws (declaration-level, not ATHROW throw sites); annotation = annotated classes/members (each row carries the rendered @Fqn(k=v,…) use). Source-dir hits are textual mentions (ref only). Bytecode rows carry File.java:line when the class retains a LineNumberTable (issue #8); stripped classes omit it. Missing paired sources degrade to location-only rows. |
jdx annotated <annotation> |
Annotation-driven view (issue #26) | Dedicated entry for usages --kind annotation. Query may be an annotation type, 'Type(k=v)' use spelling (omit leading @ on the CLI; MCP accepts @Type(k=v)), or a recipe alias: deprecated → java.lang.Deprecated, forRemoval → @Deprecated(forRemoval=true), test/junit5 → org.junit.jupiter.api.Test, junit4 → org.junit.Test, bean → org.springframework.context.annotation.Bean. --where key=value (repeatable, AND; dotted nested paths and order-insensitive {…} arrays; AnnotationDefault merging for omitted elements), --context N, --in / --exclude, --limit (50), --src <dir>. Each row carries the rendered @Fqn(k=v,…) use. |
jdx hierarchy <type> |
Type hierarchy (Ctrl+H) |
--up / --down (default both), --direct, --depth N, --in / --exclude, --limit (50). Member refs exit 3. |
jdx implementors <type> |
Who implements this? | Alias for hierarchy --down: --direct, --depth N, --in / --exclude, --limit (50) |
jdx callers <member> |
Call hierarchy in (Ctrl+Alt+H) |
--depth N (default 1), --exact (invoke-owner matching only), --in / --exclude, --limit (50). Methods + constructors only; type/field refs exit 3. Cycle-safe (…(cycle)). Override-aware by default (issue #6): includes call sites through overrides and declaring supertypes; constructors/static/private stay exact. |
jdx calls <member> |
Call hierarchy out | --depth N, --external-only (hide callees in the method's own artifact — dependency view), --in / --exclude, --limit (50). Callees stay bytecode-exact (the invoke names one owner); override expansion is callers-only. |
jdx samples <symbol> |
(no IDE equivalent) | --limit (default 3), --max-lines (default 15; per-snippet code-line cap), --in / --exclude, --prefer-sources (rank snippet-capable callers first). Ranked by exemplariness: non-test before test, non-generated before generated, fuller overloads first. Type refs match calls to any member; field refs exit 3. |
jdx literals <text> |
(no IDE equivalent) | Search string constants across bytecode (LDC loads + String field ConstantValue) and .properties resource keys/values. --regex, --ignore-case (substring only), --in / --exclude, --limit (50). Case-sensitive substring by default. Properties hits are prop path#key with the property value. Exits 1 when nothing matches. Issue #86. |
jdx services [pattern] |
(no IDE equivalent) | List META-INF/services SPI registrations (who provides com.foo.Spi?). Omit the pattern to list every registration; with a pattern, match the service interface FQN or a provider class FQN. --regex, --ignore-case, --in / --exclude, --limit (50). Does not read module-info provides (use jdx modules) or extract arbitrary resources. Exits 1 when nothing matches. Issue #25. |
jdx modules [pattern] |
(no IDE equivalent) | List JPMS module-info descriptors (requires / exports / opens / uses / provides). Omit the pattern to list every module; with a pattern, match the module name, an exported/opened package, a uses/provides service, or a provider class. --regex, --ignore-case, --in / --exclude, --limit (50). Does not read META-INF/MANIFEST.MF attributes (use jdx manifest) or extract arbitrary resources. Exits 1 when nothing matches. Issue #25. |
jdx manifest [pattern] |
(no IDE equivalent) | List META-INF/MANIFEST.MF attributes. Default: main attributes (Main-Class, Implementation-Version, Multi-Release, …). --entries lists per-entry (JarEntry) attributes instead (Name: sections — digests, Content-Type, …). Omit the pattern to list every attribute in that section; with a pattern, match the attribute name or value (and entry path under --entries). --regex, --ignore-case, --in / --exclude, --limit (50). Does not extract resource contents (use jdx resources for paths). Exits 1 when nothing matches. Issue #25. |
jdx resources [pattern] |
(no IDE equivalent) | List non-class resource paths (META-INF/…, .properties, versioned META-INF/versions/… copies, …). Omit the pattern to list every resource; with a pattern, match the entry path. --cat prints the unique match's text contents (binary / non-UTF-8 payloads refuse to dump bytes; ambiguous matches exit 2 with path --in artifact candidates). --max-lines (200) caps --cat text lines. --regex, --ignore-case, --in / --exclude, --limit (50, listing mode). Exits 1 when nothing matches. Issue #25. |
Upgrades: API diff#
| Command | IDE equivalent | Key flags |
|---|---|---|
jdx diff <old> <new> |
"What did this upgrade break?" | --visibility public|all (default public: public + protected declarations; all = every declared member, for refactors and mod/mixin work), --include-synthetic, --severity all|suspicious|breaking (cumulative), --fail-on none|breaking|any, --limit (200), --fetch, --repo, --json. Each of the two specs is a jar, class directory, glob, or group:artifact:version. Reads bytecode only — no sources, no decompilation, no classpath. Every finding names one rule (MEMBER_REMOVED, RETURN_TYPE_CHANGED, KOTLIN_SUSPEND_CHANGED, …) at breaking / suspicious / info, and every line is a canonical ref. The full rule set is docs/PROPOSAL.md §7.7. |
jdx diff gson-2.11.0.jar gson-2.14.0.jar --fail-on breaking
api diff gson-2.11.0.jar -> gson-2.14.0.jar (public surface)
info FIELD_CONSTANT_VALUE_CHANGED com.google.gson.internal.GsonBuildConfig#VERSION: "2.11.0" -> "2.14.0"
0 breaking · 0 suspicious · 17 informational · 2 types added · 0 types removed
fail-on breaking: no breaking changes (exit 0)
source: gson-2.11.0.jar (bytecode)
source: gson-2.14.0.jar (bytecode)
next: jdx members com.google.gson.internal.GsonBuildConfigdiff exits 0 whenever the comparison ran, whatever it found — the answer is in
the report, not in the process status. --fail-on breaking (or any) is the CI gate
and is the only way a successful diff exits non-zero (see the exit-code table). It needs
no workspace, no --jars and no --no-daemon, and is answered by the MCP tool
jdx_diff and GET /v1/diff?query=<old>&new=<new> with identical bytes.
Environment and maintenance#
| Command | Purpose | Key flags / notes |
|---|---|---|
jdx version |
Print the version | --json for the envelope form |
jdx upgrade |
Self-update from GitHub Releases (release installs only) | --version <tag> (default latest), --check (report only), --repo OWNER/NAME, --json. Exits 1 on unknown tag, 3 on non-release installs, 5 on download failure |
jdx doctor |
Self-diagnosis | One ok/warn/fail row per check: JDK, jrt:/, javap, JDK sources, cache, config, index DB, Kotlin sidecar, AppCDS archive (present/absent/stale via size + mtime vs the fat jar; missing/stale degrades via -Xshare:auto, never fails), daemon (probed live: running vs stale), workspace, agent setup (OpenCode project/system wiring + detected v1/v2 line via opencode --version; Claude Code .mcp.json/~/.claude.json wiring + claude --version presence; Cursor .cursor/mcp.json/~/.cursor/mcp.json wiring + cursor-agent --version presence; Kilo Code project/system wiring; Cline global cline_mcp_settings.json wiring, both scopes; Codex CLI .codex/config.toml/~/.codex/config.toml wiring + codex --version presence; GitHub Copilot CLI .mcp.json/~/.copilot/mcp-config.json wiring + copilot --version presence; Antigravity .agents/mcp_config.json/~/.gemini/config/mcp_config.json wiring + agy --version presence; Windsurf / Devin Local .devin/mcp_config.json/~/.config/devin/mcp_config.json wiring + devin --version presence; Aider / AiderDesk .aider-desk/mcp-servers.json/~/.aider-desk/mcp-servers.json wiring + aiderdesk --version presence). Exits 6 if any check fails. |
jdx setup --agent opencode|claude-code|cursor|kilo|cline|codex|copilot|antigravity|windsurf|aider --scope project|system |
Wire jdx into an agent | OpenCode: writes the jdx mcp MCP entries into opencode.json/opencode.jsonc (project checkout or ~/.config/opencode): both the v1 entry (mcp.jdx) and the v2 entry (mcp.servers.jdx, enabled → disabled) so either OpenCode line picks it up (v1 support is deprecated, slated for removal). Claude Code: writes the mcpServers.jdx entry ({"command": "jdx", "args": ["mcp"]}, the claude mcp add jdx -- jdx mcp equivalent) into .mcp.json (project checkout) or ~/.claude.json (system). Cursor: writes the same mcpServers.jdx shape ({"command": "jdx", "args": ["mcp"]}, recognised by agent mcp list in cursor-agent 2026.09.26-dd393fe) into .cursor/mcp.json (project checkout) or ~/.cursor/mcp.json (system). Kilo Code (an OpenCode fork sharing the mcp map shape): writes the single mcp.jdx entry into kilo.json/kilo.jsonc (project root or .kilo/ — the .kilo/ variant wins when both exist — or ~/.config/kilo). Cline: writes the single mcpServers.jdx transport entry ({"transport": {"type": "stdio", "command": "jdx", "args": ["mcp"]}}, the cline mcp add jdx -- jdx mcp equivalent) into the single global ~/.cline/data/settings/cline_mcp_settings.json for either scope (verified against cline 3.0.65 + extension 4.1.21 — Cline keeps no project-level MCP file). Codex CLI: writes the [mcp_servers.jdx] table (command = "jdx", args = ["mcp"], the codex mcp add jdx -- jdx mcp equivalent) into .codex/config.toml (project checkout, trusted projects) or ~/.codex/config.toml (system, $CODEX_HOME honoured). GitHub Copilot CLI: writes the mcpServers.jdx entry ({"type": "local", "command": "jdx", "args": ["mcp"]}, the copilot mcp add jdx -- jdx mcp equivalent, verified against GitHub Copilot CLI 1.0.88) into .mcp.json (project checkout — the nearest one walking up, .github/mcp.json as fallback — trusted directories) or ~/.copilot/mcp-config.json (system, $COPILOT_HOME honoured). Antigravity: writes the mcpServers.jdx entry ({"command": "jdx", "args": ["mcp"]}, the agy mcp add jdx -- jdx mcp equivalent, verified against agy 1.3.0) into .agents/mcp_config.json (project checkout — the nearest one walking up) or ~/.gemini/config/mcp_config.json (system). Windsurf / Devin Local: writes the same mcpServers.jdx shape (the devin mcp add -s project jdx -- jdx mcp equivalent) into .devin/mcp_config.json (project checkout — the nearest one walking up) or ~/.config/devin/mcp_config.json (system). Aider / AiderDesk: writes the same mcpServers.jdx shape into .aider-desk/mcp-servers.json (project checkout — the nearest one walking up) or ~/.aider-desk/mcp-servers.json (system, $AIDER_DESK_HOME_DIR honoured) — core Aider has no MCP client (Aider-AI/aider#2525), so this wires AiderDesk as the closest equivalent. Reports the detected line. Merges, never clobbers; --check reports without writing (exit 1 when absent); --remove uninstalls cleanly; re-runs are no-ops. Restart the agent after changing config (it loads once at startup). Agent-config home prefers $JDX_SETUP_HOME, then $HOME / $USERPROFILE, then the JVM user.home (issue #85: a sandboxed HOME=… isolates writes). |
jdx ws create|list|info|remove|use|add |
Project & library configuration | create <name> --jars … --src … --coord … --repo … [--jdk|--no-jdk]; add <name> <root>; use <name> [--clear] sets the default; list / info <name> / remove <name>. Stored --coord/--repo resolve on cold --fetch only — the daemon never fetches (prime caches cold, or use --jars). |
jdx cache info|gc|clear |
Index and cache maintenance | info (DB location, size, schema, counts); gc [--dry-run] [--cache-dir …] (evicts unreferenced + stale artifacts + orphan daemon logs); clear (wipes regenerable cache) |
jdx kotlin install |
Fetch the Kotlin PSI sidecar | --repo … (repeatable), --force (re-download). 7 jars, SHA-1 verified, into <cache-dir>/kotlin/ (~/.cache/jdx/kotlin/ on Linux; per-OS defaults in docs/PROPOSAL.md §17.1); present jars are skipped, re-runs resume. |
jdx help [--agent] [--json] |
Cheat sheet | --agent prints the paste-ready agent block |
jdx bench |
Benchmark the read path | --iterations N (default 3, median reported), classpath flags. Fixed load/show/members/search/hierarchy workload with §15 advisory targets; always exits 0 on success. Falls back to the local minecraft-client.jar when no roots are given. |
Serving: daemon, MCP, HTTP, batch#
| Command | Purpose | Notes |
|---|---|---|
jdx daemon start|stop|status|restart |
Warm background JVM | Per-workspace unix socket under the OS runtime dir ($XDG_RUNTIME_DIR/jdx/<hash>-v1.sock on Linux; macOS $TMPDIR/jdx-$UID, Windows %LOCALAPPDATA%/jdx/run — full table in docs/PROPOSAL.md §17.1; a missing XDG_RUNTIME_DIR falls back, never exit 3); over-long socket paths exit 3 with the JDX_RUNTIME_DIR hint (104 B macOS / 108 B elsewhere); concurrent start fails fast on the socket lock; stop never kills a reused pid; --idle 5m default (0 disables); start is idempotent. status: uptime, memory, indexed artifacts (always 0 + live-roots-first label until persistent index — issues #4/#5), query count. run (foreground) is the internal spawn target. |
jdx mcp [-w name] |
MCP stdio server | One typed jdx_* tool per query (22 tools) with generated schemas; per-call workspace override; answers byte-identical to --json. |
jdx serve [-w name] [--port 7070] [--bind 127.0.0.1] |
Local HTTP/JSON API | GET /v1/<command>?query=…&<param>=…, POST /v1/batch (NDJSON), GET /v1/health. Localhost by default; blocks until interrupted. Bodies byte-identical to --json. |
jdx batch |
Many queries, one process | NDJSON RpcRequest lines on stdin ({"command":"members","query":"…","params":{…}}), one envelope per line on stdout; exit code is the max query exit code. Roots resolved once. --json accepted and ignored (output is always envelopes). |
Classpath flags (every query command)#
--jars <jar|dir|glob> (repeatable, merged in front of the workspace; quote globs so the shell does not expand them — an unquoted multi-path expansion exits 3 with a hint) ·
--coord group:artifact:version (repeatable; local ~/.gradle/caches + ~/.m2 first) ·
--repo <url> (repeatable Maven mirror, tried before Central) ·
--fetch (allow downloads incl. -sources.jar, checksum-verified into <cache-dir>/m2/ (~/.cache/jdx/m2/ on Linux)) ·
--no-jdk (exclude the running JDK stdlib, included by default via jrt:/ + src.zip) ·
-w/--workspace <name> (also JDX_WORKSPACE env or jdx ws use default; Gradle/Maven
project roots auto-discover from the working directory) ·
--src <dir> (only usages, batch, bench, and ws create: source dirs scanned textually).
Global output flags: --json (accepted before or after the subcommand, e.g.
jdx --json show …), --no-color (piped output is always plain),
--no-daemon (force in-process). The daemon wire always carries plain text (no TTY);
the CLI re-colorizes warm text locally on a TTY with the same --no-color / console
gate as cold, so piped warm stays byte-identical to piped cold (issue #17). --json
bytes are identical cold and warm.
Exit codes and symbol references#
| Code | Meaning |
|---|---|
0 |
Success, results found |
1 |
Valid query, no results |
2 |
Ambiguous reference — candidates printed, retry with one |
3 |
Usage / argument error |
4 |
No index or workspace resolved for this query |
5 |
Artifact read error (corrupt jar, unreadable file) |
6 |
Internal error |
The one exception is jdx diff's --fail-on breaking\|any gate, which exits 1 when it
trips: a successful comparison otherwise always exits 0, and the verdict is in the
envelope as result.gate too.
References are Javadoc style — com.example.Outer, com.example.Outer#method(Type),
short Outer#method (resolved, or exit 2 with candidates). Always quote the ref:
# and ( are shell-hostile. :: and . are accepted in place of #.
Constructors are <init>, static initialisers <clinit>.
Every --json answer uses one envelope:
{"jdx":1, "ok":…, "command":…, "query":…, "result":…, "truncated":…, "warnings":…, "provenance":…}.
Warnings carry stable codes (DUPLICATE_FQN, CORRUPT_CLASS, SOURCES_VERSION_MISMATCH, …);
provenance names the artifact, the source (sources / bytecode / decompiled + engine), and
the file:lines.
Kotlin note#
Kotlin signatures come from @Metadata (suspend, properties, default args, @JvmName);
Kotlin member bodies and KDoc come from .kt sources through a side-loaded
kotlin-compiler-embeddable (never on the compile classpath, never in the fat jar).
Without the sidecar, body/source/doc over .kt-only roots degrade to
decompile/javap — they emit something and say what it is, never an error.
Run jdx kotlin install to fetch the sidecar set (SHA-1 checked) from Maven Central;
jdx doctor reports its status. File facades and multi-file parts project through
KmPackage the same way classes use KmClass (suspend, properties, @JvmName);
a multi-file facade umbrella merges its parts when the jar is open. Corrupt metadata
still falls back to the JVM projection.
Nullability from @Metadata (String?, List<String?>, boxed Integer?) renders on
method params/returns and properties; canonical symbol refs strip the ? marker.