diff options
38 files changed, 243 insertions, 244 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8efdfe7..b66df1c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,12 +18,12 @@ without a licence. uv sync ``` -That pulls [ida-codemode](https://github.com/HexRaysSA/ida-codemode) from PyPI, +That pulls [ida-nexus](https://github.com/HexRaysSA/ida-nexus) from PyPI, which is how ida-tui talks to IDA. To also attach to databases open in the IDA GUI: ```sh -uvx ida-hcli plugin install ida-codemode +uvx ida-hcli plugin install ida-nexus ``` ## Running the tests @@ -39,7 +39,7 @@ python3 tests/run.py # everything (needs IDA) ``` The IDA-backed suites need an interpreter that has `textual`, `idapro` and -`ida_codemode` on it: +`ida_nexus` on it: ```sh <ida-python> tests/test_scenarios.py /path/to/binary --only rename @@ -47,7 +47,7 @@ The IDA-backed suites need an interpreter that has `textual`, `idapro` and ``` **House rule:** a suite marked `pure` must keep running under a plain system -`python3`. This is why `idatui/codemode_client.py` defers its `ida_codemode` +`python3`. This is why `idatui/nexus_client.py` defers its `ida_nexus` import instead of doing it at module top. Please don't break that — it's what keeps the fast gate fast and lets people without IDA contribute at all. @@ -27,18 +27,18 @@ Needs **Python ≥ 3.11** and **IDA Pro 9.4+ with idalib**. uv sync ``` -That pulls [ida-codemode](https://github.com/HexRaysSA/ida-codemode) from PyPI, +That pulls [ida-nexus](https://github.com/HexRaysSA/ida-nexus) from PyPI, which is how ida-tui talks to IDA. To also attach to databases you have open in the IDA GUI, install its plugin: ```sh -uvx ida-hcli plugin install ida-codemode +uvx ida-hcli plugin install ida-nexus ``` -Hacking on ida-codemode itself? Point at a checkout instead: +Hacking on ida-nexus itself? Point at a checkout instead: ```sh -uv add --editable ../ida-codemode +uv add --editable ../ida-nexus ``` ## Run @@ -49,9 +49,9 @@ uv add --editable ../ida-codemode ``` ida-tui never owns an IDA process — it takes a **lease**. A matching database open -in the IDA GUI is reused, otherwise Code Mode starts or shares a managed idalib +in the IDA GUI is reused, otherwise IDA Nexus starts or shares a managed idalib worker. Quitting drops the lease and leaves everyone else alone. -Changes made in the GUI or another client arrive over Code Mode's IDB event +Changes made in the GUI or another client arrive over IDA Nexus's IDB event stream; ida-tui debounces bursts and refreshes its cached views automatically. On quit with unsaved changes, a final managed-worker lease can discard the session without saving; GUI-backed or still-shared sessions leave that final @@ -59,9 +59,9 @@ decision with their owner or remaining clients. If the owning GUI or worker closes, ida-tui never replaces it by spawning a headless worker implicitly. It keeps the cached view disconnected until a matching owner is reopened and an attach-only rediscovery succeeds. -Remote operations are typed, source-backed Python functions. ida-codemode -installs content-addressed modules once per handle, so ida-tui keeps normal -refactorable source without paying to resend hot listing/decompiler code. +Remote operations are typed, source-backed Python functions. ida-nexus installs +their content-addressed modules once per IDA Python interpreter, so ida-tui keeps +normal refactorable source without paying to resend hot listing/decompiler code. Operation attribution is also a per-call provider rather than a fixed string, so it can evolve from `IDA TUI` to labels such as `IDA TUI: alice`. @@ -4,12 +4,12 @@ TODO: [x] PORT TO ida-codemode-mcp as a library dependency [x] DatabaseHandle discovery prefers registered GUI sessions [x] shared managed idalib workers + SSE lease lifecycle - [x] domain operations execute against ida-domain through Code Mode + [x] domain operations execute against ida-domain through IDA Nexus [x] delete the private pickle worker and ida-pro-mcp patch injection [x] stop sweeping/reaping resources that may belong to another client [ ] run the full live Pilot suite against both GUI and managed backends [ ] add database revision/change notifications for cross-client cache invalidation - [ ] decide how "discard changes" should work (Code Mode final workers save) + [ ] decide how "discard changes" should work (IDA Nexus final workers save) - [x] add support for toggling literal types, ala `o` in IDA. (decimal to hex to reference etc.) diff --git a/docs/GRAPH_VIEW.md b/docs/GRAPH_VIEW.md index 3aced1f..0b24aa5 100644 --- a/docs/GRAPH_VIEW.md +++ b/docs/GRAPH_VIEW.md @@ -54,7 +54,7 @@ Growing a second disassembly renderer for graph mode would have been the real cost. The backend adds exactly one operation, `flowchart(addr)` in -`idatui/codemode_client.py`, which returns block ranges and typed edges — **not** +`idatui/nexus_client.py`, which returns block ranges and typed edges — **not** text. ## Two layout engines diff --git a/docs/CODEMODE_UPSTREAM.md b/docs/NEXUS_UPSTREAM.md index 7dff13e..f4dcf91 100644 --- a/docs/CODEMODE_UPSTREAM.md +++ b/docs/NEXUS_UPSTREAM.md @@ -1,14 +1,14 @@ -# Findings from porting a real client to IDA Code Mode +# Findings from porting a real client to IDA Nexus -Notes for the `ida-codemode` maintainers, gathered while porting **ida-tui** (a +Notes for the `ida-nexus` maintainers, gathered while porting **ida-tui** (a Textual TUI frontend for IDA) from a private idalib worker to -`ida_codemode.DatabaseHandle`. +`ida_nexus.DatabaseHandle`. Everything below is measured, not inferred. Where we worked around something, the workaround is named so you can judge whether the library should make it unnecessary. -**Environment:** ida-codemode 0.3.1, IDA 9.4 (idalib), Linux, single managed +**Environment:** ida-nexus 0.3.1, IDA 9.4 (idalib), Linux, single managed worker backend, quiet box. Target for timings: `targets/echo` unless stated. > **Status against the protocol-6 event-stream development tree, based on 0.6.1 @@ -19,12 +19,12 @@ worker backend, quiet box. Target for timings: `targets/echo` unless stated. > | 1 `timeout_trace` line tracing | ✅ **fixed in 0.3.2** — no `settrace` in the runtime at all | > | 2 `to_jsonable` on large results | ✅ **fixed in 0.3.2** — `dumps_json` C fast path | > | 3 2 ms `execute_sync` floor | ✅ **fixed in 0.3.2, 7.0x** — 2.055 ms → 0.294 ms | -> | 4 loader switches fatal on reopen | **partial** — normal reopen fixed in 0.5.x; direct `.i64` paths remain [issue #36](https://github.com/HexRaysSA/ida-codemode/issues/36) | +> | 4 loader switches fatal on reopen | **partial** — normal reopen fixed in 0.5.x; direct `.i64` paths remain [issue #36](https://github.com/HexRaysSA/ida-nexus/issues/36) | > | 5 IDB replaced under a live lease | **stale** — out-of-band replacement is outside the supported lifecycle, as it is for the IDA GUI | > | 6 close without save | **fixed in protocol 6** — the final managed-worker lease can choose `shutdown_database(save=False)` | > | 7 no change notification | ✅ **fixed in protocol 6** — `DatabaseHandle.subscribe_idb_events()` streams revisioned, operation-attributed IDB changes | > | 8 package exports | ✅ **fixed in 0.5.x** — a real `__all__` on the package root | -> | 9 no `py.typed` / handle Protocol | ✅ **fixed in 0.5.x** — `ida_codemode/py.typed` ships | +> | 9 no `py.typed` / handle Protocol | ✅ **fixed in 0.5.x** — `ida_nexus/py.typed` ships | > > **0.5.x restructured the package**, which is why the old "these files are > byte-identical" re-check recipe no longer works: `client.py` → `handle.py`, @@ -32,16 +32,16 @@ worker backend, quiet box. Target for timings: `targets/echo` unless stated. > and the loader options moved into a frozen `DatabaseOpenOptions` dataclass. > Everything private is now underscore-prefixed, so the cheap re-check after an > upstream pull is simply: does anything we import still appear in -> `ida_codemode.__all__`? +> `ida_nexus.__all__`? > > 0.5.3 → 0.6.1 changed **nothing** we depend on: `__init__.py`, `handle.py`, > `instances.py`, `options.py`, `errors.py` and `models.py` are byte-identical > between those two releases. 0.6.1 only collapses the six console scripts into a -> single `ida-codemode` command. +> single `ida-nexus` command. > > Both client-side workarounds re-measured at **0.99x and 0.97x** on 0.3.2 — > i.e. nothing — and are deleted. Remote code is now ordinary typed Python, -> installed as content-addressed modules by ida-codemode. Harness: +> installed as content-addressed modules by ida-nexus. Harness: > `experiments/bench_pack_trace.py`. **What the client does**, for scale: it renders a continuous disassembly listing, @@ -232,7 +232,7 @@ nonsense invocation and no ida-tui code path generates it — the project layer always passes `output_database`, and `_needs_load_options` bails when an `.i64` exists — but the library boundary should still reject or normalize it rather than launch a known-fatal IDA command. Tracked upstream as -[issue #36](https://github.com/HexRaysSA/ida-codemode/issues/36). +[issue #36](https://github.com/HexRaysSA/ida-nexus/issues/36). --- @@ -240,7 +240,7 @@ than launch a known-fatal IDA command. Tracked upstream as The original suite deleted an `.i64` while a private worker still had it open, then immediately reopened the same path. That ownership model no longer applies: -Code Mode databases are shared resources, and the IDA GUI itself does not survive +IDA Nexus databases are shared resources, and the IDA GUI itself does not survive out-of-band replacement of its open database. Detecting arbitrary filesystem replacement is therefore not part of the supported lifecycle. @@ -301,7 +301,7 @@ databases closes the subscription, so the blocking event reader does not leak. ## 8. Package exports and API surface stability — FIXED in 0.5.x -`ida_codemode/__init__.py` used to export nothing, so a library consumer had to +`ida_nexus/__init__.py` used to export nothing, so a library consumer had to import from submodules, including things that were clearly internals (`FileLock`, `REGISTRY_DIR`, `canonical_path`, `idb_key`, `scan_instances`) that we only touched because no public equivalent existed. @@ -313,9 +313,9 @@ the package root, and mark the intended-public registry helpers explicitly. the internals moved behind an underscore: ```python -from ida_codemode import DatabaseHandle, DatabaseOpenOptions, DatabaseInstance -from ida_codemode import RemoteError, DatabaseBusyError, DatabaseDisconnectedError -from ida_codemode import discover_databases, find_database_owner, wait_database_released +from ida_nexus import DatabaseHandle, DatabaseOpenOptions, DatabaseInstance +from ida_nexus import RemoteError, DatabaseBusyError, DatabaseDisconnectedError +from ida_nexus import discover_databases, find_database_owner, wait_database_released ``` The two lock-poking helpers we had reimplemented client-side @@ -342,13 +342,13 @@ can be checked against the real signature and a typo is caught statically. (We added a test asserting our kwargs are a subset of `inspect.signature(DatabaseHandle.open).parameters`, which is a poor substitute.) -**0.5.x ships `ida_codemode/py.typed`**, and the 30 keyword-only options became a +**0.5.x ships `ida_nexus/py.typed`**, and the 30 keyword-only options became a frozen `DatabaseOpenOptions` dataclass — which is strictly better, because an invented option name is now a `TypeError` at construction rather than something a `**kwargs` fake swallows. Our subset test survives in two halves (`_open_kwargs_are_real` for `open()`, `_option_fields_are_real` for the dataclass fields), because the offline contract suite must keep running with no -`ida_codemode` installed at all and therefore still fakes both. +`ida_nexus` installed at all and therefore still fakes both. --- @@ -360,7 +360,7 @@ dataclass fields), because the offline contract suite must keep running with no | ~~2~~ | ~~`to_jsonable` on large results~~ | ~~114x on serialisation~~ | ✅ fixed in 0.3.2 | | ~~3~~ | ~~2 ms `execute_sync` floor~~ | ~~shapes client design~~ | ✅ fixed in 0.3.2, 7.0x | | ~~7~~ | ~~no change/revision counter~~ | ~~correctness for shared editing~~ | ✅ fixed in protocol 6 | -| 4 | direct `.i64` forwards loader-only options | fatal worker startup | [issue #36](https://github.com/HexRaysSA/ida-codemode/issues/36) | +| 4 | direct `.i64` forwards loader-only options | fatal worker startup | [issue #36](https://github.com/HexRaysSA/ida-nexus/issues/36) | | ~~5~~ | ~~replaced/deleted IDB under lease~~ | ~~out-of-contract filesystem mutation~~ | **stale** | | ~~6~~ | ~~no close without save~~ | ~~could not discard a managed session~~ | **fixed in protocol 6: final lease decides** | | ~~8~~ | ~~package exports~~ | ~~forces internal imports~~ | ✅ fixed in 0.5.x | @@ -377,9 +377,9 @@ retired. That is the outcome this document was written for. **What is left is entirely non-performance.** Items 6 through 9 are fixed, and item 5 is stale because out-of-band replacement is not a supported lifecycle for -either Code Mode or the IDA GUI. One narrow piece remains: **4**, normalize or +either IDA Nexus or the IDA GUI. One narrow piece remains: **4**, normalize or reject loader-only options when the source is itself an existing `.i64` -([issue #36](https://github.com/HexRaysSA/ida-codemode/issues/36)). +([issue #36](https://github.com/HexRaysSA/ida-nexus/issues/36)). Happy to supply the benchmark harness (it is backend-agnostic and runs against -both our old worker and Code Mode), or to test a patch. +both our old worker and IDA Nexus), or to test a patch. diff --git a/docs/PAGING_FINDINGS.md b/docs/PAGING_FINDINGS.md index bd6c38f..f343973 100644 --- a/docs/PAGING_FINDINGS.md +++ b/docs/PAGING_FINDINGS.md @@ -3,9 +3,9 @@ Measured against a real target: `libcrypto.so.3` (5.7 MB, **10,092 functions**, biggest function **52,120 instructions**). These constraints drive the domain / paging layer. The measurements below came from the former ida-pro-mcp tool -backend. The Code Mode port preserves the adapter response shapes and conservative +backend. The IDA Nexus port preserves the adapter response shapes and conservative page sizes, but executes enumeration through ida-domain; old server caps and RTT -numbers are historical rather than Code Mode constraints. +numbers are historical rather than IDA Nexus constraints. ## Response shape (list_* / *_query tools) @@ -93,17 +93,17 @@ disasm totals are **top-level** fields, not under `asm`: (correct). The pseudocode view must handle "decompilation failed" gracefully — fall back to the disassembly view or show an error panel. -Code Mode returns the complete execution result directly; ida-tui no longer +IDA Nexus returns the complete execution result directly; ida-tui no longer needs MCP structured-content/download-URL recovery for large pseudocode bodies. -## Code Mode lifecycle +## IDA Nexus lifecycle -`CodeModeClient` owns an authenticated SSE lease on a registered database: +`NexusClient` owns an authenticated SSE lease on a registered database: * A matching GUI is preferred and remains open when the TUI exits. -* Otherwise Code Mode reuses or starts a shared managed idalib worker. +* Otherwise IDA Nexus reuses or starts a shared managed idalib worker. * Releasing one lease never terminates another client's session. A managed - worker saves and exits after its final lease under Code Mode's grace policy. + worker saves and exits after its final lease under IDA Nexus's grace policy. * Lease loss surfaces as `IDAConnectionError`; reconnect performs discovery again and may bind a newly-created instance. It does not silently swap the handle underneath an operation. diff --git a/docs/PROJECTS.md b/docs/PROJECTS.md index 0efee31..52dbf83 100644 --- a/docs/PROJECTS.md +++ b/docs/PROJECTS.md @@ -7,7 +7,7 @@ search across all of them, and (later) follow calls from one into another. ## The constraint that shapes everything -IDA still exposes one active database per GUI/idalib process. Code Mode makes +IDA still exposes one active database per GUI/idalib process. IDA Nexus makes those instances discoverable and shareable: each project entry retains one `DatabaseHandle` lease, which may target a registered GUI or a managed idalib worker. N resident project databases can therefore mean up to N processes, but @@ -32,13 +32,13 @@ crypto library. Two capabilities that feel like one, but aren't: -1. **Switching** to a binary needs a *live Code Mode lease*. +1. **Switching** to a binary needs a *live IDA Nexus lease*. 2. **Searching across** binaries does *not* — if a per-binary index (functions, strings, imports/exports) is cached on disk. That split is the unlock: project-wide search stays instant across every binary, including ones never opened this session, and only *jumping* to a hit costs a -Code Mode attach/open. +IDA Nexus attach/open. ## Layout @@ -85,11 +85,11 @@ basename and must be unique (it names the staged file). ## Runtime -- **`DatabasePool`** — one `CodeModeClient` lease per resident binary, attached +- **`DatabasePool`** — one `NexusClient` lease per resident binary, attached lazily on first switch and LRU-released when the advisory memory budget is exceeded. Eviction explicitly saves managed IDBs but never implicitly saves a GUI. Closing a lease never kills a GUI or another client's managed worker; - Code Mode owns final worker shutdown. + IDA Nexus owns final worker shutdown. - **`BinaryState`** — per binary: `client, program, nav, cur, func_index, pref/active/split, filter`. Switching snapshots the current state and restores the target's. `_after_reconnect` provides the client/program swap seam. diff --git a/docs/SPLIT_VIEW.md b/docs/SPLIT_VIEW.md index c421656..6e9b938 100644 --- a/docs/SPLIT_VIEW.md +++ b/docs/SPLIT_VIEW.md @@ -32,7 +32,7 @@ known technique: The old ida-pro-mcp backend derived the per-line marker via `cfunc.get_line_item(line, col=0, …).get_ea()`. To get the **full set**, sweep every column of the line (`get_line_item(line, x, …).get_ea()` for `x` in -`0..len`) and collect distinct non-`BADADDR` EAs. The Code Mode adapter's +`0..len`) and collect distinct non-`BADADDR` EAs. The IDA Nexus adapter's `decomp_map(ea)` operation returns `[{line, primary_ea, eas:[…]}, …]`; invert for `ea → line`. @@ -78,14 +78,14 @@ decomp→listing uses `ListingModel.ensure_ea`. Tab re-links from the new driver Still single-ea per line (one instruction highlighted); the region comes in phase 3. -**Phase 3 — rich highlight. DONE.** The Code Mode `decomp_map` operation -(`idatui/codemode_client.py`) sweeps `cfunc.get_line_item` across every column of +**Phase 3 — rich highlight. DONE.** The IDA Nexus `decomp_map` operation +(`idatui/nexus_client.py`) sweeps `cfunc.get_line_item` across every column of each pseudocode line and collects the EAs from each item's `dstr()` (`'EA: desc'` — the same source as the `/*ea*/` marker, so it aligns). `Program.decomp_map(ea)` returns the per-line ea lists (cached by name-gen); the app loads it async into `_split_eamap` / `_split_ea2line` and `_sync_split` bands the **whole** instruction region of a C line (and uses the exact ea→line inverse for the reverse). Falls -back to the single marker until the map lands. Verified on a real Code Mode database +back to the single marker until the map lands. Verified on a real IDA Nexus database (alignment + multi-instruction region band). **Phase 4 — polish. DONE.** diff --git a/experiments/bench_ops.py b/experiments/bench_ops.py index 9cc51fa..1c0ad69 100644 --- a/experiments/bench_ops.py +++ b/experiments/bench_ops.py @@ -1,4 +1,4 @@ -"""Time a realistic idatui operation mix against whatever ida-codemode is installed. +"""Time a realistic idatui operation mix against whatever ida-nexus is installed. The companion to `bench_pack_trace.py`: that one isolates a single workaround, this one answers "how much faster is the whole client, on real operations". @@ -14,16 +14,16 @@ replace or delete it:: PYTHONPATH=. ~/ida-venv/bin/python /tmp/bench_ops.py # B: current client against the OLD library (shows what the workarounds were for) - git -C ~/dev/ida-codemode checkout 4195f21 + git -C ~/dev/ida-nexus checkout 4195f21 PYTHONPATH=. ~/ida-venv/bin/python /tmp/bench_ops.py # A: the client as it SHIPPED on the old library, workarounds and all git checkout 8550474 # the commit before the workaround removal PYTHONPATH=. ~/ida-venv/bin/python /tmp/bench_ops.py - git checkout main && git -C ~/dev/ida-codemode checkout main # ALWAYS restore + git checkout main && git -C ~/dev/ida-nexus checkout main # ALWAYS restore -ida-codemode is installed **editable** into both venvs, so checking that repo out +ida-nexus is installed **editable** into both venvs, so checking that repo out swaps the backend under the TUI with no reinstall -- which is what makes this A/B cheap. """ @@ -36,7 +36,7 @@ import statistics import time from idatui import remote_ops -from idatui.codemode_client import CodeModeClient +from idatui.nexus_client import NexusClient def bench(fn, reps: int) -> tuple[float, float]: @@ -55,7 +55,7 @@ def main() -> int: ap.add_argument("--reps", type=int, default=20) args = ap.parse_args() - client = CodeModeClient(os.path.abspath(args.target)) + client = NexusClient(os.path.abspath(args.target)) client.connect() handle = client._handle diff --git a/experiments/bench_pack_trace.py b/experiments/bench_pack_trace.py index edbb9f7..2b53afd 100644 --- a/experiments/bench_pack_trace.py +++ b/experiments/bench_pack_trace.py @@ -17,7 +17,7 @@ import statistics import time from idatui import remote_ops -from idatui.codemode_client import CodeModeClient +from idatui.nexus_client import NexusClient def timed(function, reps: int = 1) -> tuple[object, float]: @@ -37,7 +37,7 @@ def main() -> int: parser.add_argument("--rows", type=int, default=200) args = parser.parse_args() - client = CodeModeClient(os.path.abspath(args.target)).connect() + client = NexusClient(os.path.abspath(args.target)).connect() index, operations_cold = timed( lambda: client.call(remote_ops.list_funcs, queries=[{"offset": 0, "count": 40}]) diff --git a/experiments/call_census.py b/experiments/call_census.py index 1680691..17a5f94 100644 --- a/experiments/call_census.py +++ b/experiments/call_census.py @@ -1,7 +1,7 @@ """Count backend round-trips per user action. Answers "are we batching, or paying a round-trip per item?" with numbers rather -than intent. Wraps ``CodeModeClient.invoke`` on the live app, drives a headless +than intent. Wraps ``NexusClient.invoke`` on the live app, drives a headless Pilot through realistic actions, and reports calls + wall time + which operations were used for each. @@ -25,7 +25,7 @@ from _fixtures import fast_keys, staged # noqa: E402 fast_keys() from idatui.app import IdaTui, ListingView # noqa: E402 -from idatui.codemode_client import CodeModeClient # noqa: E402 +from idatui.nexus_client import NexusClient # noqa: E402 class Census: @@ -34,18 +34,18 @@ class Census: def __init__(self) -> None: self.ops: collections.Counter = collections.Counter() self.n = 0 - original = CodeModeClient.invoke + original = NexusClient.invoke def counting(client, operation, *a, **kw): self.n += 1 self.ops[operation] += 1 return original(client, operation, *a, **kw) - CodeModeClient.invoke = counting + NexusClient.invoke = counting self._original = original def restore(self) -> None: - CodeModeClient.invoke = self._original + NexusClient.invoke = self._original def span(self, label: str): return _Span(self, label) diff --git a/experiments/profile_client.py b/experiments/profile_client.py index bac130e..84abd8a 100644 --- a/experiments/profile_client.py +++ b/experiments/profile_client.py @@ -21,7 +21,7 @@ import pstats import time from idatui import remote_ops -from idatui.codemode_client import CodeModeClient +from idatui.nexus_client import NexusClient from idatui.domain import Program @@ -35,7 +35,7 @@ def main() -> int: ) args = ap.parse_args() - client = CodeModeClient(os.path.abspath(args.binary)) + client = NexusClient(os.path.abspath(args.binary)) client.connect() program = Program(client) regions = client.call(remote_ops.file_regions) diff --git a/experiments/profile_remote.py b/experiments/profile_remote.py index 97f4fb1..fbf17dc 100644 --- a/experiments/profile_remote.py +++ b/experiments/profile_remote.py @@ -15,7 +15,7 @@ import argparse import os from idatui import remote_ops -from idatui.codemode_client import CodeModeClient +from idatui.nexus_client import NexusClient CALLS = { "heads": ("heads", {"count": 500, "annotate": True}), @@ -36,7 +36,7 @@ def main() -> int: parser.add_argument("--addr", default=None, help="default: the .text start") args = parser.parse_args() - client = CodeModeClient(os.path.abspath(args.binary)).connect() + client = NexusClient(os.path.abspath(args.binary)).connect() addr = args.addr if addr is None: regions = client.call(remote_ops.file_regions) diff --git a/experiments/worker_smoke.py b/experiments/worker_smoke.py index 9b55138..1c2125f 100644 --- a/experiments/worker_smoke.py +++ b/experiments/worker_smoke.py @@ -1,6 +1,6 @@ -"""Exercise the real domain.Program through an IDA Code Mode lease. +"""Exercise the real domain.Program through an IDA Nexus lease. -A matching registered GUI is reused; otherwise Code Mode starts a managed +A matching registered GUI is reused; otherwise IDA Nexus starts a managed idalib worker. Usage: ``uv run python experiments/worker_smoke.py FILE``. """ from __future__ import annotations @@ -9,15 +9,15 @@ import os import sys import time -from idatui.codemode_client import CodeModeClient +from idatui.nexus_client import NexusClient from idatui.domain import Program def main() -> int: target = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else "experiments/fibonacci.elf") - print(f"attaching Code Mode to {target}…", flush=True) + print(f"attaching IDA Nexus to {target}…", flush=True) started = time.time() - client = CodeModeClient(target) + client = NexusClient(target) client.connect(progress=lambda message: print(f" {message}", flush=True)) print( f" ready in {time.time() - started:.2f}s; backend={client.backend}; " @@ -4,7 +4,7 @@ # ./ida-tui foo.elf # open a binary and drive it — that's it # # The launcher leases a registered IDA GUI or shared managed idalib worker -# through ida_codemode. The selected Python must have ida-tui's dependencies; +# through ida_nexus. The selected Python must have ida-tui's dependencies; # override it with $IDATUI_PYTHON. set -eu diff --git a/idatui/__init__.py b/idatui/__init__.py index 7da28b3..e89d8f6 100644 --- a/idatui/__init__.py +++ b/idatui/__init__.py @@ -1,4 +1,4 @@ -"""idatui — a keyboard-first TUI using shared IDA Code Mode databases.""" +"""idatui — a keyboard-first TUI using shared IDA Nexus databases.""" from .errors import ( IDAError, @@ -10,7 +10,7 @@ from .errors import ( IDASessionError, Session, ) -from .codemode_client import CodeModeClient +from .nexus_client import NexusClient from .domain import ( Program, FunctionIndex, @@ -25,7 +25,7 @@ from .domain import ( ) __all__ = [ - "CodeModeClient", + "NexusClient", "Program", "FunctionIndex", "DisasmModel", diff --git a/idatui/app.py b/idatui/app.py index e4c2936..05b428a 100644 --- a/idatui/app.py +++ b/idatui/app.py @@ -10,7 +10,7 @@ Design notes: without ever materializing 52k lines in a widget. * All network/domain work runs in Textual worker threads; the UI never blocks. * An address-history stack backs Enter (follow) / Esc (back), IDA-style. -* Database lifecycle is lease-based through ida_codemode: matching GUI sessions +* Database lifecycle is lease-based through ida_nexus: matching GUI sessions are reused, otherwise a shared managed idalib worker is opened on demand. """ @@ -55,7 +55,7 @@ from .highlight import CTextArea, highlight_c from .journal import Journal from .errors import IDAConnectionError -from .codemode_client import CodeModeClient, registered_database +from .nexus_client import NexusClient, registered_database from .domain import Func, Head, ListingModel, Program, Struct # Styles for the disassembly listing. @@ -1087,7 +1087,7 @@ class ListingView(SearchMixin, NavMixin, ColumnCursor, ScrollView, can_focus=Tru def _span_segments(h: Head, fallback: Style): """Segments for a row's disassembly text. - Uses IDA's own token classification when Code Mode supplies it; falls + Uses IDA's own token classification when IDA Nexus supplies it; falls back to the mnemonic/rest split when spans are absent or disagree with the plain text. """ @@ -5238,7 +5238,7 @@ class IdaTui(App): self._open_path = open_path self._ttl = ttl self._load_args = load_args or "" # first-open options for a headerless blob - self._new_database = False # Ctrl+L asks Code Mode for a fresh IDB + self._new_database = False # Ctrl+L asks IDA Nexus for a fresh IDB self._title = (os.path.basename(open_path) if open_path else "") #: Where we are in the execution trace, and everything that moves us. #: Owns the trace state; the _trace/_t/_trail_* properties below @@ -5247,7 +5247,7 @@ class IdaTui(App): self._do_keepalive = keepalive self._rpc_path = rpc_path self._rpc = None - self.client: CodeModeClient | None = None + self.client: NexusClient | None = None self.program: Program | None = None self._loading_screen: LoadingScreen | None = None self._ka = None @@ -5288,7 +5288,7 @@ class IdaTui(App): self.journal = Journal() self._xref_focus_name: str | None = None self._dirty = False - # One subscription for the active database. CodeModeClient debounces + # One subscription for the active database. NexusClient debounces # bursts off the Textual worker pool; the callback re-enters here on the # UI thread to invalidate and reload the visible models. self._idb_event_watch = None @@ -5362,7 +5362,7 @@ class IdaTui(App): if self._rpc_path: self._start_rpc() # A file no loader recognises has to be described before it can be - # opened, so ask BEFORE Code Mode creates it — once IDA has made a database + # opened, so ask BEFORE IDA Nexus creates it — once IDA has made a database # the answer is baked in and changing it requires a fresh-IDB reopen. if self._project is not None: ref = self._pending_load_ref() @@ -5435,7 +5435,7 @@ class IdaTui(App): ref = self._project.by_label(self._binary) if ref is not None: path, label = ref.source, ref.label - # Release our lease first. Code Mode waits for a managed worker's final + # Release our lease first. IDA Nexus waits for a managed worker's final # lease grace, then creates the replacement IDB atomically. A GUI-backed # database is rejected by _can_reload(): the TUI must never close it. self._release_database() @@ -5634,7 +5634,7 @@ class IdaTui(App): return len(text) # -- live refresh from shared IDB changes ----------------------------- # - def _start_idb_event_watch(self, client: CodeModeClient) -> None: + def _start_idb_event_watch(self, client: NexusClient) -> None: self._stop_idb_event_watch() watch = getattr(client, "watch_idb_events", None) if watch is None: # IDA-free test doubles and pre-event adapters @@ -5661,7 +5661,7 @@ class IdaTui(App): watcher.close() def _idb_event_watch_failed( - self, client: CodeModeClient, error: BaseException + self, client: NexusClient, error: BaseException ) -> None: if client is not self.client: return @@ -5685,7 +5685,7 @@ class IdaTui(App): return anchor def _refresh_idb_events( - self, client: CodeModeClient, events: tuple[dict, ...] + self, client: NexusClient, events: tuple[dict, ...] ) -> None: """Invalidate once per external edit burst and reload the active surface.""" program = self.program @@ -5768,7 +5768,7 @@ class IdaTui(App): # -- connection loss / recovery --------------------------------------- # def _handle_exception(self, error: BaseException) -> None: - """Intercept a lost Code Mode lease so the app can rediscover the DB. + """Intercept a lost IDA Nexus lease so the app can rediscover the DB. Everything unrelated to database connectivity crashes as usual. """ @@ -5812,11 +5812,11 @@ class IdaTui(App): return if self._project is not None and self._binary is not None: ref = self._project.by_label(self._binary) - client = CodeModeClient( + client = NexusClient( ref.staged, ttl=self._ttl, load_args=ref.load_args, output_database=ref.db, spawn=False) else: - client = CodeModeClient( + client = NexusClient( self._open_path, ttl=self._ttl, load_args=self._load_args, spawn=False) client.connect(progress=lambda m: self.app.call_from_thread( @@ -5826,7 +5826,7 @@ class IdaTui(App): return self.app.call_from_thread(self._after_reconnect, client, Program(client)) - def _after_reconnect(self, client: "CodeModeClient", program: "Program") -> None: + def _after_reconnect(self, client: "NexusClient", program: "Program") -> None: old_client, old_program = self.client, self.program self._stop_idb_event_watch() if old_program is not None: @@ -5886,7 +5886,7 @@ class IdaTui(App): self._load_functions() def _open_database_client(self): # type: ignore[no-untyped-def] - """Attach through Code Mode, reusing a GUI or managed idalib database.""" + """Attach through IDA Nexus, reusing a GUI or managed idalib database.""" if self._pool is not None: # project mode: the pool owns the leases label = self._binary or self._project.refs[0].label client = self._pool.get(label, progress=lambda m: @@ -5898,13 +5898,13 @@ class IdaTui(App): return client if not self._open_path: self.app.call_from_thread( - self._status, "Code Mode needs a database or executable path") + self._status, "IDA Nexus needs a database or executable path") self.app.call_from_thread(self._dismiss_loading) return None base = os.path.basename(self._open_path) self.app.call_from_thread( - self._status, f"discovering Code Mode database for {base}…") - client = CodeModeClient(self._open_path, ttl=self._ttl, + self._status, f"discovering IDA Nexus database for {base}…") + client = NexusClient(self._open_path, ttl=self._ttl, load_args=self._load_args, new_database=self._new_database) client.connect(progress=lambda m: self.app.call_from_thread( @@ -5978,7 +5978,7 @@ class IdaTui(App): @work(thread=True, exclusive=True, group="index") def _index_binary(self) -> None: """Fold this binary's symbols + strings into the project index, so it can - be searched later even when its Code Mode lease is gone.""" + be searched later even when its IDA Nexus lease is gone.""" if self._index is None or self._project is None or self._binary is None: return ref = self._project.by_label(self._binary) @@ -6081,7 +6081,7 @@ class IdaTui(App): cursor=0, push=True, is_region=True) def _can_reload(self) -> bool: - """Whether Code Mode can replace this IDB with different options. + """Whether IDA Nexus can replace this IDB with different options. A GUI database is owned by the user and has no remote close/rollback route. Managed idalib databases can be released and reopened fresh. diff --git a/idatui/domain.py b/idatui/domain.py index b042332..1e5a863 100644 --- a/idatui/domain.py +++ b/idatui/domain.py @@ -1,4 +1,4 @@ -"""Domain / paging layer: address-centric models over IDA Code Mode. +"""Domain / paging layer: address-centric models over IDA Nexus. This is where the "millions of lines" problem is solved, so the TUI widgets only ever see a viewport-sized slice. Every hard-won constraint from @@ -7,7 +7,7 @@ ever see a viewport-sized slice. Every hard-won constraint from * Page sizes remain bounded so remote execution returns viewport-scale JSON. * Pagination advances by the number of rows actually returned. * Deep head walks are block-cached (revisits are free) and neighboring blocks - prefetch through the thread-safe Code Mode client. + prefetch through the thread-safe IDA Nexus client. * Expensive function totals are fetched once and cached. * Decompilation failures are surfaced as data, not application crashes. @@ -31,7 +31,7 @@ from . import remote_ops from .errors import IDAToolError if TYPE_CHECKING: # type hint only - from .codemode_client import CodeModeClient + from .nexus_client import NexusClient # Clamps derived from measured caps (list ~700, disasm ~500). Margin included. LIST_PAGE = 500 @@ -90,7 +90,7 @@ class Line: class Head(NamedTuple): - """One flat-listing item (from the Code Mode ``heads`` operation): a code + """One flat-listing item (from the IDA Nexus ``heads`` operation): a code instruction, a data item, or an undefined byte run. A ``NamedTuple`` rather than a dataclass because this is by far the @@ -111,7 +111,7 @@ class Head(NamedTuple): name: str | None = None raw: bytes | None = None # opcode/item bytes (filled in for code by the model) #: [(kind, text)] from IDA's own colour tags — mnem/reg/num/name/str/punct/… - #: None when Code Mode didn't provide them (or the spans + #: None when IDA Nexus didn't provide them (or the spans #: disagreed with the plain text, in which case the text wins). #: #: Held exactly as it came off the wire, and **read-only**. The worker @@ -693,7 +693,7 @@ class ListingModel: """A flat, IDA-style disassembly *listing* over one segment: code, data and undefined heads interleaved, unlike ``DisasmModel`` (one function, code only). - Backed by the Code Mode adapter's ``heads`` operation, which walks item heads + Backed by the IDA Nexus adapter's ``heads`` operation, which walks item heads and renders each via ``generate_disasm_line``. The segment is walked lazily in forward pages (``FunctionIndex`` style); line index == position in the walked head list. Random access to an address is O(distance-from-seg-start) the @@ -701,7 +701,7 @@ class ListingModel: on demand as the viewport scrolls. Synchronous + thread-safe. """ - PAGE = 500 # viewport-scale heads per Code Mode execution + PAGE = 500 # viewport-scale heads per IDA Nexus execution #: Generation marker for a skeleton (text-less) page. Never equals a real #: _text_gen, which counts up from 0, so such a page always reads as stale. _SKELETON_GEN = -1 @@ -1434,7 +1434,7 @@ class HexModel: class Program: """The bound analysis session: models, caches, and a small prefetch pool.""" - def __init__(self, client: "CodeModeClient", prefetch_workers: int = 2): + def __init__(self, client: "NexusClient", prefetch_workers: int = 2): self.client = client self._pool = ThreadPoolExecutor( max_workers=prefetch_workers, thread_name_prefix="idatui-prefetch" @@ -1485,7 +1485,7 @@ class Program: """Sorted raw segment map [(start, end, file_off, name)] — the single source for sections()/file_regions()/image_range. Cached. - Uses the Code Mode adapter's ``file_regions`` operation (a plain segment + Uses the IDA Nexus adapter's ``file_regions`` operation (a plain segment walk, ~ms), avoiding broad binary surveys on the hex-pane open path. """ if self._segments_cache is not None: @@ -1573,7 +1573,7 @@ class Program: def read_bytes(self, ea: int, n: int) -> bytes: """Raw bytes [ea, ea+n) from IDA (gaps read as zero). - The Code Mode adapter returns one contiguous hex string (C-speed in IDA). + The IDA Nexus adapter returns one contiguous hex string (C-speed in IDA). A legacy ``get_bytes`` decoding fallback remains for alternate clients. """ if n <= 0: @@ -1778,7 +1778,7 @@ class Program: except IDAToolError as e: msg = e.message if "not found" in msg.lower() and "del_type" in msg: - return "the connected Code Mode runtime cannot delete local types" + return "the connected IDA Nexus runtime cannot delete local types" return msg # -- disassembly ------------------------------------------------------- # @@ -1795,7 +1795,7 @@ class Program: """Drop local and Hex-Rays caches before an explicit view refresh. Normal edit paths use generation-based invalidation. Ctrl+R is also for - changes made by another Code Mode/IDA client, for which this Program has + changes made by another IDA Nexus/IDA client, for which this Program has seen no generation bump, so it must explicitly ask Hex-Rays to discard its cached cfunc. """ @@ -1809,7 +1809,7 @@ class Program: pass def decompile(self, ea: int, refresh: bool = False) -> Decompilation: - """Full pseudocode for a function, returned directly by Code Mode.""" + """Full pseudocode for a function, returned directly by IDA Nexus.""" if not refresh: with self._lock: hit = self._decomp.get(ea) @@ -2011,7 +2011,7 @@ class Program: def define_func(self, ea: int) -> dict: """Create a function starting at ``ea`` (IDA's 'p'). - Prefers the Code Mode operation, which works out the end when IDA can't; + Prefers the IDA Nexus operation, which works out the end when IDA can't; falls back to a plain create for alternate clients. """ try: diff --git a/idatui/errors.py b/idatui/errors.py index 7632ade..ab1365a 100644 --- a/idatui/errors.py +++ b/idatui/errors.py @@ -1,6 +1,6 @@ """TUI-facing error hierarchy and lightweight database session model. -The Code Mode adapter normalizes ``ida_codemode`` transport and execution +The IDA Nexus adapter normalizes ``ida_nexus`` transport and execution errors into these types so the domain and Textual layers do not depend on HTTP or registry implementation details. """ diff --git a/idatui/launch.py b/idatui/launch.py index e3cb091..a0201a7 100644 --- a/idatui/launch.py +++ b/idatui/launch.py @@ -1,6 +1,6 @@ -"""One-shot launcher for the IDA Code Mode-backed TUI. +"""One-shot launcher for the IDA Nexus-backed TUI. -A path first resolves to a registered GUI database; when none matches, Code Mode +A path first resolves to a registered GUI database; when none matches, IDA Nexus reuses or starts a managed idalib worker. With no path, a single registered database is selected automatically. @@ -34,9 +34,9 @@ def _log(msg: str) -> None: def _registered_databases() -> tuple[list[dict], list[dict]]: - """Ready and blocked Code Mode registrations, with normalized errors.""" + """Ready and blocked IDA Nexus registrations, with normalized errors.""" try: - from ida_codemode import InstanceState, discover_databases + from ida_nexus import InstanceState, discover_databases ready: list[dict] = [] blocked: list[dict] = [] @@ -70,7 +70,7 @@ def main(argv: list[str] | None = None) -> int: help="open a multi-binary project (created from the given " "binaries if FILE doesn't exist)") p.add_argument("--ttl", type=int, default=1800, - help="deprecated compatibility option (Code Mode uses leases)") + help="deprecated compatibility option (IDA Nexus uses leases)") p.add_argument("--no-keepalive", action="store_true", help="deprecated compatibility option (the lease is the heartbeat)") p.add_argument("--rpc", metavar="PATH", @@ -87,7 +87,7 @@ def main(argv: list[str] | None = None) -> int: g.add_argument("--base", metavar="ADDR", help="load address, e.g. 0x8000000 (any base; NOT paragraphs)") g.add_argument("--ida-args", metavar="STR", dest="ida_args", - help="legacy switches; only Code Mode-representable -p/-b/-T are accepted") + help="legacy switches; only IDA Nexus-representable -p/-b/-T are accepted") args = p.parse_args(argv) load: dict = {} @@ -166,10 +166,10 @@ def main(argv: list[str] | None = None) -> int: _log(f"attaching to registered {item.get('backend')} database: {binary}") elif not ready: detail = f" ({blocked[0].get('error')})" if blocked else "" - _log(f"no registered Code Mode database; pass a binary path{detail}") + _log(f"no registered IDA Nexus database; pass a binary path{detail}") return 2 else: - _log("several Code Mode databases are registered; pass one of these paths:") + _log("several IDA Nexus databases are registered; pass one of these paths:") for item in ready: _log(f" {item.get('exe_path') or item.get('idb_path')} " f"[{item.get('backend')}, {item.get('record_id')}]") diff --git a/idatui/codemode_client.py b/idatui/nexus_client.py index e95238f..44eb1b1 100644 --- a/idatui/codemode_client.py +++ b/idatui/nexus_client.py @@ -1,4 +1,4 @@ -"""Client adapter from ida-tui's domain operations to IDA Code Mode. +"""Client adapter from ida-tui's domain operations to IDA Nexus. ``DatabaseHandle`` is the lifecycle boundary: it discovers an already-registered GUI database, reuses a shared managed idalib worker, or starts one when needed. @@ -6,9 +6,9 @@ The TUI never owns or terminates an IDA process. Closing this client releases only its lease. Remote operations are ordinary typed Python functions declared in -``idatui.remote_ops``. Code Mode installs their content-addressed modules once -per handle; subsequent calls send only encoded arguments. The optimized -IDAPython listing/decompiler implementation remains real source in +``idatui.remote_ops``. IDA Nexus installs their content-addressed modules once +per IDA Python interpreter; subsequent calls send only encoded arguments. The +optimized IDAPython listing/decompiler implementation remains real source in ``idatui.remote_tools`` and is installed through the same module interface. """ @@ -23,55 +23,55 @@ from typing import Any from .errors import IDAConnectionError, IDATimeoutError, IDAToolError, Session -# ida_codemode is imported EAGERLY-IF-PRESENT but never at hard import cost. +# ida_nexus is imported EAGERLY-IF-PRESENT but never at hard import cost. # # The paging/graph/trace layers and their offline test suites must keep importing -# `idatui` on a machine with no IDA and no Code Mode installed -- that is the +# `idatui` on a machine with no IDA and no IDA Nexus installed -- that is the # house rule the stdlib-only worker client used to satisfy for free, and # `tests/run.py --fast` (380 checks, any python3) depends on it. A hard top-level # import here makes the whole package unimportable, so the failure is deferred to # the first operation that genuinely needs the library. -_CODEMODE_ERROR: Exception | None = None +_NEXUS_ERROR: Exception | None = None try: - from ida_codemode import ( - CodeModeConnectionError, + from ida_nexus import ( DatabaseBusyError, DatabaseDisconnectedError, DatabaseHandle, DatabaseInstance, DatabaseOpenOptions, + NexusConnectionError, RemoteError, find_database_owner, wait_database_released, ) except ImportError as _exc: # library absent: usable only for offline layers - _CODEMODE_ERROR = _exc + _NEXUS_ERROR = _exc # Bound to None rather than left undefined so the names stay patchable: the # offline contract tests inject a fake DatabaseHandle here. - CodeModeConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc] + NexusConnectionError = DatabaseDisconnectedError = RemoteError = None # type: ignore[assignment,misc] DatabaseBusyError = DatabaseHandle = DatabaseInstance = None # type: ignore[assignment,misc] DatabaseOpenOptions = find_database_owner = wait_database_released = None # type: ignore[assignment] -def _require_codemode() -> None: - """Raise an actionable error when the Code Mode library is missing. +def _require_nexus() -> None: + """Raise an actionable error when the IDA Nexus library is missing. Gated on the binding, not on the original import result, so a test that injects a fake ``DatabaseHandle`` exercises the real adapter logic. """ if DatabaseHandle is None: raise IDAConnectionError( - "ida-codemode is not installed in this environment " - f"({_CODEMODE_ERROR}). Install it (e.g. `uv sync`, or " - "`pip install ida-codemode`) so ida-tui can lease a " + "ida-nexus is not installed in this environment " + f"({_NEXUS_ERROR}). Install it (e.g. `uv sync`, or " + "`pip install ida-nexus`) so ida-tui can lease a " "database." - ) from _CODEMODE_ERROR + ) from _NEXUS_ERROR def database_owner(idb_path: str, staged_path: str | None = None): - """The Code Mode instance that owns ``idb_path``/``staged_path``, else None. + """The IDA Nexus instance that owns ``idb_path``/``staged_path``, else None. - Returns None when the Code Mode library is absent: with no library there is + Returns None when the IDA Nexus library is absent: with no library there is no client in this environment that could be holding the database, and the IDA-free layers (project staging) must keep working. Discovery errors with the library installed still propagate because unknown ownership is unsafe. @@ -89,8 +89,8 @@ def database_owner(idb_path: str, staged_path: str | None = None): def registered_database(path: str, output_database: str | None = None) -> bool: - """Whether a live/lock-held Code Mode instance owns this target.""" - _require_codemode() + """Whether a live/lock-held IDA Nexus instance owns this target.""" + _require_nexus() return ( find_database_owner( path, @@ -115,9 +115,9 @@ class _NoopKeepAlive: def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: - """Translate ida-tui's legacy first-open switches to Code Mode options. + """Translate ida-tui's legacy first-open switches to IDA Nexus options. - Code Mode has typed options for processor, natural loading address and file + IDA Nexus has typed options for processor, natural loading address and file type. It deliberately has no arbitrary command-line escape hatch; reject switches we cannot represent instead of silently loading a blob wrongly. """ @@ -146,7 +146,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: if unsupported: joined = " ".join(unsupported) raise ValueError( - "ida-codemode cannot represent arbitrary IDA load options: " + "ida-nexus cannot represent arbitrary IDA load options: " f"{joined!r}; use processor/base/file type options instead" ) return processor, loading_address, file_type @@ -155,7 +155,7 @@ def _parse_load_args(value: str) -> tuple[str | None, int | None, str | None]: class IDBEventListener: """Debounced, closeable delivery of another client's IDB changes. - Code Mode's subscription is a blocking iterator, so one daemon thread reads + IDA Nexus's subscription is a blocking iterator, so one daemon thread reads it and a second waits for a quiet period before handing a batch to the UI. Keeping the debounce here avoids a permanent Textual worker (which would make the app's worker-idle contract impossible) and bounds refresh work to @@ -164,7 +164,7 @@ class IDBEventListener: def __init__( self, - client: "CodeModeClient", + client: "NexusClient", callback: Callable[[tuple[dict[str, Any], ...]], None], *, on_error: Callable[[BaseException], None] | None = None, @@ -267,8 +267,8 @@ class IDBEventListener: subscription.close() -class CodeModeClient: - """A leased GUI/idalib database accessed through ``ida_codemode``.""" +class NexusClient: + """A leased GUI/idalib database accessed through ``ida_nexus``.""" def __init__( self, @@ -298,23 +298,23 @@ class CodeModeClient: self._last_instance: DatabaseInstance | None = None self._connect_lock = threading.Lock() - def connect(self, timeout: float = 1800.0, progress=None) -> "CodeModeClient": - _require_codemode() + def connect(self, timeout: float = 1800.0, progress=None) -> "NexusClient": + _require_nexus() with self._connect_lock: handle = self._handle if handle is not None: if handle.connected: return self raise IDAConnectionError( - "Code Mode database disconnected; explicit rediscovery required" + "IDA Nexus database disconnected; explicit rediscovery required" ) if progress: progress( - f"discovering Code Mode database for {os.path.basename(self._path)}…" + f"discovering IDA Nexus database for {os.path.basename(self._path)}…" ) try: # A Ctrl+L reload releases its current managed-worker lease, but - # that worker remains registered during Code Mode's final-lease + # that worker remains registered during IDA Nexus's final-lease # grace period. Retry only that known handoff window. A GUI or # another long-lived client remains busy and yields a clear # failure rather than being modified underneath its owner. @@ -329,7 +329,7 @@ class CodeModeClient: output_database=self._output_database, processor=self._processor, # The natural byte address is converted to IDA's - # paragraph-based -b value by Code Mode. + # paragraph-based -b value by IDA Nexus. image_base=self._loading_address, file_type=self._file_type, new_database=self._new_database, @@ -341,7 +341,7 @@ class CodeModeClient: raise if progress: progress( - "waiting for the previous Code Mode lease to close…" + "waiting for the previous IDA Nexus lease to close…" ) owner = find_database_owner( self._path, @@ -389,15 +389,15 @@ class CodeModeClient: return handle is not None and handle.owns_event(event) def subscribe_idb_events(self): - """Open Code Mode's closeable IDB-change iterator.""" + """Open IDA Nexus's closeable IDB-change iterator.""" if not self.connected: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return handle.subscribe_idb_events() - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def watch_idb_events( @@ -425,7 +425,7 @@ class CodeModeClient: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return remote(handle, **args) except RemoteError as exc: @@ -435,7 +435,7 @@ class CodeModeClient: if exc.code == "operation_timeout": raise IDATimeoutError(message) from exc raise IDAToolError(name, message) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def save_database(self) -> dict[str, Any]: @@ -443,12 +443,12 @@ class CodeModeClient: self.connect() handle = self._handle if handle is None: - raise IDAConnectionError("Code Mode database is not connected") + raise IDAConnectionError("IDA Nexus database is not connected") try: return handle.save_database() except RemoteError as exc: raise IDAToolError("save_database", str(exc)) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def discard_database(self, timeout: float = 5.0) -> bool: @@ -477,7 +477,7 @@ class CodeModeClient: time.sleep(0.05) continue raise IDAToolError("shutdown_database", str(exc)) from exc - except (DatabaseDisconnectedError, CodeModeConnectionError) as exc: + except (DatabaseDisconnectedError, NexusConnectionError) as exc: raise self._connection_error(exc) from exc def health(self) -> dict[str, Any]: @@ -544,7 +544,7 @@ class CodeModeClient: return False return wait_database_released(instance, timeout) - def __enter__(self) -> "CodeModeClient": + def __enter__(self) -> "NexusClient": return self.connect() def __exit__(self, *exc) -> None: diff --git a/idatui/pane.py b/idatui/pane.py index c31a93c..1eee847 100644 --- a/idatui/pane.py +++ b/idatui/pane.py @@ -24,7 +24,7 @@ per pane in the registry, so stop/list/capture/keys keep working across both python -m idatui.pane keys --pane <pane> Escape Requires: running inside tmux or zellij. Each pane leases a registered GUI or -shared managed idalib database through Code Mode. Uses ~/ida-venv/bin/python for +shared managed idalib database through IDA Nexus. Uses ~/ida-venv/bin/python for the TUI (needs textual) unless --python / IDATUI_PYTHON says otherwise. """ from __future__ import annotations @@ -250,8 +250,8 @@ def _pane_keys(pane: str, keys: list[str], mux: str | None = None) -> None: subprocess.run(["tmux", "send-keys", "-t", pane, *keys], check=True) -# Code Mode owns database process lifetime: a closed pane drops its lease at the -# socket/kernel boundary and Code Mode decides whether a managed worker still +# IDA Nexus owns database process lifetime: a closed pane drops its lease at the +# socket/kernel boundary and IDA Nexus decides whether a managed worker still # has clients. There is nothing for the pane layer to reap. @@ -261,7 +261,7 @@ def _count_live_panes() -> int: def _reap_orphan_workers(force: bool = False) -> int: - """Compatibility no-op: Code Mode workers are shared and lease-managed.""" + """Compatibility no-op: IDA Nexus workers are shared and lease-managed.""" del force return 0 @@ -294,7 +294,7 @@ def spawn(args) -> int: print(f"error: no such project: {project}", file=sys.stderr) return 2 - # The pane owns only the TUI. Code Mode's lease cleanup handles crashes; + # The pane owns only the TUI. IDA Nexus's lease cleanup handles crashes; # kill-pane must never reap a shared GUI/idalib database. if project is not None: # launch takes: --project FILE [binaries...]; extra binaries are added to @@ -347,7 +347,7 @@ def _wait_ready(sock: str, timeout: float, pane: str, stuck_after: float = 45.0, mux: str | None = None) -> dict[str, Any]: """Poll the socket + ping until the TUI reports ready (or timeout). - Emits a one-time hint if Code Mode discovery/opening is still not ready after + Emits a one-time hint if IDA Nexus discovery/opening is still not ready after ``stuck_after`` seconds. """ start = time.time() @@ -370,7 +370,7 @@ def _wait_ready(sock: str, timeout: float, pane: str, why = ("RPC socket not created yet" if not os.path.exists(sock) else "TUI up but analysis not ready") print(f"still waiting ({int(time.time() - start)}s): {why}. " - f"Check Code Mode registrations and worker logs.", file=sys.stderr) + f"Check IDA Nexus registrations and worker logs.", file=sys.stderr) time.sleep(0.4) last = dict(last) last["ready"] = False @@ -467,7 +467,7 @@ def list_panes(args) -> int: def reap(args) -> int: - """Deprecated no-op; shared Code Mode workers are managed by leases.""" + """Deprecated no-op; shared IDA Nexus workers are managed by leases.""" print(json.dumps({"reaped_workers": 0, "live_panes": _count_live_panes(), "forced": args.force, "deprecated": True})) return 0 @@ -572,7 +572,7 @@ def main(argv: list[str]) -> int: ls.add_argument("--prune", action="store_true", help="drop dead panes (and their sockets)") ls.set_defaults(fn=list_panes) - rp = sub.add_parser("reap", help="deprecated no-op (Code Mode uses shared leases)") + rp = sub.add_parser("reap", help="deprecated no-op (IDA Nexus uses shared leases)") rp.add_argument("--force", action="store_true", help=argparse.SUPPRESS) rp.set_defaults(fn=reap) diff --git a/idatui/pool.py b/idatui/pool.py index 573aa75..2407b44 100644 --- a/idatui/pool.py +++ b/idatui/pool.py @@ -1,6 +1,6 @@ -"""DatabasePool — LRU leases on Code Mode databases for a project. +"""DatabasePool — LRU leases on IDA Nexus databases for a project. -Code Mode may bind a lease to an existing IDA GUI or to a shared managed idalib +IDA Nexus may bind a lease to an existing IDA GUI or to a shared managed idalib worker. The pool therefore owns *client interest*, never an IDA process. Releasing an LRU entry persists managed IDBs but does not implicitly save a GUI, then closes only this TUI's lease; other clients and GUI sessions remain alive. Managed workers exit themselves after their final lease. @@ -48,8 +48,8 @@ def _pss_mb(pid: int | None) -> int: def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): # pragma: no cover - needs IDA - from .codemode_client import CodeModeClient - return CodeModeClient( + from .nexus_client import NexusClient + return NexusClient( ref.staged, ttl=ttl, load_args=ref.load_args, @@ -59,7 +59,7 @@ def _default_spawn(ref: BinaryRef, ttl: int, *, new_database: bool = False): # class DatabasePool: - """Live Code Mode database leases, keyed by project label.""" + """Live IDA Nexus database leases, keyed by project label.""" def __init__(self, project: Project, *, budget_mb: int | None = None, ttl: int = 1800, spawn=None, mem_fn=None) -> None: @@ -101,7 +101,7 @@ class DatabasePool: """A live client for ``label``, attaching or spawning as needed. Do not sweep IDA scratch files here: a registered GUI or another Code - Mode client may own the database. Code Mode's registry locks and health + Mode client may own the database. IDA Nexus's registry locks and health probes are the authority for safe discovery and stale-record cleanup. """ client = self._clients.get(label) @@ -269,5 +269,3 @@ class DatabasePool: f"{self.memory_mb()}/{self.budget_mb}MB active={self.active}>") -# Source compatibility for callers that imported the pre-Code-Mode name. -WorkerPool = DatabasePool diff --git a/idatui/project.py b/idatui/project.py index 53f3b0a..e681dfc 100644 --- a/idatui/project.py +++ b/idatui/project.py @@ -23,7 +23,7 @@ firmware image, a cleaned build tree). A source whose size/mtime no longer matches the staged copy is re-staged, and its now-stale database is dropped (the DB describes the old bytes). -The model has no IDA imports. Staging consults ida_codemode's registry before +The model has no IDA imports. Staging consults ida_nexus's registry before replacing files so it never mutates a database owned by a GUI/shared worker. """ from __future__ import annotations @@ -57,7 +57,7 @@ class BinaryRef: #: it is, a raw firmware image doesn't, and IDA defaults to metapc at 0. processor: str = "" # IDA processor name: arm, armb, mipsb, metapc, … base: int = 0 # load address (natural, e.g. 0x8000000) - ida_args: str = "" # legacy -p/-b/-T switches accepted by Code Mode adapter + ida_args: str = "" # legacy -p/-b/-T switches accepted by IDA Nexus adapter @property def db(self) -> str: @@ -311,7 +311,7 @@ class Project: """Ensure ``ref`` is staged in the sidecar; returns the staged path. Re-staging a changed source drops its database: the DB describes the old - bytes. Refuse while Code Mode reports a GUI/idalib owner; replacing a + bytes. Refuse while IDA Nexus reports a GUI/idalib owner; replacing a staged executable or IDB underneath a shared live instance is corruption. """ if not os.path.isfile(ref.source): @@ -319,15 +319,15 @@ class Project: if not self.is_stale(ref): return ref.staged try: - from .codemode_client import database_owner + from .nexus_client import database_owner owner = database_owner(ref.db, ref.staged) except Exception as exc: raise ProjectError( - f"cannot verify Code Mode ownership before staging {ref.label}: {exc}" + f"cannot verify IDA Nexus ownership before staging {ref.label}: {exc}" ) from exc if owner is not None: raise ProjectError( - f"cannot restage {ref.label}: Code Mode instance {owner.record_id} " + f"cannot restage {ref.label}: IDA Nexus instance {owner.record_id} " f"still owns {owner.idb_path}; close/release it first" ) os.makedirs(self.bin_dir, exist_ok=True) @@ -353,7 +353,7 @@ class Project: def sweep_scratch(self, ref: BinaryRef) -> int: """Delete unpacked working files (never the ``.i64``) for maintenance. - Runtime paths no longer call this: Code Mode instances are shared, so a + Runtime paths no longer call this: IDA Nexus instances are shared, so a registry owner may still be using these files. Callers must independently prove that no GUI/idalib instance owns the database. """ diff --git a/idatui/remote_ops.py b/idatui/remote_ops.py index 91ffeb7..a67b2b7 100644 --- a/idatui/remote_ops.py +++ b/idatui/remote_ops.py @@ -1,4 +1,4 @@ -"""Typed remote operations executed through ida-codemode.""" +"""Typed remote operations executed through ida-nexus.""" from __future__ import annotations # ruff: noqa @@ -1655,7 +1655,7 @@ def _bindings() -> dict[Callable[..., Any], Any]: with _BIND_LOCK: if _BOUND is not None: return _BOUND - from ida_codemode import RemoteModule + from ida_nexus import RemoteModule operations_module = RemoteModule( Path(__file__), operation_label=operation_label, codec="json" diff --git a/idatui/remote_tools.py b/idatui/remote_tools.py index db04244..6681379 100644 --- a/idatui/remote_tools.py +++ b/idatui/remote_tools.py @@ -1,8 +1,8 @@ -"""The IDAPython ida-tui runs inside the Code Mode sandbox. +"""The IDAPython ida-tui runs inside the IDA Nexus sandbox. Two features have no ida-domain surface at all and are carried over VERBATIM from the tools ida-tui was developed against (`server/patch_server.py`'s -injected BODY, which the Code Mode port deletes): +injected BODY, which the IDA Nexus port deletes): * `heads` -- the continuous listing. ida-domain enumerates defined heads and renders plain disassembly; the listing also needs coalesced undefined runs, @@ -20,8 +20,8 @@ what you see). A re-implementation drifts from it silently. This file is SOURCE SHIPPED AS TEXT to the database process; it is never imported here, because the ida_* modules do not exist in the TUI's interpreter. -`codemode_client` reads it and prepends it to the relevant snippets. Keep it -self-contained: no relative imports, nothing beyond what Code Mode provides. +`nexus_client` reads it and prepends it to the relevant snippets. Keep it +self-contained: no relative imports, nothing beyond what IDA Nexus provides. """ # ruff: noqa @@ -29,7 +29,7 @@ import re as _re # IDAPython, imported ONCE at module scope. # -# This file is never imported by the client -- codemode_client reads it as +# This file is never imported by the client -- nexus_client reads it as # TEXT and installs it as a module inside the database process -- so the # no-IDA house rule that keeps idatui importable without IDA does not apply # here, and these need not be function-local. @@ -1740,7 +1740,7 @@ def decompile(addr, include_addresses=True): Faithful to the tool ida-tui was written against, and in particular to its COST: the per-line address anchor comes from ONE ``get_line_item`` at column - 0 per line. The Code Mode port asked for the full per-column line map (what + 0 per line. The IDA Nexus port asked for the full per-column line map (what ``decomp_map`` is for) purely to fill in that anchor, which is thousands of ``get_line_item``+``dstr()`` calls per function instead of one per line, and made every pseudocode open cost the same as opening the split view. diff --git a/pyproject.toml b/pyproject.toml index a46dd45..dc68d12 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,12 +1,12 @@ [project] name = "idatui" version = "0.0.1" -description = "A keyboard-first TUI frontend for shared IDA Code Mode databases." +description = "A keyboard-first TUI frontend for shared IDA Nexus databases." requires-python = ">=3.11" -# ida-codemode supplies GUI discovery, shared idalib workers, leases, and the +# ida-nexus supplies GUI discovery, shared idalib workers, leases, and the # execute_python/ida-domain database surface. dependencies = [ - "ida-codemode>=0.5.3", + "ida-nexus>=0.7.0", "textual>=8", "pygments>=2", # Used directly for pseudocode highlighting. ] @@ -29,3 +29,4 @@ build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["idatui"] + diff --git a/tests/_fixtures.py b/tests/_fixtures.py index 16192fa..bc10235 100644 --- a/tests/_fixtures.py +++ b/tests/_fixtures.py @@ -133,7 +133,7 @@ async def build_pristine(binary: str, cache: str, app_factory) -> None: break app.program.client.save_database() # Textual's headless run_test context does not reliably emit App.Unmount on - # every platform/version; release the Code Mode lease explicitly. + # every platform/version; release the IDA Nexus lease explicitly. if app.program is not None: app.program.close() if app.client is not None: diff --git a/tests/run.py b/tests/run.py index b38a707..5d552bb 100755 --- a/tests/run.py +++ b/tests/run.py @@ -51,8 +51,8 @@ import time ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) TESTS = os.path.join(ROOT, "tests") -#: The IDA-capable interpreter. The pilot tests need textual AND the Code Mode -#: library in one python; the database process is Code Mode's to place. +#: The IDA-capable interpreter. The pilot tests need textual AND the IDA Nexus +#: library in one python; the database process is IDA Nexus's to place. DEFAULT_PY = os.path.expanduser("~/ida-venv/bin/python") #: Both shapes the suites print: "N passed, M failed" and "N checks, M failed". diff --git a/tests/test_kittygfx.py b/tests/test_kittygfx.py index 74b6bb7..cfc6512 100644 --- a/tests/test_kittygfx.py +++ b/tests/test_kittygfx.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Cross-platform checks for the optional kitty-graphics startup splash. -Pure: stdlib only, no terminal, Textual, Code Mode, or IDA. +Pure: stdlib only, no terminal, Textual, IDA Nexus, or IDA. """ from __future__ import annotations diff --git a/tests/test_launch.py b/tests/test_launch.py index d57ee5f..35a0b8d 100644 --- a/tests/test_launch.py +++ b/tests/test_launch.py @@ -3,12 +3,12 @@ The old `_sweep_locks` deleted `.id0/.id1/.id2/.nam/.til` next to the user's binary when a database failed to open. That was only defensible while the TUI -exclusively owned a private worker; under Code Mode a GUI or another client may +exclusively owned a private worker; under IDA Nexus a GUI or another client may own the database, so the sweep is gone. Its tests are replaced by one that keeps it gone -- deleting a shared database's working files is unrecoverable, and this is the cheapest guard against someone reintroducing the "helpful" cleanup. -Pure: no IDA, no Code Mode library, no Textual. +Pure: no IDA, no IDA Nexus library, no Textual. """ from __future__ import annotations @@ -47,7 +47,7 @@ def touch(*paths): def t_no_lock_sweeping(): """The launcher must not delete database working files any more. - Code Mode's registry locks, health probes and IDA itself arbitrate database + IDA Nexus's registry locks, health probes and IDA itself arbitrate database ownership now. A sweep here would delete files out from under a live GUI. """ check("_sweep_locks is gone", not hasattr(launch, "_sweep_locks")) diff --git a/tests/test_codemode_client.py b/tests/test_nexus_client.py index dd0ae08..4ebf753 100644 --- a/tests/test_codemode_client.py +++ b/tests/test_nexus_client.py @@ -1,4 +1,4 @@ -"""IDA-free contract tests for the Code Mode client adapter.""" +"""IDA-free contract tests for the IDA Nexus client adapter.""" from __future__ import annotations @@ -13,10 +13,10 @@ from dataclasses import dataclass sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from idatui.errors import IDAConnectionError, IDAToolError # noqa: E402 from idatui import remote_ops # noqa: E402 -import idatui.codemode_client as module # noqa: E402 -from idatui.codemode_client import CodeModeClient, _parse_load_args # noqa: E402 +import idatui.nexus_client as module # noqa: E402 +from idatui.nexus_client import NexusClient, _parse_load_args # noqa: E402 -#: Pure: fakes the DatabaseHandle, never touches IDA or the Code Mode library. +#: Pure: fakes the DatabaseHandle, never touches IDA or the IDA Nexus library. NEEDS_IDA = False PASS = FAIL = 0 @@ -180,13 +180,13 @@ class FakeDisconnected(Exception): def _open_kwargs_are_real(sent: dict): """(ok, detail) for the kwargs the adapter passes to DatabaseHandle.open. - Skips (passes) when ida_codemode is not installed, so the file stays pure. + Skips (passes) when ida_nexus is not installed, so the file stays pure. """ try: import inspect - from ida_codemode import DatabaseHandle as Real + from ida_nexus import DatabaseHandle as Real except ImportError: - return True, "ida_codemode not installed - signature not checked" + return True, "ida_nexus not installed - signature not checked" accepted = set(inspect.signature(Real.open).parameters) unknown = sorted(set(sent) - accepted) return not unknown, f"open() rejects {unknown}" @@ -201,9 +201,9 @@ def _option_fields_are_real(options): """ try: import dataclasses - from ida_codemode import DatabaseOpenOptions as Real + from ida_nexus import DatabaseOpenOptions as Real except ImportError: - return True, "ida_codemode not installed - fields not checked" + return True, "ida_nexus not installed - fields not checked" accepted = {field.name for field in dataclasses.fields(Real)} unknown = sorted({f.name for f in dataclasses.fields(options)} - accepted) return not unknown, f"DatabaseOpenOptions rejects {unknown}" @@ -212,7 +212,7 @@ def _option_fields_are_real(options): def main() -> int: proc, base, file_type = _parse_load_args("-parm:ARMv7-M -b800000 -TRaw") check( - "legacy switches map to typed Code Mode options", + "legacy switches map to typed IDA Nexus options", (proc, base, file_type) == ("arm:ARMv7-M", 0x8000000, "Raw"), (proc, base, file_type), ) @@ -238,7 +238,7 @@ def main() -> int: path = os.path.join(tmp, "sample.bin") with open(path, "wb") as file: file.write(b"sample") - client = CodeModeClient(path, load_args="-parm:ARMv7-A -b100") + client = NexusClient(path, load_args="-parm:ARMv7-A -b100") notes = [] client.connect(timeout=42, progress=notes.append) handle = client._handle @@ -266,7 +266,7 @@ def main() -> int: *_open_kwargs_are_real(FakeDatabaseHandle.kwargs), ) check( - "connect waits for Code Mode autoanalysis", + "connect waits for IDA Nexus autoanalysis", handle.waited == 42, getattr(handle, "waited", None), ) @@ -337,7 +337,7 @@ def main() -> int: client.health()["record_id"] == "123-abcdef", ) client.save_database() - check("save uses the public Code Mode save route", handle.saved == 1) + check("save uses the public IDA Nexus save route", handle.saved == 1) check( "GUI leases transfer rather than claiming discard", client.discard_database() is False and handle.shutdown_calls == [], @@ -389,7 +389,7 @@ def main() -> int: "GUI lifetime is never claimed by the client", client.wait_released(0) is False, ) - disconnected = CodeModeClient(path).connect() + disconnected = NexusClient(path).connect() stream_errors = [] stream_failed = threading.Event() @@ -417,7 +417,7 @@ def main() -> int: module.DatabaseBusyError = original_busy module.DatabaseDisconnectedError = original_disconnected - client = CodeModeClient(__file__) + client = NexusClient(__file__) def unknown_operation(): pass diff --git a/tests/test_pool.py b/tests/test_pool.py index 6309e13..37058bf 100644 --- a/tests/test_pool.py +++ b/tests/test_pool.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Unit tests for idatui.pool (Code Mode lease residency and LRU budget). +"""Unit tests for idatui.pool (IDA Nexus lease residency and LRU budget). A fake client keeps the policy testable without IDA or Textual. @@ -31,7 +31,7 @@ def check(name, cond, detail=""): class FakeClient: - """Stands in for a CodeModeClient lease and records saves/closes.""" + """Stands in for a NexusClient lease and records saves/closes.""" def __init__(self, ref, mem=100, backend="idalib", discardable=True): diff --git a/tests/test_project.py b/tests/test_project.py index 690f360..bae0802 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 """Unit tests for idatui.project (the multi-binary project model + staging). -IDA-free: exercises staging plus Code Mode ownership checks without opening a database. +IDA-free: exercises staging plus IDA Nexus ownership checks without opening a database. python tests/test_project.py """ diff --git a/tests/test_scenarios.py b/tests/test_scenarios.py index 45ed51b..190777c 100644 --- a/tests/test_scenarios.py +++ b/tests/test_scenarios.py @@ -5272,7 +5272,7 @@ async def run(binary, only=None): async def _run_on(binary, only=None): - # Code Mode attaches a registered GUI or starts/reuses a managed worker. + # IDA Nexus attaches a registered GUI or starts/reuses a managed worker. app = IdaTui(open_path=binary, keepalive=False) async with app.run_test(size=(140, 44)) as pilot: c = Ctx(app, pilot) diff --git a/tests/test_thumb_ui.py b/tests/test_thumb_ui.py index f12fbc4..1b4c438 100644 --- a/tests/test_thumb_ui.py +++ b/tests/test_thumb_ui.py @@ -50,7 +50,7 @@ def check(name, ok, detail=""): #: #: This suite used to delete <BIN>.i64 and reopen the SAME path for each phase. #: That was safe when the TUI owned a private worker that died with it; under -#: Code Mode the database is leased and the previous phase's worker can still +#: IDA Nexus the database is leased and the previous phase's worker can still #: hold it through its lease grace, so the delete raced a live owner and the #: next open never produced a listing (the crash this fixed). Separate paths #: cannot collide, and nothing has to wait for anyone else to let go. diff --git a/tests/test_trace_ui.py b/tests/test_trace_ui.py index 855ce4d..586ce2e 100644 --- a/tests/test_trace_ui.py +++ b/tests/test_trace_ui.py @@ -115,7 +115,7 @@ async def run() -> int: # `app._t` is assigned the moment the key is handled, so it is NOT a # signal that the VIEW has followed -- the navigation it kicks off # runs in a worker. Waiting on it and then reading the cursor was a - # race that the (slower) Code Mode backend loses. Gate on the thing + # race that the (slower) IDA Nexus backend loses. Gate on the thing # the check is about. await settle(app, lambda: app._t == 1 and lst._cursor_ea() == t.ip(1), timeout=20) @@ -12,31 +12,31 @@ wheels = [ ] [[package]] -name = "ida-codemode" -version = "0.6.1" +name = "ida-domain" +version = "0.5.1" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ida-domain" }, + { name = "idapro" }, { name = "packaging" }, - { name = "zeromcp" }, + { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/5d/55/b9b72626e371bb36b712659b567ad1d890947c237bfbcaeccfb30f12fe80/ida_codemode-0.6.1.tar.gz", hash = "sha256:44df2a986d24a7e35e64b92c910a21eb485dc4c47d1e9db1e572e8fc1aa08a44", size = 188081, upload-time = "2026-08-13T16:46:35.504Z" } +sdist = { url = "https://files.pythonhosted.org/packages/80/34/be087d3ea1c3a6573e0660cb5b40f0c4ade9ae5772cf1c5d98d52472d28b/ida_domain-0.5.1.tar.gz", hash = "sha256:c49f2c417047d882e954f651b50a709a3f27903b33ba533b794aa54d6536d16f", size = 396413, upload-time = "2026-08-10T13:32:48.45Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/20/2397e9b34cefe7ce01945cc2299a01459b9371c48027f61bfdbd4bfcf677/ida_codemode-0.6.1-py3-none-any.whl", hash = "sha256:69a39e25f7441aab794f6737133f8c6155fd794c77924e46e83cb938acf8944f", size = 104728, upload-time = "2026-08-13T16:46:34.265Z" }, + { url = "https://files.pythonhosted.org/packages/27/78/9c698d818b0fddc6648f703a0821edeeb65b18404b43f556d249c5446c96/ida_domain-0.5.1-py3-none-any.whl", hash = "sha256:bfbb17c7d0cb2ed7d3f21342e1c8787f9018d5c2a94cdfd29e06537dd026a06d", size = 201275, upload-time = "2026-08-10T13:32:46.955Z" }, ] [[package]] -name = "ida-domain" -version = "0.5.1" +name = "ida-nexus" +version = "0.7.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "idapro" }, + { name = "ida-domain" }, { name = "packaging" }, - { name = "typing-extensions" }, + { name = "zeromcp" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/80/34/be087d3ea1c3a6573e0660cb5b40f0c4ade9ae5772cf1c5d98d52472d28b/ida_domain-0.5.1.tar.gz", hash = "sha256:c49f2c417047d882e954f651b50a709a3f27903b33ba533b794aa54d6536d16f", size = 396413, upload-time = "2026-08-10T13:32:48.45Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b6/93/2f87cbd64ffc45e181542f133ba8db101c9049155b5f015d9d318f32dcfb/ida_nexus-0.7.0.tar.gz", hash = "sha256:838698c6a2456d474da833b2f4955da9f1fca4a488c95a157539a3c663a919ba", size = 220190, upload-time = "2026-08-20T21:52:18.228Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/27/78/9c698d818b0fddc6648f703a0821edeeb65b18404b43f556d249c5446c96/ida_domain-0.5.1-py3-none-any.whl", hash = "sha256:bfbb17c7d0cb2ed7d3f21342e1c8787f9018d5c2a94cdfd29e06537dd026a06d", size = 201275, upload-time = "2026-08-10T13:32:46.955Z" }, + { url = "https://files.pythonhosted.org/packages/a5/c2/58704fc74618c7867cf7542d3150a75a7678aae2fff7960fa8e5cc67d934/ida_nexus-0.7.0-py3-none-any.whl", hash = "sha256:a5006c7170a0a758a598b864d6fa248bf4eccbea848a30fc6a3eeeadf638d07a", size = 127656, upload-time = "2026-08-20T21:52:19.395Z" }, ] [[package]] @@ -53,7 +53,7 @@ name = "idatui" version = "0.0.1" source = { editable = "." } dependencies = [ - { name = "ida-codemode" }, + { name = "ida-nexus" }, { name = "pygments" }, { name = "textual" }, ] @@ -65,7 +65,7 @@ dev = [ [package.metadata] requires-dist = [ - { name = "ida-codemode", specifier = ">=0.5.3" }, + { name = "ida-nexus", specifier = ">=0.7.0" }, { name = "pygments", specifier = ">=2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8" }, { name = "textual", specifier = ">=8" }, |
