Skip to content

Commit 5d2ebdc

Browse files
bespoyasovclaude
andauthored
feat: introduce quality-of-life commands and flags, update docs (#101)
* feat: implement quality of life commands and flags * docs: update Readme files * chore: clean up * fix(cli): inline version at build time to fix bundled binary crash The previous require('../package.json') resolved against import.meta.url, which points to dist/bin/ after bundling — where no package.json exists. Use tsdown define to inline the version as a string literal at build time, with a createRequire fallback for the unbundled dev path (tree-shaken from the production bundle). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): use semver comparison for update check String equality treated any local version ahead of npm (prereleases, local builds) as outdated, causing a downgrade to @latest. Compare numerically so the update only runs when the registry version is newer. Extract isNewerVersion into core/utils for reuse. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): detect package manager from install path, not user agent The user agent describes the PM running the current process, not the one that installed the CLI. This caused cross-PM updates (e.g. running confidence update inside a pnpm script when installed via npm). Now the install path (process.argv[1]) is the primary signal, and the user agent is only used to detect ephemeral runners (npx, pnpm dlx, yarn dlx). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): handle Yarn Berry in update command Yarn Berry (v2+) removed global installs, so `yarn global add` fails with a confusing error. Detect the Yarn major version and show manual npm instructions instead. Also fix the Yarn Classic command from `yarn add -g` to `yarn global add`. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(cli): replace sudo hint with npm permissions docs link Suggesting sudo leaves root-owned files in the npm prefix and causes more permission errors later. Point users to npm's official guide for fixing global install permissions instead. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(cli): add @meta path alias for cross-domain meta imports Replace relative imports of src/meta.ts with the @meta path alias, consistent with the repo convention of using aliases for cross-domain imports. Register the alias in tsconfig.json and vitest.config.ts. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: fetch directly from registry, refactor tests --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6a29662 commit 5d2ebdc

31 files changed

Lines changed: 703 additions & 112 deletions

File tree

‎README.md‎

Lines changed: 28 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,42 @@
22
<img src="assets/logo.svg" alt="Confidence" width="120" />
33
</p>
44

5-
<h1 align="center">Confidence Quickstart</h1>
5+
<h1 align="center">Confidence CLI</h1>
66

77
<p align="center">
8-
Get up and running with <a href="https://confidence.spotify.com">Confidence</a> in minutes. One command sets up authentication, installs the right SDK, connects MCP servers, and onboards your AI assistant — so you can start managing feature flags and experiments without leaving your editor.
8+
CLI tools for setting up and managing <a href="https://confidence.spotify.com">Confidence</a> feature flags, experiments, and integrations.
99
</p>
1010

1111
<p align="center">
1212
<a href="https://confidence.spotify.com/docs/introduction"><img alt="Docs" src="https://img.shields.io/badge/docs-confidence.spotify.com-6E56CF"></a>
1313
<a href="./LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"></a>
14-
<a href="./CHANGELOG.md"><img alt="npm version" src="https://img.shields.io/npm/v/@spotify-confidence/quickstart?color=informational"></a>
1514
</p>
1615

17-
## Highlights
16+
## Packages
1817

