docs · internals
Adding an App#
Three layers must stay in sync; tests catch drift. This is the guide for extending bite — a new verb for an existing app, or a whole new app (Music and Finder are the roadmap candidates).
1. Swift handler#
Add a method to the right bridge module (or a new FooBridge.swift for a new app) in crates/bite-mcp/swift/Sources/BiteBridge/:
enum FooBridge {
static func register(_ d: Dispatcher) {
d.register("foo.do_thing") { req in
try withWatchdog(30) {
// dynamic ScriptingBridge for scriptable apps:
let app = try sbApp(bundleID: "com.apple.foo", name: "Foo")
let items = sbElements(app, "items")
// ... build a flat snake_case dict
return ["things": []]
}
}
}
}Register it in main.swift. Rules:
- Flat snake_case dicts; dates through
D.format(ISO 8601 + offset). - Errors are
BridgeErrorwith acode, and afixwhen user-actionable. - Wrap every Apple-calling handler in
withWatchdog. - Access SB elements/properties via
sbElements/sbGet/sbAt— never rawobject(at:)indexing (convention is pinned in one place). - If the app has a native framework (like Contacts or EventKit), prefer it over ScriptingBridge.
2. Rust registry#
Add the tool to crates/bite-core/src/registry.rs:
t("foo_do_thing", App::Calendar /* or a new variant */, "foo.do_thing",
"One-paragraph description for the agent.",
ps![
ParamSpec::req("id", ParamKind::Str, "what this is"),
ParamSpec::opt("limit", ParamKind::Int, "max results"),
]),The MCP schema and CLI dispatch both come from here. If you added a new App variant, extend as_str.
Destructive verbs: use td(...) and take confirm — the helper returns a would_* preview until it's true. Mutating verbs use tn(...): they are never blind-replayed after a helper death.
3. CLI verb#
Add a clap variant in crates/bite-mcp/src/cli.rs and an arm in crates/bite-mcp/src/dispatch.rs that builds the same param names and calls call(&mut h, "foo_do_thing", params). CLI date flags go through parse_date (accepts relaxed forms).
4. Prove it#
swift test --package-path crates/bite-mcp/swift # logic tests
cargo test --workspace # includes
# - tests/protocol_conformance.rs ← fails if Swift/registry drift
# - tests/mcp_smoke.rs ← end-to-end MCP
./target/debug/bite foo do-thing # live smokeUpdate plugin/skills/bite/SKILL.md (tool map + recipes) and docs/protocol.md (method table — the docs-conformance fence fails CI if the registry and the docs drift) in the same PR. If the verb changes what the tools page shows, that page changes here too.