# Crow AI Copilot Integration Skill

The user's message will contain their API key and product ID. Parse them:
- `CROW_API_KEY` — after "My API key is:"
- `PRODUCT_ID` — after "My product ID is:"
- `CROW_API_URL` — always `https://api.usecrow.ai`
- `CROW_DASHBOARD_URL` — always `https://app.usecrow.ai` (use this for all dashboard links)

If the product ID is missing, resolve it from the API key:
```bash
curl -s -H "X-Service-Key: $CROW_API_KEY" "$CROW_API_URL/api/products/resolve-key" | jq -r '.product_id'
```

If both are missing, ask the user. They can find them at app.usecrow.ai.

**Dashboard links:** The Crow dashboard puts all configuration on a single page at `$CROW_DASHBOARD_URL/configure` with tabs (Agent, Tools, Widget, Knowledge, Deploy) and subtabs. Conversations live at `$CROW_DASHBOARD_URL/conversations`. When telling the user where to check something, always output a **clickable markdown link** to the right URL, plus which tab/subtab to select. Example: "See it at [your Crow dashboard](https://app.usecrow.ai/configure) — **Tools** tab → **Client Side**"

## Before Starting

If you need to look up Crow API details beyond what's in this guide, fetch documentation from https://docs.usecrow.ai/.

---

## Step 0: Detect Mode

**Do this FIRST before anything else.**

Search the codebase for: `crow-widget`, `usecrow`, `CrowSetup`, `window.crow`, `crow-token`, `CROW_ENABLED`

### If NOT found → Fresh Install Mode

Crow is not integrated yet. Follow the full flow:
1. Explore the codebase (Phase 1)
2. Propose the integration (Phase 2) — then STOP and wait for "yes"
3. Implement step by step (Phase 3) — STOP between each step
4. Configure backend (Phase 4) — STOP after each curl
5. Summary (Phase 5)

### If found → Modify Mode

Crow is already integrated. Tell the user what you found:

> "I see Crow is already integrated in your app. I found `[file]` with the widget setup, `[N]` registered tools, and context sending.
>
> What would you like to do?
> 1. **Add or modify a client-side tool**
> 2. **Add server-side tools** (connect your API via OpenAPI)
> 3. **Update the system prompt**
> 4. **Change context fields** (what the copilot knows about the current page)
> 5. **Update suggestions or welcome message**
> 6. **Change widget appearance**
> 7. **Something else** — describe what you need"

**Wait for the user to pick an option.** Then jump to ONLY the relevant section — do NOT re-run the full integration. For each option:

| Option | What to do |
|--------|-----------|
| 1. Add/modify tool | Read existing `registerTools` in CrowSetup, add the new tool function. Then upload via `POST /client-tools` (re-upload the FULL list including existing tools). Enable it via `PUT /selected-tools`. |
| 2. Server-side tools | Read the app's backend routes. Build an OpenAPI spec. Upload via `POST /openapi`. Configure connection via `PUT /connection`. Enable tools. |
| 3. System prompt | Ask what they want changed. `POST /system-prompt` (overwrites). |
| 4. Context fields | Read existing `setContext` call in CrowSetup. Add/modify fields. No API call needed. |
| 5. Suggestions/welcome | Ask what they want. `PUT /welcome-message` and/or `PUT /initial-suggestions`. |
| 6. Widget appearance | Ask about colors/style. `PUT /widget-styles`. |
| 7. Something else | Ask clarifying questions, then do the minimal change. |

**After each change**, tell the user what you did and link to the relevant dashboard page to verify. Use the Phase 3.5 check to see current config before making changes.

---

## MANDATORY: How to Work

**STOP. Read this before doing anything.**

You MUST work incrementally. Do NOT generate all the code at once. Do NOT run all the curl commands in one go. Do NOT skip asking the user questions.

**The rule: ONE step at a time, then STOP and wait for the user.**

**Fresh Install flow:**
1. Explore the codebase (Phase 1) — then STOP and propose (Phase 2)
2. Present your plan — then STOP and wait for "yes"
3. Add ONLY the widget script — then STOP and say "check if the bubble appears"
4. Add identity — then STOP and say "send a message, check conversations"
5. Add tools + context — then STOP and say "try the tools"
6. Configure backend — then STOP after each curl and say where to check it

**Modify flow:**
1. Detect existing integration — then STOP and present options
2. User picks an option — then STOP and confirm what you'll change
3. Make the change — then STOP and tell the user where to verify

