Skip to content

Commit 41ee87b

Browse files
authored
Merge pull request #38 from DGouron/feat/cli-init-wizard
feat(cli): add reviewflow init wizard and validate command
2 parents 5dce13f + 5ca96b6 commit 41ee87b

21 files changed

+1817
-24
lines changed

README.md

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -125,40 +125,65 @@ Review behavior is defined by [Claude Code skills](https://docs.anthropic.com/en
125125

126126
## Quick Start
127127

128-
### Install globally
128+
### 1. Install
129129

130130
```bash
131131
npm install -g reviewflow
132-
# or
133-
yarn global add reviewflow
134132
```
135133

136-
### Or run directly with npx
134+
### 2. Initialize
137135

138136
```bash
139-
npx reviewflow --help
140-
npx reviewflow start
137+
reviewflow init
141138
```
142139

143-
### Configure & run
140+
The interactive wizard will:
141+
- Configure server port and usernames
142+
- Generate webhook secrets
143+
- Scan your filesystem for git repositories
144+
- Set up MCP server integration with Claude Code
144145

145-
```bash
146-
# Configure
147-
cp .env.example .env
148-
cp config.example.json config.json
149-
# Edit config.json with your repositories
146+
For non-interactive setup: `reviewflow init --yes`
147+
148+
### 3. Start
150149

151-
# Start the server
150+
```bash
152151
reviewflow start
153152
# Dashboard at http://localhost:3847
154153
```
155154

156155
Then [configure a webhook](https://dgouron.github.io/review-flow/guide/quick-start) on your GitLab/GitHub project pointing to your server.
157156

157+
### Validate your setup
158+
159+
```bash
160+
reviewflow validate
161+
```
162+
158163
For detailed setup, see the **[Quick Start Guide](https://dgouron.github.io/review-flow/guide/quick-start)**.
159164

160165
---
161166

167+
## CLI Reference
168+
169+
| Command | Description |
170+
|---------|-------------|
171+
| `reviewflow init` | Interactive setup wizard |
172+
| `reviewflow start` | Start the review server |
173+
| `reviewflow stop` | Stop the running daemon |
174+
| `reviewflow status` | Show server status |
175+
| `reviewflow logs` | Show daemon logs |
176+
| `reviewflow validate` | Validate configuration |
177+
178+
| Init Flag | Description |
179+
|-----------|-------------|
180+
| `-y, --yes` | Accept all defaults (non-interactive) |
181+
| `--skip-mcp` | Skip MCP server configuration |
182+
| `--show-secrets` | Display full webhook secrets |
183+
| `--scan-path <path>` | Custom scan path (repeatable) |
184+
185+
---
186+
162187
## Documentation
163188

164189
| Topic | Link |

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"dependencies": {
5757
"@fastify/static": "^8.0.0",
5858
"@fastify/websocket": "^11.0.0",
59+
"@inquirer/prompts": "^8.2.0",
5960
"@modelcontextprotocol/sdk": "^1.26.0",
6061
"dotenv": "^16.4.0",
6162
"fastify": "^5.0.0",

src/cli/formatters/initSummary.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export interface InitSummaryInput {
2+
configPath: string;
3+
envPath: string;
4+
port: number;
5+
repositoryCount: number;
6+
mcpStatus: 'configured' | 'already-configured' | 'claude-not-found' | 'skipped' | 'failed';
7+
gitlabUsername: string;
8+
githubUsername: string;
9+
}
10+
11+
function mcpLine(status: InitSummaryInput['mcpStatus']): string {
12+
switch (status) {
13+
case 'configured':
14+
return ' MCP server: configured';
15+
case 'already-configured':
16+
return ' MCP server: already configured';
17+
case 'claude-not-found':
18+
return ' MCP server: skipped (Claude CLI not found)';
19+
case 'skipped':
20+
return ' MCP server: skipped by user';
21+
case 'failed':
22+
return ' MCP server: configuration failed';
23+
}
24+
}
25+
26+
export function formatInitSummary(input: InitSummaryInput): string {
27+
const lines: string[] = [
28+
'',
29+
'ReviewFlow initialized successfully!',
30+
'',
31+
'Configuration:',
32+
` Config file: ${input.configPath}`,
33+
` Env file: ${input.envPath}`,
34+
` Port: ${input.port}`,
35+
` Repositories: ${input.repositoryCount}`,
36+
mcpLine(input.mcpStatus),
37+
'',
38+
'Next steps:',
39+
' 1. Configure webhook secrets on your GitLab/GitHub projects',
40+
' 2. Start the server: reviewflow start',
41+
' 3. Check status: reviewflow validate',
42+
'',
43+
];
44+
45+
return lines.join('\n');
46+
}

src/cli/parseCliArgs.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ interface LogsArgs {
2222
lines: number;
2323
}
2424

25+
interface InitArgs {
26+
command: 'init';
27+
yes: boolean;
28+
skipMcp: boolean;
29+
showSecrets: boolean;
30+
scanPaths: string[];
31+
}
32+
33+
interface ValidateArgs {
34+
command: 'validate';
35+
fix: boolean;
36+
}
37+
2538
interface VersionArgs {
2639
command: 'version';
2740
}
@@ -30,9 +43,9 @@ interface HelpArgs {
3043
command: 'help';
3144
}
3245

33-
export type CliArgs = StartArgs | StopArgs | StatusArgs | LogsArgs | VersionArgs | HelpArgs;
46+
export type CliArgs = StartArgs | StopArgs | StatusArgs | LogsArgs | InitArgs | ValidateArgs | VersionArgs | HelpArgs;
3447

35-
const KNOWN_COMMANDS = ['start', 'stop', 'status', 'logs'] as const;
48+
const KNOWN_COMMANDS = ['start', 'stop', 'status', 'logs', 'init', 'validate'] as const;
3649
type KnownCommand = (typeof KNOWN_COMMANDS)[number];
3750

3851
function hasFlag(args: string[], long: string, short?: string): boolean {
@@ -90,6 +103,33 @@ function parseLogsArgs(args: string[]): LogsArgs {
90103
};
91104
}
92105

106+
function getAllFlagValues(args: string[], long: string): string[] {
107+
const values: string[] = [];
108+
for (let index = 0; index < args.length; index++) {
109+
if (args[index] === long && args[index + 1] !== undefined) {
110+
values.push(args[index + 1]);
111+
}
112+
}
113+
return values;
114+
}
115+
116+
function parseInitArgs(args: string[]): InitArgs {
117+
return {
118+
command: 'init',
119+
yes: hasFlag(args, '--yes', '-y'),
120+
skipMcp: hasFlag(args, '--skip-mcp'),
121+
showSecrets: hasFlag(args, '--show-secrets'),
122+
scanPaths: getAllFlagValues(args, '--scan-path'),
123+
};
124+
}
125+
126+
function parseValidateArgs(args: string[]): ValidateArgs {
127+
return {
128+
command: 'validate',
129+
fix: hasFlag(args, '--fix'),
130+
};
131+
}
132+
93133
export function parseCliArgs(args: string[]): CliArgs {
94134
if (hasFlag(args, '--version', '-v')) {
95135
return { command: 'version' };
@@ -110,5 +150,9 @@ export function parseCliArgs(args: string[]): CliArgs {
110150
return parseStatusArgs(args);
111151
case 'logs':
112152
return parseLogsArgs(args);
153+
case 'init':
154+
return parseInitArgs(args);
155+
case 'validate':
156+
return parseValidateArgs(args);
113157
}
114158
}

src/frameworks/config/configLoader.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@ import { readFileSync, existsSync } from 'node:fs';
22
import { execSync } from 'node:child_process';
33
import { join } from 'node:path';
44
import { config as loadEnv } from 'dotenv';
5+
import { getConfigDir } from '../../shared/services/configDir.js';
56

6-
loadEnv();
7+
const configDir = getConfigDir();
8+
const xdgEnvPath = join(configDir, '.env');
9+
10+
if (existsSync(xdgEnvPath)) {
11+
loadEnv({ path: xdgEnvPath });
12+
} else {
13+
loadEnv();
14+
}
715

816
// Types for simplified config input
917
interface RepositoryInput {
@@ -214,13 +222,22 @@ function loadSecrets(): EnvSecrets {
214222
let cachedConfig: Config | null = null;
215223
let cachedSecrets: EnvSecrets | null = null;
216224

225+
function resolveConfigPath(): string {
226+
if (process.env.CONFIG_PATH) return process.env.CONFIG_PATH;
227+
const cwdPath = join(process.cwd(), 'config.json');
228+
if (existsSync(cwdPath)) return cwdPath;
229+
const xdgPath = join(configDir, 'config.json');
230+
if (existsSync(xdgPath)) return xdgPath;
231+
return cwdPath;
232+
}
233+
217234
export function loadConfig(): Config {
218235
if (cachedConfig) return cachedConfig;
219236

220-
const configPath = process.env.CONFIG_PATH || join(process.cwd(), 'config.json');
237+
const configPath = resolveConfigPath();
221238

222239
if (!existsSync(configPath)) {
223-
throw new Error(`Fichier de configuration non trouvé : ${configPath}`);
240+
throw new Error(`Configuration file not found: ${configPath}\nRun 'reviewflow init' to create one.`);
224241
}
225242

226243
const rawContent = readFileSync(configPath, 'utf-8');

0 commit comments

Comments
 (0)