跪拜 Guibai
← Back to the summary

iCATBox Opens Up a Unified macOS Console for iOS Debugging

Doing iOS development involves plenty of operations that aren't complicated but require hopping between several tools: checking the simulator in Terminal, opening the sandbox to find files, typing a route, grabbing logs, checking signatures, then going back to Xcode.

I wanted to move these scattered operations into a desktop console, organized around the "current project, target App, current device", so I made iCATBox.

It is now open-sourced under the MIT license, with the first public release at 1.2.0. The core is SwiftUI + AppKit; simulators use Xcode's simctl, real devices use devicectl, and there are no third-party Swift dependencies.

iCATBox console: sample project and fictional device

Screenshot from isolated preview mode; all data is synthetic project and device data. Real-device operations are blocked in this mode.

Starting from device and app, connecting the debugging workflow

iCATBox's main window is split into a device list, operation area, console, and info bar. Select a project and device first; subsequent operations reuse the same context, reducing repeated entry of Bundle ID, paths, and parameters.

Currently assembled capabilities include:

Debugging scenario Available entry points
App & device Install, launch, terminate, screenshot, screen record, device status
Files & data Open sandbox, pull or write, backup and restore
Routes Open link, environment mapping, favorites, subscriptions, regression screenshots
Logs & crashes Log streaming, real-device app console, crash reports and symbolication
Engineering maintenance Scripts, dependencies, signing certificates, provisioning profiles, pre-release checks
Hybrid development Hybrid bridge directory, Flutter tools, React Native workflows

Whether a specific action is available still depends on device type, Xcode, and system version. For example, real devices cannot directly reuse the simulator's whole-device erase and power on/off capabilities; real-device system logs may also require restarting the target app. The UI should make these differences clear.

Beyond command execution: why a console is still needed

After a GUI centralizes entry points, the execution process itself also needs to be inspectable. The project uses Process to run local tools, passes arguments as an argument array, and collects standard output, standard error, and exit codes; some tasks support continuous output.

A typical call relationship can be summarized as:

User selects an action
  → reads current project, app, and device
  → checks current state and available capabilities
  → constructs local tool arguments
  → executes and echoes output
  → refreshes UI based on results

For example, route jumps ultimately still call the corresponding device tool. The console shows execution information; if environment domain mapping or Scheme rewriting is configured, it also shows documentation links and the actual invoked address, making it easier to debug "why did it open a different environment".

Custom scripts and module commands in the project are a separate matter: they are project content that the user chooses to execute. Importing a project configuration does not automatically run these scripts.

Routes no longer update along with the source code

If a debugging tool hardcodes the actual project's route table into the app, maintenance hits a wall quickly: when pages change you have to modify source code, and other projects can't reuse it directly.

Now iCATBox lets users maintain project routes and also subscribe to JSON, Markdown, or HTML documents. Among these, a well-structured routes.json is easiest to control:

{
  "revision": "example-1",
  "title": "Example routes",
  "routes": [
    {
      "group": "Demo",
      "title": "Home",
      "url": "https://example.com/home"
    }
  ]
}

After filling the document address into project settings, you can sync manually or check for new entries every 30 minutes. By default the background only warns; manual sync shows a preview for confirmation; only when you actively enable background merging does it append automatically.

Merging dedupes by URL, preserves titles of existing entries, and does not delete local routes just because the source document removed an item. Each project can also configure allowed domains and URL Schemes.

There is an asynchronous problem worth handling specially: after a request goes out, the user may have switched projects or modified the subscription address. When the network result returns, the program re-verifies project ID, subscription address, and import rules, then reads the latest project configuration to merge, avoiding stale requests writing content into another project.

HTML and Markdown use generic link extraction; complex sites are not guaranteed to parse correctly. For stable syncing, providing a clear JSON document is recommended.

Generic tools open-source, specific business config stays local

In this open-source release, one major adjustment I made was splitting "tool capabilities" from "project data".

The public parts include route jumps, directory display, bridge code generation, Flutter tools, interface requests and field mapping, and other generic capabilities. Specific project links, bridge method definitions, page names, domains, and interface parameters are imported via local JSON.

