idac is a CLI for IDA Pro with two execution paths:
gui: talks to a live IDA desktop session through the bridge pluginidalib: opens.i64/.idbfiles in a short-lived headless worker
Most implementation lives under src/idac:
src/idac/cli.py: command registration and argument parsingsrc/idac/ops/: typed operation families, manifest, dispatch, preview execution, runtime helpers, and shared helper modulessrc/idac/cli2/renderers/: text renderingsrc/idac/transport/schema.py: wire request/response schemasrc/idac/transport/: GUI bridge transport andidalibworker transportplugin/: IDA GUI bridge plugin codetests/: CLI and backend coveragefixtures/: committed binaries, databases, logs, and source used by testsdocs/andsrc/idac/skills/idac/: user-facing command docs and agent-oriented usage guidance
- Prefer
uv run ...for repo-local commands. - Prefer targeted tests first, then broader validation if the change touches shared behavior.
- Treat committed fixture artifacts as part of the product surface. If you change fixture symbols, fixture source, or docs/examples that depend on them, regenerate the fixture outputs too.
- Do not revert unrelated worktree changes. This repo may contain user-owned untracked recovery artifacts and local editor files.
- In
src/idac/cli2, keepargparse.Namespaceat the parser boundary. Use directargs.fooaccess for fields guaranteed by that subcommand, and reservevars(args).get(...)for wrapper orargparse.SUPPRESScases. - For command-local argument normalization in
src/idac/cli2/commands/, prefer_foo_request(args) -> FooRequestplusFooRequest.to_params()rather than spreading selector/default coercion through handlers. - When request-building logic becomes nontrivial, add a focused unit test for the builder itself in addition to end-to-end CLI coverage.
- Work from the binary first. Do not search the web or external source trees unless the user explicitly asks for that or the task is specifically about external correlation.
- During type or prototype recovery, always use
idac decompile --f5oridac decompilemany --f5so readback reflects the latest imported types and signatures.--f5is the same as--no-cache. - Before
function prototype set, runfunction prototype showto read the current signature and confirm what is changing. - After meaningful type or prototype mutations, run
idac misc reanalyze ...before local rename-heavy cleanup, then reread pseudocode or locals instead of assuming propagation. - Before batch local renames, capture
idac function locals list <func> --jsonand prefer--local-idor--indexselectors once prototypes or reanalysis may have shifted the local set. - Stop a rename batch on the first miss. Reread locals, recalibrate selectors, and only then continue.
- Declare support types before dependent prototypes. If a prototype references a missing type, create the placeholder type first and retry.
- Prefer minimal
structdeclarations first. Start with the vtable pointer and directly observed fields, keep uncertain names provisional, and use blob padding for unknown regions instead of guessed scalars.
Initial setup:
uv syncUseful local commands:
uv run idac --help
uv run idac --full-help
make format
make lint
make test
make check
uv run pytest -q tests/test_idalib_classes.py
IDAC_RUN_LIVE_GUI_TESTS=1 uv run pytest -q -m gui_live tests/test_gui_transport_live.pyPrefer targeted idac <command> --help when you already know the likely command family. Use idac --full-help when you need the full command tree in one pass.
When changing commands or request/response shapes:
- update CLI wiring in
src/idac/cli.py - update the operation implementation in
src/idac/ops/ - update renderers/schema if output shape changed
- update tests and any affected docs under
README.md,docs/, orsrc/idac/skills/idac/
When changing the operation layer, keep these boundaries in mind:
src/idac/ops/runtime.pyis the shared toolkit for reusable IDA-facing helpers. Prefer adding cross-operation lookup, normalization, and readback helpers there instead of duplicating them across op modules.- keep
src/idac/ops/families/focused on command-family orchestration, typed request/result models, and user-facing error messages src/idac/ops/manifest.pyis the source of truth for supported ops, mutation flags, and preview metadatasrc/idac/ops/dispatch.pyshould derive handler registration from the manifest and registry, not maintain a parallel operation listsrc/idac/ops/preview.pyshould stay thin. If preview behavior changes, prefer encoding defaults and policy inPreviewSpecrather than branching in wrappers.src/idac/cli2/renderers/__init__.pyowns text rendering. Before adding another formatter, look for an existing helper or adjacent renderer that can absorb the behavior.- for
type declare, keepDeclarationChunkas the internal representation through parse / diagnose / bisect flows and only convert to plain dicts at the API boundary when needed by tests or wire output - if you are tempted to add a module-level wrapper around an
IdaRuntimemethod, prefer calling the runtime instance method directly unless tests or external callers genuinely need the free function - shared non-runtime helpers should live under
src/idac/ops/helpers/
When changing GUI bridge behavior:
- check both
plugin/andsrc/idac/transport/gui.py - keep protocol expectations aligned across the plugin and the CLI transport
- add or update the optional
gui_livetest when the Unix socket contract changes
When changing idalib behavior:
- inspect
src/idac/transport/idalib.pyandsrc/idac/transport/idalib_worker.py - use targeted
idalibtests before running the whole suite
The primary class-recovery fixture is:
- source:
fixtures/src/handler_hierarchy.cpp - local type header:
fixtures/src/handler_hierarchy.hpp - build script:
fixtures/scripts/build_handler_hierarchy.sh - database script:
fixtures/scripts/make_handler_hierarchy_idbs.sh
The committed artifacts are:
fixtures/build/handler_hierarchyfixtures/build/handler_hierarchy.strippedfixtures/idb/handler_hierarchy.i64fixtures/idb/handler_hierarchy_stripped.i64fixtures/idb/handler_hierarchy.logfixtures/idb/handler_hierarchy_stripped.log
There is also a smaller tiny fixture used for lighter database/backend checks.
When running fixture-generation commands or any test flow that opens IDA or idat, do not rely on the live ~/.idapro directory.
Reason:
- the installed
~/.idapro/plugins/idac_bridge_plugin.pycan import the current checkout and break batch runs if the repo is mid-change - fixture regeneration should not depend on whatever plugins happen to be installed globally
- tests and fixture refreshes should not mutate the user's real IDA profile
Use an isolated IDAUSR that keeps the license/config files but omits plugins/.
Run from the repo root:
tmpdir=$(mktemp -d /tmp/idac-test-idapro.XXXXXX)
cp ~/.idapro/ida.reg "$tmpdir"/
cp ~/.idapro/ida-config.json "$tmpdir"/ 2>/dev/null || true
cp ~/.idapro/idapro_*.hexlic "$tmpdir"/ 2>/dev/null || true
mkdir -p "$tmpdir/plugins"
export IDAUSR="$tmpdir"This preserves the license/config that idat needs, while ensuring no globally installed plugins are loaded.
Rebuild the neutral class fixture and regenerate its databases/logs with the isolated IDAUSR:
bash fixtures/scripts/build_handler_hierarchy.sh
bash fixtures/scripts/make_handler_hierarchy_idbs.shIf you also need the tiny fixture refreshed:
bash fixtures/scripts/build_tiny.sh
bash fixtures/scripts/make_idbs.shIf idat logs show plugin import errors from ~/.idapro/plugins/idac_bridge_plugin.py, rerun with an isolated IDAUSR before assuming the fixture or code under test is broken.
Normal repo tests can run without IDAUSR, but keep the isolated directory exported when you are doing fixture refreshes or any workflow that may spawn idat.
Typical commands:
uv run pytest -q tests/test_idalib_classes.py
uv run pytest -qUseful targeted suites for operation-layer work:
uv run pytest -q tests/test_ops_helpers.py
uv run pytest -q tests/test_preview.py
uv run pytest -q tests/test_idalib_types.py
uv run pytest -q tests/test_idalib_name_locals_semantics.py
uv run pytest -q tests/test_idalib_struct_enum_semantics.py
uv run pytest -q tests/test_vtable_helpers.pyThese are especially useful when editing:
- preview / manifest / registry wiring
type declarediagnostics or bisect behavior- local-variable mutation and preview behavior
- class / vtable helper logic
Optional live GUI transport coverage is marked with @pytest.mark.gui_live and skipped unless IDAC_RUN_LIVE_GUI_TESTS=1 is set.
The bundled idac skill supports both Claude Code and Codex equally.
- default install targets:
~/.claude/skills/idac~/.codex/skills/idac
- custom install destination:
idac misc skill install --dest /custom/path/idac
For fixture-driven class tests, prefer updating and validating:
tests/conftest.pytests/test_idalib_classes.pyREADME.mdsrc/idac/skills/idac/
- Do not modify the real
~/.idapro/pluginscontents as part of routine repo work. - Do not point fixture-generation commands at the live
~/.idaprounless the user explicitly asks for that. - Do not leave docs/examples referencing old fixture symbol names after a rename.
- When changing committed fixture symbols, verify the asserted mangled names from the rebuilt binary or regenerated database instead of guessing.
- Do not stage
PLAN.md; keep it as local planning scratch unless the user explicitly says otherwise. - Leave unrelated untracked scratch directories and recovery artifacts alone unless the user explicitly asks to clean them up.