**If you skip ahead without user confirmation, you are doing it wrong.**

If something isn't working, tell the user to email jai@usecrow.ai for help.

---

## Goal

Integrate the Crow AI copilot widget into any web application. The integration must:
- Be **100% additive** — never modify existing logic, only add new files and one mount point
- Be **feature-flagged** — a single env var disables it with zero runtime impact
- Be **context-aware** — subscribe to app state and send relevant context to the agent
- Work for **any framework** — React/Next.js, Vue, Angular, Svelte, vanilla JS, Rails, Django, Laravel, etc.

---

## Phase 1: Explore & Detect (Fresh Install Only)

Read these files to understand the app before writing anything:

1. **`package.json` / `Gemfile` / `requirements.txt` / `composer.json`** — detect framework and state libraries
2. **Directory structure** — find `src/`, `app/`, `pages/`, `components/`, `views/`, `stores/`, `reducers/`
3. **Main layout file** — the single template/component that wraps every page (e.g., `_app.tsx`, `App.vue`, `application.html.erb`, `base.html`, `_layout.html.twig`)
4. **Auth pattern** — how is the current user's token/session available? (localStorage, cookie, Zustand store, Vuex, Redux, server-rendered meta tag, etc.)
5. **State management** — Zustand, Redux, Pinia, Vuex, MobX, Context, custom stores, or none
6. **Routing** — Next.js, React Router, Vue Router, Rails routes, Django URLs, etc.
7. **Key pages / routes** — what are the 2-3 most important user-facing pages? What state is on each?
8. **Key user actions** — what are the most impactful things a user does? (create, search, export, enrich, filter, etc.)
9. **Existing env var pattern** — how are env vars named and accessed? (`NEXT_PUBLIC_*`, `VITE_*`, `VUE_APP_*`, `REACT_APP_*`, `ENV[...]`, `os.environ`, etc.)
10. **Backend API routes** — what REST endpoints exist? These may become server-side tools.

Based on this, choose an **integration strategy**:

| App Type | Strategy |
|---|---|
| React (Next.js Pages Router) | `CrowSetup.tsx` component + mount in `_app.tsx` |
| React (Next.js App Router) | `CrowSetup.tsx` with `'use client'` + mount in root `layout.tsx` |
| React (Vite/CRA) | `CrowSetup.tsx` + mount in `App.tsx` |
| Vue 2/3 | `crow-setup.js` plugin + register in `main.js` + `CrowSetup.vue` component |
| Angular | `crow.service.ts` + add to `AppComponent` |
| Svelte | `CrowSetup.svelte` + import in root `+layout.svelte` or `App.svelte` |
| Vanilla JS / jQuery | `crow-integration.js` script + add `<script>` tag to base HTML |
| Rails / ERB | `crow_integration.js` in `app/javascript/` + `<%= javascript_include_tag %>` in `application.html.erb` |
| Django / Jinja2 | `crow_integration.js` in `static/js/` + `{% block scripts %}` in `base.html` |
| Laravel / Blade | `crow-integration.js` + `@stack('scripts')` in `layouts/app.blade.php` |
| Any server-rendered | Script tag in base layout + `crow-integration.js` |

---

## Phase 2: Propose the Integration

Based on what you found in Phase 1, **come back with a concrete recommendation** — don't just ask open-ended questions. The user should be able to say "yes" or tweak specifics, not do homework.

Present your findings and proposal in this format:

```
## Here's what I found:

- **Framework:** [React/Vite, Next.js, Vue, etc.] with [N] pages: [page names]
- **Backend:** [Express/Django/Rails/etc.] with [N] API endpoints at [base path]
- **OpenAPI spec:** [found at /openapi.json | not found — I'll generate one]
- **Auth:** [JWT/session/cookie/none] — [how you'll handle identity]
- **State:** [Zustand/Redux/Context/server-fetched] — [what context to send]

## Here's what I'd set up:

**Server-side tools** (the copilot calls your real API):
[For each backend endpoint that would be useful as a tool, list it with a one-line description of what the copilot would use it for. Prefer server-side for data operations.]

- `operationId` — what it does and when the copilot would use it

**Client-side tools** (the copilot controls the UI):
[For each UI action that would be useful — navigation, filtering, selecting items, opening modals — list it.]

- `toolName` — what it does

**Context** (what the copilot knows about the current state):
- [field] — [what it tells the copilot]

**System prompt idea:**
"[Draft a 2-3 sentence system prompt describing the app, the copilot's role, and what it can do.]"

**Suggested actions** (what users see as quick-start buttons):
- "[button label]" → "[message that triggers a tool]"
- "[button label]" → "[message that triggers a tool]"
- "[button label]" → "[message that triggers a tool]"

**Files I'll create:**
- `<path>/CrowSetup.tsx` — main integration component
- `<path>/api/crow-token/route.ts` — identity token endpoint (if auth exists)

**Files I'll modify:**
- `<path>/App.tsx` (or equivalent) — add one mount line

**Env vars to add:**
- `CROW_ENABLED=true`
- `CROW_VERIFICATION_SECRET=<secret>`

Want me to proceed, or change anything?
```

