# Calibre CLI v7.0.0 Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.

**Goal:** Restructure the CLI into Synthetic/CrUX/RUM namespaces, add 8 new field data commands, maintain backwards compatibility via hidden deprecated aliases.

**Architecture:** Move existing command files into new directories (`synthetic/`, `deploy/`), create new API modules and CLI commands for CrUX and RUM, wire hidden deprecated aliases in the slimmed-down `site.js`, and update all docs/examples. All existing GraphQL API patterns are followed identically.

**Tech Stack:** JavaScript (ES Modules), yargs 18, graphql-request 7, chalk, columnify, ora, Jest

**Design doc:** `docs/plans/2026-05-05-v7-restructure-design.md`
**Changelog spec:** `CHANGELOG-v7-DRAFT.md`

---

### Task 1: Create deprecation utility

**Files:**
- Create: `src/utils/deprecation.js`
- Test: `__tests__/utils/deprecation.test.js`

**Step 1: Write the test**

```javascript
import { deprecatedHandler } from '../../src/utils/deprecation.js'

describe('deprecatedHandler', () => {
  let stderrOutput
  const originalWrite = process.stderr.write

  beforeEach(() => {
    stderrOutput = ''
    process.stderr.write = (chunk) => { stderrOutput += chunk }
    delete process.env.CALIBRE_SUPPRESS_DEPRECATIONS
  })

  afterEach(() => {
    process.stderr.write = originalWrite
    delete process.env.CALIBRE_SUPPRESS_DEPRECATIONS
  })

  test('prints deprecation warning to stderr', async () => {
    const inner = jest.fn()
    const handler = deprecatedHandler('site pages', 'synthetic pages', inner)
    await handler({ site: 'test' })
    expect(stderrOutput).toContain('[calibre:deprecated]')
    expect(stderrOutput).toContain('site pages')
    expect(stderrOutput).toContain('synthetic pages')
    expect(inner).toHaveBeenCalledWith({ site: 'test' })
  })

  test('suppresses warning when CALIBRE_SUPPRESS_DEPRECATIONS is set', async () => {
    process.env.CALIBRE_SUPPRESS_DEPRECATIONS = '1'
    const inner = jest.fn()
    const handler = deprecatedHandler('site pages', 'synthetic pages', inner)
    await handler({ site: 'test' })
    expect(stderrOutput).toBe('')
    expect(inner).toHaveBeenCalled()
  })
})
```

**Step 2: Run test to verify it fails**

Run: `npm test -- __tests__/utils/deprecation.test.js`
Expected: FAIL — module not found

**Step 3: Write implementation**

```javascript
import chalk from 'chalk'

const deprecatedHandler = (oldCommand, newCommand, handler) => {
  return async (args) => {
    if (!process.env.CALIBRE_SUPPRESS_DEPRECATIONS) {
      process.stderr.write(
        chalk.yellow(
          `[calibre:deprecated] "${oldCommand}" has moved to "${newCommand}"\n`
        )
      )
    }
    return handler(args)
  }
}

export { deprecatedHandler }
```

**Step 4: Run test to verify it passes**

Run: `npm test -- __tests__/utils/deprecation.test.js`
Expected: PASS

**Step 5: Lint**

Run: `npm run lint`
Expected: No errors

**Step 6: Commit**

```bash
git add src/utils/deprecation.js __tests__/utils/deprecation.test.js
git commit -m "feat: add deprecation handler utility for v7 command restructure"
```

---

### Task 2: Create shared option modules

**Files:**
- Create: `src/utils/crux-options.js`
- Create: `src/utils/rum-options.js`

**Step 1: Create CrUX options**

```javascript
const cruxOptions = {
  formFactor: {
    describe: 'Filter by device type.',
    choices: ['desktop', 'phone', 'tablet'],
    type: 'string'
  },
  timePeriod: {
    describe: 'History time window.',
    choices: [
      'three-months',
      'six-months',
      'nine-months',
      'twelve-months',
      'eighteen-months',
      'twenty-four-months'
    ],
    default: 'six-months',
    type: 'string'
  }
}

export { cruxOptions }
```

**Step 2: Create RUM options**

