Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/cli/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ For parent commands with subcommands, use `noop` from `@spotify-confidence/share
### Command Types

- **Standalone** — `login`, `logout`, `whoami`, `config`, `update` — directly perform their action
- **API commands** — `flags`, `events`, `recordings`, `docs` — CRUD operations against Confidence APIs via `@network/*`
- **API commands** — `flags`, `events`, `recordings`, `docs`, `warehouse` — CRUD operations against Confidence APIs via `@network/*`
- **Integration** — `mcp`, `plugin`, `sdk`, `migrate` — manage IDE tooling and SDK setup
- **Setup** — `flags setup`, `events setup`, `recordings setup` — delegate to quickstart TUI with pre-selected features
- **TUI launcher** — `quickstart` — launches the full interactive wizard
Expand Down
20 changes: 10 additions & 10 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ CLI tools for setting up and integrating [Confidence](https://confidence.spotify

pnpm workspace with six packages under `packages/`:

| Package | Published | Purpose |
| ------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `packages/shared-kernel/` | No (private) | Cross-domain types (`AuthState`, `IdeId`, `OnboardingGoal`, etc.) and helpers (`noop`, `isDefined`). No runtime dependencies. |
| `packages/eslint-config/` | No (private) | Shared ESLint configuration. Exports base preset and `/react` preset with React Hooks rules. |
| `packages/core/` | No (private) | Shared infrastructure — api, auth, config, session, telemetry, exec, system, sdk, mcp, utils, constants, frameworks, integrations, providers. Depends on `shared-kernel`. |
| Package | Published | Purpose |
| ------------------------- | -------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `packages/shared-kernel/` | No (private) | Cross-domain types (`AuthState`, `IdeId`, `OnboardingGoal`, etc.) and helpers (`noop`, `isDefined`). No runtime dependencies. |
| `packages/eslint-config/` | No (private) | Shared ESLint configuration. Exports base preset and `/react` preset with React Hooks rules. |
| `packages/core/` | No (private) | Shared infrastructure — api, auth, config, session, telemetry, exec, system, sdk, mcp, utils, constants, frameworks, integrations, providers. Depends on `shared-kernel`. |
| `packages/testing/` | No (private) | Test infrastructure — auth scaffolds, project scaffolds, env helpers, terminal helpers, MSW handlers. Sub-path exports: `/auth`, `/scaffold`, `/env`, `/terminal`, `/msw`, `/e2e`. Depends on `shared-kernel`. |
| `packages/quickstart/` | Yes (`@spotify-confidence/quickstart`) | Interactive TUI wizard. Depends on `core` and `shared-kernel`. |
| `packages/cli/` | Yes (`@spotify-confidence/cli`) | CLI for managing Confidence (flags, events, recordings, config). Depends on `quickstart`. |
| `packages/quickstart/` | Yes (`@spotify-confidence/quickstart`) | Interactive TUI wizard. Depends on `core` and `shared-kernel`. |
| `packages/cli/` | Yes (`@spotify-confidence/cli`) | CLI for managing Confidence (flags, events, recordings, config). Depends on `quickstart`. |

### Dependency graph

Expand Down Expand Up @@ -53,11 +53,11 @@ shared-kernel (types-only leaf)
### packages/cli/ structure

- **`bin/cli.ts`** — Entry point (yargs, `confidence` binary)
- **`src/commands/`** — Command definitions (login, logout, whoami, config, flags, events, recordings, docs, mcp, plugin, sdk, migrate, update, quickstart)
- **`src/features/`** — Feature implementations (config, docs, events, flags, ide, mcp, migrate, plugin, quickstart, recordings, sdk, update)
- **`src/commands/`** — Command definitions (login, logout, whoami, config, flags, events, recordings, docs, mcp, plugin, sdk, migrate, update, quickstart, warehouse)
- **`src/features/`** — Feature implementations (config, docs, events, flags, ide, mcp, migrate, plugin, quickstart, recordings, sdk, update, warehouse)
- **`src/input/`** — Input parsing (file reading, aliases, resolve)
- **`src/output/`** — Output formatters (json, table, format detection)
- **`src/network/`** — API clients (flags, events, recordings, docs, config, registry)
- **`src/network/`** — API clients (flags, events, recordings, docs, config, registry, warehouse)
- **`src/utils/`** — Shared utilities (auth, safely, telemetry, validation)

## Key Patterns
Expand Down
11 changes: 11 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ confidence quickstart # Launch the interactive setup wizard
| `confidence recordings targeting-key show <client>` | Show available targeting keys |
| `confidence recordings targeting-key add` | Add a targeting key to a client |

### Data Warehouses

| Command | Description |
| ---------------------------------------------------- | -------------------------------------------------- |
| `confidence warehouse validate` | Validate warehouse configuration |
| `confidence warehouse create` | Create a data warehouse connection |
| `confidence warehouse connector create-flag-applied` | Create flag assignment data connector |
| `confidence warehouse connector create-event` | Create event data connector |
| `confidence warehouse assignment-table create` | Create an assignment table for experiment analysis |
| `confidence warehouse crypto-key create` | Create a crypto key (Snowflake) |

### MCP Servers

| Command | Description |
Expand Down
120 changes: 120 additions & 0 deletions packages/cli/__tests__/commands/warehouse-setup.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { buildTestJwt, prepareAuthTokens } from '@spotify-confidence/testing/auth';
import { warehouseCommand } from '@commands/warehouse.js';
import { captureOutput } from '../helpers/capture.js';
import { createMockIntegration } from '../helpers/mock-integration.js';
import { createRunner } from '../helpers/run-command.js';

const mockIntegration = createMockIntegration({
detectPlugin: vi.fn().mockResolvedValue('cli'),
detectMcpStatuses: vi.fn().mockResolvedValue({
'confidence-flags': 'connected',
'confidence-docs': 'connected',
}),
});

const mockAuthenticate = vi.fn().mockResolvedValue({
accessToken: buildTestJwt({ email: 'test@example.com' }),
region: 'EU',
});

vi.mock('@spotify-confidence/core', async (importOriginal) => {
const actual = await importOriginal<typeof import('@spotify-confidence/core')>();
return {
...actual,
getIntegration: () => mockIntegration,
getIntegrations: () => [mockIntegration],
authenticate: (...args: unknown[]) => mockAuthenticate(...args),
};
});

const run = createRunner(warehouseCommand);

describe('warehouse setup', () => {
it('launches chat session with correct skill for bigquery', async () => {
using _auth = prepareAuthTokens('valid');
using _output = captureOutput();

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(mockIntegration.launchChat).toHaveBeenCalledWith(
expect.objectContaining({
userPrompt: expect.stringContaining('setup-warehouse-bigquery'),
cwd: process.cwd(),
}),
);
});

it('launches chat session with correct skill for snowflake', async () => {
using _auth = prepareAuthTokens('valid');
using _output = captureOutput();

await run(['warehouse', 'setup', '--warehouse-type', 'snowflake', '--ide', 'claude']);

expect(mockIntegration.launchChat).toHaveBeenCalledWith(
expect.objectContaining({
userPrompt: expect.stringContaining('setup-warehouse-snowflake'),
}),
);
});

it('passes auth token to chat session', async () => {
using _auth = prepareAuthTokens('valid');
using _output = captureOutput();

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(mockIntegration.launchChat).toHaveBeenCalledWith(
expect.objectContaining({ token: expect.any(String) }),
);
});

it('fails when plugin is not installed', async () => {
using _auth = prepareAuthTokens('valid');
using output = captureOutput();
vi.mocked(mockIntegration.detectPlugin).mockResolvedValueOnce(null);

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(output.stderr).toContain('Confidence AI plugin not installed');
expect(mockIntegration.launchChat).not.toHaveBeenCalled();
});

it('fails when MCP servers are not installed', async () => {
using _auth = prepareAuthTokens('valid');
using output = captureOutput();
vi.mocked(mockIntegration.detectMcpStatuses).mockResolvedValueOnce({
'confidence-flags': 'not-installed',
'confidence-docs': 'not-installed',
});

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(output.stderr).toContain('MCP servers not installed');
expect(mockIntegration.launchChat).not.toHaveBeenCalled();
});

it('fails when MCP auth is expired', async () => {
using _auth = prepareAuthTokens('valid');
using output = captureOutput();
vi.mocked(mockIntegration.detectMcpStatuses).mockResolvedValueOnce({
'confidence-flags': 'auth-expired',
'confidence-docs': 'connected',
});

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(output.stderr).toContain('MCP server auth expired');
expect(mockIntegration.launchChat).not.toHaveBeenCalled();
});

it('fails when authentication is unavailable', async () => {
using _auth = prepareAuthTokens('none');
using output = captureOutput();
mockAuthenticate.mockRejectedValueOnce(new Error('Authentication timed out'));

await run(['warehouse', 'setup', '--warehouse-type', 'bigquery', '--ide', 'claude']);

expect(output.stderr).toContain('Authentication failed');
expect(mockIntegration.launchChat).not.toHaveBeenCalled();
});
});
Loading
Loading