# Install AI Autocomplete (Angular) — agent instructions

You are an AI coding agent installing **AI Autocomplete** into the user's Angular app. Follow these steps in order. Do not skip the "Hand off to the user" section — those steps are not yours to perform.

Reference docs: https://ai-autocomplete.com/docs/angular/getting-started

AI Autocomplete uses two key types:

- **Public keys** (`pk_v1_...`) — safe to ship in client bundles. Sent as a Bearer token directly from the browser. Use this for the basic install path below.
- **Secret keys** (`sk_v1_...`) — server-only. Your server exchanges a secret key for a short-lived access token (`at_v1_...`) that the SDK consumes. Recommended for production. See the "Production: secret key + access token" section at the end.

---

## Step 0 — Read the SDK README

Before any code changes, run:

```
npm view @magicx-eng/ai-autocomplete-angular readme
```

Read the output end-to-end. It documents the full input/output surface, both integration tiers, both auth modes, the CSS variables, and the stable `data-aia-*` selector hooks — you'll need it in Steps 2 and 5. If you can't run shell commands, fetch `https://unpkg.com/@magicx-eng/ai-autocomplete-angular/README.md` instead — same content.

This SDK targets **Angular 15 and every later version**. It ships standalone components (and an `AIAutocompleteModule` for NgModule-based apps).

## Step 1 — Ask the user which integration tier

The SDK ships two tiers. **Stop, ask the user, and wait for their answer.** Do not pick on their behalf.

> "AI Autocomplete ships two integration tiers — which do you want?
>
> **Tier 1 — Full component (`<ai-autocomplete>`)**
> Drop-in. The SDK owns the editor, pills, dropdown, and all state. You bind inputs (auth, theme) and handle `(submitted)`. It also implements `ControlValueAccessor`, so it works with Reactive Forms. This is the right choice for most apps.
>
> **Tier 2 — Headless (`AIAutocompleteController` + `[aiaInput]` + `<ai-autocomplete-dropdown>`)**
> You own the input element and layout. A controller exposes state as RxJS observables and action methods; the `[aiaInput]` directive wires your `<textarea>` to it; the dropdown component renders pills + options. Pick this if you have a custom input (rich text, search bar, existing form field) or need full control over rendering.
>
> Which one — full component or headless?"

If the user says "you decide" or seems unsure, recommend according to their current product.

## Step 2 — Install and wire up the SDK

Sanity-check the project: there should be an `angular.json` and a `package.json` at the root. If not, ask the user where their app lives. Note whether the app is standalone-bootstrapped or NgModule-based — it changes how you register the component.

Install the SDK with the project's package manager:

- pnpm: `pnpm add @magicx-eng/ai-autocomplete-angular`
- yarn: `yarn add @magicx-eng/ai-autocomplete-angular`
- npm: `npm install @magicx-eng/ai-autocomplete-angular`

The Angular peer dependencies (`@angular/common`, `@angular/core`, `@angular/forms`, `rxjs`) are already present in any Angular app — no extra install needed.

Then add the integration code for the tier the user picked. **Hardcode the public key as the literal string `"PASTE_PUBLIC_KEY_HERE"`** — leave that placeholder in the code. The user will replace it after Step 3. Do **not** invent a key, and do **not** create an `environment.ts` entry for it. Once the install is verified end-to-end it's the user's call how to manage the key long-term.

- **Standalone components:** add `AIAutocompleteComponent` (Tier 1) or the Tier 2 pieces to the component's `imports: []`.
- **NgModule apps:** `import { AIAutocompleteModule } from "@magicx-eng/ai-autocomplete-angular";` and add `AIAutocompleteModule` to the module's `imports: []`. It re-exports every public symbol.

If you don't know where to mount the component, ask the user before guessing.

**Tier 2 rules:**
- **Always call `reset()` from your own submit handler.** Tier 1 (`<ai-autocomplete>`) does this automatically. In Tier 2 the consumer owns the input and the submit flow — don't pass `onSubmit` to the controller. Wire up submit however you like (a button click, a form submit, a custom Enter handler) and at the end call `controller.reset()`. It clears the input and rotates the per-session `session_id` so the server's history stays accurate; skipping it degrades suggestion quality on subsequent submissions.
- **Always call `controller.destroy()` in `ngOnDestroy`.** The controller owns RxJS subscriptions and a headless core instance — failing to dispose leaks them.