```javascript
import { options } from './cli.js'

const rumFilterOptions = {
  duration: {
    describe: 'Number of days to aggregate.',
    default: 7,
    type: 'number'
  },
  dateBin: {
    describe: 'Time granularity.',
    choices: ['day', 'month'],
    default: 'day',
    type: 'string'
  },
  country: {
    describe: 'Filter by country code(s) (space-separated, e.g. AU US).',
    type: 'array'
  },
  device: {
    describe: 'Filter by device type.',
    choices: ['desktop', 'mobile', 'tablet'],
    type: 'string'
  },
  connection: {
    describe: 'Filter by connection type.',
    type: 'array'
  },
  path: {
    describe: 'Filter by URL path(s) (space-separated).',
    type: 'array'
  },
  pageGrouping: {
    describe: 'Filter by page grouping UUID(s) (space-separated).',
    type: 'array'
  }
}

export { rumFilterOptions }
```

**Step 3: Lint**

Run: `npm run lint`
Expected: No errors

**Step 4: Commit**

```bash
git add src/utils/crux-options.js src/utils/rum-options.js
git commit -m "feat: add shared CrUX and RUM option definitions"
```

---

### Task 3: Create grading view utility

**Files:**
- Create: `src/views/grading.js`
- Test: `__tests__/views/grading.test.js`

**Step 1: Write the test**

```javascript
import { formatGrading } from '../../src/views/grading.js'

describe('formatGrading', () => {
  test('returns Good for good grading', () => {
    const result = formatGrading('good')
    expect(result).toContain('Good')
  })

  test('returns NI for needs-improvement grading', () => {
    const result = formatGrading('needs-improvement')
    expect(result).toContain('NI')
  })

  test('returns Poor for poor grading', () => {
    const result = formatGrading('poor')
    expect(result).toContain('Poor')
  })

  test('returns — for null', () => {
    const result = formatGrading(null)
    expect(result).toBe('—')
  })
})
```

**Step 2: Run test to verify it fails**

Run: `npm test -- __tests__/views/grading.test.js`
Expected: FAIL

**Step 3: Write implementation**

```javascript
import chalk from 'chalk'

const formatGrading = (grading) => {
  if (!grading) return '—'

  switch (grading) {
    case 'good':
      return chalk.green('Good')
    case 'needs-improvement':
      return chalk.yellow('NI')
    case 'poor':
      return chalk.red('Poor')
    default:
      return grading
  }
}

export { formatGrading }
```

**Step 4: Run test to verify it passes**

Run: `npm test -- __tests__/views/grading.test.js`
Expected: PASS

**Step 5: Commit**

```bash
git add src/views/grading.js __tests__/views/grading.test.js
git commit -m "feat: add grading formatter with accessible text+colour output"
```

---

### Task 4: Move synthetic commands and create router

**Files:**
- Move: 17 files from `src/cli/site/` to `src/cli/synthetic/` (see list below)
- Modify: `src/cli/synthetic/download-artifacts.js` (rename command string)
- Modify: `src/cli/synthetic/create-pull-request-review.js` (update hint message)
- Create: `src/cli/synthetic.js`

**Step 1: Create synthetic directory and move files**

```bash
mkdir -p src/cli/synthetic

git mv src/cli/site/pages.js src/cli/synthetic/pages.js
git mv src/cli/site/create-page.js src/cli/synthetic/create-page.js
git mv src/cli/site/update-page.js src/cli/synthetic/update-page.js
git mv src/cli/site/delete-page.js src/cli/synthetic/delete-page.js
git mv src/cli/site/snapshots.js src/cli/synthetic/snapshots.js
git mv src/cli/site/create-snapshot.js src/cli/synthetic/create-snapshot.js
git mv src/cli/site/delete-snapshot.js src/cli/synthetic/delete-snapshot.js
git mv src/cli/site/download-snapshot-artifacts.js src/cli/synthetic/download-artifacts.js
git mv src/cli/site/get-snapshot-metrics.js src/cli/synthetic/get-snapshot-metrics.js
git mv src/cli/site/metrics.js src/cli/synthetic/metrics.js
git mv src/cli/site/test-profiles.js src/cli/synthetic/test-profiles.js
git mv src/cli/site/create-test-profile.js src/cli/synthetic/create-test-profile.js
git mv src/cli/site/update-test-profile.js src/cli/synthetic/update-test-profile.js
git mv src/cli/site/delete-test-profile.js src/cli/synthetic/delete-test-profile.js
git mv src/cli/site/pull-request-reviews.js src/cli/synthetic/pull-request-reviews.js
git mv src/cli/site/create-pull-request-review.js src/cli/synthetic/create-pull-request-review.js
git mv src/cli/site/pull-request-review.js src/cli/synthetic/pull-request-review.js
```