Public repository
  ├─ Generic debugging UI and local tool invocation
  ├─ Configuration models, validation, import/export
  ├─ Route parsing, merging, and interface mapping
  └─ Fictional sample configuration

Local files outside the repository
  └─ User's own project configuration pack

The public version starts from a blank project; it does not apply any company's presets based on a fixed Bundle ID, does not auto-fill vendor interfaces, and does not auto-scan private configuration directories.

Before import, it shows the project name and counts of routes, bridge entries, Flutter pages, etc. Import creates a new project and does not overwrite existing ones.

Generic project settings: route subscription and user config entry

A bridge directory entry is essentially just data: method name, direction, example parameters, and description. Keeping the display, search, and JS debug code generator in the public code lets different projects import their own directories without hardcoding business protocols into the tool.

Routes and Flutter pages follow the same idea. This tool will not automatically guess an arbitrary Flutter page's pageName from a web URL; the project must provide its own configuration.

Analytics endpoint: editable address does not mean universal protocol

In analytics debugging there is an even more easily overlooked coupling: even if the domain is changed to an input box, the request path, parameters, and response fields may still be bound to a specific backend.

So the public version turns these into declarative adapters. The configuration can describe the interface path, form parameters, response list location, and field mapping. For example, here is a fictional event interface snippet:

{
  "path": "/v1/events",
  "parameters": {
    "device": "{udid}",
    "after": "{since}"
  },
  "rowsPath": "items",
  "fields": {
    "id": "eventId",
    "timestamp": "time",
    "eventName": "name",
    "trackingID": "definitionId",
    "customize": "properties"
  }
}

The adapter converts the response into standard events or definitions, and generic logic then matches against event names, pages, and custom fields. Point paths support nested objects and array indices, and they can also try multiple candidate paths in order.

These mappings are data; they do not execute JavaScript or other expressions. The current interface capability is form POST and field remapping; if the backend requires special encryption, signing, or a completely different transfer protocol, it needs separate implementation; you cannot just change the domain and promise a universal adapter.

Tokens, cookies, and passwords should not be part of an exported project configuration either.

Releasing a publicly buildable version

The open-source version needs verification: can a build be completed from just the public repository, and does the artifact contain only public inputs?

Currently the repository has configuration regression checks and GitHub Actions, covering old config decoding, JSON roundtrip, route parsing, domain boundaries, duplicate links, field mapping, parameter placeholders, and cookie domain/path/Secure scope.

The release process builds a universal Apple Silicon + Intel app from public source, checks the source and final app, and generates standalone ZIP, DMG, and SHA-256 checksum files. Historical versions are retained by tag; future releases do not overwrite old artifacts.

git clone https://github.com/LZQVicLi/iCATBox.git
cd iCATBox
bash scripts/test.sh
bash scripts/build.sh
bash scripts/package.sh

Source self-checks and synthetic previews have explicit scope: they cannot replace real-device screen recording, logs, permissions, installation, and restore verification. The project does not make end-to-end guarantees across all Xcode, iOS versions, and device combinations.

Download, experience, and feedback

Minimum requirements are macOS 13+ and Xcode; real-device features also depend on the local Xcode providing the corresponding devicectl capability, device trust, and development environment. Flutter, Node, etc. are on-demand external tools and are not bundled with the app.

The current public installer is ad-hoc signed and not yet Apple Developer ID notarized. After downloading, first verify the source and SHA-256 checksum; if macOS blocks it, follow the system's "Privacy & Security → Open Anyway" flow; specific steps are in the repository's signing instructions.

If you also constantly switch between Terminal, Xcode, and Finder, you can start with everyday operations like screenshots, sandbox, and routes.

Source code: github.com/LZQVicLi/iCATBox
Download: GitHub Releases

Welcome Stars, Issues, and PRs. When providing feedback, please include macOS, Xcode, App versions, and whether it's a simulator or real device; try to provide reproduction steps with a fictional project, and do not attach business config, device identifiers, or unprocessed logs.