# @test-lab-ai/cli (`testlab`)

Import existing test plans (and the test data they need) into
[test-lab.ai](https://test-lab.ai) from the command line, your CI, or an AI
agent. **Zero dependencies**, Node 18+.

## Install

```bash
npx @test-lab-ai/cli login        # one-off, no install
npm i -g @test-lab-ai/cli         # or install the `testlab` command globally
```

## Authenticate

```bash
testlab login
```

Opens your browser to approve access, then saves your key to
`~/.test-lab/config.json` (readable only by you). For CI / headless / agents,
skip the browser:

```bash
export TESTLAB_API_KEY=tl_xxxxx
# or:  testlab login --key tl_xxxxx
```

Create a key at **Settings -> API Keys**. A key belongs to one account, so it's
the import target - to import into an organization account, create the key with
that org selected.

### Multiple accounts (profiles)

Working with several clients at once? Store one account per **profile** instead
of overwriting the last:

```bash
testlab login --profile acme            # log into (and name) each account once
testlab login --profile globex
testlab auth list                       # see them all + which is active here
```

Then bind each working folder to its account - the folder file names the
profile, never the key, so it is safe to commit:

```bash
cd ~/clients/acme && testlab auth use acme --local   # writes ./.test-lab.json
testlab run 12                                        # runs as acme, automatically
```

Resolution precedence (high -> low): `--key` -> `--profile` -> `$TESTLAB_API_KEY`
-> `$TESTLAB_PROFILE` -> `./.test-lab.json` (walked up from the current dir) ->
the default profile (`testlab auth use <name>`). `testlab logout [--profile N]`
forgets one; keys always stay in `~/.test-lab/config.json`.

## Commands

```
testlab whoami                          Show the authenticated account + profile
testlab auth list                       List stored accounts (profiles); mark active
testlab auth use <name> [--local]        Switch account (global default, or --local folder)
testlab logout [--profile N] [--all]     Forget a stored account
testlab import <path> [--dry-run]        Import a file or directory of *.json
testlab plans list                      List your test plans
testlab plans create -f plan.json        Create one plan from JSON
testlab run <planIds>                    Trigger a run (12,34 or --project/--label);
                                         a pipeline plan fans out automatically.
                                         --test-type, --env, --build-id, --prefer-script,
                                         --pre-steps, --tunnel, --cookies '<json>'.
                                         Prints a jobId + results URL per job.
testlab runs get <jobId> [--wait]        Fetch a run's status, failure error, and
                                         per-step results by jobId. --wait polls
                                         until it finishes; --json for raw. Exits
                                         non-zero when the run did not pass.
testlab runs list [--plan <id>]          Recent runs (jobId + status), newest first.
testlab credentials set <key> --value <value>     Set a credential ({{credentials.<key>}})
testlab credentials list                 List credential keys (values never shown)
testlab plans label <id> --add a,b --remove c   Add/remove labels on an existing plan
testlab labels list                      List your labels
testlab labels create <name>             Create a label (also auto-created on reference)
testlab schedules list                   List your schedules (cadence + links)
testlab schedules update <id>            Update a schedule: --cron, --projects
                                         (run ALL live plans of those projects,
                                         CI-style), --plans, --env, --enable/--disable
testlab data list [--project <id|name>]  List your data fixtures (--project: a project's fixtures)
testlab data create -f fixture.json      Create a data fixture ({{data.<fixture>.<field>}};
                                         --project <id|name|none> scopes it to a project)
testlab scripts upload <file> --plan <id>  Upload a Playwright script for a plan
                                         (skips paid AI generation; --device optional)
testlab scripts get <id> [--out <file>]  Download a plan's active script (warns when
                                         it is outdated; pipe-safe on stdout)
testlab scripts status [id] [--stale]    Audit which saved scripts are outdated;
                                         omit the id to check every plan. --json for
                                         agents; exits 1 if any are outdated (CI gate)
testlab examples                         Full JSON reference for every resource
```

**`testlab examples` prints the exact JSON shape for every resource**
(credentials, labels, data fixtures, plans, pre-steps). It's the fastest
reference and is written so an AI agent can read it and build a valid import.

## Import format

`testlab import` reads a JSON file (or a directory of `*.json`). A file can be a
single plan, an array of plans, or a **bundle** with any of these sections - 
created in order: credentials -> labels -> fixtures -> plans:

```jsonc
{
  "credentials": [
    { "key": "password", "value": "hunter2" }      // secret behind {{credentials.password}}
  ],
  "labels": ["smoke", "auth"],
  "fixtures": [
    {
      "key": "newUser",                             // referenced as {{data.newUser.email}}
      "fields": [
        { "key": "email", "mode": "dynamic", "generator": "internet.email" },
        { "key": "plan",  "mode": "static",  "value": "pro" }
      ]
    }
  ],
  "plans": [
    {
      "ref": "signup",                              // handle for pre-step wiring
      "name": "Sign up",
      "prompt": "Go to https://app.example.com/signup and register with {{data.newUser.email}} / {{credentials.password}}. Confirm the welcome screen.",
      "testType": "quickTest",
      "labels": ["smoke"]
    },
    {
      "name": "Onboarding",
      "prompt": "Complete the onboarding checklist.",
      "preSteps": [ { "ref": "signup" } ]           // run "signup" first, share browser state
    }
  ]
}
```

```bash
testlab import ./bundle.json --dry-run   # validate + print plan order, write nothing
testlab import ./bundle.json             # create everything
```

The CLI topologically sorts plans by their pre-step `ref` dependencies, so order
in the file doesn't matter. Re-running is safe for credentials/labels/fixtures
(upsert / idempotent); plans are always created fresh.

## Reference syntax (inside a plan prompt)

- `{{credentials.<key>}}` - a stored secret (never shown to the AI model).
- `{{data.<fixture>.<field>}}` - a value from a data fixture (generated test data).
- `{{run.shortId}}` - a unique per-run id (for unique emails, names, etc.).

## Install the test-lab-plan skill (Claude Code, Codex, Cursor)

The companion skill that *writes* test-lab plans, so an AI agent can design a
plan and this CLI imports it:

```bash
testlab skills install                  # auto-detects the agent(s) you use
testlab skills install --agent codex    # Codex -> .agents/skills
testlab skills install --agent cursor   # Cursor -> .cursor/rules/test-lab-plan.mdc
testlab skills install --agent all      # all three
```

With no `--agent`, it detects which agents you use (the agent running the command,
plus the `.claude` / `.agents` / `.cursor` folders in your project) and installs for
each, defaulting to Claude Code if none are found.

Add `--global` for Claude Code or Codex (installs under your home directory);
Cursor user rules are set in Cursor's Settings. Restart your agent to load it.
The skill is **bundled with the CLI** and version-locked to it (offline, reproducible
installs); the public [`Test-Lab-ai/skills`](https://github.com/Test-Lab-ai/skills)
repo is the browsable source.

A skill change ships as a new CLI version, so `npm i -g @test-lab-ai/cli@latest`
updates it: the CLI auto-refreshes installed copies on first run after an upgrade,
and `testlab skills update` re-installs the bundled version on demand.

### Example prompts

With the skill installed, invoke it and describe the test in plain language. In
Claude Code: `/test-lab-plan`; in Codex or Cursor, just mention test-lab.

Create a test for a feature you just built:

```
/test-lab-plan write a test for the signup flow at https://app.example.com/signup, then create it in my account
```

Import tests you already have:

```
/test-lab-plan convert the Playwright specs in ./tests/e2e into test-lab plans and import them
```

## For AI agents

Run **`testlab examples`** for the complete JSON reference, or read `AGENTS.md`
(shipped inside this package). The workflow: read the user's existing tests -> 
convert each into the plan/fixture JSON above (explicit URL in the prompt,
secrets as `{{credentials.<key>}}`, generated data as `{{data.<fixture>.<field>}}`) -> 
`testlab import`.

## Under the hood

Every command is a thin wrapper over the public
[Import API](https://test-lab.ai/docs/api/test-plans); an agent can call that
directly instead of shelling out to the CLI.

The CLI checks npm for a newer version about once a day and prints a one-line
notice when you're behind. Set `NO_UPDATE_NOTIFIER=1` to silence it (it's also
auto-suppressed in CI and non-interactive output).