**Step 2: Update command string in download-artifacts.js**

In `src/cli/synthetic/download-artifacts.js`, change:
```javascript
const command = 'download-snapshot-artifacts [options]'
```
to:
```javascript
const command = 'download-artifacts [options]'
```

**Step 3: Update hint in create-pull-request-review.js**

In `src/cli/synthetic/create-pull-request-review.js`, change:
```javascript
`View progress by running \`calibre site pull-request-review ${args.branch} --site=${args.site}\``
```
to:
```javascript
`View progress by running \`calibre synthetic pull-request-review ${args.branch} --site=${args.site}\``
```

**Step 4: Create the synthetic group router**

Write `src/cli/synthetic.js`:

```javascript
import * as Pages from './synthetic/pages.js'
import * as CreatePage from './synthetic/create-page.js'
import * as UpdatePage from './synthetic/update-page.js'
import * as DeletePage from './synthetic/delete-page.js'
import * as Snapshots from './synthetic/snapshots.js'
import * as CreateSnapshot from './synthetic/create-snapshot.js'
import * as DeleteSnapshot from './synthetic/delete-snapshot.js'
import * as DownloadArtifacts from './synthetic/download-artifacts.js'
import * as GetSnapshotMetrics from './synthetic/get-snapshot-metrics.js'
import * as Metrics from './synthetic/metrics.js'
import * as TestProfiles from './synthetic/test-profiles.js'
import * as CreateTestProfile from './synthetic/create-test-profile.js'
import * as UpdateTestProfile from './synthetic/update-test-profile.js'
import * as DeleteTestProfile from './synthetic/delete-test-profile.js'
import * as PullRequestReviews from './synthetic/pull-request-reviews.js'
import * as CreatePullRequestReview from './synthetic/create-pull-request-review.js'
import * as PullRequestReview from './synthetic/pull-request-review.js'

const commands = [
  Pages,
  CreatePage,
  UpdatePage,
  DeletePage,
  Snapshots,
  CreateSnapshot,
  DeleteSnapshot,
  DownloadArtifacts,
  GetSnapshotMetrics,
  Metrics,
  TestProfiles,
  CreateTestProfile,
  UpdateTestProfile,
  DeleteTestProfile,
  PullRequestReviews,
  CreatePullRequestReview,
  PullRequestReview
]

const command = 'synthetic <command>'
const desc =
  'Synthetic monitoring — manage scheduled Lighthouse tests, Pages, Test Profiles, Snapshots, and Pull Request Reviews.'
const builder = yargs => {
  return yargs.commands(commands)
}
const handler = () => {}

export { command, desc, builder, handler, commands }
```

**Step 5: Lint**

Run: `npm run lint`
Expected: No errors

**Step 6: Commit**

```bash
git add -A
git commit -m "refactor: move synthetic commands from site/ to synthetic/"
```

---

### Task 5: Move deploy commands and create router

**Files:**
- Move: 3 files from `src/cli/site/` to `src/cli/deploy/`
- Modify: command strings and pagination hint
- Create: `src/cli/deploy.js`

**Step 1: Create deploy directory and move files**

```bash
mkdir -p src/cli/deploy

git mv src/cli/site/deploys.js src/cli/deploy/list.js
git mv src/cli/site/create-deploy.js src/cli/deploy/create.js
git mv src/cli/site/delete-deploy.js src/cli/deploy/delete.js
```

**Step 2: Update command strings**

In `src/cli/deploy/list.js`, change:
```javascript
const command = 'deploys [options]'
const describe = 'List all deployments for a Site.'
```
to:
```javascript
const command = 'list [options]'
const describe = 'List all deployments for a Site.'
```

Also update the pagination hint from:
```javascript
      `To see deploys after ${
        lastDeploy.revision || lastDeploy.id
      }, run: calibre site deploys --site=calibre --cursor=${
        index.pageInfo.endCursor
      }`