### Tier 1 — Full component

```ts
import { Component } from "@angular/core";
import {
  AIAutocompleteComponent,
  type AutocompleteResult,
} from "@magicx-eng/ai-autocomplete-angular";

@Component({
  selector: "app-search",
  standalone: true,
  imports: [AIAutocompleteComponent],
  template: `
    <ai-autocomplete
      [apiConfig]="apiConfig"
      mode="dark"
      (submitted)="onSubmit($event)"
    ></ai-autocomplete>
  `,
})
export class SearchComponent {
  apiConfig = { apiKey: "PASTE_PUBLIC_KEY_HERE" };

  onSubmit(result: AutocompleteResult): void {
    console.log(result.query);
    console.log(result.completed_params);
  }
}
```

The `mode` input accepts `"light"`, `"dark"`, or `"auto"` (defaults to `"auto"`, which follows `prefers-color-scheme`). Match the surrounding page if you want to pin it.

### Tier 2 — Headless

```ts
import { Component, OnInit, OnDestroy } from "@angular/core";
import {
  AIAutocompleteController,
  AIAutocompleteDropdownComponent,
  AIAutocompleteInputDirective,
} from "@magicx-eng/ai-autocomplete-angular";

@Component({
  selector: "app-search",
  standalone: true,
  imports: [AIAutocompleteDropdownComponent, AIAutocompleteInputDirective],
  template: `
    <textarea [aiaInput]="ac" placeholder="Ask anything..."></textarea>
    <ai-autocomplete-dropdown [controller]="ac"></ai-autocomplete-dropdown>
    <button type="button" (click)="submit()">Submit</button>
  `,
})
export class SearchComponent implements OnInit, OnDestroy {
  ac!: AIAutocompleteController;

  ngOnInit(): void {
    this.ac = new AIAutocompleteController({
      apiConfig: { apiKey: "PASTE_PUBLIC_KEY_HERE" },
    });
  }

  submit(): void {
    const { text, completedParams } = this.ac.getState();
    console.log(text, completedParams);
    this.ac.reset();
  }

  ngOnDestroy(): void {
    this.ac.destroy();
  }
}
```

If the user already has a custom input element (rich text editor, search bar, etc.), attach `[aiaInput]="ac"` to that element instead of a fresh `<textarea>`. The README's `AIAutocompleteController` and `<ai-autocomplete-dropdown>` sections cover the full observable/action surface and the rendering knobs.

## Step 3 — Hand off to the user (you cannot do this)

After your code changes are complete, **stop** and print the following to the user verbatim — including the bullets and URLs. These are browser tasks; the agent cannot perform them.

