@botim/mp-debug-sdk
v1.9.0
Published
Remote-debug SDK for BOTIM mini-programs — streams console, network, and error events to a BOTIM debug-relay for live inspection, with an AI-observable command channel.
Downloads
1,604
Readme
@botim/mp-debug-sdk
Remote-debug your BOTIM mini-program. Stream console, network, and error events to a debug-relay you control; let humans tail live and let AI agents query, subscribe, and issue safe commands back to the device.
Why
Mini-programs run on user devices in environments you can't easily attach a debugger to. This SDK gives you:
- Live logs of
console.*,fetch,XMLHttpRequest, and uncaught errors. - AI-observable sessions — every event indexed by
(miniProgramId, deviceId, sid)so resolver agents can pull errors and reproduce bugs without a human in the loop. - Safe AI command channel — agents can
reload,dump-state,set-feature-flag,screenshot,ping,exec(default-on remote REPL), or any custom command you allow. Anything not registered is rejected. - Built-in redaction before the event ever enters the in-memory buffer.
Install
If your project's
.npmrcpins the@botimscope to a private registry, override it for this package. This SDK is published to public npm. If your.npmrccontains a line like@botim:registry=<private-mirror-url>, plainnpm install @botim/mp-debug-sdkwill route to the private mirror, not find the package, and fail. Use:
npm install @botim/mp-debug-sdk --registry=https://registry.npmjs.org/If your repo has no @botim scope override, the flag is harmless — npm uses registry.npmjs.org by default. Don't permanently re-pin the @botim scope to public npm in your .npmrc: it would shadow whatever private registry your other @botim/* packages come from.
1. Add the env config files
Each environment of your mini-program ships with one config file at the project root, in the standard BOTIM mini-program schema:
// botim.dev.json (real schema — abridged)
{
"mp_id": "mbrx_p2p_dev",
"app_id": "me.botim.rd.p2p",
"version": "0.0.127",
"app": {
"version": "0.0.127",
"md5": "fbd6b256d4961abe1cc7703984c31857"
},
"framework": { "version": "57.0.1" },
"category": "Finance"
// ...all other BOTIM platform fields are passed through untouched
}Repeat for botim.uat.json, botim.beta.json, botim.prod.json. The plugin reads mp_id (the canonical mini-program id; the legacy miniProgramId field is also accepted) and derives a deterministic buildSignature from version + app.md5 so it correlates with each release rather than churning on platform-side metadata changes.
The plugin also auto-fills app.name (from app_id) and app.version (from version) so your enableRemoteDebug call doesn't have to repeat them.
2. Wire the Vite plugin
// vite.config.ts
import { botimDebug } from '@botim/mp-debug-sdk/vite';
// Pick the relay endpoint at build time. You control where logs go —
// the SDK never phones home to anything but this URL.
const RELAY_URL = process.env.RELAY_URL ?? 'http://localhost:8090';
export default (({ mode }: { mode: string }) => ({
plugins: [botimDebug({ mode, relayUrl: RELAY_URL })],
}));Switch envs by changing the build flag — no runtime branches:
vite build --mode dev # picks botim.dev.json
vite build --mode uat # picks botim.uat.json
vite build --mode beta # picks botim.beta.json
vite build --mode prod # picks botim.prod.jsonIf a matching botim.{mode}.json is missing or malformed, the build fails — wrong-env bundles can't ship.
Plugin options
| option | type | purpose |
|---|---|---|
| mode | string (optional) | Force a specific mode. When omitted, the plugin auto-reads mode from Vite's resolved config. |
| mapMode | (mode) => env (optional) | Map a custom Vite mode name to a BOTIM env (defaults: development → dev, production → prod, others pass through). |
| relayUrl | string (optional) | The relay endpoint the SDK will POST to. Baked into virtual:botim/config as botimConfig.relayUrl. Trailing slash stripped automatically. |
| root | string (optional) | Project root for resolving botim.{mode}.json. Defaults to Vite's resolved root. |
3. (TypeScript) reference the virtual-module shim
In any .d.ts your tsconfig picks up (or in tsconfig.json#compilerOptions.types):
/// <reference types="@botim/mp-debug-sdk/vite/virtual-shim" />Now import { botimConfig } from 'virtual:botim/config' type-checks (botimConfig.relayUrl included).
4. Enable remote debug at the entry point
import { enableRemoteDebug } from '@botim/mp-debug-sdk';
import { botimConfig } from 'virtual:botim/config';
const handle = await enableRemoteDebug({
// The relay URL the plugin baked in. Falls back to same-origin if you
// didn't pass `relayUrl` to the plugin (e.g. you're using a dev proxy).
// For a gateway-hosted relay this is the gateway origin (optionally with a
// path prefix), e.g. 'https://proxy-uat-ae.botim.me/botim-cli-gateway'.
endpoint: botimConfig.relayUrl ?? location.origin,
// Relay connect token (rdt_…). REQUIRED for gateway-hosted relays, which
// gate attach on `Authorization: Bearer rdt_…`. Mint one bound to this
// (mpId, env) with `botim-cli relay token --mp <id> --env uat` (24 h TTL).
// Omit for the legacy standalone relay, which ignores it.
token: import.meta.env.VITE_RELAY_TOKEN,
config: botimConfig,
// app is optional — plugin auto-fills from app_id + version in
// botim.{env}.json. Override only when you want a different name/version
// reported in events.
// app: { name: 'checkout-mp', version: '1.4.0' },
// dev/uat/beta builds: consent is implicit
// prod builds: provide a hostToken or set userOptIn after a privacy dialog
consent: { userOptIn: true },
// Optional host hooks the AI command channel can invoke
builtins: {
reload: () => location.reload(),
getState: () => app.snapshot(),
setFeatureFlag: (key, value) => app.flags.set(key, value),
screenshot: async () => await canvas.toBase64PNG(),
},
onError: (err) => console.warn('[debug-sdk]', err),
});That's it — console.*, network calls, and uncaught errors now stream to the relay.
Identity & zero-config
The SDK identifies a mini-program by its app_id (reverse-DNS, e.g.
me.botim.ai.authcodelogin) — the same value across beta/uat/prod.
Auto-resolution (build time, Vite plugin):
- Reads
botim_config.{env}.json(falls back to legacybotim.{env}.json) for the selected env first. - If the selected env's file is missing, it borrows
app_idfrom any other env's file —app_idis env-stable, and the reportedenvstays the one you built for. - If no config file exists anywhere, it synthesizes a stable id
mp-<8charhash>(from the project dir + env) and logs a warning.
Migration note: identity is now
app_id, notmp_id. Builds that previously registered under a shortmp_idwill register under theirapp_id.
Zero-config runtime: import from @botim/mp-debug-sdk/auto and only pass the
relay endpoint:
import { enableRemoteDebug } from '@botim/mp-debug-sdk/auto';
await enableRemoteDebug({
endpoint: RELAY_URL, // config (app_id, env, …) auto-resolved
// token is still required for gateway-hosted relays (they gate attach on
// `Authorization: Bearer rdt_…`); omit only for a legacy standalone relay.
token: import.meta.env.VITE_RELAY_TOKEN,
});Override anything when needed (config, consent, app, …). The bare
@botim/mp-debug-sdk entry still takes an explicit config for non-Vite setups.
endpoint is always required. Import enableRemoteDebug from only ONE of the
two entries (/auto or the bare package), not both.
Cross-origin setup
The SDK posts directly to endpoint — there's no proxy required. Your relay must allow the page's origin via CORS. A reference debug-relay implementation typically defaults to CORS_ORIGINS=* for development; tighten via env when going to production:
CORS_ORIGINS=https://my-mp.example.com,https://staging.example.comEnable debugging from vite.config (no app-source changes)
Add the botimDebug plugin with a relay token and it injects the debug bootstrap into
your build — works in dev (vite serve) and in the built/deployed bundle. Remove the
plugin line to turn it off; your app code is never touched.
// vite.config.ts
import { botimDebug } from '@botim/mp-debug-sdk/vite';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd());
return {
plugins: [
// …your existing plugins…
botimDebug({
mode,
relayUrl: 'https://proxy-uat-ae.botim.me/botim-cli-gateway',
relayToken: env.VITE_RELAY_TOKEN, // rdt_… from `botim-cli relay token`; presence = ON
}),
],
};
});The plugin bakes endpoint, token, the resolved { miniProgramId, env } (from
botim_config.<env>.json), and consent: { userOptIn: true } into an enableRemoteDebug(...)
call injected at the top of <head>. Run VITE_RELAY_TOKEN=rdt_… npm run dev, or set it for a
build and botim-cli deploy --yes.
The
rdt_…is a live 24h credential and ships in the built bundle. Pass it via env, gate to non-prod (relayToken: mode !== 'production' ? env.VITE_RELAY_TOKEN : undefined), and remove thebotimDebugline when done. Hosts without anindex.htmlwire at the entry point instead.
5. Remote REPL (exec) — default-on
Once enableRemoteDebug returns, an attached agent can send a JS snippet to the device and read back the value, captured console.* output, or thrown error. No host wiring needed.
# From any terminal that can reach your relay:
curl -sX POST "<RELAY_URL>/v1/mp/<MP_ID>/devices/<DEVICE_ID>/commands" \
-H 'content-type: application/json' \
-d '{"name":"exec","args":{"code":"console.log(\"hi\"); return window.location.href"}}'Result event:
{
"type": "command-ack",
"payload": {
"command": "exec",
"ok": true,
"result": {
"value": "https://my-mp.example.com/page",
"logs": [{"method":"log","args":["hi"],"ts": 1735324800123}],
"durationMs": 2
}
}
}Inside the snippet, BOT, window, and document are bound as locals (with BOT resolved best-effort from window.BOT or Angular DI; null if unavailable). Top-level await works. Code is capped at 8 KB and 30 s.
To opt out (e.g. you ship a non-debug release that still uses the SDK for telemetry only):
await enableRemoteDebug({
// ...,
builtins: { exec: false },
});To inject extra locals for your app:
await enableRemoteDebug({
// ...,
builtins: { exec: { globals: { app: myApp, store: myStore } } },
});
// agent can then call: { code: "return store.getState().user" }Pre-buffer redaction (headers, JWT/long-token regex) is applied to
result.valueandresult.logsbefore they leave the device buffer. The existing prod consent gate (hostTokenoruserOptIn) gates whether attach happens at all. Seedocs/recipes/exec.mdfor deeper notes.
6. Custom commands (optional)
Any AI agent with the right scope can request a command. The SDK only runs commands registered via registerCommand:
handle.registerCommand('clear-cart', async () => {
await store.clearCart();
return { cleared: true };
});
handle.registerCommand('set-locale', async (args) => {
if (typeof args.locale !== 'string') throw new Error('args.locale required');
i18n.setLocale(args.locale);
return { locale: args.locale };
});Anything not registered gets a command-rejected event with reason unknown-command.
7. Lifecycle
await handle.flush(); // force-send buffered events
await handle.stop(); // uninstall interceptors + drain queue
console.log(handle.sid); // server-issued session idProduction safety
- No-op on disable:
enableRemoteDebug({ enabled: false, ... })returns an inert handle. No I/O, no globals wrapped. - Synchronous validation: bad config or missing consent throws immediately, before any interceptor is installed.
- No-throw runtime: once attached, transport/network/redaction errors only surface via
onError. Your app never observes a thrown error from the SDK. - Defense-in-depth redaction: header denylist + JWT/long-token regex + body byte cap, applied before events enter the buffer.
- Single in-flight long-poll: at most one outbound request per device for the AI command channel.
- Self-event suppression: the SDK captures
fetchreference at import time and routes its own ingest/poll calls through that pre-interception fetch — no risk of the SDK observing itself and creating a feedback loop.
Debugging a live mini-program
Once your build is wired and shipped, see docs/live-debugging.md for the end-to-end runbook against your debug-relay deployment. It covers:
- pointing the Vite plugin at the live relay URL,
- pulling errors and tailing sessions from the admin UI,
- a dedicated For AI agents section with
curlrecipes for/v1/mp/{mpId}/events, the SSE live-tail, and command POSTs.
API reference
| Symbol | Purpose |
|---|---|
| enableRemoteDebug(options) | Boot the SDK; returns a RemoteDebugHandle. |
| botimDebug({ mode?, relayUrl?, mapMode? }) | Vite plugin; wire in vite.config.ts. |
| resolveBotimConfig(mode, root) | Pure resolver, for non-Vite bundlers. |
| BotimConfig | Type of botim.{env}.json after resolution. |
| BotimConfigError | Synchronous throw on bad/missing config. |
| BotimConsentError | Synchronous throw on missing consent in prod. |
| RemoteDebugHandle.registerCommand(name, handler) | Allow an AI command. |
| RemoteDebugHandle.flush() | Force-flush the in-memory buffer. |
| RemoteDebugHandle.stop() | Uninstall and drain the queue. |
AI debug skill (Claude Code)
A Claude Code skill that teaches AI agents how to wire and consume this SDK lives at .claude/skills/botim-debug-relay/SKILL.md. Open Claude Code from this repo and the skill auto-loads — no setup needed. To use it from anywhere on your machine:
cp -r .claude/skills/botim-debug-relay ~/.claude/skills/The file in this repo is a mirror. The canonical copy lives in
botim-debug-relay/.claude/skills/botim-debug-relay/SKILL.md. Do not edit the SDK-side copy directly — your changes will be overwritten by the next sync. Edit in the relay repo, then runbash bin/sync-skill.shfrom that repo to propagate here.
License
ISC © BOTIM