```
to:
```javascript
      `To see deploys after ${
        lastDeploy.revision || lastDeploy.id
      }, run: calibre deploy list --site=${args.site} --cursor=${
        index.pageInfo.endCursor
      }`
```

In `src/cli/deploy/create.js`, change:
```javascript
const command = 'create-deploy [options]'
const describe = 'Create a deployment.'
```
to:
```javascript
const command = 'create [options]'
const describe = 'Create a deployment.'
```

In `src/cli/deploy/delete.js`, change:
```javascript
const command = 'delete-deploy [options]'
const describe = 'Delete a deploy from a selected Site.'
```
to:
```javascript
const command = 'delete [options]'
const describe = 'Delete a deploy from a selected Site.'
```

**Step 3: Create the deploy group router**

Write `src/cli/deploy.js`:

```javascript
import * as DeployList from './deploy/list.js'
import * as DeployCreate from './deploy/create.js'
import * as DeployDelete from './deploy/delete.js'

const commands = [DeployList, DeployCreate, DeployDelete]

const command = 'deploy <command>'
const desc =
  'Manage deployment markers — annotate your performance charts across Synthetic, CrUX, and RUM data.'
const builder = yargs => {
  return yargs.commands(commands)
}
const handler = () => {}

export { command, desc, builder, handler, commands }
```

**Step 4: Lint**

Run: `npm run lint`
Expected: No errors

**Step 5: Commit**

```bash
git add -A
git commit -m "refactor: move deploy commands from site/ to deploy/"
```

---

### Task 6: Wire deprecations in site.js and update cli-commands.js

**Files:**
- Modify: `src/cli/site.js`
- Modify: `src/cli-commands.js`

**Step 1: Rewrite site.js**

Replace all contents of `src/cli/site.js` with the slimmed version containing hidden deprecated wrappers. The file imports from the new locations (`./synthetic/`, `./deploy/`), exports only `create`, `list`, `delete` as visible commands, and registers all 20 deprecated commands with `describe: false` and `deprecatedHandler`-wrapped handlers.

See the design doc Phase 4 for the full structure. Every deprecated command must:
- Use the **old** command string (e.g., `'pages [options]'`, `'deploys [options]'`, `'create-deploy [options]'`)
- Set `describe: false`
- Use `builder` from the original module
- Wrap `handler` with `deprecatedHandler(oldPath, newPath, originalHandler)`

Full list of 20 deprecated entries:

| Old command string | Old path | New path |
|---|---|---|
| `pages [options]` | `site pages` | `synthetic pages` |
| `create-page <name> [options]` | `site create-page` | `synthetic create-page` |
| `update-page [options]` | `site update-page` | `synthetic update-page` |
| `delete-page [options]` | `site delete-page` | `synthetic delete-page` |
| `snapshots [options]` | `site snapshots` | `synthetic snapshots` |
| `create-snapshot [options]` | `site create-snapshot` | `synthetic create-snapshot` |
| `delete-snapshot [options]` | `site delete-snapshot` | `synthetic delete-snapshot` |
| `download-snapshot-artifacts [options]` | `site download-snapshot-artifacts` | `synthetic download-artifacts` |
| `get-snapshot-metrics [options]` | `site get-snapshot-metrics` | `synthetic get-snapshot-metrics` |
| `metrics [options]` | `site metrics` | `synthetic metrics` |
| `test-profiles [options]` | `site test-profiles` | `synthetic test-profiles` |
| `create-test-profile <name> [options]` | `site create-test-profile` | `synthetic create-test-profile` |
| `update-test-profile [options]` | `site update-test-profile` | `synthetic update-test-profile` |
| `delete-test-profile [options]` | `site delete-test-profile` | `synthetic delete-test-profile` |
| `pull-request-reviews [options]` | `site pull-request-reviews` | `synthetic pull-request-reviews` |
| `create-pull-request-review [options]` | `site create-pull-request-review` | `synthetic create-pull-request-review` |
| `pull-request-review <branch>` | `site pull-request-review` | `synthetic pull-request-review` |
| `deploys [options]` | `site deploys` | `deploy list` |
| `create-deploy [options]` | `site create-deploy` | `deploy create` |
| `delete-deploy [options]` | `site delete-deploy` | `deploy delete` |

**Step 2: Update cli-commands.js**

Add imports for `Synthetic`, `Deploy` (and stub imports for `Crux`, `Rum` — these will be created in Tasks 9-10 but we need the import to exist). For now, only add `Synthetic` and `Deploy` — `Crux` and `Rum` will be added in their respective tasks.

```javascript
import * as ConnectionList from './cli/connection-list.js'
import * as DeviceList from './cli/device-list.js'
import * as LocationList from './cli/location-list.js'
import * as MetricList from './cli/metric-list.js'
import * as Request from './cli/request.js'
import * as Site from './cli/site.js'
import * as Synthetic from './cli/synthetic.js'
import * as Deploy from './cli/deploy.js'
import * as Team from './cli/team.js'
import * as Test from './cli/test.js'
import * as Token from './cli/token.js'

