npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@independo/capacitor-inderun

v1.1.0

Published

On-device AI with cloud fallback for Capacitor apps. Apple Foundation Models on iOS, ML Kit GenAI (Gemini Nano) on Android, an OpenAI-compatible endpoint as fallback — one API, with streaming and cancellation.

Readme

@independo/capacitor-inderun

On-device AI with cloud fallback, for Capacitor apps.

Runs a prompt on the device when the device can — Apple Foundation Models on iOS, ML Kit GenAI (Gemini Nano) on Android — and falls back to an OpenAI-compatible cloud endpoint when it cannot. One API across iOS, Android and the web, with streaming and cancellation. MIT.

It is a thin bridge, not a second engine. The package delegates to the released IndeRun platform SDKs instead of re-implementing routing, provider logic, or normalized error handling:

  • web: @independo/inderun-web (npm)
  • iOS: IndeRun Swift package (github.com/independo-gmbh/inderun, SwiftPM)
  • Android: app.independo.inderun:inderun-* (Maven Central)

This repository is the SwiftPM/npm-publishable home of the Capacitor bridge only. The native SDKs it wraps live in the main independo-gmbh/inderun monorepo.

Install

pnpm add @independo/capacitor-inderun @capacitor/core

Then sync native projects with your normal Capacitor workflow.

Platform Requirements

| Platform | Minimum version | |----------|------------------------| | iOS | 16.0 | | Android | API 26 (Android 8.0) | | Web | Any modern browser |

The iOS floor comes from the IndeRun Swift package this bridge depends on, not from the bridge itself. On-device execution needs more than the floor: Apple Foundation Models requires an Apple Intelligence–capable device on iOS 26+, and ML Kit GenAI requires AICore / Gemini Nano support. Availability is checked at runtime and the cloud provider serves the request when it is missing.

Host Project Requirements

The IndeRun SDKs this bridge wraps are newer than a freshly generated Capacitor app's defaults, so cap add alone is not enough. Each of these is a hard requirement — the corresponding build failure is named so it is searchable:

Android (android/variables.gradle and android/build.gradle in your app):

| Setting | Value | Failure if unset | |---|---|---| | compileSdkVersion | 37 | "requires libraries and applications that depend on it to compile against version 37 or later" | | minSdkVersion | 26 | manifest merger conflict | | Android Gradle Plugin | 9.1.0+ (Gradle 9.x) | "requires Android Gradle plugin 9.1.0 or higher" | | org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.10 on the app's buildscript classpath | — | "Module was compiled with an incompatible version of Kotlin … metadata is 2.4.0, expected version is 2.2.0" |

The Kotlin one is the surprising entry: the inderun-* artifacts carry Kotlin 2.4.x metadata, which the Kotlin plugin AGP brings by default cannot read. The plugin's own build hoists a newer KGP for its standalone build, but a consuming app resolves KGP from its own buildscript classpath, so the app has to add it too:

// android/build.gradle
buildscript {
    dependencies {
        classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.4.10'
    }
}

AGP 9 also rejects getDefaultProguardFile('proguard-android.txt'); switch to proguard-android-optimize.txt.

iOS: set IPHONEOS_DEPLOYMENT_TARGET to 16.0 in your Xcode project before npx cap sync ios. The Capacitor CLI reads that value to generate CapApp-SPM/Package.swift, so syncing with the default leaves a manifest pinned below this package's floor and the build fails with "requires minimum platform version 16.0 for the iOS platform, but this target supports 15.0".

Supported IndeRun Version

This release tracks IndeRun 0.3.2 on all three platforms:

| Platform | Artifact | Constraint | |----------|----------|------------| | Web | @independo/inderun-web, @independo/inderun-contracts | 0.3.2 (exact) | | iOS | inderun SwiftPM package | >=0.3.2 <0.4.0 | | Android | app.independo.inderun:inderun-* | 0.3.2 (exact) |

