Developers

The config lives in your repo. Your agent does the setup.

Everything you configure is a typed config file that coding agents edit natively. The console is a second front door onto the same state, and the CLI and MCP server are part of the product.

usercurrent.config.tsExample
import {
  defineConfig, defineEvent, defineSegment, defineExperience,
} from '@usercurrent/cli';

export default defineConfig({
  events: [
    defineEvent({
      name: 'pricing_page_viewed',
      description: 'Viewed the pricing page.',
    }),
  ],
  segments: [
    defineSegment({
      id: 'high_intent_trial',
      name: 'Trial users, high intent',
      condition: {
        kind: 'event',
        event: 'pricing_page_viewed',
        window: { value: 7, unit: 'days' },
        count: { op: 'gte', value: 1 },
      },
    }),
  ],
  experiences: [
    defineExperience({
      slot: 'upgrade_banner',
      name: 'Upgrade banner',
      variants: [
        { id: 'default', props: { text: 'Compare plans.' } },
        { id: 'high_intent', props: { text: 'Trial ends soon.' } },
      ],
      fallback: 'default',
      rules: [
        { id: 'r1', segment: 'high_intent_trial', variant: 'high_intent' },
      ],
    }),
  ],
});
An event, a segment and a personalized slot, as one file. Names are examples.

Agent-first setup

From an empty integration to a reviewed pull request

You do not write the instrumentation by hand. Your agent reads the codebase, and you review what it proposes.

  1. Give your agent the skills

    Nine skills cover setup, the tracking plan, personalization, the catalog, experiments, insights, audits and in-app messages. They work as a Claude Code plugin or as plain Agent Skills in any agent that reads them.

  2. It reads the codebase

    The agent finds your framework, proposes events and a feature catalog, and compares your tracking calls with the plan.

  3. It instruments and opens a pull request

    Events, segments, slots and flags are written to a config file in your repo, next to the code that uses them.

  4. You review and publish

    The diff shows what changes. Publishing is a dry run until you apply it, and an agent key cannot publish to Production.

Terminal
# In Claude Code, or any agent that reads Agent Skills
> Set up UserCurrent for this app. Use the usercurrent-setup skill.

# The agent scans the code, proposes a tracking plan, instruments the
# app and writes usercurrent.config.ts. You check the result:
uc validate
uc scan
uc diff

npm package comingThe skills are built and ship as @usercurrent/agent-skills. For now you copy them into the repo.

Config as code

One state, shared by the repo and the console

The config registry is the record. Each object is owned either by code or by the console, and the tools check which before they write.

  • Owned by code

    Read-only in the console, which can propose a change as a draft. Segments and slots that ship with a feature are a common choice.

  • Owned by the console

    Edited and published there, so a marketer can publish without a code review. uc pull brings them into the repo.

  • No silent overwrites

    uc diff and uc publish detect drift. A console edit is never overwritten by a file edit, or the other way round, without you saying so.

  • Read as data

    The config file is parsed, never run. It accepts a declarative subset of TypeScript, so a checked-out repo cannot run code on publish.

What a config file can declare

  • events
  • segments
  • experiences and slots
  • flags
  • experiments
  • journeys
  • funnels
  • features
  • campaigns
  • surfaces
  • dashboards
  • saved insights

CLI and MCP server

The same operations for a person at a terminal and an agent over MCP

Both call the same service layer as the REST API, with the same scopes. Agent changes to Production queue as approval requests for a person.

CLI

Built

The uc command. --json prints machine-readable output, and exit codes tell a script or an agent what happened.

uc init
Write a starter usercurrent.config.ts.
uc validate
Check the config and compile it with the compiler the control plane runs on publish.
uc scan
Compare the app's track, decide, peek and renderSlot calls with the tracking plan.
uc diff
Show, object by object, how the file differs from the project. Exits 1 when it does.
uc publish --environment ENV
Save the file's segments, slots, experiences, surfaces and campaigns as drafts and publish them. A dry run until --apply.
uc pull
Write console edits back into the file. Refuses to overwrite local edits.
uc features propose | apply | list
Draft the feature catalog from the tracking plan and code, apply the reviewed drafts.
uc catalog types | items | item | upsert | import
Read and load the item catalog: upsert a file, or import NDJSON or CSV as a full or incremental sync.

