# docs v0.1

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, then the quickstart. After that, the four cards below cover the rest.

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.4.3
$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.

$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 --no-agent-files to skip it.

What's where

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