# Ghidra-CLI Open Source Release Plan (Daemon-Only Architecture) > Historical planning document: this does not reflect the current implementation. > Current architecture is direct CLI-to-Java bridge (`GhidraCliBridge.java`) as documented in `README.md` and `AGENTS.md`. ## Overview Prepare ghidra-cli for open source release with a **daemon-only architecture**. Binary analysis is slow enough that a persistent Ghidra process (via daemon) is always preferable to spawning new processes per command. **Key architectural change**: Remove HeadlessExecutor as an execution path. All query operations MUST go through the daemon, which maintains a persistent GhidraBridge connection to Ghidra. ## Planning Context ### Decision Log | Decision | Reasoning Chain | |----------|-----------------| | Daemon-only for queries | Binary analysis takes 5-30+ seconds cold start -> persistent daemon amortizes this -> always faster for real workflows -> user confirmed this approach | | Remove HeadlessExecutor fallback | Fallback creates confusion about which path is used -> daemon is always better -> remove fallback, require daemon explicitly | | Use IPC layer over legacy RPC | IPC uses local sockets (faster, no port conflicts) -> cross-platform (Unix sockets + Windows named pipes) -> already implemented in src/ipc/ | | Wire CommandQueue to GhidraBridge | Queue already exists with caching -> handler.rs already routes to bridge -> just need to connect the pieces | | Keep standalone commands local | Config, setup, doctor, version don't need Ghidra -> run locally without daemon | ### Rejected Alternatives | Alternative | Why Rejected | |-------------|--------------| | Keep HeadlessExecutor as fallback | Creates two code paths to maintain -> daemon is always better -> simpler to require daemon | | Auto-start daemon | Adds complexity -> explicit start is clearer -> user knows daemon is running | | Remove daemon RPC entirely | Some users may have scripts using RPC -> deprecate but keep for now | ### Constraints & Assumptions - **User-specified**: Daemon-only architecture for query operations - **Technical**: GhidraBridge and bridge.py already functional - **Technical**: IPC protocol and transport already implemented - **Pattern**: handler.rs already translates IPC Commands to bridge calls ## Current State Analysis ### What Already Works ``` ✓ GhidraBridge (src/ghidra/bridge.rs) - persistent TCP to Ghidra ✓ bridge.py - Python server inside Ghidra with command handlers ✓ IPC protocol (src/ipc/protocol.rs) - typed Command enum ✓ IPC transport (src/ipc/transport.rs) - cross-platform sockets ✓ IPC client (src/ipc/client.rs) - high-level client API ✓ IPC server (src/daemon/ipc_server.rs) - accepts connections ✓ Handler (src/daemon/handler.rs) - routes commands to bridge ✓ Daemon lifecycle (start/stop/status/ping) ``` ### What Needs Wiring ``` ✗ CommandQueue.execute_command() is stubbed (returns TODO message) ✗ main.rs uses daemon_rpc (legacy) not ipc (new) ✗ Fallback to HeadlessExecutor still exists ✗ Query operations bypass daemon entirely ``` ## Milestones > All file paths are relative to repository root ### Milestone 1: Fix Cargo.toml Repository URL **Files**: `Cargo.toml` **Requirements**: - Update repository URL from localhost to GitHub **Acceptance Criteria**: - URL matches `https://github.com/akiselev/ghidra-cli` **Code Changes**: ```diff --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" authors = ["Alexander Kiselev"] description = "Rust CLI to run Ghidra headless for reverse engineering with Claude Code and other agents" license = "GPL-3.0" -repository = "http://127.0.0.1:62915/git/akiselev/ghidra-cli" +repository = "https://github.com/akiselev/ghidra-cli" ``` --- ### Milestone 2: Wire IPC Client in main.rs **Files**: `src/main.rs` **Requirements**: - Replace `daemon_rpc::DaemonClient` with `ipc::client::DaemonClient` - Route query commands through IPC when daemon is running - Return error (not fallback) when daemon required but not running **Acceptance Criteria**: - `ghidra query functions` uses IPC when daemon running - `ghidra query functions` errors with "start daemon" message when daemon not running - No fallback to HeadlessExecutor **Code Intent**: - In `run_with_daemon_check()`: Replace `daemon_rpc::DaemonClient::connect(port)` with `ipc::client::DaemonClient::connect(socket_path)` - Add match on command type: query-based commands REQUIRE daemon, others can run standalone - Remove the `else { run(cli) }` fallback for query commands - Add helpful error message: "This command requires the daemon. Start with: ghidra daemon start --project " --- ### Milestone 3: Implement CommandQueue Execute **Files**: `src/daemon/queue.rs` **Requirements**: - Implement `execute_command()` to actually execute commands via GhidraBridge - Replace the TODO stub with real execution **Acceptance Criteria**: - Commands submitted to queue are executed via bridge - Results are cached and returned **Code Intent**: - Change `execute_command()` to take a reference to `GhidraBridge` - Translate `Commands` enum to bridge operations - Call `bridge.send_command()` for each operation type - Parse JSON response and return formatted result --- ### Milestone 4: Add XRefs Support to Handler **Files**: `src/daemon/handler.rs`, `src/ipc/protocol.rs` **Requirements**: - Ensure XRefs commands are handled in the daemon - Handler routes XRefsTo/XRefsFrom to bridge **Acceptance Criteria**: - `ghidra query xrefs --to main` works via daemon - Returns JSON array of cross-references **Code Intent**: - Verify `Command::XRefsTo` and `Command::XRefsFrom` exist in protocol.rs - Add handling in `handler.rs` for these commands - Call `bridge.xrefs_to()` / `bridge.xrefs_from()` --- ### Milestone 5: Add --to Parameter for XRefs Query **Files**: `src/cli.rs`, `src/main.rs` **Requirements**: - Add `--to` parameter to query subcommand - Pass target to daemon when querying xrefs **Acceptance Criteria**: - `ghidra query xrefs --to main --project x --program y` parses correctly - Target is sent to daemon in IPC request **Code Intent**: - Add `to: Option` to QueryArgs in cli.rs - In main.rs query handling, include target in IPC command --- ### Milestone 6: Deprecate HeadlessExecutor **Files**: `src/ghidra/headless.rs`, `src/ghidra/mod.rs` **Requirements**: - Mark HeadlessExecutor as deprecated - Remove direct calls from main.rs - Keep module for potential future use (import/analyze operations) **Acceptance Criteria**: - No query operations use HeadlessExecutor - Compile succeeds with deprecation warnings only **Code Intent**: - Add `#[deprecated(note = "Use daemon for query operations")]` to HeadlessExecutor - Remove/comment out direct HeadlessExecutor usage in main.rs handle_* functions - Route through daemon instead --- ### Milestone 7: Fix Compiler Warnings **Files**: Multiple (same as original plan) **Requirements**: - Remove unused imports - Handle dead code appropriately **Acceptance Criteria**: - `cargo build` produces no warnings (or only deprecation warnings for HeadlessExecutor) **Code Intent**: - Remove unused imports from handler.rs, queue.rs, etc. - Keep IPC layer code (now actually used!) - Remove truly dead methods --- ### Milestone 8: Add E2E Tests for Daemon Queries **Files**: `tests/e2e.rs` **Requirements**: - Test query operations through daemon - Test XRefs query **Acceptance Criteria**: - Tests start daemon, run queries, stop daemon - All tests pass **Code Intent**: - Add test helper to start/stop daemon - Add `test_daemon_query_functions()` - Add `test_daemon_query_xrefs()` - Add `test_daemon_required_error()` - verify error when daemon not running --- ### Milestone 9: Expand README.md **Files**: `README.md` **Requirements**: - Document daemon-first architecture - Explain why daemon is required for queries - Quick start with daemon workflow **Acceptance Criteria**: - README explains daemon requirement - Includes daemon workflow example **Code Intent**: - Expand README with: - Overview explaining persistent Ghidra benefit - Quick start: `ghidra daemon start`, then queries - Architecture section explaining daemon design - All 9 sections from original plan --- ## Milestone Dependencies ``` M1 (Cargo.toml) ----+ | M2 (IPC Client) ----+----> M4 (XRefs Handler) ----> M5 (--to param) | M3 (Queue Execute) -+----> M6 (Deprecate Headless) | +----> M7 (Warnings) ----> M8 (Tests) ----> M9 (README) ``` ## Architecture After Changes ``` CLI Command ↓ [Command Type?] ├─ Daemon Control (start/stop) → Handle locally ├─ Config/Setup/Doctor → Handle locally └─ Query/Decompile/etc → REQUIRES DAEMON ↓ [Daemon Running?] ├─ NO → Error: "Start daemon first" └─ YES → IPC Client ↓ Send Command (local socket) ↓ IPC Server receives ↓ handler.rs routes ↓ GhidraBridge.send_command() ↓ bridge.py in Ghidra ↓ Response back ↓ Format & display ``` ## Key Benefits 1. **Faster queries**: Ghidra stays loaded, no 5-30s startup per command 2. **Simpler code**: One execution path, not two 3. **Better UX**: Consistent behavior, clear daemon requirement 4. **Easier debugging**: All queries go through same path