Skip to Content
    QAFKA
    CTRL K
    CTRL K
    • Introduction
      • Quick Start
      • Configuration
      • React Native Widget
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Invitations
      • Settings
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Configuration
        • Members
        • Documents
        • Tools
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • API Keys
      • API Key Security
    • Introduction
      • Quick Start
      • Configuration
      • React Native Widget
      • Theming
      • Context
      • Navigation
      • External Navigation
      • Handling Tools
      • Voice Chat
      • Sub-Projects
      • Error Handling
      • CLI
      • Dashboard
      • Invitations
      • Settings
        • Project
        • Overview
        • Conversations
        • Chat Test
        • Sub-Projects
        • Analysis
        • Configuration
        • Members
        • Documents
        • Tools
        • Action Logs
        • Navigation Rules
        • External Destinations
        • Chat Theme
        • API Keys
      • API Key Security

    On This Page

    • Mental Model
    • Installation
    • Quick Start
    • What qafka init Creates
    • Generated Screen Template
    • What qafka sync Does
    • 1. Handler stubs
    • 2. Screen self-heal
    • 3. Capability-driven <Qafka> props
    • 4. Tool-result components
    • Built-in Tool Filtering
    • qafka.tools.json (Slim Metadata)
    • Drift and Orphans
    • Commands
    • qafka login
    • qafka init
    • qafka project
    • qafka sync
    • qafka add component <Name>
    • qafka add screen [path]
    • qafka analyze
    • qafka upload
    • qafka test
    • qafka keys
    • Self-Hosted / Staging
    Question? Give us feedback Edit this page 
    GuidesCLI

    CLI

    The Qafka CLI bridges your dashboard project and your codebase. It scaffolds the chat screen, handler dispatcher, and tool-result components on first run, then keeps them in sync as you add or change tools in the dashboard.

    Mental Model

    Three commands cover the workflow. You authenticate once, scaffold once, and sync every time the dashboard changes:

    CommandWhen you run itWhat it does
    qafka loginOnce per machineAuthenticates against the backend; cached in ~/.qafka/auth.json
    qafka initOnce per appPicks a project and scaffolds files (will prompt for login if you skipped it)
    qafka syncEvery time the dashboard changesPulls tools, wires missing handlers, patches <Qafka> props, generates components

    The dashboard is the source of truth for tool definitions; your handlers.ts and qafkaComponents/ are the source of truth for what’s wired locally. qafka sync reconciles the two — idempotently and non-destructively.

    Installation

    # Global install (recommended for repeat use) npm install -g qafka # Or run on demand without installing npx qafka init

    Quick Start

    # 1. Authenticate. Cached credential, no need to repeat per project. qafka login # 2. In your app's repo: pick a project and scaffold files. qafka init # 3. After making changes in the dashboard, sync them down. qafka sync

    If you skip step 1, qafka init will prompt for your email + password the first time it needs the backend.

    What qafka init Creates

    After init, your project has:

    .qafka/ config.json # CLI cache (git-ignored): projectId, apiUrl, keys, paths qafka.config.js # Public app config (commit-safe — keys are dashboard-restricted) qafka.tools.json # Slim cache for drift detection (commit this) src/qafkaComponents/ index.ts # Barrel (auto-managed by `qafka add component` and sync) src/qafkaTools/ handlers.ts # Central dispatcher with @qafka:handlers-start/end markers app/qafka.tsx # Chat screen mounting <Qafka>

    The screen path is auto-detected (Expo Router → app/qafka.tsx, react-navigation / plain RN → src/screens/qafka.tsx). Pass --screen-path to override.

    Generated Screen Template

    The scaffolded screen mounts <Qafka> with a baseline of host-app props. None are capability-driven — they’re the props every real-world app ends up using:

    import React from 'react'; import { Qafka } from '@qafka/react-native'; import { handleToolSuggested } from '../src/qafkaTools/handlers'; // Public keys — commit-safe (bundle-ID restricted, attestation-validated). const apiKey = __DEV__ ? 'qafka_test_xxx' // inlined from your dashboard test key : 'qafka_prod_xxx'; // inlined from your dashboard production key const QafkaChatScreen = () => { return ( <Qafka apiKey={apiKey} locale="en" components={{}} // 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, {}) } /> ); }; export default QafkaChatScreen;

    If qafka init finds both a TEST and PROD key in the dashboard, it inlines them with a __DEV__ switch. With one key only, it inlines that one and leaves a comment nudging you to create the other. With no keys, it emits 'YOUR_TEST_KEY' placeholders.

    The SDK defaults apiUrl to https://api.qafka.com, so the prop is intentionally omitted — add it manually for self-hosted or staging environments.

    What qafka sync Does

    qafka sync runs four jobs in order, each idempotent:

    1. Handler stubs

    For every dashboard tool with no matching entry in handlers.ts, sync appends a typed handler stub. The stub respects the tool’s dashboard configuration:

    const handleGetProducts: ToolHandler = (tool, addResponse, ctx) => { // @qafka:tool tool_xxxxxxxxxxxxxxxxxxxxxxxxxxxx // TODO: implement getProducts. const { category, limit } = tool.parameters || {}; // Dashboard binds dataPath="data.products" → SDK descends this path before rendering. // Keep the nested shape below in sync (or nothing reaches the component). addResponse({ data: { products: [/* TODO: items rendered by <ProductRow> */] } }, tool); };

    What the generator does:

    • Parameter destructure: pulls every parameter the tool declares so you don’t have to remember their names.
    • Dashboard binding comment: reminds you that dataPath and the addResponse shape are coupled.
    • Nested payload skeleton: if the dashboard sets dataPath: "data.products", sync emits { data: { products: [/* TODO */] } } — array leaf for list/table, object leaf for card/detail. Mismatched shapes silently render nothing.

    Re-running sync never overwrites a handler you’ve already implemented — appendHandlerToFile checks the registry block in handlers.ts for the key and skips if present. If you delete handlers.ts and re-run sync, every wired handler is regenerated automatically (self-healing).

    2. Screen self-heal

    If the screen file registered in .qafka/config.json is missing (you deleted it, or a new developer cloned a project without scaffolding), sync recreates it from the same template qafka init uses, inlining keys from the cached config. No need to re-run init.

    3. Capability-driven <Qafka> props

    Sync inspects each tool’s shape and patches missing <Qafka> JSX attributes:

    Capability detected byProps added
    cardTemplateId setonCardDeepLink, onCardSuggestMessage, onCardExternalNavigation, onCardToolTrigger, onCardCTAClick
    fileInput setonFileUploadRequest, onExtractionResult
    Any step has external_navigation actiononExternalSuggestion
    Any step has api_actiononApiActionsSuggested, onApiActionExecute
    Any step has email, webhook, or api_actiononActionResult
    Tool has more than one steponStepCompleted

    Inserted props are placeholders with TODO comments — edit them, move them, or rename them; sync’s idempotency check is AST-based, so it won’t re-insert a prop you’ve reshaped.

    Navigation routing (onNavigationSuggest / onNavigationAction) is not capability-driven — wire it manually because the implementation depends on your router (Expo Router vs react-navigation).

    4. Tool-result components

    For every tool whose uiConfig.itemComponent names a non-default React component (e.g. ProductCard, ProductRow), sync:

    • Creates src/qafkaComponents/<Name>.tsx if missing
    • Adds a barrel re-export
    • Inserts the component identifier into <Qafka components={{ ... }}>

    The generated component stub adapts its body to the dashboard’s response config. For a per-item list (default/horizontal/vertical layout), data is one item:

    export const ProductCard = ({ data, onAction }: ToolComponentProps) => { // Typical pattern: wrap your real list-item / card component and forward // navigation through onAction so the host screen can route on it. Example: // // <Pressable onPress={() => onAction?.('navigate', data)}> // <YourRealComponent item={data} /> // </Pressable> return ( <Pressable onPress={() => onAction?.('navigate', data)} style={{ padding: 12, backgroundColor: '#f5f5f5', borderRadius: 8 }} > <Text style={{ fontWeight: '600', marginBottom: 4 }}>ProductCard</Text> <Text style={{ fontFamily: 'Courier', fontSize: 11 }}> {JSON.stringify(data, null, 2)} </Text> </Pressable> ); };

    For a custom-layout list (the SDK passes the full array to the component), sync emits a data.map(...) skeleton with a built-in empty-state guard:

    export const ProductRow = ({ data, onAction }: ToolComponentProps) => { // `data` is the FULL array (custom layout). Iterate it yourself. if (!data?.length) return null; return ( <View style={{ flex: 1, gap: 10 }}> {data.map((item: any, idx: number) => ( <Pressable key={item?.id ?? idx} onPress={() => onAction?.('navigate', item)} style={{ padding: 12, backgroundColor: '#f5f5f5', borderRadius: 8 }} > <Text style={{ fontFamily: 'Courier', fontSize: 11 }}> {JSON.stringify(item, null, 2)} </Text> </Pressable> ))} </View> ); };

    The doc comment at the top of every generated component states the response shape, the dashboard dataPath, and (for per-item lists) the maxItems cap — so you don’t have to flip back to the dashboard to remember the binding.

    Built-in Tool Filtering

    System-controlled tools (qafka_response_status, qafka_add_followup, …) execute server-side and never need a host-app handler or component. The dashboard returns them in /tools-v2 for prompt construction; the CLI filters them out:

    ✔ Fetched 7 tool(s) (skipped 2 built-in)

    If your qafka.tools.json was created by an older CLI and still has built-in entries, sync silently prunes them — they won’t appear as orphans.

    qafka.tools.json (Slim Metadata)

    Each tool you have wired locally is recorded as:

    { "version": 3, "tools": { "get_products": { "id": "tool_xxxxxxxxxxxxxxxxxxxxxxxxxxxx", "syncedRevision": "2026-04-28T16:15:13.554Z" } } }

    Two fields, both load-bearing:

    • id anchors the tool against rename and lets sync match local entries to dashboard records by stable identifier.
    • syncedRevision is the dashboard updatedAt at the last sync — sync compares it to the current updatedAt to flag drift (parameter shape changed since you generated the stub).

    Older fields (handlerKey, handlersFile, syncedAt) were redundant with .qafka/config.json and handlers.ts and have been removed in v3. Files written by older CLIs are migrated silently on read.

    Commit this file — it shares the “last known shape” snapshot with the rest of your team so drift detection is consistent across machines.

    Drift and Orphans

    When syncedRevision doesn’t match the dashboard’s current updatedAt, sync prints:

    ⚠ Drift detected (handler shape may need updating): · get_products Metadata was refreshed; review handler signatures against the dashboard.

    Drift is informational — the metadata is auto-refreshed, but your handler body is yours. Open the dashboard, check what changed, and adjust.

    When a tool you have wired locally no longer exists on the dashboard:

    ⚠ Orphans (local tool not on dashboard): · old_promo_lookup Not removed automatically. Delete from handlers.ts when you no longer need them.

    Sync never deletes user code. Clean up handlers.ts by hand once you’re sure you don’t need the orphan.

    Commands

    qafka login

    Authenticate against the Qafka backend. Required before any project-scoped command — and the first thing you should run on a fresh machine. Credentials are cached in ~/.qafka/auth.json so subsequent commands don’t ask again.

    qafka login

    Options: -e, --email <email>, -p, --password <password>, --backend-url <url>.

    qafka init

    One-shot setup: login (if needed) → pick project → scaffold files.

    qafka init

    Options:

    FlagDescription
    --no-scaffoldConfig-only; skip source generation
    -y, --yesAccept all detected defaults (non-interactive)
    --screen-path <path>Override the screen file path (e.g. app/(tabs)/qafkachat.tsx)
    --no-installSkip installing @qafka/react-native
    --no-pluginSkip registering the Qafka expo plugin in app.json
    --backend-url <url>Self-hosted / staging API root

    qafka project

    Pick or switch the active project without re-scaffolding files. Useful when working with multiple Qafka projects from one repo.

    qafka project

    qafka sync

    Pull the latest tool definitions and reconcile them with handlers.ts, the screen, and qafkaComponents/. Idempotent.

    qafka sync

    The output is a single short report:

    ✔ Fetched 7 tool(s) (skipped 2 built-in) ✓ Added 1 new handler stub(s): + new_lookup ✓ Wired 2 <Qafka> prop(s) on app/qafka.tsx: + onCardDeepLink + onCardCTAClick ✓ Generated 1 tool UI component(s): + src/qafkaComponents/ProductRow.tsx ✓ Wired 1 component(s) into <Qafka components={{ ... }}>: + ProductRow

    qafka add component <Name>

    Generate a tool-result component without running a full sync. Useful when you want a stub for a tool you’ll author later.

    qafka add component PromoCard

    Creates src/qafkaComponents/PromoCard.tsx, updates the barrel, and patches every registered screen.

    qafka add screen [path]

    Add another chat screen (e.g. user-side vs merchant-side chat). The new path is appended to paths.screens in .qafka/config.json, so subsequent add component and sync runs patch every registered screen.

    qafka add screen app/(merchant)/qafkachat.tsx

    qafka analyze

    Scan the codebase for navigation screens and write a navigation schema.

    qafka analyze

    Useful flags:

    FlagDescription
    -p, --path <path>Project path (defaults to current directory)
    -o, --output <file>Output file (defaults to navigation-schema.json)
    --aiUse the AI-powered analyzer (Expo Router, react-navigation, others)
    --autoTry the traditional parser first, fall back to AI on failure
    --deepExtract per-screen metadata via AI
    --incrementalWith --deep, only re-analyze screens that changed since the last run

    qafka upload

    Upload an analyzed navigation schema to the backend.

    qafka upload

    Options: -f, --file <file> (defaults to navigation-schema.json), --project-id <id> (auto-detected from .qafka/config.json).

    qafka test

    Send a test message to your project and print the AI response — handy for poking at tools from the terminal.

    qafka test --message "Show me the product catalog"

    Options: -m, --message <message> (defaults to "Hello!"), --api-key <key>.

    qafka keys

    Manage project API keys.

    qafka keys list qafka keys create --name "iOS Production" --type PRODUCTION qafka keys revoke <keyId> qafka keys enable <keyId> qafka keys delete <keyId>

    revoke is reversible (apps using the key get 401 immediately). delete is permanent — reserve it for confirmed key compromise.

    For most cases the dashboard (Dashboard › API Keys) is friendlier — full restriction surface (platforms, bundle IDs, rate limits) in one place, with the plain key value shown once and a copy button.

    Self-Hosted / Staging

    Every command accepts --backend-url <url> to point at a non-production API root. The URL is also persisted into .qafka/config.json after init, so subsequent sync calls don’t need the flag.

    qafka init --backend-url https://qafka.staging.example.com
    Last updated on June 3, 2026
    Error HandlingDashboard

    MIT 2026 QAFKA