Metadata-Version: 2.3
Name: bn-cli
Version: 0.14.0
Summary: Agent-friendly Binary Ninja CLI with a GUI companion bridge
Author: banteg
Author-email: banteg <4562643+banteg@users.noreply.github.com>
Requires-Dist: tiktoken>=0.12.0
Requires-Python: >=3.12
Project-URL: Repository, https://github.com/banteg/bn
Project-URL: Issues, https://github.com/banteg/bn/issues
Project-URL: Changelog, https://github.com/banteg/bn/blob/master/CHANGELOG.md
Description-Content-Type: text/markdown

# bn

`bn` is a coding agent-first CLI for Binary Ninja. It gives a shell session or tool-calling agent stable commands, structured output, and access to the same live Binary Ninja database you already have open in the GUI.

See the [changelog](CHANGELOG.md) for release notes by version and date.

## Headline Features

- Query live Binary Ninja state from the shell: targets, functions, callsites, decompile text, IL, disassembly, xrefs, types, strings, imports, and reusable bundles.
- Execute unrestricted Python inside the Binary Ninja process, with stable helpers for common reads and symbol/function resolution.
- Apply mutations with `--preview`, capture decompile diffs, and verify the live post-state before reporting success.
- Emit structured `json` or `ndjson` output, auto-spill large results to files, and return token counts so agents can budget context intelligently.

## Install

Recommended setup: install the CLI, the Binary Ninja companion plugin, and the bundled Codex skill.

Install the CLI on your PATH:

```bash
uv tool install bn-cli
```

Install the Binary Ninja companion plugin:

```bash
bn plugin install
```

That links the packaged `bn_agent_bridge` companion into your Binary Ninja plugins directory.

Install the bundled Codex skill:

```bash
bn skill install
```

That symlinks the packaged `bn` skill into `$CODEX_HOME/skills/bn` by default. Use `--mode copy` if you want a standalone copy instead. Restart Codex to pick up a new or renamed skill.

Upgrade later with `uv tool upgrade bn-cli`.

Use `BN Agent Bridge\\Restart Bridge` from Binary Ninja's command palette to restart the socket bridge. After plugin code or version changes, reload Python plugins or restart Binary Ninja so Python imports the new module code.

## How It Works

- `bn` has two parts:
  - a normal Python CLI that you can run from your shell or agent tool harness
  - a Binary Ninja GUI plugin that owns all Binary Ninja API access
- The plugin creates one fixed bridge socket and one fixed registry file.
- The CLI discovers that bridge, connects to it, and forwards commands into the live GUI session.
- Because the Binary Ninja side runs as a GUI plugin, it works with a personal license and does not require a commercial headless license.

## Quick Start

Open a binary or `.bndb` in Binary Ninja, then run:

```bash
bn doctor
bn target list
bn refresh
bn function list
bn decompile sub_401000
```

If exactly one BinaryView is open, target-specific commands can omit `--target` entirely. If multiple targets are open, pass `--target <selector>` from `bn target list`.

## Target Selection

Use `bn target list` to see available targets:

```bash
bn target list
```

Targets can be selected with:

- the `selector` field from `bn target list`
- the full `target_id`
- the BinaryView basename
- the full filename
- the view id
- `active` when you explicitly want the GUI-selected target

In normal use, prefer the `selector` field. For a single open database, this is usually just the `.bndb` basename:

```bash
bn decompile update_player_movement_flags --target SnailMail_unwrapped.exe.bndb
```

`--target` may appear anywhere in the command, including before the top-level command, and `BN_TARGET` provides a shell-wide default:

```bash
bn --target SnailMail_unwrapped.exe.bndb decompile update_player_movement_flags
bn function --target SnailMail_unwrapped.exe.bndb search attachment
export BN_TARGET=SnailMail_unwrapped.exe.bndb
```

Omitting `--target` only works when exactly one target is open. If multiple targets are open, the CLI rejects the command instead of silently falling back to `active`.

## Output Behavior

Every command supports:

- `--format json`
- `--format text`
- `--format ndjson`
- `--out <path>`
- `--match <regex>` with `--before` and `--after` text context
- `--no-spill` to stream a complete result to stdout

Interactive read commands default to `text`. Mutation, setup, and export commands default to `json`.
Add `--format json` when you need stable fields for automation or piping into structured tooling.

Examples:

```bash
bn function list --format ndjson
bn function list --min-address 0x401000 --max-address 0x40ffff
bn function search --regex 'attach|detach'
bn decompile sample_track_floor_height_at_position --out /tmp/floor.json
```

If `--out` is set, the command writes the rendered result to that path and prints a compact JSON envelope with the artifact path, byte size, token count, tokenizer, hash, and summary. Agents can use that envelope to decide whether to read the full artifact, keep a summary, or defer loading it into context.