**Guidelines for good proposals:**
- **Prefer server-side tools** for anything that reads/writes data. The copilot calling real API endpoints is the "wow" moment — not client-side state manipulation.
- **Client-side tools** are for UI actions only: navigation, filtering, opening detail views, applying selections.
- **Suggestions must map to real tools.** Every suggested action button should trigger something the agent can actually do with the tools you're proposing.
- **System prompt should reference the tools by name** so the LLM knows what's available.
- **Context should be minimal but useful** — current page, current item being viewed, active filters. Don't dump the entire app state.

**WAIT for the user to confirm before continuing.** Do not write code or call the Crow API until they say yes. If they want changes, adjust the plan first.

---

## Phase 3: Generate the Integration (Step by Step)

**Do this in stages, not all at once:**

### Step 1: Get the widget showing

First, ONLY add the widget script tag and mount point. No tools, no context, no identity yet — just get the bubble visible on the page. Tell the user:

> "I've added the Crow widget to your app. Start your dev server and you should see a chat bubble in the bottom-right corner. Confirm it's showing up and I'll set up tools next."

**Wait for confirmation before continuing.**

### Step 2: Add identity

Once the widget is visible, add the identity token endpoint and `setIdentityTokenFetcher`. Tell the user:

> "Identity is set up. Open the widget and send a message — then check [$CROW_DASHBOARD_URL/conversations]($CROW_DASHBOARD_URL/conversations) to see your user show up with their name/email."

**Wait for confirmation before continuing.**

### Step 3: Add tools and context

Now add `registerTools` and `setContext`. Tell the user which tools you're adding and what they do. Then configure the backend (Phase 4).

### Step 4: Configure the backend

Run the Phase 4 curl commands. After each one, tell the user what you did and where to see it.

### Step 5: Test end-to-end

Ask the user to try each tool through the widget and confirm it works.

Below are the detailed implementation patterns for each step.

### A. Feature Flag Pattern (per framework)

| Framework | Flag Env Var | Access Pattern |
|---|---|---|
| Next.js | `NEXT_PUBLIC_CROW_ENABLED` | `process.env.NEXT_PUBLIC_CROW_ENABLED === 'true'` |
| Vite/Vue/Svelte | `VITE_CROW_ENABLED` | `import.meta.env.VITE_CROW_ENABLED === 'true'` |
| CRA | `REACT_APP_CROW_ENABLED` | `process.env.REACT_APP_CROW_ENABLED === 'true'` |
| Rails | `CROW_ENABLED` | `ENV['CROW_ENABLED'] == 'true'` in view helper |
| Django | `CROW_ENABLED` | `settings.CROW_ENABLED` passed to template context |
| Laravel | `CROW_ENABLED` | `env('CROW_ENABLED')` in Blade |
| Vanilla JS | `window.CROW_ENABLED` | Set by server-rendered meta tag or inline script |

### B. The CrowSetup Component / Module

This is the heart of the integration. It must:

1. **Load the Crow widget script** and **wait for it to be ready** before calling `window.crow()`
2. **Identify the user** — call `window.crow('setIdentityTokenFetcher', ...)` once auth is ready
3. **Send context** — subscribe to app state and call `window.crow('setContext', {...})` on changes
4. **Set greeting + suggested actions** — route/state-aware welcome messages and CTAs
5. **Register client tools** — functions the agent can call on behalf of the user
6. **Auto-open triggers** — open the widget at key moments (e.g., after search, on first visit)

**CRITICAL: `window.crow()` is only available AFTER the widget script loads.** All calls to `window.crow()` must happen inside a callback that fires after the script is ready. In React, use a `useEffect` that polls for `window.crow` or listens for the script's load event. In vanilla JS, put everything inside `script.onload`.

#### For React apps — generate `CrowSetup.tsx`:

