# @contextune/sdk

> Network-intercept behavioural SDK: captures analytics events at the transport layer and surfaces a structured snapshot for AI agent context.

Contextune helps customer-facing AI agents respond better by giving them behavioural context they normally can't see. It captures a live view of what the user is doing in the browser, the pages they visit, where they click and scroll, where they hesitate, and injects it into the agent's context window. The agent can then infer intent from behaviour instead of from the latest message alone. It's built for cases where what the user just did on the page changes what the agent should say: travel concierges, in-product support agents, and ecommerce shopping assistants.

`@contextune/sdk` is the client-side piece. It maintains that behavioural profile in the browser and exposes it as a structured snapshot you can drop into a prompt. It runs in plain JavaScript or TypeScript with no framework or model assumptions, makes no network requests of its own, and keeps the snapshot in memory until you read it.

The snapshot captures signals an agent can't infer from the message alone: the running event log with elapsed time between events, scroll depth, rage clicks, the marketing parameters that brought the user in, and device context.

[contextune.com](https://contextune.com) · [docs.contextune.com](https://docs.contextune.com)

## Install

```bash
npm install @contextune/sdk
```

No peer dependencies.

## Use

```ts
import { Contextune } from "@contextune/sdk";

Contextune.init({ source: "ga4" });

// Later, at the point your agent is invoked:
const snapshot = Contextune.getSnapshot();
// Inject `snapshot` into the agent's system prompt or first message.
```

`getSnapshot()` returns a structured object:

```ts
{
  event_log: [
    { name: 'page_view', elapsed_s: 0 },
    { name: 'add_to_cart', elapsed_s: 34, properties: { item: 'field-jacket-m' } },
    { name: 'checkout_started', elapsed_s: 67 },
  ],
  marketing_params: {
    utm_source: 'google', utm_medium: 'cpc', utm_campaign: 'summer',
    utm_term: null, utm_content: null,
    referrer: 'https://www.google.com/',
    landing_page: 'https://shop.example.com/?utm_source=google',
  },
  device_info: {
    user_agent: '...',
    viewport_w: 1440, viewport_h: 900,
    language: 'en-GB',
    platform: 'MacIntel',
    device_type: 'desktop',
  },
}
```

## Two modes

### Network-intercept mode

Pass `source` to tap an existing analytics stream. The SDK patches `fetch`, `sendBeacon`, and `XMLHttpRequest`, filters traffic from the configured provider, and extracts structured events without any changes to your existing analytics setup.

```ts
Contextune.init({ source: "ga4" }); // tap GA4 traffic
Contextune.init({ source: "segment" }); // tap Segment traffic
Contextune.init({ source: "mixpanel" }); // tap Mixpanel traffic
```

### Direct-call mode

Omit `source` entirely. No network interception; only `track()` and `page()` calls are captured. Use this when you want Contextune itself as the lightweight analytics layer.

```ts
Contextune.init();

Contextune.track("filter-applied", { facet: "size", value: "M" });
Contextune.track("checkout-started", { cart_value: 129 });
```

## Options

```ts
Contextune.init({
  // 'ga4' | 'segment' | 'mixpanel' — tap an existing analytics stream.
  // Omit for direct-call mode (only track()/page() are captured).
  source: "ga4",

  // Cap on the rolling event log. Default 50.
  eventLogSize: 50,

  // Event log lifetime.
  // 'navigation' (default) — cleared on page reload.
  // 'ct-session' — survives reloads; scoped to 30 min of inactivity.
  persistence: "navigation",

  // Context blocks. Both default to true.
  context: {
    extras: {
      marketing: true, // UTM params, referrer, landing page
      device: true, // viewport, language, platform, device type
    },
  },

  // DOM-derived behavioural signals (optional).
  tracking: {
    scroll: true, // fire 'scroll' at 25/50/75/100% depth milestones
    rageClicks: true, // fire 'rage_click' on ≥3 clicks within a 1-second window
  },
});
```

## Subscribing to events

React to events as they arrive rather than polling `getSnapshot()`.

```ts
const unsubscribe = Contextune.subscribe(filter, handler);
```

`handler` receives two arguments:

| Argument | Type                 | Description                                                        |
| -------- | -------------------- | ------------------------------------------------------------------ |
| `entry`  | `EventLogEntry`      | The event that just landed                                         |
| `state`  | `ContextuneSnapshot` | Full snapshot captured immediately **before** this event was added |

`filter` controls which events trigger the handler:

```ts
// Fire on every event
const unsubscribe = Contextune.subscribe("event", (entry, state) => {
  console.log(entry.name, entry.elapsed_s);
});

// Fire only when the predicate returns true
const unsubscribe = Contextune.subscribe(
  (entry) => entry.name === "checkout_started",
  (entry, state) => sendAgentContext(state),
);

// Cleanup — call the returned function to detach
unsubscribe();
```

Subscriptions are framework-agnostic. They work in vanilla JS, React, Vue, or any other environment. Multiple subscribers can be active simultaneously. All are cleared automatically on `Contextune.destroy()`.

If `subscribe` is called before `init()`, the returned unsubscribe is a no-op.

## Lifecycle

- `Contextune.init(options?)`: idempotent singleton. Returns the existing instance if already initialised. Call `destroy()` first to re-initialise with different options.
- `Contextune.getSnapshot(options?)`: returns the current snapshot, or `null` before `init()`.
- `Contextune.track(name, properties?)`: push a named event into the log directly.
- `Contextune.page(url?)`: record a page view. Call on each SPA route change.
- `Contextune.subscribe(filter, handler)`: react to events as they arrive. Returns an unsubscribe function.
- `Contextune.clear()`: empty the event log (in memory and, under `ct-session`, its persisted copy) while keeping the SDK running — patches, subscribers, and the session clock stay intact.
- `Contextune.destroy()`: full teardown — removes transport patches, clears the buffer and all subscribers, resets the singleton, and under `ct-session` removes the stored log and expires the cookie so the profile can't be recovered.

## Page views (single-page apps)

Call `Contextune.page()` on every route change so the event log reflects navigation:

```ts
import { Contextune } from "@contextune/sdk";

// e.g. in a Next.js usePathname() effect or router subscription:
Contextune.page();
// or pass an explicit URL to record a pre-redacted path:
Contextune.page("/checkout/confirmation");
```

## Snapshot formats

`getSnapshot()` accepts an optional `format`:

```ts
Contextune.getSnapshot(); // ContextuneSnapshot object (default)
Contextune.getSnapshot({ format: "js" }); // same — explicit
Contextune.getSnapshot({ format: "json" }); // JSON string
Contextune.getSnapshot({ format: "toon" }); // TOON string (token-optimised for LLMs)
```

TOON (Token-Oriented Object Notation) is a compact, flat serialisation designed to minimise token usage in LLM prompts. Flat blocks render as YAML-style `key: value` pairs; the event log renders as a CSV table.

## Privacy

The SDK never makes a network request of its own. The event log redacts properties named `password`, `email`, `credit_card`, `cvv`, `ssn`, and any key matching `/(password|secret|token|api[_-]?key|jwt|session)/i`. URL-shaped values have credential-bearing query parameters stripped and UUID path segments replaced with `[id]`. Redaction is applied before any event enters the ring buffer.

## Bundle size

~13 KB minified / ~4–5 KB gzipped. No peer dependencies.

## Supported browsers

All evergreen browsers ✅

**Via CDN (no bundler):**

```html
<script src="https://unpkg.com/@contextune/sdk"></script>
<script>
  Contextune.init();
  const snapshot = Contextune.getSnapshot();
</script>
```

**Via package manager (bundled apps):**

```bash
npm install @contextune/sdk
```

## Contributing

This repository is a read-only mirror. The SDK is developed in a private monorepo and published here on each release, so changes land as `Release vX.Y.Z` commits rather than direct merges. Issues and pull requests are still welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for how they get incorporated.

## License

MIT. See [`LICENSE`](./LICENSE). Copyright 2026 Reidworks.
