Mobile Capacitor
This chapter explains how to put a JSKIT app on an Android phone with the Capacitor shell.
The normal result is:
- your JSKIT web app runs inside an Android shell
- the app can talk to your backend
- deep links and auth callback routing still go through normal JSKIT routes
- you can rebuild, reinstall, tunnel, and restart with
jskit mobile ...
Install it
From the app root:
npx jskit add package @jskit-ai/mobile-capacitorThat command:
- installs
@jskit-ai/mobile-capacitor - installs the required Capacitor packages
- seeds
config.mobileif it is missing - renders
capacitor.config.json - provisions
android/ - refreshes the managed Android shell files from
config.mobile
After that, run:
npx jskit mobile android doctorIf doctor passes, the app is ready for the normal mobile workflow. The jskit mobile android ... commands expect the mobile runtime package to be installed in package.json and recorded in .jskit/lock.json; they do not install @jskit-ai/mobile-capacitor for you.
What you need on your machine
Required Host Tools
Before this workflow is healthy, the machine needs all of the following:
adbonPATH- Android SDK installed, with the required platform and build-tools
- a full JDK, not just a runtime
javaonPATHjavaconPATHANDROID_HOMEorANDROID_SDK_ROOTset, orandroid/local.propertieswithsdk.dir=...JAVA_HOMEpointing at a full JDK ifjava/javacare not already resolved correctly fromPATH
The end state should be:
adb devices -lworksjava -versionworksjavac -versionworksnpx jskit mobile android doctorpasses
If you installed Android Studio, its bundled JDK is often the easiest working Java home:
export JAVA_HOME="$HOME/android-studio/jbr"
export PATH="$JAVA_HOME/bin:$PATH"Put the app on your phone
Start the backend first:
PORT=3000 npm run serverThen use the all-in-one command:
npx jskit mobile android devThis is the one-stop shop for local Android phone testing.
It runs these commands in this exact order:
npx jskit mobile android sync
npx jskit mobile android tunnel
npx jskit mobile android runThat first step is important: for bundled apps, npx jskit mobile android sync runs npm run build first and then runs cap sync android. So yes, npx jskit mobile android dev does build the app before it installs/runs the Android shell.
If more than one Android device is attached:
npx jskit mobile android devices
npx jskit mobile android dev --target <device-id>That is the shortest path.
Manual commands
If you want to run the steps yourself instead of using mobile android dev, these are the commands that matter.
List devices
npx jskit mobile android devicesPrints the Android devices currently visible to adb.
Sync the Android shell
npx jskit mobile android syncThis:
- refreshes the managed mobile files from
config.mobile - builds the JSKIT web app into
dist/ - runs
cap sync android
The refresh step requires the managed files from the package install to exist. If capacitor.config.json or .jskit/mobile-capacitor.md is missing, install the package again with npx jskit add package @jskit-ai/mobile-capacitor.
Create the local tunnel
npx jskit mobile android tunnelIf you want a specific device:
npx jskit mobile android tunnel --target <device-id>This command creates the adb reverse tunnel that lets the phone reach your laptop's local backend.
Install and run the app
npx jskit mobile android runOr:
npx jskit mobile android run --target <device-id>This launches the Android shell through Capacitor.
Restart cleanly
npx jskit mobile android restartOr:
npx jskit mobile android restart --target <device-id>This:
- clears app data
- force-stops the Android app
- cold-starts
MainActivity
Use it when you want a clean signed-out state.
The command set
These are the CLI mobile commands used by the package workflow:
jskit mobile android devices- list visible Android devices
jskit mobile android doctor- validate config, shell files, SDK, and host readiness
jskit mobile android dev- run the standard local-phone development flow
jskit mobile android sync- rebuild and sync the Android shell
jskit mobile android tunnel- create and verify the
adb reversetunnel
- create and verify the
jskit mobile android run- launch the Android shell
jskit mobile android restart- clear app data and cold-start the app
jskit mobile android build- build the Android release bundle
The default local-dev setup
When jskit add package @jskit-ai/mobile-capacitor seeds config.mobile for the first time, it creates a working local-dev default.
The important part is:
config.mobile = {
enabled: true,
strategy: "capacitor",
assetMode: "bundled",
apiBaseUrl: "http://127.0.0.1:3000",
auth: {
callbackPath: "/auth/login",
customScheme: "your-app-slug"
}
};That default is good for local phone development against your laptop.
It also means:
- the backend must be running on your laptop
- the app needs an
adb reversetunnel
If apiBaseUrl later points to a real remote https://... backend, the tunnel is no longer required.
A good day-to-day loop
Once the package is installed, the normal loop is:
PORT=3000 npm run server
npx jskit mobile android devIf you want to rerun steps manually, a predictable sequence is:
npx jskit mobile android sync
npx jskit mobile android tunnel
npx jskit mobile android runAnd if you need a clean state afterward:
npx jskit mobile android restartBehind the scenes
config.mobile is the source of truth
The Android shell is not configured by hand in scattered native files. JSKIT renders the shell from config.mobile.
That config drives:
- Capacitor app id and app name
- Android package name
- app version values
- API base URL
- auth callback path
- custom URL scheme
The shell is still a JSKIT web app
The UI still comes from the JSKIT web client.
Stage 1 mobile does not turn JSKIT into a native UI framework. It packages the web app into a Capacitor Android shell.
That is why jskit mobile android sync still builds the web app first.
Managed files
The package manages:
capacitor.config.json.jskit/mobile-capacitor.md- the Android shell under
android/ - managed Android identity files such as:
android/app/build.gradleandroid/variables.gradleandroid/app/src/main/res/values/strings.xmlMainActivity.javaorMainActivity.kt
- the managed deep-link block in
AndroidManifest.xml
After the package is installed, jskit mobile android sync refreshes those managed files from config.mobile.
Why the tunnel exists
Inside the Android shell, the phone's 127.0.0.1 is the phone itself, not your laptop.
So when local development uses:
http://127.0.0.1:3000the phone still needs a bridge back to your laptop.
That bridge is:
adb reverse tcp:3000 tcp:3000jskit mobile android tunnel wraps that and shows adb reverse --list afterward so you can see the active mapping.
What happens to URLs
Inside the Capacitor shell there are two relevant URL worlds:
- shell/webview origin:
https://localhost/...
- backend/API origin:
- usually
config.mobile.apiBaseUrl
- usually
JSKIT keeps app code using normal relative server paths such as:
/api/session/api/bootstrap/socket.io/...
The mobile runtime adapts those requests to the configured backend origin.
That is why app code does not need a separate "mobile API client" pattern for the standard JSKIT server routes.
Why local HTTP works on Android
For the local-dev case, the backend URL is often plain http.
The mobile shell therefore needs:
- Capacitor native HTTP enabled
- Android cleartext traffic allowed for the generated shell
Without those, Android or the WebView will block local backend traffic even if the tunnel itself is correct.
Auth and deep links
The mobile shell does not invent a separate auth route system.
The normal contract stays:
- OAuth start opens in the external browser/custom tab
- callback returns through the app's custom scheme
- callback path is still the normal JSKIT path:
/auth/login
Deep links and auth completion are normalized back into the normal JSKIT router.
What doctor is actually checking
jskit mobile android doctor is the preflight command for this whole lane.
It checks:
config.mobile- managed shell file freshness
- Android shell presence
- deep-link wiring
- Android SDK basics
- host readiness for the shell path
If mobile work looks strange, run doctor first.