const commands = [
  Site,
  Synthetic,
  Deploy,
  Test,
  Team,
  ConnectionList,
  DeviceList,
  LocationList,
  MetricList,
  Token,
  Request
]

export default commands
```

**Step 3: Lint and test**

Run: `npm run lint && npm test`
Expected: lint passes, existing tests may need snapshot updates (see Task 7)

**Step 4: Commit**

```bash
git add src/cli/site.js src/cli-commands.js
git commit -m "feat: wire deprecated aliases in site.js, add synthetic and deploy to cli-commands"
```

---

### Task 7: Update existing tests for moved commands

**Files:**
- Move: `__tests__/cli/site/snapshots.test.js` → `__tests__/cli/synthetic/snapshots.test.js`
- Move: `__tests__/cli/site/pages.test.js` → `__tests__/cli/synthetic/pages.test.js`
- Move: `__tests__/cli/site/get-snapshot-metrics.test.js` → `__tests__/cli/synthetic/get-snapshot-metrics.test.js`
- Create: `__tests__/cli/deprecation.test.js`

**Step 1: Move test files and update command paths**

```bash
mkdir -p __tests__/cli/synthetic
git mv __tests__/cli/site/snapshots.test.js __tests__/cli/synthetic/snapshots.test.js
git mv __tests__/cli/site/pages.test.js __tests__/cli/synthetic/pages.test.js
git mv __tests__/cli/site/get-snapshot-metrics.test.js __tests__/cli/synthetic/get-snapshot-metrics.test.js
```

In each moved test file, update the CLI args from `'site <subcommand>'` to `'synthetic <subcommand>'`. For example in `snapshots.test.js`:
- Change `args: 'site snapshots --site=test'` to `args: 'synthetic snapshots --site=test'`

In `pages.test.js`:
- Change `args: 'site pages --site=test'` to `args: 'synthetic pages --site=test'`

In `get-snapshot-metrics.test.js`:
- Change `args: 'site get-snapshot-metrics --site=test --snapshot=1000'` to `args: 'synthetic get-snapshot-metrics --site=test --snapshot=1000'`

**Step 2: Delete old snapshots and regenerate**

```bash
rm -rf __tests__/cli/synthetic/__snapshots__
npm test -- --updateSnapshot __tests__/cli/synthetic/
```

**Step 3: Write deprecation integration test**

Create `__tests__/cli/deprecation.test.js`:

```javascript
import {
  runCLI,
  setupIntegrationServer,
  teardownIntegrationServer
} from '../utils'

import listPages from '../fixtures/listPages.json'