```tsx
// Guard: if flag is off, return null immediately inside the exported function.
// NEVER put `export` inside an `if` block — it's invalid syntax.
export function CrowSetup(props: CrowSetupProps) {
  if (process.env.NEXT_PUBLIC_CROW_ENABLED !== 'true') return null;
  return <CrowSetupInner {...props} />;
}

// All hooks live in the inner component so the early return above doesn't
// violate React's Rules of Hooks.
function CrowSetupInner({ /* destructure props */ }: CrowSetupProps) {
  // 1. Load the script and wait for window.crow to be available
  const [crowReady, setCrowReady] = useState(false);

  useEffect(() => {
    // If already loaded (e.g., hot reload), mark ready immediately
    if (window.crow) { setCrowReady(true); return; }

    const script = document.createElement('script');
    script.src = `${CROW_API_URL}/static/crow-widget.js`;
    script.dataset.productId = PRODUCT_ID;
    script.dataset.apiUrl = CROW_API_URL;
    script.onload = () => setCrowReady(true);
    document.body.appendChild(script);

    return () => { document.body.removeChild(script); };
  }, []);

  // 2. Register tools, set context, etc. ONLY when crow is ready
  useEffect(() => {
    if (!crowReady) return;
    // All window.crow() calls go here — see sections C, D, F for details
    window.crow('registerTools', { toolName: async (params) => { /* ... */ } });
    window.crow('setIdentityTokenFetcher', async () => { /* ... */ });
    window.crow('setContext', { currentRoute: window.location.pathname });
  }, [crowReady]);
}
```

Mount point (single line added to `_app.tsx` / `App.tsx` / `layout.tsx`):
```tsx
{process.env.NEXT_PUBLIC_CROW_ENABLED === 'true' && <CrowSetup />}
```

For App Router (Next.js 13+), wrap in a dynamic import with `ssr: false`:
```tsx
const CrowSetup = dynamic(() => import('./CrowSetup'), { ssr: false });
```

#### For script-tag apps — generate `crow-integration.js`:

```js
// crow-integration.js
// Feature flag: set window.CROW_ENABLED = true before loading this script,
// or gate the <script> tag server-side (e.g., if ENV['CROW_ENABLED'])
if (!window.CROW_ENABLED) { /* exit */ }

(function() {
  const CROW_PRODUCT_ID = '<PRODUCT_ID>';
  const CROW_API_URL = 'https://api.usecrow.ai';

  // 1. Load the widget script
  var script = document.createElement('script');
  script.src = CROW_API_URL + '/static/crow-widget.js';
  script.dataset.productId = CROW_PRODUCT_ID;
  script.dataset.apiUrl = CROW_API_URL;
  document.body.appendChild(script);

  // 2. Identity — adapt to the app's auth pattern
  script.onload = function() {
    window.crow('setIdentityTokenFetcher', async function() {
      // Return the user's Crow JWT from /api/crow-token
    });

    // 3. Context
    window.crow('setContext', {
      currentPage: window.location.pathname,
    });

    // 4. Client tools
    window.crow('registerTools', {
      // Tool implementations go here
    });
  };
})();
```

### C. Identity Integration

Crow requires a JWT signed with HS256 using the product's `verification_secret` to identify users. **You cannot pass the customer's raw auth token** — Crow can't verify it.

**ALWAYS create a `/api/crow-token` endpoint** in the customer's backend.

#### Step 1: Get the verification_secret

```bash
curl -s -H "X-Service-Key: $CROW_API_KEY" \
  "$CROW_API_URL/api/products/$PRODUCT_ID" | jq -r '.verification_secret'
```

Add it to the customer's backend env as `CROW_VERIFICATION_SECRET`.

#### Step 2: Add `/api/crow-token` route to customer's backend

The route validates the user's existing auth session, then mints a Crow JWT:

**Next.js (App Router):**
```typescript
// app/api/crow-token/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { SignJWT } from 'jose';

const CROW_VERIFICATION_SECRET = process.env.CROW_VERIFICATION_SECRET;

export const GET = async (req: NextRequest) => {
  // Use your existing auth middleware to get the user
  const user = await getAuthenticatedUser(req); // adapt to your auth
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

  if (!CROW_VERIFICATION_SECRET) {
    return NextResponse.json({ error: 'CROW_VERIFICATION_SECRET not configured' }, { status: 500 });
  }
  const secret = new TextEncoder().encode(CROW_VERIFICATION_SECRET);
  const token = await new SignJWT({
    user_id: user.id,
    email: user.email || undefined,
  })
    .setProtectedHeader({ alg: 'HS256' })
    .setExpirationTime('1h')
    .sign(secret);
  return NextResponse.json({ token });
};
```

