Local-only starter app
What you’ll build
A one-screen Capacitor app with no credentials and no server: a text box, a Run on device button, a list of what the device can run, and an output area. On an eligible device it shows the result and where it ran. On an unsupported device it shows the refusal and the providers that were rejected. Both outcomes are correct.
Prerequisites
- Node 24 or newer, and the host requirements from the Capacitor quickstart.
- A physical iOS or Android device. Xcode 26 for iOS, Android Studio for Android.
Create the app
Create a new Capacitor app and add the native platforms, following the Capacitor getting started guide (opens in new tab). The starter works with whichever package manager your app uses. This page shows npm commands; use the equivalent command of your own package manager (for example pnpm create @capacitor/app@latest, pnpm add and pnpm build):
npm init @capacitor/app@latestnpx cap add iosnpx cap add androidRun the commands inside the new project folder (after npm init has created it and you have run npm install there if the prompts did not). Before you sync iOS, set the deployment target to 16.0 and apply the Android settings described in the quickstart.
Add the plugin
npm install @independo/capacitor-inderun@1.2.0 @capacitor/core@8Set the iOS deployment target and the Android values from the quickstart, then sync:
npx cap syncAdd the screen
Put this markup in the app’s index.html body:
<main> <h1>IndeRun local-only starter</h1> <label for="prompt">Text to rewrite</label> <textarea id="prompt" rows="4">Rewrite in plain language: Your appointment has been rescheduled.</textarea> <button id="run" type="button">Run on device</button> <h2>What this device can run</h2> <ul id="capabilities"></ul> <h2>Result</h2> <pre id="output" aria-live="polite"></pre></main>Save this as a TypeScript module and load it from your app’s entry file (for example import "./starter";):
import { createIndeRunCapacitor, type IndeRunError } from "@independo/capacitor-inderun";
// iOS and Android register the platform's on-device model; no cloud provider is configured.const ai = createIndeRunCapacitor();
type RejectedProvider = { providerId: string; reasons: { code: string }[] };
const byId = <T extends HTMLElement>(id: string) => document.getElementById(id) as T;const prompt = byId<HTMLTextAreaElement>("prompt");const button = byId<HTMLButtonElement>("run");const capabilityList = byId<HTMLUListElement>("capabilities");const output = byId<HTMLPreElement>("output");
function addItem(list: HTMLElement, text: string) { const item = document.createElement("li"); item.textContent = text; list.append(item);}
async function showCapabilities() { capabilityList.replaceChildren(); for (const provider of await ai.checkCapabilities()) { const { available, reason } = provider.capabilities; addItem( capabilityList, `${provider.providerId} (${provider.descriptor.type}): ` + (available ? "available" : `unavailable: ${reason ?? "no reason reported"}`) ); }}
async function runLocalOnly() { button.disabled = true; output.textContent = "Running…"; try { const result = await ai.run({ schemaVersion: "1.0", task: { kind: "text_to_text" }, prompt: prompt.value, constraints: { privacy: "local_required" } }); output.textContent = [ result.output.text, "", `provider: ${result.provenance?.providerId}`, `locality: ${result.provenance?.locality}` ].join("\n"); } catch (error) { const refusal = error as IndeRunError; const rejected = (refusal.details?.rejectedProviders ?? []) as RejectedProvider[]; output.textContent = [ `Refused: ${refusal.message}`, ...rejected.map( (entry) => `- ${entry.providerId}: ${entry.reasons.map((r) => r.code).join(", ")}` ) ].join("\n"); } finally { button.disabled = false; }}
button.addEventListener("click", () => void runLocalOnly());void showCapabilities();The request uses local_required and no cloud provider is configured, so nothing can leave the device.
Build and run
Build your web assets with the project’s build script (npm run build, or pnpm build with pnpm), then sync and run on a connected device. npx cap run (opens in new tab) syncs first, builds and deploys; --list shows the available targets:
npm run buildnpx cap run ios --listnpx cap run ios --target <device-id>npx cap run android --listnpx cap run android --target <device-id>Expected result
On an eligible device, the capability list shows the on-device provider as available. After you press Run on device, the output area shows the rewritten text, the provider id and locality: on_device.
On an unsupported device, the capability list shows the provider as unavailable with the platform’s reason, and pressing the button shows Refused: No eligible local provider found for task 'text_to_text'. followed by each rejected provider and its reason codes. That refusal is the correct outcome: the device cannot run the task under local_required, and nothing ran. See Read the outcome.
Record your run
If you want a record of what you saw on your device (model, OS version, available or refused, reason codes), see the device record template (opens in new tab). Please record only what you observed.
Diagnostic app
For advanced bridge testing, the plugin repository ships a larger diagnostic example app (opens in new tab) (pinned to v1.2.0).
IndeRun — open-source AI execution for cross-platform apps. Built and maintained by Independo GmbH (opens in new tab), supported by netidee (opens in new tab).
MIT-licensed SDK · No SDK usage telemetry.
© 2025–2026 Independo GmbH