# Documentation.

dif.sh is a handful of CLI commands and a markdown format. These docs walk through both.

The CLI lives in your repo. Experiments are `.md` files under `dif/`. `dif build` compiles the active ones into a typed runtime client, an audience module, and a `dif/context.json` your coding agent reads on session start. Nothing about the system needs to live outside git.

Start with [install](#install), then the [quickstart](#quickstart). After that, the four cards below cover the rest.

[01 — start install & first run Three install paths. Pick the one that fits the machine you're on. read →](#install) [02 — concepts the .md format One file per experiment. YAML frontmatter, freeform body, surfaces and audience resolvers alongside. read →](https://dif.sh/docs/format/) [03 — cli the commands `init` · `connect` · `new` · `validate` · `build` · `qa` · `conclude` · `scaffold-audiences`. read →](https://dif.sh/docs/cli/) [04 — sdk @dif.sh/sdk Tiny JS runtime, plus the React and Svelte adapters. Buckets the user, fires exposure events, and, on Cloud, tracks metrics through `dif.track()`. read →](https://dif.sh/docs/sdk/)

## Install

Three paths. They produce the same binary; pick whichever fits the machine.

### Shell installer

One-line, no Node required. macOS and Linux only. Drops the binary at `~/.local/bin/dif` by default.

```
$curl -fsSL https://dif.sh/install.sh | sh
```

Pin a release, or point the install at a different directory with `DIF_INSTALL_DIR`:

```
$curl -fsSL https://dif.sh/install.sh | sh -s -- --version v0.6.1
$curl -fsSL https://dif.sh/install.sh | DIF_INSTALL_DIR="$HOME/bin" sh
```

### Homebrew

macOS and Linux. Easiest if you're already using `brew`.

```
$brew install dif-sh/tap/dif
```

### npm wrapper

Works anywhere Node 18+ runs, including Windows. The package's postinstall script downloads the matching binary for your platform. The SDK and framework packages need Node 20.6+.

```
$npm install -g @dif.sh/cli
```

## Quickstart

From an empty repo to a tested experiment rendering in your app.

### 1\. `dif init`

Scaffold the workspace. One `dif/` directory holds everything.

```
$dif init
✓ wrote dif/config.yaml
✓ created dif/experiments/{active,concluded}/
✓ created dif/surfaces/home.md
✓ created dif/audiences/{locale,device_type}.ts
✓ added dif guidance to CLAUDE.md, AGENTS.md
```

### 2\. `dif new`

Draft a new experiment. Owner comes from `git config user.email`.

```
$dif new checkout-cta-v2 --surface home
→ reading dif/surfaces/home.md
  found 0 prior learnings
→ drafted dif/experiments/active/checkout-cta-v2.md
  status: draft, owner: ada@acme.dev
```

Open the file, fill in the hypothesis, set `status: active`.

### 3\. `dif validate`

Every check, run in one pass. Errors include the source location.

```
$dif validate
✓ all checks passed
```

### 4\. `dif build`

Compile the runtime artifacts your app imports and the context file your agent reads.

```
$dif build
✓ validated 1 active experiment
✓ client    → dif/generated/client.ts
✓ audiences → dif/generated/audiences.ts
✓ context   → dif/context.json
```

### 5\. Render it

Install the SDK, import the generated client once at boot, and call `dif()` at the render site. Full reference on the [SDK page](https://dif.sh/docs/sdk/).

```
$npm install @dif.sh/sdk
```

```
import "./dif/generated/client";
import { attributes } from "./dif/generated/audiences";
import { dif } from "@dif.sh/sdk";

dif.init({
  userId: () => currentUser?.id ?? null,
  attributes: () => attributes(),
});

const cta = dif("checkout-cta-v2", {
  control:   () => "Place order",
  variant_a: () => "Get it today",
});

button.textContent = cta();
```

## Workspace layout

`dif init` creates a single `dif/` tree. The generated files appear after `dif build`.

```
dif/
  config.yaml
  context.json          # written by dif build
  audiences/
    locale.ts           # one resolver per audience attribute
    device_type.ts
  experiments/
    active/
    concluded/
  generated/            # gitignored
    client.ts
    audiences.ts
  surfaces/
    home.md
```

`dif/generated/` is gitignored. `dif/context.json` lives outside it and is meant to be checked in, so coding agents and other repo tooling can read the active-experiment summary on session start. `dif init` also adds a managed dif block to `CLAUDE.md`, `AGENTS.md`, `.cursorrules`, and `.claude/skills/`; pass `--agents none` (or the deprecated `--no-agent-files` alias) to skip it.

## What's where

-   The `.md` format (frontmatter, surfaces, audiences, exclusion groups): [format](https://dif.sh/docs/format/).
-   Every CLI command, every flag, every exit code: [cli](https://dif.sh/docs/cli/).
-   The three Claude Code skills `dif init` installs for authoring, concluding, and generating surfaces: [skills](https://dif.sh/docs/skills/).
-   The JS SDK (`dif.init()`, `dif()`, sinks, exposure events, server assignment, and the React and Svelte adapters): [sdk](https://dif.sh/docs/sdk/).
-   The full `dif/config.yaml` schema: [config](https://dif.sh/docs/config/).
-   Diagnostic codes and FAQ: [troubleshooting](https://dif.sh/docs/troubleshooting/).