**Express / generic Node.js:**
```typescript
import jwt from 'jsonwebtoken';
app.get('/api/crow-token', authMiddleware, (req, res) => {
  const token = jwt.sign(
    { user_id: req.user.id, email: req.user.email },
    process.env.CROW_VERIFICATION_SECRET,
    { algorithm: 'HS256', expiresIn: '1h' }
  );
  res.json({ token });
});
```

**Rails:**
```ruby
# app/controllers/api/crow_token_controller.rb
class Api::CrowTokenController < ApplicationController
  before_action :authenticate_user!
  def show
    payload = { user_id: current_user.id, email: current_user.email, exp: 1.hour.from_now.to_i }
    token = JWT.encode(payload, ENV['CROW_VERIFICATION_SECRET'], 'HS256')
    render json: { token: token }
  end
end
```

**Django:**
```python
import jwt, time
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse

@login_required
def crow_token(request):
    payload = {"user_id": str(request.user.id), "email": request.user.email, "exp": int(time.time()) + 3600}
    token = jwt.encode(payload, settings.CROW_VERIFICATION_SECRET, algorithm="HS256")
    return JsonResponse({"token": token})
```

#### Step 3: Update `setIdentityTokenFetcher` to call the endpoint

```tsx
window.crow('setIdentityTokenFetcher', async () => {
  const authToken = await getAuthToken(); // customer's existing auth token getter
  if (!authToken) return '';
  try {
    const res = await fetch('/api/crow-token', {
      headers: { Authorization: `Bearer ${authToken}` },
    });
    if (!res.ok) return authToken; // fallback — identity won't work but tools still do
    const { token } = await res.json();
    return token;
  } catch {
    return authToken;
  }
});
```

### D. Context Fields to Send

Based on the app's stores/state, determine which fields are most useful:

```js
window.crow('setContext', {
  // Universal
  currentRoute: router.pathname,

  // User state
  userId: user?.id,
  userPlan: user?.plan,

  // App-specific (examples)
  selectedItemId: store.selectedItem?.id,
  searchQuery: store.searchQuery,
  resultCount: store.results?.length,
  hasData: store.items?.length > 0,
});
```

**IMPORTANT: Gate derived context fields on the user's actual action, not on data presence.** Many apps pre-load data on mount — if you send `resultCount: results.length` unconditionally, the agent will think the user searched when they haven't:

```js
// BAD: pre-loaded data makes agent think user searched
resultCount: results?.length ?? 0,

// GOOD: only send count when user actually triggered a search
resultCount: searchQuery ? (results?.length ?? 0) : 0,
```

### E. Context-Aware Greeting + Suggested Actions

Generate route-aware greeting and CTAs:

```js
const updateGreetingAndActions = () => {
  const route = getCurrentRoute();

  if (route.matches('/dashboard')) {
    window.crow('setGreeting', 'What would you like to do today?');
    window.crow('setSuggestedActions', [
      { label: 'View reports', message: 'Show me my reports' },
    ]);
  } else if (route.matches('/search') && hasResults) {
    window.crow('setGreeting', `Found ${resultCount} results.`);
    window.crow('setSuggestedActions', [
      { label: 'Save results', message: 'Save these results' },
    ]);
  }
};
```

**WARNING: `setSuggestedActions` overrides whatever the LLM set** — use a ref guard to call it only once per state transition:

```tsx
const hasSetActionsRef = useRef(false);

useEffect(() => {
  const unsubscribe = store.subscribe((state, prev) => {
    if (!prev.results && !!state.results) {
      hasSetActionsRef.current = false; // allow one more update
    }
  });
  return unsubscribe;
}, []);

const updateActions = () => {
  if (hasSetActionsRef.current) return;
  hasSetActionsRef.current = true;
  window.crow('setSuggestedActions', [...]);
};
```

### F. Client Tools

For each key user action identified in Phase 1, generate a tool.

**CRITICAL FORMAT: `registerTools` takes `{ toolName: asyncFunction }` — the value MUST be a function, NOT an object.** This is wrong: `{ toolName: { description: '...', handler: fn } }`. This is right: `{ toolName: async (params) => { ... } }`.