The npm and Gradle pins are exact and all three are bumped together — a partial bump is how the platforms drift apart. The SwiftPM constraint is ranged rather than exact so an app that also depends on inderun directly can still unify its package graph; it stops at the next minor because that is where an 0.x SDK's breaking changes live.

This package versions independently of the IndeRun monorepo under plain semver — the numbers are unrelated, and this bridge's own version says nothing about which IndeRun release it wraps. Read that off the table above.

Usage

import { createIndeRunCapacitor } from "@independo/capacitor-inderun";

const inderun = createIndeRunCapacitor({
  openAI: {
    model: "gpt-5.2",
    endpointUrl: "/api/inderun/openai-responses",
    auth: "none"
  }
});

const result = await inderun.run({
  schemaVersion: "1.0",
  task: { kind: "text_to_text" },
  prompt: "Summarize why a thin bridge matters.",
  constraints: { privacy: "cloud_required" }
});

Streaming (Mode 2)

stream() returns the same StreamRun shape the platform SDKs return directly: the run's handle, its canonical event sequence, and a cancel hook.

const run = await inderun.stream({
  schemaVersion: "1.0",
  task: { kind: "text_to_text" },
  prompt: "Explain routing in two sentences."
});

console.log(run.handle.runId); // available before the first event

let text = "";
for await (const event of run.events) {
  switch (event.type) {
    case "content_delta":
      text += event.payload.text;       // append
      break;
    case "content_snapshot":
      text = event.payload.text ?? "";  // replace — an empty one retracts
      break;
    case "terminal":
      // exactly one of "completed" | "error" | "cancelled"
      console.log(event.payload.outcome);
      break;
  }
}

run.cancel("user navigated away"); // idempotent; a no-op after the terminal

Event semantics, ordering, the terminal guarantees, cancellation and fallback are the engines' contract, identical on every platform, and documented once in Streaming (Mode 2). Two things are specific to reaching them through a bridge:

  • The bridge hop is not order-preserving. That is why sequence rather than arrival is the contract's ordering authority. events already yields strictly by it; if you attach your own listener to STREAM_EVENT_NAME instead, ordering is yours to enforce.
  • A failed run is not a rejected promise, and a bridge transport fault is a third thing again. See Error Handling.

API

  • createIndeRunCapacitor(options) — returns a handle that lazily configure()s on the first run() or stream() and memoizes it. Safe to call once at app startup.
  • run(request) — Mode 1. Resolves with the canonical IndeRun TaskResult.
  • stream(request) — Mode 2. Resolves with a StreamRun (handle, events, cancel). events is single-use.
  • checkCapabilities() — every registered provider's static declaration and live availability, without executing a task. Resolves with ProviderCapabilitySnapshot[]. Use it for a provider or settings screen; availability changes between calls, so do not cache it across a run() or stream().
  • The low-level plugin methods configure(options), startStream(options) and cancelStream(options) are also exported, along with the listener event names STREAM_EVENT_NAME ("indeRunStreamEvent") and STREAM_ERROR_NAME ("indeRunStreamError").

The two listener event names are public contract. Native emits exactly these, and an app may attach its own listener to them; renaming one is a breaking change.

The IndeRunCapacitorPlugin and ConfigureOptions contracts — including the openAI, systemModel and onnx bootstrap configs and the allowDirectOpenAIEndpoint flag — are defined and documented in src/definitions.ts.