describe('deprecated commands', () => {
  beforeAll(async () => await setupIntegrationServer(listPages))
  afterAll(async () => await teardownIntegrationServer())

  test('site pages shows deprecation warning on stderr', async () => {
    const stderr = await runCLI({
      args: 'site pages --site=test',
      testForError: true
    })
    expect(stderr).toContain('[calibre:deprecated]')
    expect(stderr).toContain('synthetic pages')
  })

  test('site pages still returns valid output on stdout', async () => {
    const stdout = await runCLI({
      args: 'site pages --site=test'
    })
    expect(stdout).toBeTruthy()
  })
})
```

**Step 4: Run all tests**

Run: `npm test`
Expected: PASS

**Step 5: Commit**

```bash
git add -A
git commit -m "test: update tests for synthetic/deploy restructure, add deprecation tests"
```

---

### Task 8: Enhance site list with monitoring status

**Files:**
- Modify: `src/api/site.js` (LIST_QUERY)
- Modify: `src/cli/site/list.js` (table output)

**Step 1: Update LIST_QUERY in `src/api/site.js`**

Add `monitoringStatus` to the query:

```graphql
query {
  organisation {
    sites {
      name
      slug
      createdAt

      team {
        name
        slug
      }

      monitoringStatus {
        synthetic
        crux
        rum
      }
    }
  }
}
```

**Step 2: Update `src/cli/site/list.js` table**

Add a `monitoring` column that shows active statuses. In the `rows` map:

```javascript
const rows = index.map(row => {
  const statuses = []
  if (row.monitoringStatus?.synthetic) statuses.push(chalk.green('synthetic'))
  if (row.monitoringStatus?.crux) statuses.push(chalk.green('crux'))
  if (row.monitoringStatus?.rum) statuses.push(chalk.green('rum'))

  return {
    slug: chalk.grey(row.slug),
    name: row.name,
    monitoring: statuses.join(' ') || chalk.grey('—'),
    created: `${dateFormat(new Date(row.createdAt), 'h:mma d-MMM-yyyy')}`
  }
})
```

JSON output passes through the raw API response unchanged (it already includes `monitoringStatus`).

**Step 3: Lint and test**

Run: `npm run lint && npm test`
Expected: PASS

**Step 4: Commit**

```bash
git add src/api/site.js src/cli/site/list.js
git commit -m "feat: show monitoring status (synthetic/crux/rum) in site list"
```

---

### Task 9: Implement CrUX API module and commands

**Files:**
- Create: `src/api/crux.js`
- Create: `src/cli/crux.js`
- Create: `src/cli/crux/summary.js`
- Create: `src/cli/crux/history.js`
- Create: `src/cli/crux/urls.js`
- Create: `src/cli/crux/url.js`
- Create: `__tests__/fixtures/cruxSummary.json`
- Create: `__tests__/cli/crux/summary.test.js`
- Modify: `src/cli-commands.js` (add Crux)

This is a large task. See the design doc Phases 8 for full GraphQL queries (derived from the queries provided in the user's initial message). Each command follows the exact same pattern as existing commands: spinner → API call → format output (JSON/CSV/table).

**Step 1: Create API module `src/api/crux.js`**

Four functions: `summary`, `history`, `urls`, `url`. Each builds a GraphQL query string and calls `request()`. Query shapes are derived from `GetCruxData`, `ListCruxUrls`, and `GetCruxUrlData` as provided by the user.

Map `--form-factor` CLI values to GraphQL enum: `desktop` → `DESKTOP`, `phone` → `PHONE`, `tablet` → `TABLET`.

Map `--time-period` CLI values to GraphQL enum: `three-months` → `THREE_MONTHS`, etc.

**Step 2: Create CLI commands**

Each command file in `src/cli/crux/` follows the standard pattern:
- Import from `../../api/crux.js`
- Import `options` from `../../utils/cli.js` and `cruxOptions` from `../../utils/crux-options.js`
- Export `command`, `describe`, `builder`, `handler`
- Handler: spinner → API call → JSON/CSV/table output

Key details per command:
- `summary.js`: describe = `'Display Chrome UX Report (CrUX) origin-level performance data and Core Web Vitals assessment.'`
- `history.js`: describe = `'Display Chrome UX Report (CrUX) historical trends for a site.'`; add `--limit` option (default 25)
- `urls.js`: describe = `'List Chrome UX Report (CrUX) monitored URLs with their metrics and Core Web Vitals assessment.'`
- `url.js`: command = `'url <uuid> [options]'`; describe = `'Display Chrome UX Report (CrUX) data for a specific monitored URL.'`

When API returns null/empty data, print: `No CrUX data available for this site. CrUX requires sufficient Chrome user traffic.`

**Step 3: Create group router `src/cli/crux.js`**

```javascript
import * as Summary from './crux/summary.js'
import * as History from './crux/history.js'
import * as Urls from './crux/urls.js'
import * as Url from './crux/url.js'

const commands = [Summary, History, Urls, Url]

const command = 'crux <command>'
const desc =
  'Chrome UX Report (CrUX) — real-world performance data from Chrome users.'
const builder = yargs => {
  return yargs.commands(commands)
}
const handler = () => {}