```js
// CORRECT — each value is a direct async function
window.crow('registerTools', {
  setCounter: async ({ value }) => {
    window.crow('setToolStatus', 'Setting counter...');
    document.getElementById('counter').textContent = value;
    window.crow('setToolStatus', '');
    return { status: 'success', value };
  },
  searchItems: async ({ query }) => {
    window.crow('setToolStatus', 'Searching...');
    const res = await fetch(`/api/search?q=${query}`);
    const data = await res.json();
    window.crow('setToolStatus', '');
    return { status: 'success', results: data };
  }
});

// WRONG — do NOT pass objects with description/handler keys
// window.crow('registerTools', {
//   setCounter: { description: '...', handler: async () => {} }  // BROKEN
// });
```

### G. Auto-Open Triggers

Open the widget at high-value moments:

```js
// After a key action completes
window.crow('open');

// On first visit
if (isFirstTimeUser) {
  setTimeout(() => window.crow('open'), 500);
}
```

---

## Phase 3.5: Check Existing Configuration

Before making any API changes, fetch the current config to see what's already set up. This prevents overwriting existing configuration:

```bash
curl -s -H "X-Service-Key: $CROW_API_KEY" \
  "$CROW_API_URL/api/products/$PRODUCT_ID" | jq '{
    agent_name: .agent_name,
    system_prompt: (.system_prompt // "" | .[0:100]),
    welcome_message: .welcome_message,
    initial_suggestions: .initial_suggestions,
    all_tools: [.all_tools[]?.name],
    selected_tools: .selected_tools,
    api_base_url: .api_base_url,
    has_widget_styles: (.widget_styles != null)
  }'
```

Show the user what's already configured and note what you'll keep vs override. If there's an existing system prompt or tools, ask: "You already have [X] configured. Should I replace it or build on top of it?"

---

## Phase 4: Configure Crow Backend via API

Use curl to configure the Crow backend. **Check the response after every curl call** — verify it contains `"success": true` or the expected data. If a call fails, show the error to the user instead of continuing silently.

**After each step, tell the user where they can see and edit what you just configured on the Crow dashboard.** This helps them understand the system and feel in control.

### 4a. Upload Client Tools

Generate a `client-tools.json` file, then upload it:

```bash
curl -s -X POST "$CROW_API_URL/api/products/$PRODUCT_ID/client-tools" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tools": [
    {
      "name": "toolName",
      "description": "Clear description of what this tool does and when to use it.",
      "parameters": {
        "type": "object",
        "properties": {
          "param1": { "type": "string", "description": "Description" }
        },
        "required": ["param1"]
      }
    }
  ]}'
```

Tell the user: "I just uploaded your client tools. You can see and edit them at [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — **Tools** tab → **Client Side**"

### 4b. Configure Server-Side Tools (OpenAPI)

If the app has backend API endpoints the AI should call directly, build an OpenAPI spec and upload it:

```bash
curl -s -X POST "$CROW_API_URL/api/products/$PRODUCT_ID/openapi" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"openapi_spec": {
    "openapi": "3.0.0",
    "info": { "title": "App API", "version": "1.0.0" },
    "paths": {
      "/api/items": {
        "get": {
          "operationId": "listItems",
          "summary": "List all items",
          "description": "Detailed description for the LLM.",
          "responses": { "200": { "description": "Success" } }
        }
      }
    }
  }}'
```

Configure how Crow authenticates to the customer's API:

```bash
curl -s -X PUT "$CROW_API_URL/api/products/$PRODUCT_ID/connection" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"api_base_url": "https://your-app.com", "auth_type": "jwt_forward"}'
```

