Cali v2 is a role-oriented CLI for React Native and Expo teams that want repeatable mobile QA, review, performance review, and implementation runs. It keeps deterministic setup in the CLI, gives the agent only the tools it needs, and writes a structured report that works locally or in CI.
- commands:
qa,review,perf-review,dev - local mobile mode:
--local android|ios - CI mode: implicit detection with optional
--ci github-actions|easoverride - one shared
cali-context.jsonruntime contract - explicit tool packs per command
- publisher-based outputs
- additive
--prompt
- command: the user-facing role entrypoint such as
cali qaorcali review - local: local mobile mode selector for
qaandperf-review - context file: the optional explicit JSON input for workspace, repository, PR/task, mobile, build, output, and role-specific sections
- environment/context adapter: provider-specific metadata loading for GitHub Actions, EAS, local flags, and explicit JSON context
- tool pack: a bounded set of tools exposed to the role, such as
agent-device,react-devtools,repo-read, orrepo-write - publisher: an output target that enriches or writes the report, such as local files or blob-hosted screenshots
cali qa- mobile QA pass with
agent-device
- mobile QA pass with
cali review- findings-first PR/repository review (experimental)
cali perf-review- runtime performance review with
agent-deviceandreact-devtools(experimental)
- runtime performance review with
cali dev- repository-backed implementation flow (experimental)
All commands use one shared cali-context.json contract. Commands only require the sections they actually use.
{
"workspaceRoot": ".",
"repository": {
"provider": "github.com",
"owner": "acme",
"name": "mobile-app",
"webUrl": "https://github.com/acme/mobile-app",
"defaultBranch": "main",
"currentBranch": "feature/onboarding-copy"
},
"pullRequest": {
"number": 42,
"title": "Fix onboarding CTA",
"body": "Acceptance criteria: the new CTA copy is visible on Screen B.",
"url": "https://github.com/acme/mobile-app/pull/42",
"labels": ["mobile", "qa"],
"isDraft": false,
"baseBranch": "main",
"headBranch": "feature/onboarding-copy"
},
"mobile": {
"platform": "android",
"artifactPath": "./artifacts/app.apk",
"appId": "com.example.myapp",
"deviceName": "Pixel 9"
},
"build": {
"id": "gha-run-123",
"workflowUrl": "https://github.com/acme/mobile-app/actions/runs/123",
"logsUrl": "https://github.com/acme/mobile-app/actions/runs/123/job/456"
},
"output": {
"outputDir": "./artifacts/qa"
},
"qa": {
"acceptanceCriteria": ["Screen B shows the updated CTA copy", "The CTA remains tappable"]
},
"perfReview": {
"targetFlow": "Checkout",
"profilingGoals": ["rerenders", "slow interactions"]
},
"dev": {
"allowedValidations": ["bun test", "bunx tsc --noEmit"],
"writePolicy": "workspace",
"pushPolicy": "disabled"
}
}Flags always win over the context file. For example, --platform, --artifact, --app-id, --output-dir, --pr-number, or --task-id override the JSON values. For mobile runs, Cali can infer --platform from common artifact extensions (.apk, .aab, .app, .app.tar.gz, .ipa). For local mobile runs, --app-id is optional when Cali can infer it from the artifact.
For safety, Cali sanitizes credential-bearing repository URLs when loading context and publishes a reduced safe context in report.json by default.
cali qa \
--local ios \
--artifact ./artifacts/MyApp.app \
--prompt "verify the onboarding copy on Screen B"Local mobile behavior:
- each run gets a unique
agent-devicesession name such asios-a1b2c - local Android reuses the single booted emulator/device when exactly one is available, otherwise pass
--device - local runs try
open --relaunchbefore reinstalling - local iOS reuses the single booted simulator when exactly one is available, otherwise pass
--device - debug artifacts usually need Metro running for the duration of the QA run; start and stop Metro outside Cali
cali qa --platform ios --artifact ./artifacts/MyApp.app
cali qa --platform android --artifact ./artifacts/app.apk
cali review --context ./cali-context.jsonIn GitHub Actions and EAS, Cali detects the provider automatically from the environment. Use --ci only to override detection. Use --local android|ios for local mobile runs.
Use --quiet to suppress the retro banner in scripted environments. Cali also suppresses the banner automatically when CI=true.
cali perf-review \
--context ./cali-context.json \
--platform android \
--artifact ./artifacts/app.apk \
--prompt "profile the checkout flow"cali dev --context ./cali-context.json --prompt "implement issue 123"Cali supports two model auth paths:
export AI_GATEWAY_API_KEY="your-ai-gateway-key"
export QA_MODEL="openai/gpt-5.4-mini"export ANTHROPIC_API_KEY="your-anthropic-api-key"
export QA_MODEL="anthropic/claude-sonnet-4.6"AI_GATEWAY_API_KEY=your-ai-gateway-key
QA_MODEL=openai/gpt-5.4-minior:
ANTHROPIC_API_KEY=your-anthropic-api-key
QA_MODEL=anthropic/claude-sonnet-4.6The CLI loads .env automatically from the current workspace before it starts a run.
Cali defaults to openai/gpt-5.4-mini. If gateway credentials are present, that model is routed through AI Gateway. Direct provider support in this package is Anthropic only.
Optional publisher/runtime credentials:
BLOB_READ_WRITE_TOKENfor blob screenshot uploads
Some commands shell out to local binaries:
qa: requiresagent-deviceperf-review: requiresagent-deviceandagent-react-devtoolsreview: requiresgitandrgdev: requiresgit,rg, andzsh
Install examples:
npm i -g agent-device
npm i -g agent-react-devtoolsOn macOS/Linux, Git and zsh are usually present already. Install ripgrep if rg is missing.
If you want Android app id inference from an .apk without passing --app-id, Cali now reads AndroidManifest.xml directly from the archive. It can also fall back to SDK aapt when the manifest is not readable.
If one of these is missing, Cali stops with an actionable error instead of trying to install it automatically.
Cali discovers local skills from:
~/.cali/skills./.cali/skills./.agents/skills~/.agents/skills
Required role skills:
qa:agent-deviceperf-review:agent-device,react-devtools
Cali auto-installs missing required skills with npx skills into ~/.cali/skills, falling back to ./.cali/skills when needed. CLI binaries are still not auto-installed.
If you want to install the same skills yourself into a standard skills directory, use:
npx skills add callstackincubator/agent-device --agent codex --skill agent-device --copy -y
npx skills add callstackincubator/agent-skills --agent codex --skill react-devtools --copy -yThe CI-native entrypoint is cali <command>, with provider detection handled automatically in GitHub Actions and EAS. Use --ci <provider> only to override detection.
Supported providers:
github-actionseas
For CI runs, Cali derives runtime context from provider env plus CLI overrides directly inside the command.
Required provider inputs:
- GitHub Actions:
GITHUB_EVENT_PATHCALI_PLATFORMor--platformwhen the artifact extension does not identify the platformCALI_ARTIFACT_PATHor--artifact- optional
CALI_APP_ID - optional
CALI_DEVICE_NAME - optional
CALI_OUTPUT_DIR - optional
CALI_LOGS_URL
- EAS:
QA_PLATFORMor--platformwhen the artifact extension does not identify the platformAPP_PATHor--artifact- optional
APPLICATION_ID(EAS can often provide this from app config; otherwise Cali tries artifact inference) - optional
CALI_DEVICE_NAME - optional
BUILD_ID - optional
WORKFLOW_URL - optional
LOGS_URL - optional
PR_JSON
Core CI command:
cali qa --quiet --platform ios --artifact ./artifacts/MyApp.app
cali qa --quiet --platform android --artifact ./artifacts/app.apkIf the artifact is a debug build, start Metro before cali qa, wait until it is ready, and stop it in CI cleanup. Release builds normally do not need Metro.
Optional helper:
cali export-ci --report ./artifacts/qa/report.json
cali export-ci --android ./artifacts/android/report.json --ios ./artifacts/ios/report.jsonMinimal GitHub Actions example:
- name: Install required CLIs
run: npm i -g agent-device
- name: Run Cali QA
env:
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
CALI_PLATFORM: android
CALI_ARTIFACT_PATH: ${{ steps.download_build.outputs.artifact_path }}
CALI_APP_ID: com.example.myapp
run: node ./packages/cali/dist/index.js qa --quiet
- name: Export CI comment
run: node ./packages/cali/dist/index.js export-ci --report ./artifacts/qa/report.json
- name: Publish PR comment
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh pr comment "${{ github.event.pull_request.number }}" --body-file ./artifacts/qa/ci-comment.mdgh is preinstalled on GitHub-hosted runners. For self-hosted runners or container jobs, install it explicitly and provide GH_TOKEN.
Reference wrapper:
Minimal EAS example:
- id: install_agent_device
run: npm i -g agent-device
- id: run_cali_qa
env:
AI_GATEWAY_API_KEY: ${{ secrets.AI_GATEWAY_API_KEY }}
QA_PLATFORM: android
APP_PATH: ${{ steps.download_build.outputs.artifact_path }}
APPLICATION_ID: dev.expo.myapp
BUILD_ID: ${{ env.BUILD_ID }}
WORKFLOW_URL: ${{ workflow.url }}
PR_JSON: ${{ toJSON(github.event.pull_request) }}
run: node ./packages/cali/dist/index.js qa --quiet
- id: export_cali_ci
run: node ./packages/cali/dist/index.js export-ci --report ./artifacts/qa/report.jsonReference wrapper:
For multi-platform PR comments, export once from both platform reports:
cali export-ci \
--android ./artifacts/android/report.json \
--ios ./artifacts/ios/report.json \
--output-dir ./artifacts/combined-commentIf you want Cali to stay GitHub-agnostic, keep posting outside Cali and use the rendered output directly:
export GH_TOKEN="${GITHUB_TOKEN}"
gh pr comment "$PR_NUMBER" --body-file ./artifacts/combined-comment/ci-comment.mdCreate cali.config.ts in the project root:
export default {
defaultCommand: 'qa',
workspaceRoot: '.',
skillPaths: ['.agents/skills'],
commands: {
qa: {
contextPath: './cali-context.json',
mobileDefaults: {
platform: 'android',
},
extraInstructions: ['Prioritize auth and onboarding flows.'],
},
review: {
outputPublishers: ['file'],
},
perfReview: {
extraInstructions: ['Focus on rerender hotspots first.'],
},
},
}If defaultCommand is set, running plain cali with no command will execute that default command instead of showing help.
Built-in tool pack ids:
skillsagent-devicerepo-readrepo-writereact-devtools
Command defaults:
qa:skills,agent-devicereview:repo-read,skills(experimental)perf-review:skills,agent-device,react-devtools,repo-read(experimental)dev:repo-read,repo-write,skills(experimental)
From the repository root:
bun install
bun run build:cli
bunx tsc --noEmit -p packages/cali/tsconfig.jsonUseful package-local commands:
cd packages/cali && bun run dev:qa -- --helpcd packages/cali && bun run dev:review -- --helpcd packages/cali && bun run dev:perf-review -- --helpcd packages/cali && bun run dev:dev-command -- --help
The file publisher writes:
report.jsonsection.mdstatus.txtsummary.txttop-issue.txtscreenshots.mdscreenshots.jsonpublisher-manifest.json
The default output directory is artifacts/<command>.
For qa, Cali writes this output contract even for blocked runs during CI/bootstrap startup, as long as the output directory itself is writable.
export-ci writes a smaller shared CI contract:
ci-comment.mdci-output.json
Single-platform ci-output.json combines:
kindstatussummarytopIssuescreenshots
Multi-platform ci-output.json combines:
kindstatussummarytopIssueplatforms.androidplatforms.ios
For qa and perf-review, screenshots are saved under artifacts/<command>/screenshots.
If BLOB_READ_WRITE_TOKEN is set, the blob publisher uploads screenshots and enriches the report with blob URLs.
For implementation details, runtime contracts, and guidance for extending Cali with new commands, see AGENTS.md.