Use `--match` when an agent only needs a few lines from otherwise large text output. Use `--no-spill` when a downstream process must consume stdout directly and is prepared for the full result.

`bn function list` and `bn function search` return the full matching set for the selected target or address range. Large results auto-spill to an artifact instead of forcing manual pagination. Spill is token-based and currently triggers above 10,000 tokens. When that happens, stdout stays empty and stderr carries the spill metadata as plain text, including the artifact path and size counts.

## Extraction Commands

Common read-only commands:

```bash
bn target list
bn target info

bn function list
bn function list --min-address 0x401000 --max-address 0x40ffff
bn function search attachment
bn function search --regex 'attach|detach|follow'
bn function info end_track_attachment_follow_state
bn function containing 0x401234
bn callsites crt_rand --within bonus_pick_random_type
bn callsites crt_rand
bn callsites crt_rand --within-file /tmp/rng-functions.txt --format ndjson
bn proto get end_track_attachment_follow_state
bn local list end_track_attachment_follow_state
bn refresh

bn decompile end_track_attachment_follow_state
bn il end_track_attachment_follow_state
bn disasm end_track_attachment_follow_state
bn disasm 0x401234 --before 5 --after 10
bn address info global_player+0x308
bn data read global_player+0x308 --type u32 --count 4
bn xrefs end_track_attachment_follow_state
bn xrefs global_player+0x308
bn refs end_track_attachment_follow_state
bn xrefs field TrackRowCell.tile_type
bn comment get --address 0x401000

bn types --query Player
bn types show Player
bn struct show Player
bn types declare --file /path/to/win32_min.h --preview
bn strings --query follow
bn imports
bn search text 'crt_rand' --view hlil --max-results 50
bn search constant 0x370 --max-results 50
bn schema --format json --out /tmp/bn-schema.json
```

`bn function search` stays case-insensitive substring matching by default. Add `--regex` when you need regular expressions. `bn function list` and `bn function search` both accept `--min-address` and `--max-address` to filter by function start address.

`bn callsites` is the direct-call lane for exact return-address recovery. It reports both the native `call_addr` and the post-call `caller_static`, where `caller_static = call_addr + instruction_length`. Without a scope it derives caller functions from Binary Ninja's inbound code-reference index. Use `--within <function>` or `--within-file <path>` to narrow the work; the file format is one function identifier per non-empty line, with `#` comments ignored.

Read commands that take a function also accept an address inside that function. Address-bearing commands resolve numeric addresses, function names, data/import symbols, and `symbol+offset` expressions.

Whole-database searches stop after five seconds by default and return partial-result metadata when the time or result limit is reached. Raise the budget explicitly with `--timeout <seconds>` when a slower IL view is intentional.

Each callsite row also includes:

- `call_index`: zero-based ordinal for matching callsites in the containing function, ordered by `call_addr`
- `within_query`: the original unresolved scope token from `--within` or `--within-file`
- `hlil_statement`: the smallest recoverable HLIL expression or statement containing the call, or `null` when Binary Ninja only exposes a coarse enclosing region
- `pre_branch_condition`: the nearest enclosing pre-call HLIL condition when it can be recovered confidently, otherwise `null`

`hlil_statement` is intentionally local-or-null. If the best available HLIL mapping expands to a broad whole-function or multi-statement blob, `bn callsites` suppresses it instead of returning noisy context.

## Bundles And Python

`bn decompile` is the HLIL-text convenience lane. It is useful for quick function reading, but typed layouts remain authoritative in `bn types show` and `bn struct show`.

Export a reusable function bundle:

```bash
bn bundle function end_track_attachment_follow_state --out /tmp/end_track_attachment_follow_state.json
```

Run Python inside the Binary Ninja process for one-off inspection and BN-native scripting:

```bash
bn py exec --code "print(hex(bv.entry_point)); result = {'functions': len(list(bv.functions))}"

bn py exec --stdin <<'PY'
print(hex(bv.entry_point))
result = {"functions": len(list(bv.functions))}
PY
```

Use `--stdin` or `--script` for multiline Python snippets. Use `--code` for true one-liners only.

```bash
bn py exec --stdin <<'PY'
out = []
for f in bv.functions:
    if 0x416000 <= f.start < 0x41C000:
        out.append((f.start, f.symbol.short_name))
out.sort()
print("\n".join(f"{addr:#x} {name}" for addr, name in out))
PY
```

Use a quoted heredoc for multiline Python snippets.

When you need counts from BN iterators such as `f.hlil.instructions`, materialize them explicitly with `list(...)` or consume them with `sum(1 for ...)` instead of assuming sequence semantics.

The `py exec` environment includes:

- `bn`
- `binaryninja`
- `bv`
- `current_view` (an alias for `bv`)
- `address`, `function`, and `functions_containing`
- `read_u8`, `read_u16`, `read_u32`, `read_u64`
- `read_i8`, `read_i16`, `read_i32`, `read_i64`
- `read_ptr`, `read_f32`, `read_f64`, and `read_cstr`
- `result`

These small functions cover common API-shape mistakes without introducing a wrapper API or restricting the full Binary Ninja module or view object. Stdout and `result` are both returned. If `result` is not JSON-serializable, `bn` returns `repr(result)` and includes a warning instead of silently stringifying the whole response. Python failures include the in-process traceback and any stdout produced before the exception.

## Agent Protocol

`bn schema --format json` emits the protocol version plus the complete global and per-command argument surface without contacting Binary Ninja. `bn doctor --format json` reports the live plugin's supported operations and feature flags. Bridge failures use structured error codes and details in JSON/NDJSON mode, while text mode includes concise suggestions.

## Mutation Commands

Mutations follow the same target-selection rules as other target-specific commands.

Examples:

```bash
bn symbol rename sub_401000 player_update --preview
bn comment set --address 0x401000 "interesting branch" --preview
bn comment get --address 0x401000
bn proto get sub_401000
bn proto set sub_401000 "int __cdecl player_update(Player* self)" --preview
bn local list sub_401000
bn local rename sub_401000 0x401000:local:StackVariableSourceType:-20:2:12345 speed --preview
bn local retype sub_401000 0x401000:local:StackVariableSourceType:-20:2:12345 float --preview
bn types declare "typedef struct Player { int hp; } Player;" --preview
bn struct field set Player 0x308 movement_flag_selector uint32_t --preview
```

Preview mode applies the change, refreshes analysis, captures affected decompile diffs, and then reverts the mutation.

Non-preview writes only report success after reading the live BN session back and verifying that the requested post-state actually landed. If verification fails, the CLI returns a nonzero exit code and reverts the mutation.

After any live type or prototype mutation, do an explicit readback:

```bash
bn proto get sub_401000
bn struct show Player
bn types show Player
bn decompile sub_401000
```

For declaration and struct mutations, preview results also include `affected_types` with before/after layouts and a unified diff. If a field edit is already identical, the result is marked with `changed: false` and a `No effective change detected` message.

For the first few changed functions, `affected_functions` also includes short `before_excerpt` and `after_excerpt` snippets around the first changed HLIL lines.

Mutation results now distinguish:

- `verified`
- `noop`
- `unsupported`
- `verification_failed`

When verification fails, JSON output also includes `requested` and `observed` state for the failed op.

`bn types declare` now uses Binary Ninja's source parser when available. When you pass `--file`, the CLI also forwards the source path so relative includes resolve the same way they would during header import in the GUI.

If a declaration only parses functions or extern variables and introduces no named types to persist, `types declare` returns a verified no-op instead of failing with `No named types found in declaration`.

`bn local list` and `bn function info` return stable `local_id` values for parameters and locals. Prefer those IDs for `bn local rename` and `bn local retype`; legacy name-based targeting still works for compatibility.

## Troubleshooting

Check bridge state:

```bash
bn doctor
```

If `bn target list` is empty:

- make sure Binary Ninja is open
- make sure a binary or `.bndb` is open
- make sure the plugin is installed with `bn plugin install`
- reload Binary Ninja plugins or restart Binary Ninja after plugin changes

If `bn doctor` sees a bridge registry but reports `Operation not permitted` under Codex,
the Codex sandbox is blocking the Unix socket that connects to the live Binary Ninja GUI
process. Let Codex run `bn` outside the sandbox by adding this rule to
`~/.codex/rules/default.rules`:

```text
prefix_rule(pattern=["bn"], decision="allow")
```

Restart Codex or reload rules after editing the file. This is only needed for Codex
sandboxed runs; normal shells can use `bn` without that rule.

If multiple targets are open, omitted `--target` is rejected. Pass `--target <selector>` from `bn target list`, or use `--target active` only when you intentionally want the GUI-selected target.

If decompile text still looks stale after a type change, run:

```bash
bn refresh
```

That forces an analysis refresh, but it still may not fully eliminate Binary Ninja's stale `__offset(...)` presentation in every case.

## Development

Run tests with:

```bash
uv run pytest
```

Run the CLI from the repo without installing it globally:

```bash
uv run bn --help
```

Install an editable development build as a uv tool with:

```bash
uv tool install -e .
```

Verify that package, plugin, and lockfile versions agree:

```bash
uv run python scripts/release.py --check
```

For a release, update every version field with one command before editing the changelog and creating the tag:

```bash
uv run python scripts/release.py 0.14.0
```

Pushing the matching `v0.14.0` tag runs the publish workflow, which tests, builds, publishes the distributions to PyPI through Trusted Publishing, and creates the GitHub release from the same artifacts.