> **Two browser steps you need to do before this works:**
>
> 1. Go to **https://ai-autocomplete.com/edit** and create your product. This configures what your autocomplete will suggest (the prompts, parameter types, and option lists).
> 2. Go to **https://ai-autocomplete.com/account/keys**, create a **public key**, copy it, and paste it in place of `PASTE_PUBLIC_KEY_HERE` in the component. The public key starts with `pk_v1_`.
>
> Once both are done, reload the page (or restart `ng serve` if hot reload didn't pick it up). Reply when you've confirmed it works (or share what's failing).

Do not pretend you can open URLs, create the product, or generate the key on the user's behalf. Wait for their reply before moving to Step 4.

## Step 4 — Verify it works

Once the user replies that they've pasted the key, walk them through verification:

1. Run `ng serve` (or reload the page).
2. Open the page where you mounted the autocomplete and confirm it renders without console errors.
3. If the network tab shows **401 Unauthorized** on `/api/suggest`, the key is wrong or it isn't a public key — ask the user to re-copy it from `/account/keys` and confirm it starts with `pk_v1_`.
4. If you see **404** or "no suggestions returned", the product likely wasn't created yet — ask the user to confirm they finished Step 3.1.

Wait for an explicit "yes, it works" before moving on.

## Step 5 — Offer to customize the styling

Once the user confirms the autocomplete works, ask:

> "Want to customize the look? The SDK exposes CSS variables and stable `data-aia-*` selector hooks for everything from pill colors to the submit button. I can apply changes against your existing palette/theme — tell me what you want different (e.g. 'match my brand primary', 'tighten the pill padding', 'switch to a solid dropdown'), or say 'looks fine' to wrap up."

If the user wants changes, refer to the **CSS Variables** and **Selector Hooks** tables in the SDK README you read in Step 0.

Apply overrides in a **global stylesheet** (e.g. `src/styles.css`), not a component's scoped styles — Angular's default (emulated) view encapsulation rewrites component-scoped selectors so they won't reach the SDK's elements. Add a class via `class="my-autocomplete"` on `<ai-autocomplete>` (or the wrapping element in Tier 2). All defaults use `:where()` (zero specificity) so any class you write wins.

For per-mode overrides, note that `data-mode` lives on the **inner** `.magicx-aia` container, not the `<ai-autocomplete>` host — target a descendant: `.my-autocomplete [data-mode="dark"] { ... }`.

For changes the variables don't cover, target the `data-aia-*` selector hooks (e.g. `[data-aia-dropdown]`, `[data-aia-pill]`, `[data-aia-option]`) — those are the stable public surface. Don't target hashed CSS-module class names.

If the user says "looks fine" or doesn't want changes, you're done. Mention briefly that for production they may want to switch to the access-token flow below, but don't build it unless asked.

---

## Production: secret key + access token (recommended)

Public keys work, but for production browser apps we recommend the access-token flow:

- The **secret key** (`sk_v1_...`) lives **only on the user's backend server** — never in the browser bundle, never in `environment.ts`, never in a frontend `.env`.
- The user's **backend** exchanges the secret key for a short-lived **access token** (`at_v1_...`) by calling AI Autocomplete.
- The **frontend** SDK fetches that access token from a route on the user's *own* backend and uses it. The browser never touches the secret key.

Only build this once the basic install above is verified, and confirm with the user before adding server code. **If the project has no backend (e.g. a static SPA with no server runtime), STOP and tell the user — you cannot do the secret-key flow without a server. Stay on public keys.**

### A. Add server-side env vars

These live in the user's **backend** environment (wherever the server runs). They must NOT be exposed to the browser bundle.

```
MAGICX_SECRET_KEY=sk_v1_...
MAGICX_PRODUCT_ID=<the product UUID from /edit>
```

The product UUID is visible in the URL of the product editor at `/edit`. Ask the user to copy it.

### B. Add the token-exchange route on the backend

> **This code runs on the user's backend server, not in the browser.** The secret key is read from server-side env and used in an outbound `fetch` from the server. The browser never sees it.

The backend hits `POST https://api.ai-autocomplete.com/api/auth/token` with the secret key as a Bearer token and the `product_id` in the body. The endpoint returns `{ access_token, expires_at, token_type }`. Forward that response to the browser unchanged. (Use whatever server the user runs — Express/Fastify/Nest/serverless. Same upstream URL, same body, same passthrough.)

```ts
// SERVER-ONLY (e.g. an Express handler at GET /api/ac-token)
app.get("/api/ac-token", async (_req, res) => {
  const upstream = await fetch("https://api.ai-autocomplete.com/api/auth/token", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.MAGICX_SECRET_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ product_id: process.env.MAGICX_PRODUCT_ID }),
  });
  if (!upstream.ok) return res.status(502).send("Token exchange failed");
  res.json(await upstream.json());
});
```

### C. Switch the SDK to access-token mode (browser-side)

> **This code runs in the browser.** It contains no secret key — only the path of the user's own backend route from Step B.

```ts
// BROWSER
apiConfig = {
  type: "accessToken" as const,
  getAccessToken: async () => {
    const res = await fetch("/api/ac-token");
    const { access_token, expires_at } = await res.json();
    return { accessToken: access_token, expiresAt: expires_at };
  },
};

// <ai-autocomplete [apiConfig]="apiConfig" (submitted)="onSubmit($event)"></ai-autocomplete>
```

### D. Remove the public key

Once the access-token flow is verified, drop the hardcoded public key from the component — you don't need it anymore.

Full docs: https://ai-autocomplete.com/docs/angular/authentication