19-
- **Zero-to-flags in one command** — run `npx @spotify-confidence/quickstart` and the interactive wizard handles the rest
20-
- **Auto-detects your stack** — React, Next.js, Node, Python, Swift, Kotlin, Java, and Go; installs the matching Confidence SDK
21-
- **Connects your AI assistant** — configures MCP servers and IDE plugins for Claude Code, Cursor, or Codex so your agent can manage flags inline
22-
- **Guided onboarding** — generates project context files that teach your AI assistant how your Confidence setup works
23-
- **Migrating from another platform?** Pair with [Confidence AI Plugins](https://github.com/spotify/confidence-ai-plugins) for one-command migrations from PostHog, Eppo, Statsig, or Optimizely
18+
This monorepo contains two published packages:
2419

25-
## Quick Start
20+
| Package | Description | |
21+
| ------- | ----------- | --- |
22+
| [`@spotify-confidence/cli`](./packages/cli/) | CLI for managing Confidence — flags, events, recordings, config, MCP | [![npm](https://img.shields.io/npm/v/@spotify-confidence/cli?color=informational)](https://www.npmjs.com/package/@spotify-confidence/cli) |
23+
| [`@spotify-confidence/quickstart`](./packages/quickstart/) | Interactive TUI wizard for first-time project setup | [![npm](https://img.shields.io/npm/v/@spotify-confidence/quickstart?color=informational)](https://www.npmjs.com/package/@spotify-confidence/quickstart) |
24+
25+
## Get Started
26+
27+
**Set up a new project** with the interactive wizard:
2628

2729
```bash
2830
npx @spotify-confidence/quickstart
2931
```
3032

33+
**Or install the CLI** for ongoing management:
34+
35+
```bash
36+
npm install -g @spotify-confidence/cli
37+
confidence login
38+
confidence flags setup
39+
```
40+
3141
Requires Node.js 24+.
3242

3343
## Confidence AI Plugins
@@ -47,57 +57,6 @@ Found a bug or have a feature request? [Open an issue](https://github.com/spotif
4757

4858
---
4959

50-
## Usage
51-
52-
```
53-
confidence-quickstart [command] [options]
54-
```
55-
56-
### Commands
57-
58-
| Command | Description |
59-
| ----------- | ----------------------------------- |
60-
| _(default)_ | Launch the interactive setup wizard |
61-
| `start` | Alias for the default command |
62-
| `help` | Show the help message |
63-
64-
### Options
65-
66-
| Option | Description |
67-
| ---------------- | ------------------------------------------------- |
68-
| `--dir <path>` | Project directory to run the wizard in |
69-
| `--dry-run` | Run without making real API calls |
70-
| `--debug` | Enable debug output and preserve terminal history |
71-
| `--no-telemetry` | Disable anonymous usage telemetry |
72-
73-
## Security Note
74-
75-
During the **project onboarding** step, the wizard spawns your chosen AI agent to integrate the Confidence SDK into your project. You will be prompted to confirm before this step begins. Each agent runs with different permissions:
76-
77-
- **Claude Code** — Spawned in non-interactive (`--print`) mode using its standard permission model. It can read files and execute tools within your project directory as allowed by your existing Claude Code settings. No elevated trust flags are applied.
78-
- **Cursor** — Spawned in agent mode with `--trust`, which grants full read/write access to files in your project directory and allows it to run shell commands (e.g. `npm install`) without individual approval prompts. MCP server connections are also auto-approved (`--approve-mcps`).
79-
- **Codex** — Spawned in `exec` mode with `--sandbox danger-full-access`, which grants unrestricted filesystem and network access. It can read and write any file, run shell commands, and make network requests without sandboxing restrictions. Full access is required because the onboarding agent needs to install npm packages, modify project configuration files, and download SDK dependencies — operations that Codex's default sandbox would block.
80-
81-
All agents are scoped to your project directory and run with a timeout. The wizard installs a Confidence skill/plugin and connects MCP servers _before_ spawning the agent, so configuration files may already be written to your project at that point.
82-
83-
## Telemetry
84-
85-
The wizard collects anonymous usage data (e.g. which steps you complete) to help improve the experience. No personal or project data is collected.
86-
87-
To opt out:
88-
89-
```bash
90-
npx @spotify-confidence/quickstart --no-telemetry
91-
```
92-
93-
Or set the environment variable:
94-
95-
```bash
96-
CONFIDENCE_TELEMETRY=false npx @spotify-confidence/quickstart
97-
```
98-
99-
Telemetry is automatically disabled in CI environments and during development.
100-
10160
## Development
10261

10362
On Windows, clone with symlinks enabled so `CLAUDE.md` is not checked out as a copy (which breaks Prettier):
@@ -109,13 +68,14 @@ git clone -c core.symlinks=true https://github.com/spotify/confidence-cli.git
10968
If the repo is already cloned, run `git checkout -- CLAUDE.md` after `git config core.symlinks true`.
11069

11170
```bash
112-
pnpm install
113-
pnpm try # Run the wizard locally via tsx
114-
pnpm test # Run vitest
115-
pnpm lint # ESLint + Prettier check
116-
pnpm typecheck # TypeScript type checking
117-
pnpm qa # Run all checks (typecheck + lint + test)
118-
pnpm build # Build for distribution
71+
pnpm install # Install all workspace deps
72+
pnpm --filter @spotify-confidence/quickstart try # Run the wizard locally via tsx
73+
pnpm --filter @spotify-confidence/cli try # Run the CLI locally via tsx
74+
pnpm test # Run all tests
75+
pnpm lint # ESLint + Prettier check
76+
pnpm typecheck # TypeScript type checking
77+
pnpm qa # Run all checks (typecheck + lint + test)
78+
pnpm build # Build all packages
11979
```
12080

12181
### Troubleshooting

‎packages/cli/README.md‎

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
<p align="center">
2+
<img src="../../assets/logo.svg" alt="Confidence" width="120" />
3+
</p>
4+
5+
<h1 align="center">Confidence CLI</h1>
6+
7+
<p align="center">
8+
Manage <a href="https://confidence.spotify.com">Confidence</a> feature flags, events, session recordings, and MCP integrations from the command line.
9+
</p>
10+
11+
<p align="center">
12+
<a href="https://confidence.spotify.com/docs/introduction"><img alt="Docs" src="https://img.shields.io/badge/docs-confidence.spotify.com-6E56CF"></a>
13+
<a href="../../LICENSE"><img alt="License" src="https://img.shields.io/badge/license-Apache--2.0-blue"></a>
14+
<a href="./CHANGELOG.md"><img alt="npm version" src="https://img.shields.io/npm/v/@spotify-confidence/cli?color=informational"></a>
15+
</p>
16+
17+
## Install
18+
19+
```bash
20+
npm install -g @spotify-confidence/cli
21+
```
22+
23+
Requires Node.js 24+.
24+
25+
## Quick Start
26+
27+
```bash
28+
confidence login # Sign in via browser OAuth
29+
confidence flags setup # Set up feature flags in your project
30+
confidence events list # List all event definitions
31+
confidence quickstart # Launch the interactive setup wizard
32+
```
33+
34+
## Commands
35+
36+
### Authentication
37+
38+
| Command | Description |
39+
| ------------------- | ------------------------------------------------ |
40+
| `confidence login` | Sign in to Confidence via browser OAuth |
41+
| `confidence logout` | Clear stored credentials |
42+
| `confidence whoami` | Show current user, org, region, and token expiry |
43+
44+
### Feature Flags
45+
46+
| Command | Description |
47+
| ------------------------ | ------------------------------------------------------ |
48+
| `confidence flags setup` | Set up feature flags in your project via guided wizard |
49+
50+
### Events
51+
52+
| Command | Description |
53+
| -------------------------------------- | --------------------------------------- |
54+
| `confidence events list` | List all event definitions |
55+
| `confidence events create --name <id>` | Create a new event definition |
56+
| `confidence events update <name>` | Update an event definition's schema |
57+
| `confidence events delete <name>` | Delete an event definition |
58+
| `confidence events usage <name>` | Show recent event usage statistics |
59+
| `confidence events setup` | Set up event tracking via guided wizard |
60+
61+
### Session Recordings
62+
63+
| Command | Description |
64+
| --------------------------------------------------- | ----------------------------------- |
65+
| `confidence recordings setup` | Set up recordings via guided wizard |
66+
| `confidence recordings policy list` | List recording policies |
67+
| `confidence recordings policy create` | Create a new recording policy |
68+
| `confidence recordings policy get <policy>` | Show policy details with rules |
69+
| `confidence recordings rule add` | Add a sampling rule to a policy |
70+
| `confidence recordings rule enable <rule>` | Enable a recording rule |
71+
| `confidence recordings rule disable <rule>` | Disable a recording rule |
72+
| `confidence recordings targeting-key show <client>` | Show available targeting keys |
73+
| `confidence recordings targeting-key add` | Add a targeting key to a client |
74+
75+
### MCP Servers
76+
77+
| Command | Description |
78+
| -------------------------- | -------------------------------------------- |
79+
| `confidence mcp install` | Install MCP servers for your AI coding agent |
80+
| `confidence mcp uninstall` | Remove MCP server configuration |
81+
| `confidence mcp status` | Check MCP server connection status |
82+
| `confidence mcp list` | List available MCP servers |
83+
| `confidence mcp auth` | Refresh MCP server credentials |
84+
85+
### Configuration
86+
87+
| Command | Description |
88+
| ----------------------------------- | ------------------------------- |
89+
| `confidence config set <key> <val>` | Set a configuration value |
90+
| `confidence config get <key>` | Get a configuration value |
91+
| `confidence config list` | List all configuration values |
92+
| `confidence config reset` | Reset configuration to defaults |
93+
94+
### Other
95+
96+
| Command | Description |
97+
| ----------------------- | ---------------------------------------------- |
98+
| `confidence quickstart` | Launch the interactive Confidence setup wizard |
99+
| `confidence update` | Update the CLI to the latest version |
100+
| `confidence --help` | Show help with examples |
101+
| `confidence --version` | Show CLI version |
102+
103+
## Global Options
104+
105+
| Option | Description |
106+
| ----------- | ------------------------------------------ |
107+
| `--json` | Force JSON output |
108+
| `--output` | Output format: `json`, `table`, or `plain` |
109+
| `--profile` | Use a named auth profile |
110+
| `--help` | Show help |
111+
| `--version` | Show version number |
112+
113+
## Output Formats
114+
115+
The CLI auto-detects the best output format: tables in interactive terminals, JSON when piped. Override with `--json` or `--output`:
116+
117+
```bash
118+
confidence events list --json # Force JSON
119+
confidence whoami --output plain # Force plain text
120+
confidence events list | jq '.data[].name' # Pipe-friendly JSON
121+
```
122+
123+
## License
124+
125+
[Apache License 2.0](../../LICENSE)
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import {
2+
server,
3+
registryReturns as registryReturnsHandler,
4+
registryFails as registryFailsHandler,
5+
} from '@spotify-confidence/testing';
6+
7+
import { updateCommand } from '@commands/update.js';
8+
import { captureOutput } from '../helpers/capture.js';
9+
import { createRunner } from '../helpers/run-command.js';
10+
11+
const mockExecFile = vi.fn<(...args: unknown[]) => Promise<{ stdout: string; stderr: string }>>();
12+
const mockDetectPM = vi.fn<() => string>();
13+
14+
vi.mock('@spotify-confidence/core', async (importOriginal) => {
15+
const actual = await importOriginal<typeof import('@spotify-confidence/core')>();
16+
return { ...actual, execFile: (...args: unknown[]) => mockExecFile(...args) };
17+
});
18+
19+
vi.mock('../../src/features/update/detect-pm.js', () => ({
20+
detectPackageManager: () => mockDetectPM(),
21+
}));
22+
23+
vi.mock('@meta', () => ({
24+
APP_NAME: 'confidence',
25+
CLI_VERSION: '1.0.0',
26+
}));
27+
28+
vi.mock('ora', () => {
29+
const spinner = {
30+
start: vi.fn().mockReturnThis(),
31+
stop: vi.fn().mockReturnThis(),
32+
succeed: vi.fn().mockReturnThis(),
33+
fail: vi.fn().mockReturnThis(),
34+
};
35+
return { default: () => spinner };
36+
});
37+
38+
const run = createRunner(updateCommand);
39+
40+
beforeEach(() => {
41+
vi.clearAllMocks();
42+
mockDetectPM.mockReturnValue('npm');
43+
});
44+
45+
function registryReturns(version: string) {
46+
server.use(registryReturnsHandler(version));
47+
}
48+
49+
function registryFails() {
50+
server.use(registryFailsHandler());
51+
}
52+
53+
describe('update', () => {
54+
it('reports up to date when versions match', async () => {
55+
using output = captureOutput();
56+
registryReturns('1.0.0');
57+
58+
await run(['update']);
59+
60+
expect(output.stderr).toBe('');
61+
});
62+
63+
it('runs the install command for the detected package manager', async () => {
64+
using _output = captureOutput();
65+
registryReturns('2.0.0');
66+
mockDetectPM.mockReturnValue('npm');
67+
mockExecFile.mockResolvedValueOnce({ stdout: '', stderr: '' });
68+
69+
await run(['update']);
70+
71+
expect(mockExecFile).toHaveBeenCalledWith('npm', [
72+
'install',
73+
'-g',
74+
'@spotify-confidence/cli@latest',
75+
]);
76+
});
77+
78+
it('advises npx users to run the latest version manually', async () => {
79+
using output = captureOutput();
80+
registryReturns('2.0.0');
81+
mockDetectPM.mockReturnValue('npx');
82+
83+
await run(['update']);
84+
85+
expect(output.stdout).toContain('npx');
86+
expect(output.stdout).toContain('@latest');
87+
expect(mockExecFile).not.toHaveBeenCalled();
88+
});
89+
90+
it('reports a friendly error when the registry is unreachable', async () => {
91+
using output = captureOutput();
92+
registryFails();
93+
94+
await run(['update']);
95+
96+
expect(output.stderr).toContain('npm registry');
97+
});
98+
99+
it('advises Yarn Berry users to reinstall with npm', async () => {
100+
using output = captureOutput();
101+
registryReturns('2.0.0');
102+
mockDetectPM.mockReturnValue('yarn');
103+
mockExecFile.mockResolvedValueOnce({ stdout: '4.1.0\n', stderr: '' });
104+
105+
await run(['update']);
106+
107+
expect(output.stdout).toContain('Yarn Berry');
108+
expect(output.stdout).toContain('npm install -g');
109+
expect(mockExecFile).toHaveBeenCalledTimes(1);
110+
});
111+
112+
it('runs yarn global add for Yarn Classic', async () => {
113+
using _output = captureOutput();
114+
registryReturns('2.0.0');
115+
mockDetectPM.mockReturnValue('yarn');
116+
mockExecFile.mockResolvedValueOnce({ stdout: '1.22.19\n', stderr: '' });
117+
mockExecFile.mockResolvedValueOnce({ stdout: '', stderr: '' });
118+
119+
await run(['update']);
120+
121+
expect(mockExecFile).toHaveBeenCalledWith('yarn', [
122+
'global',
123+
'add',
124+
'@spotify-confidence/cli@latest',
125+
]);
126+
});
127+
128+
it('reports permission errors with a link to npm docs', async () => {
129+
using output = captureOutput();
130+
registryReturns('2.0.0');
131+
mockExecFile.mockRejectedValueOnce(new Error('EACCES: permission denied'));
132+
133+
await run(['update']);
134+
135+
expect(output.stderr).toContain('Permission denied');
136+
expect(output.stderr).toContain('docs.npmjs.com');
137+
});
138+
});

0 commit comments

Comments
 (0)