Auth types: `jwt_forward` (forwards user's token), `bearer` (static token), `api_key` (custom header), `none`.

Tell the user: "Server-side tools configured. See them at [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — **Tools** tab → **Server Side**"

### 4c. Enable Tools

Enable all tools (both client-side and server-side):

```bash
curl -s -X PUT "$CROW_API_URL/api/products/$PRODUCT_ID/selected-tools" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"tool_names": ["toolName", "listItems"], "tool_settings": {
    "toolName": { "client_side": true },
    "listItems": { "client_side": false }
  }}'
```

Tell the user: "Tools enabled. Toggle them on/off at [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — **Tools** tab → **All Tools**"

### 4d. Set System Prompt

Write a system prompt that describes the app, the available tools, and the context fields:

```bash
curl -s -X POST "$CROW_API_URL/api/products/$PRODUCT_ID/system-prompt" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"system_prompt": "You are a helpful assistant for [App Name]. You help users with [primary actions]. Available context: currentRoute, userId, etc. Available tools: toolName (does X), listItems (does Y)."}'
```

Tell the user: "System prompt set. Read and edit it at [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — **Agent** tab → **Instructions**"

### 4e. Set Welcome Message & Suggestions

**IMPORTANT: Every suggestion MUST map to something the agent can actually do** — either a registered tool or a question the system prompt equips it to answer. Never suggest actions the agent has no tool or knowledge for. Derive suggestions from the tools you just configured.

```bash
curl -s -X PUT "$CROW_API_URL/api/products/$PRODUCT_ID/welcome-message" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"welcome_message": "Hi! How can I help you today?"}'

curl -s -X PUT "$CROW_API_URL/api/products/$PRODUCT_ID/initial-suggestions" \
  -H "X-Service-Key: $CROW_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"initial_suggestions": [{"label": "<short button text>", "message": "<message that triggers one of the configured tools>"}]}'
```

Example: if you configured a `searchLeads` tool, a good suggestion is `{"label": "Search leads", "message": "Search for coffee shops in NYC"}`. A bad suggestion is `{"label": "Show analytics", "message": "Show me my analytics"}` if there's no analytics tool.

Tell the user: "Welcome message and suggestions set. Edit them at [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — **Agent** tab → **Suggestions**"

### Client-Side vs Server-Side Decision

| Use server-side (OpenAPI) when... | Use client-side when... |
|---|---|
| Tool calls the app's REST API | Tool reads/writes browser state (navigation, DOM) |
| Tool needs data the browser doesn't have | Tool needs access to client-side stores (Redux, Zustand) |
| You want Crow to handle auth forwarding | Tool triggers UI actions (open modal, scroll) |
| Keeps the frontend PR clean | Tool needs to run without API connectivity |

**Prefer server-side** for data fetching — Crow handles auth/retry/error formatting.

---

## Phase 5: Output Summary

After generating all files, output:

```
## Crow Integration Summary

### Files Created
- `<path>/CrowSetup.tsx` (or equivalent) — main integration component
- `<path>/api/crow-token/route.ts` (or equivalent) — identity token endpoint

### Files Modified
- `<path>/_app.tsx` (or equivalent) — added one mount line
- `.env.local` — added CROW_VERIFICATION_SECRET and CROW_ENABLED flag

### Backend Configuration (via API)
- ✅ System prompt set
- ✅ Client tools uploaded and enabled
- ✅ Server-side tools uploaded (if applicable)
- ✅ Welcome message configured
- ✅ Initial suggestions set

### To activate:
1. Set `NEXT_PUBLIC_CROW_ENABLED=true` (or equivalent) in your env
2. Set `CROW_VERIFICATION_SECRET=<secret>` in your backend env
3. Start your app and verify the widget appears

### Customize anything
You can tweak any of the settings I configured — ask me to change them, or edit them directly on the [Crow dashboard]($CROW_DASHBOARD_URL/configure):

| What | Where |
|------|-------|
| System prompt | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Agent → Instructions |
| Welcome message & suggestions | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Agent → Suggestions |
| Agent name & identity | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Agent → Identity |
| Client-side tools | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Tools → Client Side |
| Server-side tools (OpenAPI) | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Tools → Server Side |
| Enable/disable tools | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Tools → All Tools |
| Widget colors & appearance | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Widget → Widget |
| Chat bubble style | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Widget → Chat Bubble |
| Deploy (script tag) | [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) — Deploy → Script Tag |
| View conversations | [$CROW_DASHBOARD_URL/conversations]($CROW_DASHBOARD_URL/conversations) |

### Testing checklist
- [ ] Widget appears on target pages
- [ ] User identity is correctly set (check [$CROW_DASHBOARD_URL/conversations]($CROW_DASHBOARD_URL/conversations))
- [ ] Context fields update when app state changes
- [ ] Each tool executes correctly
- [ ] Feature flag disables the widget completely when set to false

### Need help?
If anything isn't working, email jai@usecrow.ai or book a demo at https://cal.com/jai-crow/demo
```

---

## Phase 6: Verify Configuration

After all curl calls, fetch the config back and confirm everything was saved:

```bash
curl -s -H "X-Service-Key: $CROW_API_KEY" \
  "$CROW_API_URL/api/products/$PRODUCT_ID" | jq '{
    system_prompt_set: (.system_prompt != null and .system_prompt != ""),
    tools_count: (.all_tools | length),
    selected_tools: .selected_tools,
    welcome_message: .welcome_message,
    suggestions_count: (.initial_suggestions // [] | length),
    api_connection: .api_base_url
  }'
```

Show the user the result. If anything is missing or null that should have been set, diagnose and retry the specific curl call.

---

## Incremental Changes (Modify Mode Reference)

When in Modify Mode (Step 0 detected existing integration), use Phase 3.5 to check current config before making any change. Then make ONLY the requested change:

- **"Add a new tool"** → Read existing `registerTools` call. Add the new tool function. Re-upload the FULL tool list via `POST /client-tools` (including existing tools — it overwrites). Re-enable via `PUT /selected-tools`.
- **"Add server-side tools"** → Read backend routes. Build OpenAPI spec. `POST /openapi`. `PUT /connection` for auth. Enable new tools.
- **"Change the system prompt"** → `POST /system-prompt` (overwrites the whole prompt)
- **"Update widget colors"** → `PUT /widget-styles`
- **"Add a new page context"** → Modify CrowSetup's `setContext` call. No API call needed.
- **"Update suggestions"** → `PUT /initial-suggestions` (overwrites the whole list)
- **"Disable a tool"** → Fetch current `selected_tools`, remove the tool, `PUT /selected-tools`

**After every change:** Tell the user what you did and link to [$CROW_DASHBOARD_URL/configure]($CROW_DASHBOARD_URL/configure) with the relevant tab noted. Ask if they want to change anything else.

---

## Common Issues

| Symptom | Cause | Fix |
|---------|-------|-----|
| "No handler registered" | `window.crow()` called before widget script loaded | Wait for `script.onload` or check `crowReady` state |
| Tools silently don't work | Tool registered in code but not enabled via API (or vice versa) | Must be in BOTH `registerTools` AND `selected-tools` API call |
| Identity shows anonymous | Raw auth token passed instead of Crow JWT | Create `/api/crow-token` endpoint that signs with `verification_secret` |
| Widget blocked by modal | UI library sets `pointer-events: none` on body | Add CSS: `#crow-widget-root { pointer-events: auto !important; }` |
| Context shows stale data | Pre-loaded data sent as if user triggered it | Gate context fields on user actions, not data presence |
| `setSuggestedActions` keeps resetting | Called in a subscription without a guard | Use a ref guard, call once per state transition |
| Curl returns 401 | Using wrong auth header | Use `X-Service-Key` header with your API key |
| Curl returns 422 | Wrong field name in request body | Check the exact field names (e.g., `initial_suggestions` not `suggestions`) |
| registerTools silently ignored | Passed `{ name: { handler: fn } }` instead of `{ name: fn }` | Value must be a direct async function, not an object with description/handler |
| Next.js App Router SSR error | CrowSetup uses browser APIs | Wrap with `dynamic(() => import('./CrowSetup'), { ssr: false })` |
| Vite env var undefined | Wrong prefix | Must use `VITE_` prefix: `VITE_CROW_ENABLED` |

---

## Key Principles

1. **Never break existing code** — if the flag is off, the integration is completely inert
2. **One new file, one mount line** — that's all that changes in the app
3. **Use the app's existing state** — don't introduce new data fetching, read from existing stores
4. **Use `setToolStatus` for slow operations** — users need feedback during multi-step tools
5. **Context > instructions** — richer context in `setContext` means smarter agent responses
6. **Tools should be atomic** — each tool does one clear thing; avoid mega-tools
7. **Test the flag** — verify that removing the env var leaves zero trace in the app
8. **Tool handlers must block** — always `await` async work so `setToolStatus` stays visible
9. **Set `setToolStatus` BEFORE navigation** — widget may not render it in time on the new page
10. **Re-register tools on route change in SPAs** — put registration in `useEffect` with route dependency
11. **`setSuggestedActions` overrides the LLM** — use a ref guard, call once per state transition
12. **Gate context on user intent** — don't send pre-loaded data as if the user triggered it
13. **Tools must be enabled via API AND registered in code** — missing either causes silent failure
14. **Prefer filter-state updates over imperative triggers** — reactive fetching > manual reload
15. **CSS modal fix** — add `#crow-copilot-root, #crow-widget-root { pointer-events: auto !important; }` for UI libraries that block pointer events
16. **Handle all terminal states in polling loops** — don't let polls run forever on missed states
17. **Exclude pages cleanly** — remove script tag AND widget DOM on excluded pages