ComingA GitHub Action that validates on every pull request and comments the diff, and a local dev mode.

MCP server

Built

Hosted and remote, with OAuth. Tools are derived from the REST API, which has an OpenAPI description.

Reads

  • Analytics queries and readouts
  • Profiles, with personal data masked
  • Decisions and why: explain_decision
  • Journeys, approvals and the decision timeline
  • Catalog item types, items, versions and imports

Writes

  • Drafts of any config object
  • Catalog upserts and imports (a person confirms retirements)
  • Feature proposals and campaign drafts
  • Preview links for a visitor
  • Approval requests: agent changes to Production queue for a person

Rate limitedPer key and per address, with a plan-hash guard on publish.

Catalog

Upload what you offer, then recommend from it

Declare an item type, send items from your server or import a whole feed, and point a slot at the type. A full sync that would retire a large share of the feed waits for a person.

SDKs

Web, server and Astro

One identity and one profile across all three. Each SDK always answers: when the edge cannot, a decision is your code default.

Builtnpm packages comingThe SDKs are built and tested in the repository. They install from a packaged tarball today.

Web

@usercurrent/sdk-web

Browser TypeScript with an offline queue. Collection is opt-in until you set consent. Slots render fallback-first and report an exposure for what they showed.

browser
import { init } from '@usercurrent/sdk-web';
import { renderSlot } from '@usercurrent/sdk-web/slots';

const uc = init({
  endpoint: 'https://events.example.com',
  key: 'pk_live_...',
  defaults: { upgrade_banner: { text: 'Compare plans.' } },
});

// Call from your consent UI, with the choices the visitor made
uc.setConsent({ analytics: true, personalization: true });

uc.identify('u_4821', { plan: 'trial' });
uc.track('pricing_page_viewed');

const banner = document.querySelector('#upgrade-banner');

// Fallback-first: the default renders at once, the decision replaces it only if it differs
renderSlot(uc, banner, 'upgrade_banner', (decision, el) => {
  el.textContent = String(decision.props.text);
});

Server

@usercurrent/sdk-server

Node 24 and Cloudflare Workers. Track and decide for many visitors from one client, with the visitor read from the web SDK's cookie.

worker.ts
import { createClient, visitorFromRequest } from '@usercurrent/sdk-server';

const uc = createClient({
  key: env.UC_PUBLIC_KEY,
  secretKey: env.UC_SECRET_KEY,
  edge: env.EDGE, // a service binding, or endpoint: 'https://events.example.com'
  defaults: { upgrade_banner: { text: 'Compare plans.' } },
});

export default {
  async fetch(request, env, ctx) {
    const visitor = visitorFromRequest(request);
    const { decisions } = await uc.decide(visitor, ['upgrade_banner']);
    uc.track(visitor, 'pricing_page_viewed');
    ctx.waitUntil(uc.flush());
    return render(decisions.upgrade_banner.props);
  },
};

Astro

@usercurrent/astro

Headless slots for an Astro site on Workers or Node. One decide per request, and the variant is in the first HTML or in a server island.

astro.config.mjs and a page
// astro.config.mjs
import { defineConfig } from 'astro/config';
import usercurrent from '@usercurrent/astro';

export default defineConfig({
  integrations: [
    usercurrent({
      client: './src/lib/usercurrent.ts',
      slots: { home_hero: { fallback: { headline: 'Welcome' } } },
    }),
  ],
});

// src/pages/index.astro: the variant is in the first HTML, with no change after load
---
import { usercurrentOf } from '@usercurrent/astro/runtime';
const hero = await usercurrentOf(Astro).slot('home_hero');
---
<Hero {...hero.props} />

Hand your agent a repository

Start with uc init, then let the setup skill propose the rest.

Why this button?

It is this page's own suggestion for now. As you read, this site keeps a small profile of who each page is for and what it is about, in your browser, and picks this button from it. Nothing is sent.

See your vector on the live demo