CLI
The qafka CLI (npm package qafka, current version 0.6.0) bridges your dashboard project and your React Native codebase. It scaffolds the chat screen and tool handlers, wires up a development credential, and keeps everything in sync as you add or change tools in the dashboard. It can also analyze your app’s (or a website’s) navigation so the assistant can suggest screens to visit.
Installation
# Global install (recommended for repeat use)
npm install -g qafka
# Or run on demand without installing
npx qafka initRequires Node.js 20 or later.
Mental model
qafka login # once per machine — browser login
qafka init # once per app — scaffold files + dev credential
qafka sync # every time the dashboard changes — pull tools, wire handlersThe dashboard is the source of truth for tool definitions; your handlers.ts file is the source of truth for what’s wired locally. qafka sync reconciles the two — idempotently and non-destructively (it never overwrites a handler you’ve already implemented, and never deletes your code).
qafka login
Authenticates against the Qafka backend via your browser and then runs the same project-selection flow as qafka project.
qafka loginIt opens <dashboard>/login in your browser, starts a short-lived local callback server on 127.0.0.1:3456, and waits (5 minute timeout) for the dashboard to redirect back with a signed-in JWT. The callback is protected by a per-login CSRF state token; a callback with a missing or mismatched state is rejected. On success the JWT is verified against /auth/me and cached in ~/.qafka/auth.json (mode 0600) so subsequent commands don’t ask again.
Options:
| Flag | Description |
|---|---|
--dashboard-url <url> | Dashboard URL to open for login (overrides the DASHBOARD_URL env var). |
There is no email/password login — authentication is always through the browser.
qafka project
Pick or switch the active project(s) without scaffolding source files. Prompts you to log in first if you aren’t already.
qafka projectThis writes .qafka/config.json (mode 0600, gitignored) with the selected project id(s) and writes/refreshes .qafka/qafka-runtime.js — see What qafka init creates below.
qafka init
One-shot setup for a new app: runs the same login/project-selection flow as qafka project, then scaffolds source files.
qafka initOptions:
| Flag | Description |
|---|---|
--no-scaffold | Config-only — run the login/project step but skip source file generation. |
-y, --yes | Accept all detected defaults (non-interactive). |
--screen-path <path> | Override the screen file path (must end in .tsx), e.g. app/(tabs)/qafkachat.tsx. |
--no-install | Skip installing @qafka/react-native. |
--no-plugin | Skip registering the Qafka Expo config plugin in app.json. |
Re-running qafka init is safe: existing files are skipped, never overwritten.
What qafka init creates
.qafka/
config.json # CLI cache (gitignored, 0600): selected project id(s), scaffold paths
qafka-runtime.js # SDK dev runtime config (gitignored, 0600): TEST key per project
qafka.config.d.ts # TypeScript augmentation for projectId autocomplete (gitignored)
qafka.tools.json # Slim metadata for drift/orphan detection (commit this)
src/qafkaComponents/
index.ts # Barrel (managed by `qafka add component` and `qafka sync`)
src/qafkaTools/
handlers.ts # Central dispatcher with @qafka:handlers-start/end markers
app/qafka.tsx # Chat screen mounting <Qafka>
CLAUDE.md # Pointer to the tool-authoring guide shipped inside the installed SDKThe screen path is auto-detected from your project layout (Expo Router → app/qafka.tsx; react-navigation or bare RN → src/screens/qafka.tsx). Pass --screen-path to override, or answer the interactive prompt.
init also installs @qafka/react-native (unless --no-install), registers the Expo config plugin in an Expo project (unless --no-plugin), and finishes by running the same steps as qafka sync.
There is no committed, keys-in-source qafka.config.js. The credential lives only in .qafka/qafka-runtime.js, which init writes as chmod 0600 and adds to .gitignore — see React Native Configuration for the shape the SDK reads and how devConfig resolves it.
Generated screen template
import React from 'react';
import { Qafka } from '@qafka/react-native';
import { handleToolSuggested } from '../src/qafkaTools/handlers';
// Simulator/emulator dev auth only — dead-code-eliminated from release builds.
const qafkaDevConfig = __DEV__ ? require('../.qafka/qafka-runtime') : undefined;
const QafkaChatScreen = () => {
return (
<Qafka
locale="en"
components={{}}
// Required: stable end-user identifier. Replace "anonymous" with your
// signed-in user id (or a stable placeholder like `anon-${deviceId}`
// for pre-login state) so operator dashboards and tool action
// templates can group activity per user.
endUserId="anonymous"
// Optional: structured profile data for operator dashboards and tool
// templates ({{endUser.data.<field>}}). Never sent to the AI model.
// endUserData={{ email: user.email, plan: user.plan }}
// User/session data passed to tool handlers and prompt resolution
// (e.g. { userId, firstName, locale }). Reference via {{user.field}} in tools.
context={{}}
// Set to true once your app has authenticated the user.
isAuthenticated={false}
onClose={() => {}}
onToolSuggested={(tools, addResponse) =>
handleToolSuggested(tools, addResponse, {})
}
devConfig={qafkaDevConfig}
/>
);
};
export default QafkaChatScreen;The scaffold does not write a projectId prop — add it yourself (see Quick Start). The devConfig wiring above (the top-level const plus the devConfig={qafkaDevConfig} prop) is added automatically by qafka init and kept in sync by qafka sync / qafka refresh.
qafka add
Scaffold additional Qafka assets without a full sync.
qafka add component <name>
Generates a tool-result component stub, wires it into the components barrel, and patches every registered screen’s components={{ ... }} map.
qafka add component PromoCardCreates src/qafkaComponents/PromoCard.tsx (skipped if it already exists). The <name> argument is sanitized to safe identifier characters before it’s used in a file path.
qafka add screen [path]
Adds another chat screen — e.g. a second chat surface for a different audience. The new path is appended to paths.screens in .qafka/config.json, so later qafka add component and qafka sync runs patch every registered screen.
qafka add screen "app/(support)/qafkachat.tsx"qafka sync
Pulls the latest tool definitions from the dashboard and reconciles them with your code. Idempotent — safe to re-run any time.
qafka syncEach run:
-
Handler stubs. For every dashboard tool with no matching entry in
handlers.ts, appends a typed handler stub inside the@qafka:handlers-start/@qafka:handlers-endmarkers. Built-in server-side tools (e.g.qafka_response_status) are filtered out — they need no host-app handler. -
Screen self-heal. If the registered screen file is missing, it’s regenerated from the same template
qafka inituses. -
Capability-driven
<Qafka>props. Inspects each tool’s shape and patches missing props onto the<Qafka>element:Detected on a tool Props added cardTemplateIdsetonCardDeepLink,onCardSuggestMessage,onCardExternalNavigation,onCardToolTrigger,onCardCTAClickfileInputsetonFileUploadRequest,onExtractionResultA step action of type external_navigationonExternalSuggestionA step action of type email,webhook, orapi_actiononActionResultMore than one step onStepCompletedapi_actionsteps only addonActionResult— there is no separateonApiActionsSuggested/onApiActionExecutecallback. Inserted props are placeholders withTODOcomments; the idempotency check is AST-based, so reshaping or renaming a prop after insertion won’t cause it to be re-inserted. Navigation routing (onNavigationSuggest/onNavigationAction) is not capability-driven — wire it manually since it depends on your router. -
Tool UI components. For every tool whose dashboard
itemComponentnames a custom component, createssrc/qafkaComponents/<Name>.tsxif missing, adds a barrel export, and wires it into<Qafka components={{ ... }}>.
sync also refreshes .qafka/qafka-runtime.js’s routing metadata (preserving the dev key already written) and ensures the CLAUDE.md tool-authoring pointer is present.
qafka.tools.json
A slim, committed metadata file the CLI uses to detect drift and orphans — which local tool a dashboard tool ID maps to, and the dashboard’s updatedAt at the last sync. When a dashboard tool’s shape changed since the last sync, sync reports it as drift (informational — your handler body isn’t touched). When a locally-wired tool no longer exists on the dashboard, sync reports it as an orphan — it’s never removed automatically; clean up handlers.ts by hand.
qafka refresh
Re-fetches the latest development key for every project registered in .qafka/config.json, non-interactively.
qafka refreshOptions:
| Flag | Description |
|---|---|
-y, --yes | Add missing <Qafka devConfig> wiring on a screen without prompting. |
After refreshing keys, refresh checks every registered screen for the devConfig wiring described above. If a screen’s <Qafka> element exists but is missing the wiring, it prompts to add it (or adds it silently with --yes; in a non-TTY session without --yes it only warns).
qafka analyze
Scans your app’s navigation (React Native or Next.js) or crawls a website, and writes a navigation schema the dashboard uses to power navigation suggestions.
qafka analyzeBy default it detects the project type and runs a deterministic, local parser: Expo Router or React Navigation for React Native, or the Next.js file-system router for Next.js projects. No network call and no login required for this default path.
Common options:
| Flag | Description |
|---|---|
-p, --path <path> | Project path (defaults to the current directory). |
-o, --output <file> | Output file (defaults to navigation-schema.json). |
--ai | Opt in to AI-powered analysis instead of the local deterministic parser. |
--auto | Automatically fall back to the AI analyzer if local parsing fails. This fallback only applies to React Native — Next.js analysis always stays local (it fails with a diagnostic instead). |
--deep | Deep analysis: extracts per-screen metadata (component/state/handler names, input placeholders, button labels, visible text, navigation targets) via AI. Each screen is summarized locally first — only the summary is sent, never raw source. |
--incremental | With --deep: only re-analyze screens that changed since the last run. |
--print-summaries | With --deep: print the local screen summaries and exit without sending anything — lets you see exactly what would leave your machine. |
Website crawling (--web):
| Flag | Description |
|---|---|
--web <url> | Crawl a website and generate web navigation rules instead of analyzing app source. Requires login and a selected project. |
--depth <n> | Max crawl depth (default 2). |
--max-pages <n> | Max pages to crawl (default 25). |
-y, --yes / --all | Analyze all crawled pages without the interactive picker. |
--data | Also extract structured data from the crawled pages, written to --data-output. |
--data-output <file> | Structured-data output file (default static-data.json), used with --data. |
--only <rules|data|branding|documents> | Run only one of the web sub-steps instead of all of them. |
--project-id <id> | Project to target (auto-detected from .qafka/config.json if omitted). |
The --web flow crawls and analyzes the site, then uploads the resulting navigation rules to the dashboard as part of the run — you don’t run qafka upload afterward for the web path.
qafka upload
Uploads a navigation schema produced by qafka analyze (the app-source path, not --web) to the backend. Requires login — API keys can’t be used for this, only your JWT session.
qafka uploadOptions:
| Flag | Description |
|---|---|
-f, --file <file> | Schema file to upload (defaults to navigation-schema.json). |
--project-id <id> | Project to upload to (auto-detected from .qafka/config.json if omitted). |
Backend
Commands that talk to the backend (project, sync, refresh, upload) use the URL recorded at qafka login — you don’t need to pass it again per command.