Skip to content
Mobile
Agent

native

Read before Capacitor, TLS, Electron, downloads, or native-path work.

BOOST
From plugin
zmninjang
1089 skills9 agents1 command
Install
$ npx -y skills add ZoneMinder/zmNinjaNg --agent claude-code

How it fires

How this agent gets triggered: by you, by Claude, or both.

  • Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
  • You can call itInvoke it directly when you want it.

Context preview

The summary Claude sees to decide when to auto-load this agent.

Read before Capacitor, TLS, Electron, downloads, or native-path work.

Agent definition

native.md

Native playbook

Read before Capacitor, TLS, Electron, downloads, or native-path work.

  • Native or CI-untestable changes need real-device verification before merge. State it in handoff or PR.
  • Prefer least-breaking hardening. If a fix necessarily breaks an existing native behavior, document accepted risk.
  • Plugin import style, TLS trust, and download paths: Native contract in `AGENTS.project.md`. Additionally, plugins match the `@capacitor/core` major version and get mocks in `tests/setup.ts`.
  • Use `hooks/useCapacitorListener` for plugin listeners.
  • Native logging (`Log.*`, `NSLog`, `CAPLog.print`) never passes through the Logging contract's sanitizer, which lives on the JS side. Never log a URL handed across the bridge, and never log a media or HTTP error object raw. A heuristic gate in `agents-contracts.test.ts` scans `.swift` and `.java` and demands a `// log-safe:` reason on every hit, including its false positives. Stream and event URLs carry the access token in the query, media3 quotes the failing URL in its message and again in the cause chain, and logcat is readable by anyone with adb (refs #307). Log an error code and a scrubbed message instead.
  • Never declare an Android permission ahead of the targetSdk that enforces it. `ACCESS_LOCAL_NETWORK` was declared while targeting API 36, where local network access is implicitly granted through `INTERNET`. The declaration replaced that grant on Android 17 devices with a default-denied `NEARBY_DEVICES` toggle nothing requested, so every LAN server timed out (#350). Declaration and runtime request land together with the bump; the gate is in `src/tests/agents-contracts.test.ts`.
  • Never resolve a promise with a `registerPlugin` proxy: the proxy intercepts every property access as a native method call, so `.then` on it is probed as an unimplemented method, the promise that adopted it never settles, and the awaiter hangs forever with no catch. Resolve with a wrapper the plugin sits inside (`useCapacitorListener` takes `{ plugin }`) and destructure after the await. A mocked plugin is a plain object and not a thenable, so no unit test sees this; nine call sites carried it until #507. Gate: `src/tests/agents-contracts.test.ts`.
  • `iosScheme` cannot be `http` or `https`. `CAPInstanceDescriptor.normalize()` drops any scheme `WKWebView.handlesURLScheme()` claims and falls back to `capacitor`, so the iOS origin is always `capacitor://localhost` and cleartext comes from `NSAllowsArbitraryLoads` instead. Setting it reads as configuration and does nothing.
  • WKWebView hands server-trust challenges to the navigation delegate for main-frame navigation only. A subresource over a self-signed cert - an `<img>` MJPEG stream, a WSS connection - fails in the network process with no callback, while CapacitorHttp reaches the same server through `SSLTrustURLProtocol`. Android's `onReceivedSslError` covers subresources, so this is an iOS-only blank tile (suspected in #507, unconfirmed).
  • Capacitor's iOS HTTP plugin calls `setCookiesFromResponse` on every response, and that ends in `syncCookiesToWebView()`, which walks the entire shared cookie jar and sends one `WKHTTPCookieStore.setCookie` IPC message per cookie held. ZM sets `ZMSESSID` on every reply, so the jar fills and each later response re-pushes all of it. A montage issues one event-count request per tile; 74 of them queued 129 cookie messages at the WebKit networking process, the same process serving every `<img>` MJPEG load (#507). Android never showed it - its `CookieManager` is in-process, with no IPC queue to overflow. The plugin exposes no flag to disable the sync, so `AppDelegate` sets `HTTPCookieStorage.shared.cookieAcceptPolicy = .never`; the app reads no cookie and authenticates with a token in the query string. Gate: `src/tests/agents-contracts.test.ts`.
  • A prebuilt xcframework whose `minos` exceeds the app's `IPHONEOS_DEPLOYMENT_TARGET` kills the app at launch on every OS in the gap, before any of our code runs. llama.cpp publishes its xcframework built for iOS 16.4 while the app targets 16.0, so 2.2.0 crashed on launch for every 16.0-16.3 device (#421). The `minos` mismatch is not itself what terminates: dyld loads the framework anyway and dies binding `cblas_sgemm$NEWLAPACK$ILP64`, an Accelerate symbol that does not exist before 16.4. Weak linking does NOT save this - it tolerates a framework being absent, not a symbol its dependency lacks; a weak-linked build crashed identically. The engine therefore lives in `LlamaBridge.framework`, which the app embeds but never links and loads with `Bundle.load()` behind `#available(iOS 16.4, *)`; a shell-script build phase embeds `llama.framework` without linking it, because Xcode only embeds a package product for the target that links it. Nothing the app links may reference llama - check with `otool -L App.debug.dylib | grep llama`, which must come back empty.
  • A new framework target needs `MARKETING_VERSION` set and must not set `CURRENT_PROJECT_VERSION` (it inherits the project-level value from `App/Version.xcconfig`), or `GENERATE_INFOPLIST_FILE` produces an Info.plist with no `CFBundleShortVersionString` / `CFBundleVersion`, or a stale one, and App Store Connect rejects the upload (90056/90057). Build numbers come from the build, never a tracked file: Gradle computes `versionCode`, `sync-version.js` writes the gitignored `App/BuildNumber.xcconfig`. Gate: `src/lib/__tests__/sync-version.test.ts`. Simulator runs never catch this - check the built `Info.plist` (`PlistBuddy -c "Print :CFBundleShortVersionString"`) in a Release build.
  • An `@objc` protocol compiled into two binaries is two distinct protocols to the ObjC runtime: the conformance one image registers is invisible to the other's copy, so `as?` fails even though the class is found and instantiated. A protocol shared across a dynamically loaded boundary needs its own framework that both sides link (`LlamaBridgeInterface`), not the same file in two targets.
  • SPM caches are purgeable
Read more
Ships withzmninjang

Documentation A ZoneMinder client for web, desktop, iOS, and Android. It shows live camera feeds, a montage of many cameras at once, and events on a timeline, and has an AI chat agent you can ask about your cameras.

Get the whole plugin

Other agents on zmninjang.

glossary
Agent

glossary

One name per concept, for code identifiers, briefs, docs, and commit messages. Each entry says what the thing is, not what it…