| name | cometchat |
| description | Entry-point for CometChat integration in any React, React Native, Angular, Android, Flutter, or iOS project — web (React/Next.js/React Router/Astro), React Native (Expo/bare), Angular (12-15), native Android (V6 stable, V5 legacy), Flutter (V6 stable, V5 legacy), and native iOS (V5 stable). Detects the framework, gathers requirements through an interactive conversation, and writes production-quality integration code. |
| license | MIT |
| metadata | {"author":"CometChat","version":"3.0.0","tags":"cometchat dispatcher entry react nextjs react-router astro expo react-native angular android flutter ios chat"} |
Ground truth: the cometchat detect --json output + the per-platform *-core skills this dispatcher routes to + the packages/registry catalogs. (Official docs linked below.) Verify symbols against the installed package/source before relying on them.
Use this skill when
The user wants to add CometChat to any kind of project. Trigger phrases:
/cometchat (or invoke the cometchat skill via your agent's mechanism — keyword "cometchat" or "integrate chat" works in most agents)
- "add cometchat", "integrate cometchat", "add chat to my app"
- "add messaging", "add chat ui", "add in-app chat"
- Customize an existing CometChat integration — "make the chat match my brand", "change the chat colors / theme", "style the message bubbles", "translate the chat / add a language", "change the notification (or call) sounds". These route through this dispatcher too: detect the framework, then load the family's
*-theming + *-customization (+ cometchat-i18n for localization) skills. If .cometchat/state.json already exists, jump to the Step 7 iteration menu's customization options instead of re-running Phase A.
This is the entry point for every framework. Do NOT invoke
framework-specific skills directly — this dispatcher detects the
framework first and routes to the right ones.
Supported frameworks:
| Family | Frameworks |
|---|
| Web | React (Vite/CRA), Next.js, React Router v6/v7, Astro |
| React Native | Expo (managed + Expo Router), bare RN CLI |
| Angular | Angular 17-21 (standalone components; @cometchat/chat-uikit-angular@5) |
| Android | V6 stable (Compose + Kotlin Views, chatuikit-{compose,kotlin}-android:6.x) / V5 legacy (Java + Kotlin Views, chat-uikit-android:5.x) |
| Flutter | V6 stable (Bloc-based, cometchat_chat_uikit:^6.0) / V5 legacy (GetX-based, cometchat_chat_uikit:^5.2) |
| iOS | V5 stable (Swift; SwiftUI + UIKit hosting; CometChatUIKitSwift:~> 5.1) |
The web family loads @cometchat/chat-uikit-react + @cometchat/chat-sdk-javascript. The RN family loads @cometchat/chat-uikit-react-native + @cometchat/chat-sdk-react-native. The Angular family loads @cometchat/chat-uikit-angular + @cometchat/chat-sdk-javascript. The Android family loads com.cometchat:chatuikit-{compose,kotlin}-android:6.x (V6 — stable, recommended) or com.cometchat:chat-uikit-android:5.x (V5 — legacy) from Maven Central. The Flutter family loads cometchat_chat_uikit:^6.0 (V6 — stable, recommended; calls fold into the same package) or cometchat_chat_uikit:^5.2 (V5 — legacy; pair with cometchat_calls_uikit:^5.0 for calls) from the Cloudsmith Dart pub-hosted registry. The dispatcher decides which set after Step 1's detection.
How v3 works
v3 skills are interactive and conversational. You don't just detect
the framework and dump code. You have a conversation with the developer
to understand their project, their use case, and exactly where chat
should go — THEN you write code that fits.
Pattern skills (loaded from your context as plain files — not via your agent's skill-loading mechanism):
cometchat-core (web) / cometchat-native-core (RN) / cometchat-angular-core (Angular) / cometchat-android-{v5,v6}-core (Android) / cometchat-flutter-{v5,v6}-core (Flutter) — init, login, provider chain, env vars, anti-patterns
cometchat-components (web) / cometchat-native-components (RN) / cometchat-angular-components (Angular) / cometchat-android-v5-components or cometchat-android-v6-{compose,kotlin}-components (Android) / cometchat-flutter-v6-components (V6 only — V5 splits into -conversations/-messages/-users-groups) — component catalog, props, composition
cometchat-placement (web) / cometchat-native-placement (RN) / cometchat-angular-placement (Angular) / cometchat-android-v5-placement or cometchat-android-v6-{compose,kotlin}-placement (Android) / cometchat-flutter-v6-placement (V6 only) — WHERE to put chat
- One per-framework skill (
cometchat-{react,nextjs,react-router,astro}-patterns, cometchat-native-{expo,bare}-patterns, cometchat-angular-patterns, the v5/v6 Android sub-tree, or the Flutter v5/v6 sub-tree) — framework-specific details
Key principle: ask, don't assume. Every piece of information you need
from the user should be asked explicitly. Don't guess the route path,
don't guess where the trigger button goes, don't guess the auth system.
Steps
Clarification contract (ALL coding agents) — read references/asking-questions.md. Everywhere the steps below say "ask the user", "render an AskUserQuestion", "MUST ask", or "NON-NEGOTIABLE prompt", they mean: follow that contract. AskUserQuestion is only Claude Code's name for it — on Codex / Cursor / Gemini CLI / Windsurf / any agent without that primitive, render the question as a numbered text list and WAIT for a real answer (never default, never infer; auto/approval mode does not authorize skipping). Map the answer by value/label, not ordinal position, accept a free-text fallback, and if running headless, STOP and emit the question rather than guessing. This contract governs every prompt in this skill and all skills it routes to.
Step 1 — Detect framework + map the project
If the CLI itself can't run — degrade, don't dead-end. Every step
below calls npx @cometchat/skills-cli …. That can fail to launch
(no network to fetch the package, npx/Node absent, a sandbox that
blocks process spawn or the npm registry) — distinct from the CLI
running and returning a JSON error (handled in the error-handling
section near the end). When a command can't launch at all:
detect → detect by hand. Everything the CLI's detector keys on
is in the files this step already tells you to read: package.json
deps (@cometchat/chat-uikit-react → web, …-react-native → RN,
…-angular → Angular), pubspec.yaml (Flutter), build.gradle
(Android), Podfile/Package.swift (iOS), plus the router/version
markers. Determine the framework + version from those and continue.
config show → treat as no saved config; read .cometchat/config.json
directly if it exists, else proceed greenfield and ask the questions.
- Action commands (
auth login, apply-feature, builder export,
verify) genuinely need the CLI. If one can't run, don't silently
skip it — tell the user it's unavailable, give the manual dashboard
equivalent (e.g. fetch creds from app.cometchat.com → Credentials;
toggle the extension in Extensions), and continue best-effort.
Never STOP the whole flow just because npx is unavailable — manual
detection + reading the project is a complete substitute for routing.
First, check if .cometchat/config.json exists:
npx @cometchat/skills-cli config show --json
If config exists with previous answers, tell the user:
"I see you've set up CometChat before. Using your saved config:
Framework: {framework}, App: {appId}, Intent: {intent}.
Want to continue with these, or start fresh?"
If no config, run detection:
npx @cometchat/skills-cli detect --json
The JSON output includes framework (one of reactjs, nextjs, react-router, astro, expo, react-native, angular, android, flutter, ios, or null), framework-specific fields (router, expo_mode, react_native_version, android_version, flutter_version, env_prefix), and a compatibility.supported flag. If supported is false, stop and surface the warnings.
If framework === null — greenfield with no project at all (ENG-35718)
A null framework means the working directory has no recognizable scaffold — no package.json, no pubspec.yaml, no AndroidManifest.xml, no Podfile, no Xcode project. The earlier behavior dumped the user into a vague "want me to create one?" with two unlabeled options. Replace it with a named-scaffold prompt so the user picks a target deterministically:
"I don't see a project here yet. Want me to create one? Pick the closest match — you can change details after:
- React (Vite) — fastest web demo;
npm create vite@latest cometchat-demo -- --template react-ts
- Next.js (App Router) — production-grade web with SSR;
npx create-next-app@latest cometchat-demo --typescript --app
- Expo (managed) — fastest mobile demo;
npx create-expo-app@latest cometchat-demo --template
- Other — tell me what framework you're using and I'll skip the scaffold step.
What sounds right?"
Render this as an AskUserQuestion with the same 4 options. After the user picks, run the scaffold command, cd into the new directory, then re-run detect so the rest of the dispatcher behaves as if framework had been detected normally. Do NOT proceed to Step 2 without a real framework — every step below assumes one exists.
If the user declines all four (chooses "Other" and then says "no, I just wanted to look around"), stop politely and tell them: "You can run /cometchat again from inside a React / Next.js / Expo / Angular / Android / iOS / Flutter project and I'll detect it automatically." Don't try to integrate into an empty directory — every subsequent step will fail.
Android — android_version is load-bearing. When framework === "android", the detect output includes android_version: "v5" | "v6" | null. The cohort selects which V5 or V6 pattern set to load — V6 (stable + recommended, chatuikit-{compose,kotlin}-android:6.x, Compose + Kotlin Views split) and V5 (legacy, chat-uikit-android:5.x, Java + Kotlin Views) are different SDKs with different APIs. Treat them as separate routing targets even though both live under --family android. V6 went GA 2026-05-25 (docs) — was beta in earlier /cometchat releases; the prompt + recommendation flipped from V5 → V6 in v4.3.0.
If android_version is null, the project is greenfield (no cometchat dep yet). Ask the user:
"Which CometChat Android UI Kit do you want to use? V6 is the latest stable SDK (recommended; Compose + Kotlin Views split, went GA 2026-05-25). V5 is the legacy SDK (Java + Kotlin Views — still supported but no new features)."
Save the choice into .cometchat/config.json under androidVersion:
npx @cometchat/skills-cli config save --android-version v5 --json
The CLI accepts --android-version v5 / v6 and --flutter-version v5 / v6 flags (added 2026-05-22 — F46 fix). Subsequent /cometchat runs read this from config and don't re-ask.
Flutter — flutter_version is load-bearing too. When framework === "flutter", the detect output includes flutter_version: "v5" | "v6" | null. V6 (stable + recommended) is Bloc-based (cometchat_chat_uikit:^6.0, calls folded into the same package). V5 (legacy) is GetX-based (cometchat_chat_uikit:^5.2; pair with cometchat_calls_uikit:^5.0 for calls). The two cohorts have different state-management primitives, different barrel exports, and different theme APIs — never mix them. Same --family flutter install ships both sets; routing picks the right one. V6 went GA 2026-05-25 (docs) at version 6.0.1 — was 6.0.0-beta2 in earlier /cometchat releases; the prompt + recommendation flipped from V5 → V6 in v4.3.0.
If flutter_version is null, ask the user:
"Which CometChat Flutter UI Kit do you want to use? V6 is the latest stable SDK (recommended; Bloc-based, calls folded into the same package, went GA 2026-05-25 at 6.0.1). V5 is the legacy SDK (GetX-based — still supported but no new features)."
Save the choice into .cometchat/config.json under flutter_version.
Then read the project yourself — this is critical.
For web frameworks (reactjs, nextjs, react-router, astro):
package.json — name, dependencies, scripts
- The source directory structure — list all directories under
src/ or app/
- Find the router:
createBrowserRouter, app/ directory, pages/, react-router.config.ts, astro.config.*
- Find the layout:
App.tsx, layout.tsx, root.tsx, Layout.astro
- Find the nav: components with "nav", "header", "sidebar" in name
- Find existing pages/routes: list them so you can reference them later
For React Native (expo, react-native):
package.json — name, RN version, all dependencies, scripts
- Entry file —
index.js or App.{tsx,jsx} or app/_layout.tsx (Expo Router)
- Navigation — look for
@react-navigation/native, @react-navigation/stack, @react-navigation/bottom-tabs, or expo-router
- Existing screens — list all files under
screens/, src/screens/, app/, or wherever routes live
- Existing nav structure — read the root navigator to see stack vs tab vs drawer layout
For Angular (angular):
package.json — name, @angular/core version (17-21 supported; the v5 kit's peer range is >=17 <22), all @angular/* deps
angular.json — workspace config; identify the project name + sourceRoot; the build.options.styles array (where the kit's css-variables.css is registered) + the assets glob
- Bootstrap — standalone:
src/main.ts (bootstrapApplication) + src/app/app.config.ts (providers). v5 is standalone-first — there is no NgModule/CUSTOM_ELEMENTS_SCHEMA. (A legacy NgModule app must migrate to standalone, or pin the v4 legacy kit — different skill.)
- Routing —
src/app/app.routes.ts (provideRouter / loadComponent); list all routes
- Layout —
src/app/app.component.{ts,html}; identify nav, sidebar, header components
- Existing pages/components — list under
src/app/pages/, src/app/components/, or wherever the project organizes them
- Environment files —
src/environments/environment.ts (and .prod.ts); credentials live here, NOT in .env
For Android (android):
settings.gradle (or .kts) — module list; usually :app plus optional library modules
- Root
build.gradle (or .kts) — top-level plugins, repositories, classpath versions
- App
build.gradle (or .kts) — dependencies (this is where the cometchat dep lives), Android plugin, applicationId, minSdk/targetSdk/compileSdk, buildConfigField entries
AndroidManifest.xml — root <application> class, permissions, <activity> entries, deep links
- Source dirs:
app/src/main/java/<pkg>/ (Java) and/or app/src/main/kotlin/<pkg>/ (Kotlin)
- For V6 stack split: presence of
androidx.compose.ui:ui or compose.material3 in deps signals Compose; otherwise Kotlin Views (XML layouts under app/src/main/res/layout/). Many V6 projects have both — ask the user which surface chat lands in.
gradle.properties and local.properties — credentials live here as cometchat.appId=... / cometchat.region=... / cometchat.authKey=..., exposed to code as BuildConfig fields via buildConfigField in the app build.gradle. NOT in .env.
Application class (e.g. MyApp extends android.app.Application) — init goes in onCreate(). Note the class FQN; you'll wire CometChatUIKit.init(this, settings, callback) here.
For Flutter (flutter):
pubspec.yaml — package name + Dart SDK constraint + Flutter SDK constraint + dependencies: (this is where cometchat_chat_uikit lives)
- For V5: a typical project has BOTH
cometchat_chat_uikit:^5.2 AND cometchat_calls_uikit:^5.0 if calls are needed. V6 folds calls into the single cometchat_chat_uikit:^6.0 package (no separate calls package; depends on cometchat_calls_sdk: >=5.0.2 <6.0.0 transitively).
lib/ — Dart source. The app entry is lib/main.dart (the void main() => runApp(...) site); init goes in main() or in a top-level Stateful/State.initState().
lib/<config>.dart (or similar) — credentials. There is NO Flutter .env convention — credentials are typically defined as const Dart values in a config file, OR injected at compile time via --dart-define=COMETCHAT_APP_ID=... flags read inside Dart with String.fromEnvironment. NOT a .env file at runtime.
android/app/build.gradle and ios/Runner/Info.plist — platform-specific config (FCM service registration, Push capabilities, microphone/camera Info.plist entries for calls). Flutter projects DO have these subdirs but they're configured Flutter-side; do not run the native skill flows.
- For V5 vs V6: V5 uses GetX (
get in deps); V6 uses flutter_bloc + equatable. Their controllers, observers, and theming are different — match the integration code to the detected cohort.
For iOS (ios):
<App>.xcodeproj and/or <App>.xcworkspace — Xcode project / workspace. The .xcworkspace is preferred when CocoaPods is in use (open it, NOT the .xcodeproj).
Podfile (CocoaPods) — pin pod 'CometChatUIKitSwift', '~> 5.1' and run pod install. Look for platform :ios, '13.0' (or higher) — V5 needs iOS 13+.
Package.swift (Swift Package Manager) — dependencies: block adds https://github.com/cometchat/cometchat-uikit-ios. Both SPM and CocoaPods are supported; check which the project uses.
- App entry —
<App>App.swift (SwiftUI) or AppDelegate.swift + SceneDelegate.swift (UIKit). Init goes in @main struct App.init() (SwiftUI) or application(_:didFinishLaunchingWithOptions:) (UIKit).
Info.plist — for calls add NSMicrophoneUsageDescription + NSCameraUsageDescription; for push add the APNs entitlement and a BackgroundModes entry with remote-notification (and voip for VoIP push).
- Credentials: NO
.env at runtime. Use a Secrets.swift const enum/struct (gitignored) or an *.xcconfig file with COMETCHAT_APP_ID = ... exposed as Build Settings. The skill teaches both.
- Mixed-stack apps (SwiftUI + UIKit): the kit ships UIKit
UIViewControllers and exposes them via UIViewControllerRepresentable for SwiftUI hosting. Identify which surface chat lands in (SwiftUI screen vs UIKit nav stack) and follow the matching pattern.
Store this mental map — you'll use it throughout the conversation.
Post-detect order of operations — run these gates IN THIS ORDER
The moment detect --json returns, run the following in order before
you write anything or commit to a plan. Each is detailed in its own
subsection below; this is the precedence when more than one applies:
- Pattern skills installed? (see "Pattern skills not installed? — CHECK THIS FIRST") — if the framework's pattern skills aren't loaded, install them and have the user re-run now, before anything else. A late discovery is the worst-timed interruption.
version_conflict gate (see "Version-conflict pre-flight") — STOP if the installed UI Kit major doesn't match what the skills target, or both V5+V6 are declared.
coexistence gate (see "Coexistence pre-flight") — STOP if a competing chat/calling SDK is present; carry the Firebase/SW advisories into the push journey.
- Step 1.5 "I see you" summary (below) — only once the gates pass, narrate what you found, then continue to Step 2.
Gates 1–3 are hard stops that can each send the user away to fix something; run them before the Step 1.5 summary so you don't narrate a plan you then have to retract. (The subsections appear below in roughly this order, but this list is the authoritative precedence.)
Then show the user what you found — Step 1.5 (the "I see you" moment)
This is the most important moment of the whole flow. After running detection + reading the project, narrate what you found in 3–5 specific, observation-grounded bullets BEFORE asking any question. The user should feel that you understand their project before deciding whether to trust you with it.
The shape (use it verbatim — the structure earns trust):
Taking a look at your project...
- {Framework} + {Build tool} {version} {with TypeScript / JavaScript / etc., as detected}
- {Router or nav state} — {one observation about how routing is set up, or "no router yet" for greenfield}
- {Auth system status} — {"NextAuth detected → I'll wire token-based login", or "no auth detected → I'll start with dev mode + a test user; you can upgrade later"}
- {Existing CometChat state} — {"existing
cometchat/ folder with X — I'll patch around it", or "fresh start — no prior CometChat code"}
- {One personal observation} — {something specific you noticed: "Tailwind classes throughout", "shadcn/ui components", "monorepo with apps/* and packages/*"}
Ready to set this up? I'll walk you through account setup, then ask where chat should live.
The rules for this moment:
- Be specific, not generic. "Vite + React 19 + TypeScript" beats "a React project." Read the actual versions from
package.json.
- Five bullets max. Beyond five, it stops feeling observational and starts feeling like a recital. Cut to what's interesting about the project.
- Lead with what's load-bearing. Framework + version, router, auth, existing CometChat, then one personal touch. The personal touch is what makes it land — show you actually looked.
- Skip the bullet if there's nothing to say. No router on greenfield → say "no router yet"; don't invent one. No auth → say "no auth detected"; don't list every package you didn't find.
- End with a single confident question. Not five questions. The flow continues into Step 2 (credentials) or Step 3 (placement) — let the next step ask.
Examples of good vs bad bullets:
| ✓ Good (specific, observational) | ✗ Bad (generic, unfounded) |
|---|
| "Vite + React 19 + TypeScript, Tailwind for styling" | "A React project with TypeScript" |
"React Router v7 detected (routes.ts + react-router.config.ts)" | "Some routing is configured" |
"NextAuth in auth.config.ts — I'll mint CometChat tokens server-side via your existing session cookie" | "Authentication is set up" |
"shadcn/ui detected (components/ui/*) — I'll use your existing Button + Dialog primitives in the chat trigger" | "Some UI components are present" |
"Monorepo: apps/web is your dashboard, apps/marketing is the public site — I'll integrate into apps/web" | "This is a monorepo" |
For greenfield projects (the test case):
Taking a look at your project...
- Vite + React 19 + TypeScript — fresh
cometchat-test-app scaffold
- No router yet — for the demo, chat will mount in
src/App.tsx directly; we can move it to a route later
- No auth system detected — I'll set you up in dev mode; we'll pick a test user (default
cometchat-uid-1 through uid-5) when I confirm credentials in the next step; production auth is a one-flag upgrade later
- Fresh start — no existing CometChat code to patch around
Ready to set this up? I'll get you a CometChat account first, then ask where chat should live.
This moment costs ~5 seconds of conversation but anchors the rest. Skip it and the user feels like they're talking to a script. Run it well and the rest of the flow feels effortless.
Compatibility baselines (the CLI enforces these):
- Web: react@<18 → upgrade required; nextjs@<13 → warning; astro@<4 → warning
- RN: react-native@<0.70 → upgrade required; expo@<49 → upgrade required
- Angular: @angular/core@<17 → upgrade required (v5 kit peer range is
>=17 <22); skill verified against v21 standalone
- Android V5: minSdk@<21 → upgrade required; minSdk@<24 → warning; AGP@<7.0 → warning
- Android V6: minSdk@<28 → upgrade required (V6 raised the floor from API 23 to API 28); Kotlin@<1.9 → warning; for Compose stack, Compose BOM@<2024.x → warning
- Flutter V5: Dart SDK <2.17 → upgrade required; Flutter <2.5 → warning; Android
minSdk 24 (Flutter platform default) when V5 is in use
- Flutter V6: Dart SDK <2.17 → upgrade required; Flutter <2.5 → warning; Android
minSdk 26 REQUIRED (cometchat_calls_sdk in V6 raised the floor)
- iOS V5: iOS deployment target <13 → upgrade required; Swift <5.0 → upgrade required; Xcode 15+ requires
ENABLE_USER_SCRIPT_SANDBOXING = NO in Build Settings (or the post_install Podfile hook)
Version-conflict pre-flight — STOP if detect reports version_conflict (P0-6)
(Gate 2 in the post-detect order above — runs after the pattern-skills check, before the coexistence gate and the Step 1.5 summary.)
The moment detect --json returns, check version_conflict. If it is non-null, STOP — do NOT write integration code yet. It fires when the project already has a CometChat UI Kit at a major version these skills don't target (e.g. chat-uikit-react v5 in a v6-targeted web project), or when BOTH a V5 and a V6 UI Kit are declared (Android/Flutter half-migrated / duplicate tree). Proceeding emits guidance for the wrong major → duplicate class / unresolved reference / won't-compile failures the user can't easily trace. Surface version_conflict.detail to the user and ask how to reconcile (upgrade the installed kit to the targeted major, or load the matching-version skills, or remove the cohort they're not integrating) before Step 2. Re-run detect after they fix it. This is a gate, not a warning.
Coexistence pre-flight — STOP if detect reports a competing chat SDK (P0-10)
(Gate 3 in the post-detect order above — runs after the version-conflict gate, before the Step 1.5 summary.)
Also check coexistence in the same detect --json output. It surfaces three collisions you must resolve before mounting CometChat:
coexistence.competing_sdks lists other chat / calling / push providers already in the project (Sendbird, Stream, Twilio, PubNub, TalkJS, Agora, OneSignal). If any entry has kind: "chat" or "calling", STOP — silently adding CometChat means two chat providers mounted in one tree, duplicate WebSocket connections, and a confusing double UI. Ask the user (use the clarification contract — references/asking-questions.md):
"I see {name} ({coordinate}) already in this project. How do you want to proceed?"
- Replace it — I'll integrate CometChat and leave the old SDK's code in place for you to remove (I won't delete it without confirmation).
- Run alongside — keep both, namespaced; chat stays on {name}, CometChat powers a new surface. (Heavier; two realtime stacks.)
- Switch fully — integrate CometChat now and you'll migrate {name}'s usages over later.
WAIT for the answer; route accordingly. Never auto-delete the competing SDK's files or deps — that's the user's call.
coexistence.existing_firebase: true — the project already wires Firebase. CometChat push is FCM-based, so on the -push journey you must reuse the same Firebase app (don't call initializeApp a second time) and merge rather than overwrite firebase-messaging-sw.js. Carry this fact into the push skill; it's an advisory, not a hard stop for chat-only integration.
coexistence.existing_service_workers — lists existing SW files at paths CometChat web push would write to. Before generating firebase-messaging-sw.js, confirm you won't clobber the app's offline/caching logic; merge the messaging handlers into the existing worker instead.
A competing chat/calling SDK is a gate (ask before proceeding). Firebase + service-worker entries are advisories you must honour on the push journey.
Pattern skills not installed? — CHECK THIS FIRST
(Gate 1 in the post-detect order above — the highest-precedence gate; run it before the version-conflict/coexistence gates and before the Step 1.5 summary, even though it appears last in this section for readability.)
Run this check the moment detect returns a framework — BEFORE the Step 1.5 "I see you" summary, BEFORE credentials (Step 2), BEFORE any AskUserQuestion, and BEFORE creating a Visual Builder. It is a gate, not a contingency. If the framework's pattern skills are missing, install them and tell the user to re-run right now. Do NOT lazily discover the gap later (e.g. when you try to read a pattern skill at code-gen) — agents don't hot-reload skills mid-session (see the re-run note below), so discovering it late forces the user to re-run after they've logged in, picked an app, and customized their Visual Builder. That's the worst-timed possible interruption. Catch it at second 5, not at the finish line.
The dispatcher routes to web pattern skills (cometchat-{core,components,placement,*-patterns}), RN pattern skills (cometchat-native-{core,components,placement,*-patterns}), or Angular pattern skills (cometchat-angular-{core,components,placement,patterns}) based on the detected framework. If the matching set isn't loaded — i.e. the user has only the dispatcher in .claude/skills/, OR they installed only @cometchat/skills (web) but the project is RN/Angular, OR vice versa — install the missing package yourself. Do NOT stop and ask the user to run the npx command manually — that turns a 0-step recovery into a 2-step recovery for no benefit.
To check whether the pattern skills are loaded, attempt to read cometchat-core/SKILL.md (web), cometchat-native-core/SKILL.md (RN), or cometchat-angular-core/SKILL.md (Angular) from your loaded skills context. If the read fails, the package isn't installed — run the installer.
If framework is expo or react-native AND cometchat-native-core is NOT loaded:
npx @cometchat/skills add --family native
If framework is reactjs, nextjs, react-router, or astro AND cometchat-core is NOT loaded:
npx @cometchat/skills add --family web
Important — --family <name> is non-negotiable. Without the family flag, npx @cometchat/skills add (default) installs only the cross-family base skills (cometchat dispatcher + i18n + a11y + calls dispatcher). It does NOT install cometchat-core, cometchat-react-patterns, cometchat-placement, etc. — the framework-specific skills with the actual integration patterns. Running base-only twice doesn't load the missing skills; only --family <name> does. Skipping the flag here causes the agent to write integration code from training memory instead of from the framework-specific skills, which produces real bugs (e.g. the minHeight: 0 flex-shrink trap in cometchat-react-patterns rule 6a — only in that skill).
If framework is angular AND cometchat-angular-core is NOT loaded:
npx @cometchat/skills add --family angular
If framework is android AND neither cometchat-android-v5-core nor cometchat-android-v6-core is loaded:
npx @cometchat/skills add --family android
This installs both V5 and V6 skill sets — the dispatcher's Step 4 routing picks the correct one based on android_version.
If framework is flutter AND neither cometchat-flutter-v5-core nor cometchat-flutter-v6-core is loaded:
npx @cometchat/skills add --family flutter
Same shape as Android — installs both V5 and V6 skill sets; routing picks the right one based on flutter_version.
If framework is ios AND cometchat-ios-core is NOT loaded:
npx @cometchat/skills add --family ios
iOS only ships V5 today (no V6 beta yet), so there's no version-detection step — the install command lands the single iOS skill set.
After the install completes, tell the user:
"I just installed the {family} pattern skills into your workspace. Please re-run /cometchat to continue — your config is saved in .cometchat/config.json and credentials in .env, so the next run picks up at code generation without re-asking the questions you've already answered."
Why re-run rather than continue in this session: Claude Code, Cursor, Cline, Kiro, and other modern AI coding agents snapshot the skill set at session start and do NOT hot-reload SKILL.md mid-session. Even though the install writes the new skill files into .claude/skills/ (or .cursor/skills/ / .kiro/skills/ / .agents/skills/ — your agent's skills dir), your in-context skill set is the snapshot. Continuing would mean writing code from training memory — which the skills explicitly forbid (this is the same failure mode that produced the UIKitSettingsBuilder and getLoggedinUser casing bugs at v2.2.0). The re-run is the only safe path.
Non-default IDE — pass --ide. The default installer target is claude (.claude/skills/). If the dispatcher's own SKILL.md was loaded from a different location, pass the matching flag:
| Dispatcher loaded from | Install command |
|---|
.claude/skills/cometchat/SKILL.md (default) | npx @cometchat/skills add |
.agents/skills/cometchat/SKILL.md (Replit / Cursor / Copilot / Cline / Codex) | npx @cometchat/skills add --ide replit |
.cursor/skills/cometchat/SKILL.md (legacy Cursor path) | npx @cometchat/skills add --ide cursor |
.kiro/skills/cometchat/SKILL.md | npx @cometchat/skills add --ide kiro |
You can usually infer the IDE from where the dispatcher itself was loaded. If uncertain, default to claude. (Same logic applies to @cometchat/skills-native for RN projects.)
Do NOT attempt to write web UI Kit code into an RN project (CSS imports + <a href> + document.* fail at runtime) or RN UI Kit code into a web project (react-native-gesture-handler, @gorhom/bottom-sheet, native bubble components have no browser equivalents). Likewise, never use the React UI Kit (@cometchat/chat-uikit-react) in an Angular project — it's a different package (@cometchat/chat-uikit-angular) with NgModule imports, kebab-case selectors, content-projection slots, and no React reconciler. And never mix V5 and V6 skill sets within Android or Flutter — the artifact coordinates, package names, state-management primitives (V5 GetX vs V6 Bloc on Flutter; V5 Java/Views vs V6 Compose+Kotlin on Android), and theme system differ; the skills target one cohort each.
Step 2 — Set up credentials (onboarding)
CRITICAL: All onboarding happens via CLI commands. NEVER send the user
to a browser or dashboard for credential copy-pasting. The CLI handles
signup, login, app creation, and credential writing — all from the
terminal — for every framework.
Hard rule — signup flows MUST ask, not infer
For Sign-Up paths specifically (Step 2b → 2b.5 → 2b.6 → 2c), the following structured prompts are NON-NEGOTIABLE and MUST be rendered via AskUserQuestion:
| Prompt | When | Skipped only if |
|---|
| Role (Step 2b.6) | After auth me if meta.role === null | auth me returned a non-null role |
| Intent (Step 2b.6) | After auth me if meta.intent === null | auth me returned a non-null intent |
| App name (Step 2c) | Before provision setup | NEVER — even if a suggested default exists, user must explicitly confirm |
| Region (Step 2c) | Before provision setup | last_app.region exists AND user accepted the "use same region?" confirmation |
| Industry (Step 2c) | Before provision setup | last_app.industry exists AND user accepted the "same industry?" confirmation |
| Product (Step 3.0 Tier 3) | After Step 2c if Tier 1 (auth me last_app.product) AND Tier 2 (natural-language inference from slash-command + prior turn) both returned null | Tier 1 OR Tier 2 resolved a non-null value |
Auto-mode classifiers do NOT authorize bypassing these prompts. "Allowed by auto mode classifier" applies to Bash/tool approvals — it does NOT apply to structured user prompts. The agent must surface each prompt regardless of approval mode. Rationalizations like "fresh test scaffold = just exploring, skip the prompt" or "moving forward without stops for the demo" are explicit spec violations — the dashboard's /select-product + /create-app + /choose-role + /choose-intent flows ask these in the browser; the CLI flow MUST ask them in the terminal for parity. Skipping them silently picks defaults that affect downstream Step 3a (intent → archetype) and Step 3b.1 (industry → recommendation upsells) — the user can't correct values they never saw asked.
If config has appId set, verify credentials are in .env and skip to Step 3.
Otherwise check:
npx @cometchat/skills-cli auth status --json
If status is "logged-in", skip to Step 2b.5 (fetch dashboard profile).
If status is "logged-out", ask:
Ask the user (preserve this exact shape — question, header, multiSelect, options[].label, options[].description — agents have varying primitive names but they all support this structured form):
- question: "Let's set up CometChat. Do you have an account?"
- header: "Account"
- multiSelect: false
- options:
- label: "Create a new account", description: "Free signup — I'll handle it right here, no browser needed."
- label: "Sign in to existing account", description: "Log in and pick one of your apps."
- label: "I'll paste credentials myself", description: "I already have my App ID, Region, and Auth Key."
Option 1 → Step 2b. Option 2 → Step 2a. Option 3 → Step 2d.
Step 2a — Sign in (existing account, browser flow)
npx @cometchat/skills-cli auth login
This command:
- Generates a short-lived session via the CLI auth API.
- Opens
https://app.cometchat.com/login?sessionId=<hex> in the user's default browser.
- Polls the auth API every 5 seconds for up to 15 minutes.
- When the user finishes signing in, the dashboard marks the session authenticated. The next poll receives the bearer token and stores it in the OS keychain.
- Prints
✓ Logged in as <email> (backend: keychain-macos).
Let the CLI block — do NOT background it, do NOT race it with other prompts.
Terminal error handling (surface verbatim, stop, do not retry silently):
ACCESS_DENIED — user clicked Deny in the dashboard.
EXPIRED — 15-minute window elapsed.
TIMEOUT — max polls exhausted before user authorized.
ABORTED — user Ctrl-C'd the CLI.
NETWORK — couldn't reach the auth host.
ALREADY_AUTHENTICATED — this session was already consumed. Re-run auth login to mint a fresh session.
AUTH_FAILED — the bearer was rejected (401) — e.g. a stored token expired or was revoked, surfaced by a later auth me / auth status. Re-run auth login to re-authenticate. (Distinct from the device-auth codes above: this is the post-login "my session went stale" case, normalized from a 401 in cometchat-api.ts.)
API_ERROR:<code> (and any unlisted code) — an unexpected server-side error; do NOT pattern-match it. Fall through to the generic handler: show the CLI's human_message / suggestion if present, then the raw error in parentheses (see the error-handling rule near the end of this skill).
After success, verify via auth status --json and proceed to Step 2b.5.
Step 2b — Sign up (new account, browser flow)
npx @cometchat/skills-cli auth signup
Same polling flow as Step 2a, but the CLI opens the signup URL. The browser handles email, name, password, verification email, role, industry. The CLI never sees any of those values.
No role / name / verification-code questions in the chat. The dashboard owns that flow now; skipping it keeps the user's password and verification code out of the transcript.
Error codes match Step 2a. After success, verify via auth status --json and proceed to Step 2b.5.
Step 2b.5 — Fetch the user's dashboard profile
The dashboard's signup flow (/auth/signup → /choose-role → /choose-intent) collects the user's name, email, role, and product intent during browser onboarding — none of those values touch the CLI. Now that the bearer token is in the keychain, fetch them via cometchat auth me so the rest of this skill can:
- Greet the user by name in subsequent steps
- Skip the placement-intent question (Step 3a) when
meta.intent === "exploring" (the user already told the dashboard they're just exploring)
- Tailor explanation depth by
meta.role (frontend developer → UI examples; engineering manager → architecture trade-offs)
npx @cometchat/skills-cli auth me --json
Response shape:
{
"status": "logged-in",
"email": "you@example.com",
"name": "Your Name",
"role": "frontend",
"other_role": null,
"intent": "building",
"last_app": {
"id": "27xxxxx",
"name": "my-marketplace-chat",
"region": "us",
"industry": "online_marketplaces",
"technology": "react",
"product": "chat-messaging"
}
}
Field meanings (from the dashboard's signup screens):
-
role: "frontend" / "backend" / "fullstack_engineer" / "startup_founder" / "product_leader/manager" / "engineering_leader/manager" / "others" (when others, other_role carries the freeform value)
-
intent: "building" / "evaluating" / "exploring" (this is the dashboard's intent — distinct from Step 3a's placement_intent which asks about app archetype)
-
last_app: most-recently-created app on the user's account (or null if they have none). The dashboard's /create-app and /select-product screens write industry, technology, product into the app's metadata — we surface them here so Step 2c can pre-fill region/industry instead of asking again.
-
last_app.product: the surface the user picked on the dashboard's /select-product screen — "chat-messaging" / "voice-video" / "ai-agent" / "byo-agent" / null. Load-bearing for Step 3.0 — it decides which dispatcher (chat or calls) handles the integration. chat-messaging runs the existing chat flow; voice-video hands off to cometchat-calls; ai-agent and byo-agent are out of scope for /cometchat today and point at the AI Agents docs.
Dashboard ↔ canonical mapping (load-bearing — wired in cometchat-api.ts:listApps):
Dashboard metadata.features | Canonical last_app.product |
|---|
chat | chat-messaging |
calling |
Store the response in working memory. Reference these fields downstream:
- Step 2c (new app): if
last_app is non-null, default --region and --industry to the values from last_app and only ask the user to confirm — do NOT re-prompt from scratch.
- Step 3.0 (product branch): route by
last_app.product. chat-messaging continues into Step 3a; voice-video short-circuits to the cometchat-calls dispatcher; ai-agent and byo-agent exit with a docs pointer.
- Step 3a: if
meta.intent === "exploring", skip the placement-intent question and route straight to the "Just exploring" branch (one route/screen with <CometChatConversations /> and cometchat-uid-1 pre-logged-in).
- Step 3b.1 (industry tailoring): use
last_app.industry to layer industry-specific upsells onto the placement recommendation — moderation features for dating apps, HIPAA-aware production patterns for healthcare, link-preview for marketplaces, etc. Augment, don't replace.
- Step 5 explanations: if
meta.role === "frontend", lead with concrete component composition + CSS examples; if meta.role === "engineering_leader/manager", lead with placement architecture trade-offs (where state lives, what gets cached, how routing fits the project's pattern).
- Greeting: if
meta.name is non-null, greet by name in any user-facing message during the rest of the flow ("Got it, {name} — let's pick an app").
Failure modes — each is non-blocking; degrade to the original generic flow:
status: "logged-out" → bearer expired between Step 2a/b and now (rare). Re-run auth login and retry.
status: "auth-required" → 401 from server. Same fix.
status: "error" → network or unexpected. Skip silently, proceed to Step 2b.6 (will ask role + intent there).
name is null → no greeting (don't ask, the dashboard will collect it on next browser visit).
role is null → ask in Step 2b.6 (this is the case for accounts that signed up via auth signup from the CLI — they never saw the dashboard's /choose-role screen).
intent is null → ask in Step 2b.6.
last_app.product is null (or last_app itself is null) → resolved in Step 3.0 via natural-language inference, falling back to a prompt only if ambiguous. Do NOT ask product upfront in Step 2b.6 — the dashboard's /select-product is a structured form because a UI can't read intent; the agent flow can.
last_app is null → ask region/industry normally in Step 2c.
After this step, proceed to Step 2b.6.
Step 2b.6 — Backfill role + intent if missing
The dashboard's /choose-role and /choose-intent screens collect two profile fields used to tailor the rest of the integration:
role shapes the depth and angle of code explanations in Step 5
intent decides whether Step 3a's placement question is needed (exploring short-circuits to a single screen)
(product is resolved separately in Step 3.0 via natural-language inference + lazy ask — not here.)
If auth me returned non-null values for both, skip this step entirely and go to Step 2c — the dashboard already collected them, do not re-ask.
If either is null (typical for accounts created via cometchat auth signup rather than the dashboard browser flow), ask the user. The options below mirror the dashboard exactly so anyone who later visits the dashboard sees consistent terminology.
If role === null — ask the user (preserve the structured shape — question/header/multiSelect/options[].label/options[].description — agents have varying primitive names but all support this form):
-
question: "What's your role? (We'll tailor explanations to match.)"
-
header: "Role"
-
multiSelect: false
-
options (label → store as role value):
| Label | role value |
|---|
| Frontend Developer | frontend |
| Backend Developer | backend |
| Fullstack Developer | fullstack_engineer |
| Founder | startup_founder |
| Product Manager | product_leader/manager |
| Engineering Manager | engineering_leader/manager |
| Other | others |
If the user picks Other, follow up with a free-form question for other_role (single short text field; store the freeform value in other_role).
If intent === null — ask the user (preserve the structured shape):
Do NOT ask for product in this step. The dashboard's /select-product is a structured form because a UI can't read intent; the CLI/agent flow is conversational and the user has often already said what they want. Asking upfront ("Chat & Messaging? Voice & Video? Both?") is friction the dashboard has but the agent doesn't need.
The product decision happens in Step 3.0 (Branch by product), where it actually matters for routing. Step 3.0 first tries to infer from the user's slash-command args and prior turn, and only falls back to a prompt if it can't tell. By then the agent has more context (the framework is detected, credentials are set up, the user has shared more about their goal), so inference works better than upfront.
This step (Step 2b.6) is for role + intent only — those are profile fields the dashboard collected upfront, so we collect them here too if they're missing.
Store the answers in working memory under the same keys (role, other_role, intent, product) as if they had come from auth me. Downstream steps (3.0 + 3a + 5) treat dashboard-supplied and CLI-collected values identically.
Note: These answers are not yet persisted back to the dashboard /me endpoint — they only live for the current session. A follow-up will add a cometchat auth me --update mode that PATCHes them server-side so the next session won't re-ask. Until then, the user may be asked again in a fresh session.
After this step, proceed to Step 2c.
Step 2c — Pick or create an app
Run this immediately — do NOT ask the user to go to any dashboard:
npx @cometchat/skills-cli provision list --json
If the user has existing apps, show them and ask which to use:
"I found these CometChat apps on your account:
- my-marketplace-chat (us) — Developer plan
- test-app (eu) — Developer plan
Which one should I use, or should I create a new one?"
For an existing app, fetch credentials and wire everything in one call. Pass --framework from Step 1 detection (one of reactjs, nextjs, react-router, astro, expo, react-native):
npx @cometchat/skills-cli provision setup \
--app-id "<selected-appId>" --framework "<framework>" --json
This creates/updates the env file with the correct prefix AND writes .cometchat/config.json in one step. Output is compact: { appId, region, framework, envFile, configPath } — no authKey echoed back.
If no apps exist (or user wants new), collect via structured AskUserQuestion prompts — NON-NEGOTIABLE. Do NOT silently default app name / region / industry on signup flows. The agent must surface each prompt and let the user pick. Auto-mode does NOT authorize bypassing these — they're load-bearing for the rest of the integration (industry drives Step 3b.1 recommendations downstream).
-
App name — SUGGEST <project-name>-chat from package.json name, then ask the user to confirm or override (preserve the structured shape):
- question: "What should we name your CometChat app?"
- header: "App name"
- Pre-filled default:
<project-name>-chat. User must explicitly accept or change before the CLI runs provision setup.
-
Region — if Step 2b.5 returned last_app.region, default to that value and ask only "Use the same region as your <last_app.name> app (<region>)? [Y/n]". Otherwise MUST ask via structured prompt:
- question: "Which region for your CometChat app?"
- header: "Region"
- options: US (recommended), EU, India
Region key mapping (CLI expects lowercase):
| Label | --region value |
|---|
| US | us |
| EU | eu |
| India | in |
-
Industry — if Step 2b.5 returned last_app.industry, default to that value and ask only "Same industry as your previous app (<industry>)? [Y/n]". Otherwise MUST ask via structured prompt:
- question: "What industry is your app in? (Drives industry-specific recommendations downstream.)"
- header: "Industry"
- options — full 11-option set (see table below); do NOT pick a default like
saas_businesses without asking
Industry key mapping — present these labels (verbatim from the dashboard's /create-app set, canonical in packages/cli/src/utils/onboarding-options.ts INDUSTRIES) and pass the mapped --industry value:
| Label | --industry value |
|---|
| Online Marketplaces | online_marketplaces |
| Healthcare | healthcare |
| Dating | dating |
| Events & Streaming | events_and_streaming |
| Online Education | online_education |
| Community & Social | community_and_social |
| SaaS & Multi-Tenant | saas_businesses |
| Sports & Gaming | sports_and_gaming |
| Team Comms & Workflows | team_comms_and_workflows |
| On-Demand Services | on_demand_services |
| Other | other |
Confirm before creating, then:
npx @cometchat/skills-cli provision setup \
--name "<name>" --region "<region>" --industry "<industry_key>" \
--framework "<framework>" --json
The authKey is written to the env file but is NOT echoed to stdout, so credentials don't appear multiple times in the transcript.
Tell the user: "Your CometChat account and app are ready. Credentials saved to <envFile>. Let's set up the integration."
Step 2d — Paste keys manually
Tell the user which env vars to set based on the detected framework:
| Framework | Env file | Variables |
|---|
| reactjs (Vite) | .env | VITE_COMETCHAT_APP_ID, VITE_COMETCHAT_REGION, VITE_COMETCHAT_AUTH_KEY |
| nextjs | .env.local | NEXT_PUBLIC_COMETCHAT_APP_ID, NEXT_PUBLIC_COMETCHAT_REGION, NEXT_PUBLIC_COMETCHAT_AUTH_KEY |
| react-router | .env | VITE_COMETCHAT_APP_ID, VITE_COMETCHAT_REGION, VITE_COMETCHAT_AUTH_KEY |
| astro | .env | PUBLIC_COMETCHAT_APP_ID, PUBLIC_COMETCHAT_REGION, PUBLIC_COMETCHAT_AUTH_KEY |
| expo (managed + Expo Router) | .env | EXPO_PUBLIC_COMETCHAT_APP_ID, EXPO_PUBLIC_COMETCHAT_REGION, EXPO_PUBLIC_COMETCHAT_AUTH_KEY |
| react-native (bare CLI) | .env | COMETCHAT_APP_ID, COMETCHAT_REGION, COMETCHAT_AUTH_KEY (paired with react-native-dotenv) |
| angular | src/environments/environment.ts | cometchat: { appId, region, authKey } (TypeScript object — Angular does NOT use .env) |
| android | local.properties (gitignored) → BuildConfig | cometchat.appId, cometchat.region, cometchat.authKey in local.properties; expose to code via buildConfigField "String", "COMETCHAT_APP_ID", "\"...\"" in app/build.gradle. Code reads BuildConfig.COMETCHAT_APP_ID. |
| flutter | lib/cometchat_config.dart (or --dart-define flags) | A const Dart class with , , . Add to so the file isn't committed. For CI, use and read via instead. Flutter does NOT load at runtime. |
"Grab your credentials from https://app.cometchat.com → Your App →
API & Auth Keys. Create the env file above and tell me when done."
Bare RN extra step. Bare RN doesn't ship a public-env-prefix convention — pair the env file with react-native-dotenv:
npm install --save-dev react-native-dotenv
and add the plugin to babel.config.js:
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: [["module:react-native-dotenv"]],
};
Then import { COMETCHAT_APP_ID, COMETCHAT_REGION, COMETCHAT_AUTH_KEY } from "@env"; in the provider.
iOS — write to Secrets.swift or an .xcconfig, NOT .env. iOS has no runtime .env lookup. The CLI's provision setup --framework ios writes a .env only as a credentials handoff. During Step 5, migrate values into one of:
Secrets.swift const enum (preferred for local dev):
// Secrets.swift (add to .gitignore)
enum Secrets {
static let cometchatAppID = "<APP_ID>"
static let cometchatRegion = "<REGION>"
static let cometchatAuthKey = "<AUTH_KEY>" // dev only
}
Use as: UIKitSettings().set(appID: Secrets.cometchatAppID).set(region: Secrets.cometchatRegion).set(authKey: Secrets.cometchatAuthKey).build().
.xcconfig Build Settings (preferred for CI; secrets stay out of source files):
// Secrets.xcconfig (add to .gitignore; reference from project Build Settings)
COMETCHAT_APP_ID = <APP_ID>
COMETCHAT_REGION = <REGION>
COMETCHAT_AUTH_KEY = <AUTH_KEY>
Expose as Info.plist entries ($(COMETCHAT_APP_ID) etc.) and read via Bundle.main.object(forInfoDictionaryKey: "COMETCHAT_APP_ID") as? String.
Never read .env at runtime in iOS. The handoff .env exists only so the integration agent can find the values during initial setup.
Flutter — write to a Dart const file, NOT .env. Flutter has no runtime .env lookup. The CLI's provision setup --framework flutter writes a .env only as a credentials handoff. During Step 5, migrate values into one of:
- Dart const file (preferred for local dev, simpler):
// lib/cometchat_config.dart (add to .gitignore)
class CometChatConfig {
static const String appId = '<APP_ID>';
static const String region = '<REGION>';
static const String authKey = '<AUTH_KEY>'; // dev only
}
Then in main.dart:
import 'cometchat_config.dart';
final settings = (UIKitSettingsBuilder()
..appId = CometChatConfig.appId
..region = CometChatConfig.region
..authKey = CometChatConfig.authKey
..subscriptionType = CometChatSubscriptionType.allUsers)
.build();
--dart-define flags (preferred for CI; secrets never hit the repo):
const appId = String.fromEnvironment('COMETCHAT_APP_ID');
const region = String.fromEnvironment('COMETCHAT_REGION');
const authKey = String.fromEnvironment('COMETCHAT_AUTH_KEY');
Run with flutter run --dart-define=COMETCHAT_APP_ID=... --dart-define=COMETCHAT_REGION=... --dart-define=COMETCHAT_AUTH_KEY=....
Never read .env at runtime in Flutter. The handoff .env exists only so the integration agent can find the values during initial setup.
Android — write to local.properties + expose via BuildConfig. Android has no runtime .env lookup; credentials are injected at compile time as BuildConfig fields. The CLI's provision setup --framework android writes a .env as a credentials handoff — you (the agent) must during Step 5 mirror those values into:
local.properties (project root, gitignored):
cometchat.appId=<APP_ID>
cometchat.region=<REGION>
cometchat.authKey=<AUTH_KEY> # dev only — omit / use auth tokens in production
app/build.gradle (or .kts) — read those properties and surface them via buildConfigField:
def localProps = new Properties()
def localPropsFile = rootProject.file('local.properties')
if (localPropsFile.exists()) localProps.load(new FileInputStream(localPropsFile))
android {
defaultConfig {
buildConfigField "String", "COMETCHAT_APP_ID", "\"${localProps['cometchat.appId'] ?: ''}\""
buildConfigField "String", "COMETCHAT_REGION", "\"${localProps['cometchat.region'] ?: ''}\""
buildConfigField "String", "COMETCHAT_AUTH_KEY", "\"${localProps['cometchat.authKey'] ?: ''}\""
}
buildFeatures { buildConfig true }
}
- Application code reads
BuildConfig.COMETCHAT_APP_ID etc. — never hardcoded strings, never read from .env at runtime.
Angular — write to src/environments/environment.ts, NOT .env. Angular bundles environment.ts into the build at compile time; there is no runtime .env lookup. Insert (or extend) the cometchat block:
// src/environments/environment.ts
export const environment = {
production: false,
cometchat: {
appId: "<APP_ID>",
region: "<REGION>",
authKey: "<AUTH_KEY>", // dev only — omit from environment.prod.ts
},
};
Then import { environment } from "../environments/environment"; and access environment.cometchat.appId in the init service. The CLI's provision setup --framework angular writes a .env as a credentials handoff — you (the agent) must migrate the values into environment.ts during Step 5. Never read .env from Angular runtime code.
After they confirm, verify:
npx @cometchat/skills-cli config init --json
Never log the Auth Key
After writing credentials, don't echo the Auth Key back in the transcript. Confirm as ✓ Wrote <PREFIX>COMETCHAT_AUTH_KEY (hidden).
Step 3 — Interactive requirements gathering
This is the core of v3. A multi-step conversation that gathers everything you need before writing a single line of code.
Step 3 sub-flow order (NON-NEGOTIABLE — run in this exact order)
- 3.0 — Branch by product (chat vs calls) — sets
product
- 3.1 — Customization preference (Visually vs In code) — sets
customize ← MUST run before 3a
- 3a — Intent ("What are you building?")
- 3b — Recommendation (skip on Visually path per §3.1)
- 3c — Placement
- 3d — Authentication
- 3e — User mapping (optional)
- 3f — Plan confirmation
Common skip-bug (observed 2026-05-22 in a real Android run): the agent
runs 3a + 3c after Step 2 (provision) and goes straight to writing code,
skipping 3.1 entirely. Step 3.1 must fire on EVERY chat-messaging
integration on a builder-supported platform (react, react-native, ios,
android, flutter — see §3.1 table). The only exception is Angular, which
auto-routes to In-code with the explicit fallback note.
The customer-visible symptom of the skip: agent never asks "How do you
want to customize your chat experience? Visually vs In code". Customer
gets the In-code path silently. Visual Builder feature is invisible to them.
3.0. Branch by product (chat vs calls)
product decides which dispatcher (chat or calls) handles the rest of Step 3. Resolve it in this priority order:
1. Use last_app.product if non-null — set by the dashboard's /select-product screen. Trust it.
2. Otherwise, infer from the user's slash-command args + prior turn. Most users name what they want; don't make them re-pick from a list:
| User said something like… | Set product to |
|---|
| "add chat", "integrate messaging", "build chat", "I want messaging" | chat-messaging |
| "add calling", "integrate voice/video", "add video calls", "set up voip" | voice-video |
| "add both chat and calls", "chat with calling on top", "messaging and calls" | chat-messaging+voice-video |
Confirm inferred values briefly so the user can redirect if you guessed wrong:
"Got it — setting up Chat & Messaging. (If you wanted calling instead, say so and I'll switch.)"
3. Only if you can't tell (plain /cometchat with no further context, ambiguous wording like "set up CometChat") — MUST ask via AskUserQuestion. NON-NEGOTIABLE. Do NOT silently default to chat-messaging or rationalize a shortcut (e.g. "fresh test scaffold → just exploring") to skip this prompt. Auto-mode does NOT authorize bypassing. The product decision drives Step 3a (chat) vs cometchat-calls dispatcher handoff — getting it wrong forces a re-run.
Once product is resolved, route — the rest of Step 3 is chat-shaped, and a calls-only integration shouldn't sit through it.
product value | Branch | What runs |
|---|
chat-messaging | Continue to 3a | Existing chat flow — placement intent, recommendation, framework skills, scaffold chat surfaces. |
voice-video | Hand off to cometchat-calls | Stop here. Invoke the cometchat-calls dispatcher with the framework + family + credentials already established. Calls dispatcher routes to the per-family cometchat-{family}-calls skill in standalone mode — no chat UI Kit, full calls surface, VoIP push wired by default, IncomingCall mounted at app root. |
chat-messaging+voice-video | Run 3a–6 (chat), THEN hand off to cometchat-calls in additive mode | Full chat flow first. After Step 6 verifies the chat integration, invoke cometchat-calls in additive mode — patches the existing provider to mount IncomingCall at root, adds call buttons inline on chat surfaces, opt-in VoIP push. Skip apply-feature calls — cometchat-calls covers it end-to-end. |
ai-agent / byo-agent | Offer chat-first scaffold + docs pointer (ENG-35724) | Don't dead-end. Tell the user: "AI Agents have their own integration path at https://www.cometchat.com/docs/ai-agents — but the agent UI lives inside a chat surface. Want me to set up the chat integration first? Once chat is wired, you can plug the AI Agent (or your BYO model) into the same cometchat-features flow without re-doing the integration." If yes, treat product = "chat-messaging" and continue to 3a. If no, exit politely (don't push). Two segments hit this dead-end and bounced out — give them a path forward without rebuilding the AI integration in this dispatcher today. |
If config has placement_intent set from a previous run, confirm it and skip to Step 3b. The product branch above still runs first — a chat-mode integration that's now adding calls re-enters via the chat-messaging+voice-video row, not the chat-only row.
Why this branch comes before 3a: Step 3a's options are all chat archetypes (Messaging app, Marketplace, SaaS, …). For a voice-video product, that taxonomy doesn't apply — the user isn't picking where chat lives, they're picking where the call trigger lives (profile button vs /calls route vs always-on lock-screen-ringer). The cometchat-calls dispatcher owns its own placement question with the right options for the calls surface.
3.1 — Customization preference (Visually vs In code)
Only runs if product includes chat-messaging (skip entirely for voice-video — the Visual Builder is chat-only today).
Only runs if the resolved framework maps to a builder-supported platform. Use this table — these are the same cohorts the dashboard's Visual Builder emits code for:
| Skills cohort/framework | Builder platform value | If Visually picked |
|---|
reactjs, nextjs, react-router, astro | react | Build with React UI Kit, mount via <CometChatApp /> |
expo, react-native | react-native | Build with RN UI Kit, mount in app root. cometchat builder export --platform react-native IS supported — it copies the builder's src/config/ (store.ts + config.json) and patches config.json with the JSON-envelope settings. |
android-v5, android-v6 | android | Build with Android UI Kit (V5 schema; V6 customers get a one-line migration shim) |
ios | ios | Build with iOS UI Kit |
flutter-v5, flutter-v6 | flutter | Build with Flutter UI Kit (V5 schema; V6 same shim story as Android) |
angular | not supported | See "Angular" note below — auto-route to In code with an explicit one-time message. |
Native Visually exports are V5-shaped today (F22 — transitional). The Android, Flutter (and iOS) Builder canonicals emit V5-flavored UI Kit screens; a V6 native project's own code is untouched, but the Builder-exported screens come in at the V5 schema (the "migration shim" noted above bridges them). If a V6 native customer wants a single-version (pure-V6) dependency tree, the In-code path is the cleaner choice. React/Next/Astro and React Native are unaffected (web is V6-native; RN uses the src/config/ store envelope, not a V5 canonical). The per-family cometchat-{android-v6,flutter-v6} skills carry the full warning — surface this at the choice so the decision is informed.
Angular — explicit fallback
The dashboard's Visual Builder export pipeline doesn't have an Angular emitter — https://preview.cometchat.com/downloads/cometchat-builder-{platform}.zip ships only react, react-native, ios, android, flutter. Skills can't bridge that gap by emitting Angular code from React/JSON output because the kit's Angular package (@cometchat/chat-uikit-angular) uses different selectors, modules, and content-projection slots than React.
For Angular projects:
-
Do NOT show the Visually-vs-In-code prompt. It's a dead choice — there's no Visually path to take.
-
Surface this once, then move on. Print a brief, friendly note in the chat — single message, no follow-up question:
"The Visual Builder doesn't ship Angular code yet (the dashboard's export covers React / React Native / iOS / Android / Flutter today). I'll set up the code-driven Angular integration instead — you can theme via CSS variables (--cometchat-*, scoped to .cometchat) later. Want to be notified when an Angular Visual Builder lands? Drop a 👍 on https://github.com/cometchat/cometchat-skills/discussions/categories/feature-requests."
Tweak wording to fit the conversation tone (greeting the user by name if meta.name is set, etc.) but keep it factual: WHY it's missing + WHAT we're doing instead + WHERE to register interest.
-
Set customize=code in .cometchat/config.json so subsequent /cometchat reruns skip the prompt silently. Run via Bash:
npx @cometchat/skills-cli config save --customize code --json
-
Continue to Step 3a (intent). The rest of the chat flow proceeds as normal — cometchat-angular-{core,components,placement,patterns} handle the actual code emission.
-
Stale customize=visual from a previous framework? If auth me/config carries customize=visual from a prior run on a different framework, OVERWRITE it to code for this Angular session — same config save call as above — and surface the same note. The dispatcher must never try to call builder create --platform angular (the CLI rejects it with "Missing or invalid --platform"; the customer would see a confusing CLI error instead of the explanatory note).
Next.js Pages Router — explicit fallback (F17)
Visual Builder is architecturally incompatible with Next.js Pages Router. The canonical's src/CometChat/ has 25+ per-component CSS imports, but Pages Router enforces "global CSS only in pages/_app.tsx" — Webpack fails the build on the first component import. App Router tolerates per-component CSS; Pages Router does not. There's no client-side workaround short of rewriting the canonical's styling architecture.
The CLI guards this — builder create --platform react will refuse when detect.router === "pages" with an explicit F17 message. The dispatcher must surface this BEFORE calling the CLI, same shape as the Angular fallback:
For Next.js Pages Router projects (detect.framework === "nextjs" AND detect.router === "pages"):
-
Do NOT show the Visually-vs-In-code prompt. Same dead choice as Angular.
-
Surface this once:
"Visual Builder isn't compatible with your Pages Router setup — the canonical's per-component CSS imports break Pages Router's strict 'global CSS in _app.tsx only' rule (F17). Two options: migrate to App Router (the Next.js 13+ default), or stay on Pages Router and I'll use the code-driven integration. Want to be notified when Pages Router compatibility lands? https://github.com/cometchat/cometchat-skills/discussions/categories/feature-requests"
-
Set customize=code in .cometchat/config.json:
npx @cometchat/skills-cli config save --customize code --json
-
Continue to Step 3a (intent). cometchat-nextjs-patterns §4 covers the Pages Router provider mount (_app.tsx with next/dynamic + ssr: false).
-
App Router projects (detect.router === "app") are fully supported by Visual Builder — proceed with the Visually-vs-In-code prompt as normal.
If config has customize set from a previous run AND the framework is in the builder-supported table above, confirm it ("Continuing the visual-builder flow you started last time — builder ID abc123. Re-fetch latest config? [Y/n]" for customize=visual; just "Continuing in-code customization." for customize=code) and skip the question. On Y for the re-fetch, jump straight to the builder fetch step below.
If .cometchat/builder.json already exists (customer started a builder in a prior /cometchat invocation but didn't finish), surface it before asking: "Found an existing builder for this project (abc123, created 3 days ago). Resume it, start fresh, or switch to code-driven?" — three options via AskUserQuestion. "Resume" jumps to the builder fetch step; "Start fresh" creates a new one; "Code-driven" sets customize=code and falls through to 3a.
Otherwise, ask:
AskUserQuestion:
- Question: "How do you want to customize your chat experience?"
- Header: "Customize"
- Options — which one is listed first and labeled "(Recommended)" depends on the framework:
- Default (web React / Next.js App Router / React Router v7 / Astro, React Native, Android, Flutter, iOS): present Visually first, labeled "(Recommended)". This is deliberate — the Visual Builder is the headline path. Do NOT reorder In-code ahead of it or mark In-code recommended, even though In-code is the historical default. (Frameworks that don't support the builder — Angular, Next.js Pages Router — skip this prompt entirely per the notes above, so they never reach here.)
- iOS: now a full default (no carve-out). ENG-35337 is RESOLVED end-to-end: the iOS Visual Builder export BUILDS and LAUNCHES —
pod 'CometChatBuilder' (1.1.2) + UIKitSwift 5.1.13 + CallsSDK 5.0.0 → xcodebuild BUILD SUCCEEDED, installed + launched clean on a physical iPhone (verified 2026-06-17; the earlier sim-only EXCLUDED_ARCHS quirk was an environment issue, not a project defect). Do NOT pin > 1.1.2 — a 1.1.3 may not exist (see cometchat-ios-core §Visual Builder). iOS gets the same Visually-recommended default as every other builder-supported platform.
- Visually — configure in the browser (no code) — "Open CometChat's UI Kit Builder in your browser to enable/disable features and configure colors, layout, and styling through settings — no code (it's a configuration tool, not a drag-and-drop canvas). Then come back here and I'll wire the result into your app."
- In code — code-driven defaults — "I'll scaffold a clean integration with sensible defaults. You customize later by editing files / CSS variables."
Persist the answer to .cometchat/config.json as customize: "visual" | "code" so reruns don't re-ask.
If the customer picks "In code": continue to Step 3a as before. Nothing else in Step 3 changes for the In-code path.
If the customer picks "Visually": run the visual-builder sub-flow below, then resume the rest of Step 3 (3a intent and 3c placement still run — placement asks where the single <CometChatApp /> mount goes; the visually-customized features replace what 3b would otherwise recommend).
3.1.v — Visually sub-flow
-
Create the builder. Run via Bash:
cometchat builder create \
--app-id <appId-from-config> \
--platform <react|react-native|ios|android|flutter from the table above> \
--json
Parse the JSON. On success, you get { status: "created", builderId, builderUrl, appId, platform, createdAt }. Cache file .cometchat/builder.json is written automatically by the CLI.
Error handling:
Not logged in → run cometchat auth login (or hand back to Step 1 auth) and retry.
AUTH_SCOPE (token lacks vcb:write) → tell the customer: "Your CometChat account doesn't have the Visual Builder add-on enabled — falling back to code-driven customization." Set customize=code and continue to Step 3a.
- Any other API error → print the error, set
customize=code (graceful fallback), continue to 3a.
-
Open the builder URL in the customer's default browser. Try the OS-appropriate command via Bash:
- macOS:
open "<builderUrl>"
- Linux:
xdg-open "<builderUrl>" (suppress stderr — many SSH-only / headless boxes don't have a display)
- Windows:
start "" "<builderUrl>"
If the open command exits non-zero (headless, no display, locked down), don't treat it as a failure — print the URL prominently with: "Couldn't open your browser automatically. Open this URL on a machine where you can use a browser: <builderUrl>"
Either way, print the URL itself in the terminal so the customer can copy/paste if the auto-open failed silently.
Tell the customer (F31, verified 2026-05-22 via headless Playwright): "If you're not already logged into app.cometchat.com in your browser, you'll see the dashboard signup/login screen first. Log in with the SAME account you used to run cometchat auth login (typically your work email) — the dashboard will then redirect you to the Visual Builder UI for this specific builder ID." Without this heads-up, customers seeing the signup screen think skills opened the wrong URL.
-
Wait for the customer. Customizing in the Visual Builder typically takes minutes to hours — the customer may walk away. Use AskUserQuestion to block:
- Question: "When you're done customizing in the browser, let me know."