systemModel, onnx and allowDirectOpenAIEndpoint are web-only and ignored on iOS and Android, which register their own on-device provider from configure() regardless. systemModel registers the browser-managed on-device provider (Chrome's Prompt API) and is what makes constraints.privacy = "local_required" routable in a browser. Both on-device web providers are Mode 1 only.

onnx carries two caveats. It needs the consumer to install the optional @huggingface/transformers peer dependency — this bridge does not declare it — and to supply real model weights, because the web SDK's runtime injection seam is a function and so cannot cross a JSON bridge hop: only the default Transformers.js runtime is reachable through configure(), never the fixture runtime the upstream demos use offline. Register it only when the weights are there; a provider that cannot load turns a clean routing refusal into a provider error.

Two asymmetries in the low-level surface, both hidden by the ergonomic API:

  • run(request) passes the request at the options root, while startStream({ streamId, request }) nests it, because that envelope also carries the bridge-local correlation id. stream() hides this.
  • The plugin method checkCapabilities() resolves { providers: [...] } rather than the array itself, because a Capacitor plugin method cannot resolve a top-level array on either native platform. IndeRunCapacitor.checkCapabilities() unwraps it, so app code sees the same array the three platform SDKs return.

ProviderCapabilitySnapshot and the ProviderDescriptor / ProviderDynamicCapabilities it contains are declared in src/definitions.ts rather than imported, because — unlike TaskRequest or StreamEvent — they are not generated contracts: each platform SDK declares its own copy and there is no schema or validator for them upstream. src/web.ts returns the web SDK's snapshots into the bridge's type uncast, so the shapes staying identical is a compile error rather than a convention. Note capabilities.streamingAvailable and cancellationAvailable are absent, not null, when the runtime has nothing to add: absence means inherit the static declaration.

Platform Notes

  • Web requires at least one provider to be registered from configure(). The web SDK ships an OpenAI-compatible provider, a Web ONNX Runtime provider and a browser system-model provider, but only the OpenAI-compatible one declares streaming support — so a local_required stream in a browser is refused at routing time, while a local_required run can be served on-device.
  • iOS always registers the Apple on-device provider and optionally registers OpenAI when configured.
  • Android always registers the ML Kit on-device provider and optionally registers OpenAI when configured.
  • Keep credentials behind authContextRef. That keeps a secret out of the request payload and out of source; it does not make a key safe to ship, since anything an installed app or a browser can read, someone with that app or browser can read. For a key you own, put it behind a backend you control and point endpointUrl at that — for browser apps, a same-origin proxy with auth: "none".

Current Limitations

  • Mode 1 run() and Mode 2 stream() are supported. Mode 3 sessions are not.
  • No backpressure across the bridge. Native pushes events; a slow consumer buys memory, not throttling, because events buffer in JS until they are read. Runs are finite and terminal-bounded, and any drop or throttle policy would be behaviour — which belongs in the engines, not in a bridge.
  • Which providers can actually stream is decided upstream, not here; see the provider matrix.
  • An unrecognized StreamEvent.type is passed through untouched rather than rejected, so a consumer built against an older contract revision keeps working when a newer one adds an event type. Ignore what you do not recognize.
  • No plugin-level credential management API is exposed in this first cut.
  • The bridge is intentionally thin; cloud provider bootstrap still has to come from the app.

Error Handling

A streaming run has three distinct failure surfaces, and conflating them is the easiest mistake to make:

| Surface | When | How it reaches you | | --- | --- | --- | | Rejection | Request validation, or no streaming-capable provider | stream() rejects with an IndeRunError | | Terminal error event | A provider failed, or the whole planned chain did | Not a rejection — for await completes normally and the last event is terminal with payload.outcome === "error" | | Bridge fault | The transport lost or could not encode events | events throws an Internal IndeRunError |

So a run that fails after starting ends your loop normally. Branch on event.payload.outcome to tell completion from failure from cancellation.

Errors thrown by configure() and run() conform to IndeRunError from @independo/inderun-contracts; branch on error.errorClass (the shared error taxonomy). The bridge unwraps the native error envelope before re-throwing, so callers receive the same IndeRunError shape on every platform.

try {
  await inderun.run(request);
} catch (error) {
  if (error && typeof error === "object" && "errorClass" in error) {
    // error.errorClass is one of the IndeRun error taxonomy values
  }
}

License

MIT. See LICENSE.

Sponsorship & Development

This project is sponsored by netidee and developed by Independo GmbH.

Explore more open-source tools and research from Independo.