export { command, desc, builder, handler, commands }
```

**Step 4: Add Crux to cli-commands.js**

Add `import * as Crux from './cli/crux.js'` and add `Crux` to the commands array (after `Deploy`).

**Step 5: Create test fixtures and tests**

Create `__tests__/fixtures/cruxSummary.json` with a mock response matching the `GetCruxData` query shape. Write `__tests__/cli/crux/summary.test.js` following the existing test pattern (mock server, `runCLI`, snapshot).

**Step 6: Lint and test**

Run: `npm run lint && npm test`
Expected: PASS

**Step 7: Commit**

```bash
git add -A
git commit -m "feat: add CrUX commands (summary, history, urls, url)"
```

---

### Task 10: Implement RUM API module and commands

**Files:**
- Create: `src/api/rum.js`
- Create: `src/cli/rum.js`
- Create: `src/cli/rum/summary.js`
- Create: `src/cli/rum/history.js`
- Create: `src/cli/rum/pages.js`
- Create: `src/cli/rum/config.js`
- Create: `__tests__/fixtures/rumSummary.json`
- Create: `__tests__/cli/rum/summary.test.js`
- Modify: `src/cli-commands.js` (add Rum)

Follows the same pattern as Task 9.

**Step 1: Create API module `src/api/rum.js`**

Four functions: `summary`, `history`, `pages`, `config`. Query shapes from `GetSiteRumDashboard`, `GetSiteRumDashboardHistory`, `GetSiteRumPages`, and site rumConfig field.

Build the `RumFilterInput` from CLI flags:
- `--duration` → `filter.duration` (number)
- `--date-bin` → `filter.dateBin` (`day` → `DAY`, `month` → `MONTH`)
- `--country` → `filter.countryCode` (array)
- `--device` → `filter.isDesktopDevice`/`isMobileDevice`/`isTabletDevice` (boolean flags)
- `--path` → `filter.path` (array)
- `--page-grouping` → passed as `pageGroupingUuids` variable

Metrics default: `['lcp', 'cls', 'inp', 'ttfb', 'fcp', 'rtt']`

**Step 2: Create CLI commands**

Each command in `src/cli/rum/`:
- `summary.js`: Display live visitors, countries, aggregate metrics, UX ratings. Table format: `Metric | p75 | Rating | Good% | NI% | Poor%`
- `history.js`: Display per-date metrics. Add `--limit` (default 25). Table format: `date | lcp | cls | inp | ttfb | fcp | sessions`
- `pages.js`: Display page-level breakdown. Options: `--sort-by` (default `sessionCount`), `--limit` (default 25), `--offset` (default 0). Pagination hint on truncated output.
- `config.js`: Display RUM configuration. Simple key-value output.

When API returns null/empty: print `No RUM data available. Check that RUM is enabled for this site with: calibre rum config --site=<slug>`

**Step 3: Create group router `src/cli/rum.js`**

```javascript
import * as Summary from './rum/summary.js'
import * as History from './rum/history.js'
import * as Pages from './rum/pages.js'
import * as Config from './rum/config.js'

const commands = [Summary, History, Pages, Config]

const command = 'rum <command>'
const desc =
  'Real User Metrics (RUM) — field performance data from your real users.'
const builder = yargs => {
  return yargs.commands(commands)
}
const handler = () => {}

