Desktop binary analysis tool for iOS/macOS apps. Built with Electron + TypeScript, vanilla DOM renderer (no framework). All analysis runs locally.
bun install # Install deps (also sets up Husky hooks via prepare)
bun run build # Build all targets (main, preload, renderer, css, mcp)
bun run start # Build + launch Electron
bun run typecheck # tsc --noEmit
bun test # Run all tests (bun:test)
bun run check # Lint + format check
bun run check:fix # Lint + format with auto-fix
bun run dist # Build + package with electron-builderThree Electron processes with strict context isolation:
- Main (
src/main/) — analysis orchestrator, file I/O, IPC handlers - Renderer (
src/renderer/) — vanilla TypeScript DOM manipulation, no framework - Preload (
src/preload/) — typedwindow.apibridge via contextBridge - MCP (
src/mcp/) — standalone MCP server for AI agents (runs under Bun, no Electron). Important: After runningbun run build:mcp, you must restart Claude Code (or the MCP client) to pick up changes — the server process keeps running old code until restarted. If MCP tool results don't reflect your code changes, this is likely why. - Shared (
src/shared/) — types shared across all processes (types.ts,ipc-types.ts)
IPC: renderer invokes main via typed channels defined in src/shared/ipc-types.ts. Main sends progress/completion/error events back. All IPC types are in InvokeChannelMap and SendChannelMap.
src/main/analysis/orchestrator.ts runs a multi-step pipeline: extract container → discover binaries → parse Mach-O headers → load commands → chained fixups → strings → symbols → ObjC metadata → code signature → security scan → hook detection → framework detection → bundle file scan → file tree + localisation.
Each step yields to the event loop (setImmediate) and reports progress via callback.
Each parser is a self-contained module: macho.ts, load-commands.ts, symbols.ts, strings.ts, objc.ts, codesign.ts, chained-fixups.ts, plist.ts, xrefs.ts. They operate on DataView with explicit endianness and use BigInt for addresses/offsets.
Extracted containers cached in ~/.appinspect/cache/ keyed by MD5(path+size+mtime). Cache mtime is touched on access for LRU-style pruning (7-day expiry). The Settings panel can clear the cache on demand; entries used by open sessions and fresh -tmp- dirs are kept.
- Files: kebab-case (
load-commands.ts,data-table.ts) - Types/interfaces: PascalCase. Use
import typefor type-only imports - Functions: camelCase. Constants: UPPER_SNAKE_CASE
- Exports: named exports, no default exports
- Error handling: parsers with a hard-failure mode (input is either valid and fully parseable or not —
macho.ts,plist.ts,codesign.ts) return the sharedResult<T>fromsrc/main/parser/result.ts({ ok, data } | { ok, error }) and never throw for malformed-but-expected input. Collection extractors (strings,symbols,load-commands,objc,chained-fixups,xrefs) return partial data best-effort, skipping malformed entries — an empty result is meaningful, not a failure. Use try-catch with informative messages at boundaries - BigInt: used internally for addresses, converted to number/string before IPC serialisation (see
bigintReplacerin MCP server) - Counts and units: use the shared helpers in
src/shared/format.ts(formatCount(),plural(),formatSize()) instead of hardcoding plurals or units — e.g.plural(n, "source map"), not`${n} source map${n === 1 ? "" : "s"}`or a fixed"Source Maps (1)". Supply an explicit plural when the default+sis wrong (plural(n, "Dependency", "Dependencies")). UI labels use sentence case - Linter: Biome (see "Linting & Formatting" section)
- Keep implementations minimal. No unnecessary abstractions, helpers, or wrapper functions - unless it will sufficiently improve readability, or reduce code duplication
- Follow existing patterns in the file you're editing
Biome handles linting and formatting. Configuration in biome.json.
bun run lint # Run linter only
bun run format # Format files in place
bun run format:check # Check formatting (CI mode)
bun run check # Lint + format check (unused symbols are errors, not warnings)
bun run check:fix # Lint + format with auto-fixStyle rules:
- Indentation: Tabs
- Line width: 100 characters
- Semicolons: Always
- Quotes: Double quotes
- Trailing commas: None
- Arrow function parens: Always
- Bracket spacing: Yes
VS Code users: Install the recommended Biome extension for format-on-save.
Strict mode enabled. Key flags: noUncheckedIndexedAccess, noFallthroughCasesInSwitch, noImplicitOverride. Target ES2022. skipLibCheck: true.
Framework: bun:test (describe, it, expect). Tests live in __tests__/ directories next to the code they test.
bunfig.toml sets [test] root = "src". Without it bun test walks the whole tree and also picks up the compiled *.test.js files that bun run build leaves in dist/ — stale copies that keep passing long after their TypeScript sources have changed.
src/main/parser/__tests__/— parser unit tests (fixtures infixtures.ts)src/main/analysis/__tests__/— security scan testssrc/main/ipa/__tests__/— IPA extraction tests
When adding new features or modifying analysis logic, add or update corresponding tests. New scan/analysis features must also be exposed via the MCP server (src/mcp/) so AI agents can access them.
When adding a significant user-facing feature (new tab, new MCP tool, major capability), update README.md to document it. Small additions that most users wouldn't care about should not be in the README just for the sake of it.
Treat the README as a description of the product at any point in time, not a changelog. Fold new items into the existing prose and lists at the same level of detail as comparable features — don't add "new", "now supports", "including the new X", dedicated examples, or extra explanation just because it is the change you made. A reader with no knowledge of release history should not be able to tell which part is newest.
Husky runs Biome auto-fix, re-stages changes, then runs bun run check && bun run typecheck && bun test before every commit. Auto-fixable issues are corrected automatically; unfixable lint errors will fail the commit. Do not bypass with --no-verify.
Must work on macOS, Windows, and Linux.
- macOS: traffic light buttons,
.appbundle directory support in file picker - Windows: title bar overlay, PowerShell for IPA extraction
- Linux: standard unzip
Use process.platform checks when platform-specific behaviour is needed. Never assume Unix-only APIs.
electron— desktop framework@modelcontextprotocol/sdk— MCP server (AI agent integration)bplist-parser+plist— binary/XML plist parsingfflate— ZIP compression/decompression (dev dependency, used in tests)electron-builder— packaginghusky— git hooks
Each process builds separately with Bun. Main and preload target Node (CJS, --external electron). Renderer targets browser. MCP targets Bun. CSS is a plain copy.