export { command, desc, builder, handler, commands }
```

**Step 4: Add Rum to cli-commands.js**

Add `import * as Rum from './cli/rum.js'` and add `Rum` to the commands array (after `Crux`).

**Step 5: Create test fixtures and tests**

**Step 6: Lint and test**

Run: `npm run lint && npm test`
Expected: PASS

**Step 7: Commit**

```bash
git add -A
git commit -m "feat: add RUM commands (summary, history, pages, config)"
```

---

### Task 11: Enhance metric-list with --type flag

**Files:**
- Modify: `src/api/metric.js`
- Modify: `src/cli/metric-list.js`

**Step 1: Add CrUX and RUM query support to `src/api/metric.js`**

Add new queries for `cruxMetrics` and `rumMetrics` root fields. Add a `type` parameter to the `list` function that selects which query to run.

**Step 2: Add --type flag to metric-list.js builder**

```javascript
type: {
  describe: 'Filter metrics by data source.',
  choices: ['synthetic', 'crux', 'rum']
}
```

Pass `args.type` to the API `list()` call.

**Step 3: Lint and test**

Run: `npm run lint && npm test`
Expected: PASS

**Step 4: Commit**

```bash
git add src/api/metric.js src/cli/metric-list.js
git commit -m "feat: add --type flag to metric-list for filtering by data source"
```

---

### Task 12: Update Node.js API exports

**Files:**
- Modify: `index.js`

**Step 1: Add new exports**

```javascript
export * as Crux from './src/api/crux.js'
export * as Rum from './src/api/rum.js'
```

**Step 2: Build**

Run: `npm run build`
Expected: esbuild bundles `dist/index.cjs` without errors

**Step 3: Commit**

```bash
git add index.js
git commit -m "feat: export Crux and Rum from Node.js API"
```

---

### Task 13: Update documentation

**Files:**
- Modify: `README.md`
- Modify: `package.json` (version, description, keywords)
- Modify: `CHANGELOG.md`
- Create: `examples/bash/crux-summary.sh`
- Create: `examples/bash/rum-pages.sh`
- Create: `examples/bash/deploy-create.sh`
- Modify: `examples/bash/README.md`
- Create: `examples/nodejs/crux/summary.js`
- Create: `examples/nodejs/crux/history.js`
- Create: `examples/nodejs/crux/urls.js`
- Create: `examples/nodejs/rum/summary.js`
- Create: `examples/nodejs/rum/pages.js`
- Create: `examples/nodejs/rum/config.js`
- Modify: `examples/nodejs/README.md`
- Delete: `CHANGELOG-v7-DRAFT.md`

**Step 1: Update package.json**

- `"version": "7.0.0"`
- `"description": "Performance monitoring with Synthetic testing, Chrome UX Report, and Real User Metrics"`
- Add keywords: `"rum"`, `"crux"`, `"web-vitals"`, `"real-user-monitoring"`

**Step 2: Update README.md**

- Update features to mention three pillars
- Update usage examples with new namespaces
- Update package exports to show `Crux` and `Rum`

**Step 3: Update CHANGELOG.md**

Prepend content from `CHANGELOG-v7-DRAFT.md` to `CHANGELOG.md`. Delete `CHANGELOG-v7-DRAFT.md`.

**Step 4: Create bash examples**

Each example script follows the pattern in `examples/bash/create-test.sh`: set `-euo pipefail`, require `CALIBRE_API_TOKEN`, call CLI with `--json`, pipe through `jq`.

**Step 5: Create Node.js examples**

Each example follows the pattern in existing `examples/nodejs/` files: import from `'calibre'`, call API function, log result.

**Step 6: Update example READMEs**

Add new examples to the listings in both `examples/bash/README.md` and `examples/nodejs/README.md`.

**Step 7: Regenerate CLI_COMMANDS.md**

Run: `npm run generate-cli-md`

Verify:
- No deprecated `site <command>` entries
- All new commands present
- `site` section shows only `create`, `list`, `delete`

**Step 8: Commit**

```bash
git add -A
git commit -m "docs: update README, CHANGELOG, examples, and CLI_COMMANDS.md for v7.0.0"
```

---

### Task 14: Final verification

**Step 1: Run full lint**

Run: `npm run lint`
Expected: Zero warnings, zero errors

**Step 2: Run full test suite**

Run: `npm test`
Expected: All tests pass

**Step 3: Run build**

Run: `npm run build`
Expected: `dist/index.cjs` built successfully

**Step 4: Regenerate and diff CLI_COMMANDS.md**

Run: `npm run generate-cli-md`
Run: `git diff CLI_COMMANDS.md`

Verify no deprecated commands appear, all new commands are present.

**Step 5: Smoke test help output**

Run: `node src/cli.js --help`
Verify: `synthetic`, `deploy`, `crux`, `rum` appear. No deprecated entries.

Run: `node src/cli.js site --help`
Verify: Only `create`, `list`, `delete` shown.

Run: `node src/cli.js synthetic --help`
Verify: All 17 subcommands listed.

**Step 6: Smoke test deprecation**

Run: `node src/cli.js site pages --site=test 2>&1 | head -1`
Verify: `[calibre:deprecated] "site pages" has moved to "synthetic pages"`

**Step 7: Commit any final adjustments**

```bash
git add -A
git commit -m "chore: final verification and cleanup for v7.0.0"
```
