@delegance/claude-autopilot 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.2.0] — 2026-04-21
4
+
5
+ ### Added
6
+ - `autopilot doctor` — prerequisite checker: verifies Node 22+, tsx, gh CLI auth, claude CLI, OPENAI_API_KEY, git config, superpowers plugin; shows exact fix command for each failure; exits 1 if any blockers
7
+ - `autopilot setup` now runs `doctor` automatically at the end so users immediately see what still needs attention
8
+ - `autopilot preflight` kept as alias for `doctor`
9
+
3
10
  ## [1.1.0] — 2026-04-21
4
11
 
5
12
  ### Added
package/README.md CHANGED
@@ -94,9 +94,17 @@ npx autopilot init
94
94
 
95
95
  Available presets: `nextjs-supabase`, `t3`, `python-fastapi`, `rails-postgres`, `go`.
96
96
 
97
- ### `autopilot preflight`
97
+ ### `autopilot doctor`
98
98
 
99
- Checks prerequisites (Node version, `gh` CLI auth, `OPENAI_API_KEY`).
99
+ Checks prerequisites and shows exact fix commands for each failure.
100
+
101
+ ```bash
102
+ npx autopilot doctor # Check prerequisites and show exact fix commands
103
+ ```
104
+
105
+ Verifies: Node 22+, tsx, gh CLI auth, claude CLI, OPENAI_API_KEY, git user config, superpowers plugin. Exits 1 if any blockers are found. Also runs automatically at the end of `autopilot setup`.
106
+
107
+ `autopilot preflight` is kept as an alias for `doctor`.
100
108
 
101
109
  ## GitHub Actions
102
110
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delegance/claude-autopilot",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
5
  "description": "Claude Code automation pipeline: spec \u2192 plan \u2192 implement \u2192 validate \u2192 PR",
6
6
  "keywords": [
package/src/cli/index.ts CHANGED
@@ -8,16 +8,17 @@
8
8
  * autopilot run --base main diff against a specific branch
9
9
  * autopilot run --dry-run show what would run, no execution
10
10
  * autopilot watch re-run pipeline on every file save (debounced)
11
- * autopilot preflight check prerequisites
11
+ * autopilot doctor check prerequisites (alias: preflight)
12
12
  */
13
13
  import { runInit } from './init.ts';
14
14
  import { runCommand } from './run.ts';
15
15
  import { runWatch } from './watch.ts';
16
16
  import { runSetup } from './setup.ts';
17
+ import { runDoctor } from './preflight.ts';
17
18
 
18
19
  const args = process.argv.slice(2);
19
20
 
20
- const SUBCOMMANDS = ['init', 'run', 'watch', 'hook', 'autoregress', 'preflight', 'setup', 'help', '--help', '-h'] as const;
21
+ const SUBCOMMANDS = ['init', 'run', 'watch', 'hook', 'autoregress', 'doctor', 'preflight', 'setup', 'help', '--help', '-h'] as const;
21
22
  const VALUE_FLAGS = ['base', 'config', 'files', 'format', 'output', 'debounce'];
22
23
 
23
24
  // Detect first non-flag arg as subcommand, default to 'run'
@@ -47,7 +48,7 @@ Commands:
47
48
  run Run the pipeline on git-changed files (default)
48
49
  watch Watch for file changes and re-run pipeline on each save
49
50
  init Scaffold autopilot.config.yaml from a preset
50
- preflight Check prerequisites
51
+ doctor Check prerequisites and show exact fix commands (alias: preflight)
51
52
  autoregress Run snapshot regression tests (run|diff|update|generate)
52
53
 
53
54
  Options (run):
@@ -75,9 +76,12 @@ switch (subcommand) {
75
76
  await runInit(process.cwd());
76
77
  break;
77
78
 
78
- case 'preflight':
79
- await import('./preflight.ts');
79
+ case 'doctor':
80
+ case 'preflight': {
81
+ const result = await runDoctor();
82
+ process.exit(result.blockers > 0 ? 1 : 0);
80
83
  break;
84
+ }
81
85
 
82
86
  case 'help':
83
87
  case '--help':
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import * as fs from 'node:fs';
3
3
  import * as path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
4
5
  import { runSafe } from '../core/shell.ts';
5
6
 
6
7
  const PASS = '\x1b[32m✓\x1b[0m';
@@ -30,125 +31,140 @@ function loadEnvFile(filePath: string): Record<string, string> {
30
31
  return vars;
31
32
  }
32
33
 
33
- const checks: Check[] = [];
34
-
35
- // 1. Node version
36
- const nodeVersion = process.version;
37
- const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]!, 10);
38
- checks.push({
39
- name: `Node.js ${nodeVersion}`,
40
- result: nodeMajor >= 22 ? 'pass' : 'fail',
41
- message: nodeMajor < 22 ? `Node 22+ required — current: ${nodeVersion}. Install via nvm: nvm install 22` : undefined,
42
- });
43
-
44
- // 2. tsx available
45
- const localTsx = path.join(process.cwd(), 'node_modules', '.bin', 'tsx');
46
- const tsxVersion = fs.existsSync(localTsx)
47
- ? runSafe(localTsx, ['--version'])
48
- : runSafe('tsx', ['--version']);
49
- checks.push({
50
- name: 'tsx available',
51
- result: tsxVersion ? 'pass' : 'fail',
52
- message: !tsxVersion ? 'tsx not found — run: npm install @delegance/claude-autopilot (includes tsx)' : undefined,
53
- });
54
-
55
- // 3. gh CLI authenticated
56
- const ghAuth = runSafe('gh', ['auth', 'status']);
57
- checks.push({
58
- name: 'gh CLI authenticated',
59
- result: ghAuth !== null ? 'pass' : 'fail',
60
- message: ghAuth === null ? 'gh CLI not authenticated — run: gh auth login' : undefined,
61
- });
62
-
63
- // 4. autopilot.config.yaml in cwd
64
- const configYaml = path.join(process.cwd(), 'autopilot.config.yaml');
65
- checks.push({
66
- name: 'autopilot.config.yaml',
67
- result: fs.existsSync(configYaml) ? 'pass' : 'warn',
68
- message: !fs.existsSync(configYaml)
69
- ? 'autopilot.config.yaml not found in current directory — copy from a preset: presets/nextjs-supabase/autopilot.config.yaml'
70
- : undefined,
71
- });
72
-
73
- // 5. Local env file exists
74
- const envFile = ENV_CANDIDATES.find(f => fs.existsSync(f));
75
- checks.push({
76
- name: `Local env file (${envFile ?? 'none found'})`,
77
- result: envFile ? 'pass' : 'warn',
78
- message: !envFile
79
- ? `No env file found. Looked for: ${ENV_CANDIDATES.join(', ')}. Create one with your OPENAI_API_KEY.`
80
- : undefined,
81
- });
82
-
83
- // 6. OPENAI_API_KEY set
84
- const envVars = envFile ? loadEnvFile(envFile) : {};
85
- const hasOpenAI = !!process.env.OPENAI_API_KEY || !!envVars['OPENAI_API_KEY'];
86
- checks.push({
87
- name: 'OPENAI_API_KEY',
88
- result: hasOpenAI ? 'pass' : 'warn',
89
- message: !hasOpenAI
90
- ? `OPENAI_API_KEY not set Codex review steps will be skipped`
91
- : undefined,
92
- });
93
-
94
- // 7. claude CLI available
95
- const claudeVersion = runSafe('claude', ['--version']);
96
- checks.push({
97
- name: 'claude CLI',
98
- result: claudeVersion ? 'pass' : 'fail',
99
- message: !claudeVersion
100
- ? 'claude CLI not found — required for autofix. Install Claude Code: https://claude.ai/claude-code'
101
- : undefined,
102
- });
103
-
104
- // 8. git user config
105
- const gitName = runSafe('git', ['config', 'user.name']);
106
- const gitEmail = runSafe('git', ['config', 'user.email']);
107
- const gitConfigOk = !!(gitName?.trim()) && !!(gitEmail?.trim());
108
- checks.push({
109
- name: 'git user config',
110
- result: gitConfigOk ? 'pass' : 'warn',
111
- message: !gitConfigOk
112
- ? 'git user.name / user.email not set — commits will fail.'
113
- : undefined,
114
- });
115
-
116
- // 9. superpowers plugin
117
- const home = process.env.HOME ?? '';
118
- const superpowersPaths = [
119
- path.join(home, '.claude', 'plugins', 'cache', 'claude-plugins-official', 'superpowers'),
120
- path.join(home, '.claude', 'plugins', 'cache', 'superpowers-marketplace', 'superpowers'),
121
- path.join(home, '.claude', 'plugins', 'superpowers'),
122
- ];
123
- const superpowersOk = superpowersPaths.some(p => fs.existsSync(p));
124
- checks.push({
125
- name: 'superpowers plugin',
126
- result: superpowersOk ? 'pass' : 'warn',
127
- message: !superpowersOk
128
- ? 'superpowers plugin not detected — install: /plugin install superpowers@claude-plugins-official'
129
- : undefined,
130
- });
131
-
132
- // Print results
133
- console.log('\n\x1b[1m[preflight] Autopilot prerequisite check\x1b[0m\n');
134
- let failures = 0;
135
- let warnings = 0;
136
- for (const check of checks) {
137
- const icon = check.result === 'pass' ? PASS : check.result === 'warn' ? WARN : FAIL;
138
- console.log(` ${icon} ${check.name}`);
139
- if (check.message) {
140
- console.log(` \x1b[2m${check.message}\x1b[0m`);
34
+ export interface DoctorResult {
35
+ blockers: number;
36
+ warnings: number;
37
+ }
38
+
39
+ export async function runDoctor(): Promise<DoctorResult> {
40
+ const checks: Check[] = [];
41
+
42
+ // 1. Node version
43
+ const nodeVersion = process.version;
44
+ const nodeMajor = parseInt(nodeVersion.slice(1).split('.')[0]!, 10);
45
+ checks.push({
46
+ name: `Node.js ${nodeVersion}`,
47
+ result: nodeMajor >= 22 ? 'pass' : 'fail',
48
+ message: nodeMajor < 22 ? `Node 22+ required — current: ${nodeVersion}. Install via nvm: nvm install 22` : undefined,
49
+ });
50
+
51
+ // 2. tsx available
52
+ const localTsx = path.join(process.cwd(), 'node_modules', '.bin', 'tsx');
53
+ const tsxVersion = fs.existsSync(localTsx)
54
+ ? runSafe(localTsx, ['--version'])
55
+ : runSafe('tsx', ['--version']);
56
+ checks.push({
57
+ name: 'tsx available',
58
+ result: tsxVersion ? 'pass' : 'fail',
59
+ message: !tsxVersion ? 'tsx not found — run: npm install @delegance/claude-autopilot (includes tsx)' : undefined,
60
+ });
61
+
62
+ // 3. gh CLI authenticated
63
+ const ghAuth = runSafe('gh', ['auth', 'status']);
64
+ checks.push({
65
+ name: 'gh CLI authenticated',
66
+ result: ghAuth !== null ? 'pass' : 'fail',
67
+ message: ghAuth === null ? 'gh CLI not authenticated — run: gh auth login' : undefined,
68
+ });
69
+
70
+ // 4. autopilot.config.yaml in cwd
71
+ const configYaml = path.join(process.cwd(), 'autopilot.config.yaml');
72
+ checks.push({
73
+ name: 'autopilot.config.yaml',
74
+ result: fs.existsSync(configYaml) ? 'pass' : 'warn',
75
+ message: !fs.existsSync(configYaml)
76
+ ? 'autopilot.config.yaml not found in current directory — copy from a preset: presets/nextjs-supabase/autopilot.config.yaml'
77
+ : undefined,
78
+ });
79
+
80
+ // 5. Local env file exists
81
+ const envFile = ENV_CANDIDATES.find(f => fs.existsSync(f));
82
+ checks.push({
83
+ name: `Local env file (${envFile ?? 'none found'})`,
84
+ result: envFile ? 'pass' : 'warn',
85
+ message: !envFile
86
+ ? `No env file found. Looked for: ${ENV_CANDIDATES.join(', ')}. Create one with your OPENAI_API_KEY.`
87
+ : undefined,
88
+ });
89
+
90
+ // 6. OPENAI_API_KEY set
91
+ const envVars = envFile ? loadEnvFile(envFile) : {};
92
+ const hasOpenAI = !!process.env.OPENAI_API_KEY || !!envVars['OPENAI_API_KEY'];
93
+ checks.push({
94
+ name: 'OPENAI_API_KEY',
95
+ result: hasOpenAI ? 'pass' : 'warn',
96
+ message: !hasOpenAI
97
+ ? `OPENAI_API_KEY not set — Codex review steps will be skipped`
98
+ : undefined,
99
+ });
100
+
101
+ // 7. claude CLI available
102
+ const claudeVersion = runSafe('claude', ['--version']);
103
+ checks.push({
104
+ name: 'claude CLI',
105
+ result: claudeVersion ? 'pass' : 'fail',
106
+ message: !claudeVersion
107
+ ? 'claude CLI not found — required for autofix. Install Claude Code: https://claude.ai/claude-code'
108
+ : undefined,
109
+ });
110
+
111
+ // 8. git user config
112
+ const gitName = runSafe('git', ['config', 'user.name']);
113
+ const gitEmail = runSafe('git', ['config', 'user.email']);
114
+ const gitConfigOk = !!(gitName?.trim()) && !!(gitEmail?.trim());
115
+ checks.push({
116
+ name: 'git user config',
117
+ result: gitConfigOk ? 'pass' : 'warn',
118
+ message: !gitConfigOk
119
+ ? 'git user.name / user.email not set — commits will fail.'
120
+ : undefined,
121
+ });
122
+
123
+ // 9. superpowers plugin
124
+ const home = process.env.HOME ?? '';
125
+ const superpowersPaths = [
126
+ path.join(home, '.claude', 'plugins', 'cache', 'claude-plugins-official', 'superpowers'),
127
+ path.join(home, '.claude', 'plugins', 'cache', 'superpowers-marketplace', 'superpowers'),
128
+ path.join(home, '.claude', 'plugins', 'superpowers'),
129
+ ];
130
+ const superpowersOk = superpowersPaths.some(p => fs.existsSync(p));
131
+ checks.push({
132
+ name: 'superpowers plugin',
133
+ result: superpowersOk ? 'pass' : 'warn',
134
+ message: !superpowersOk
135
+ ? 'superpowers plugin not detected — install: /plugin install superpowers@claude-plugins-official'
136
+ : undefined,
137
+ });
138
+
139
+ // Print results
140
+ console.log('\n\x1b[1m[doctor] Autopilot prerequisite check\x1b[0m\n');
141
+ let blockers = 0;
142
+ let warnings = 0;
143
+ for (const check of checks) {
144
+ const icon = check.result === 'pass' ? PASS : check.result === 'warn' ? WARN : FAIL;
145
+ console.log(` ${icon} ${check.name}`);
146
+ if (check.message) {
147
+ console.log(` \x1b[2m${check.message}\x1b[0m`);
148
+ }
149
+ if (check.result === 'fail') blockers++;
150
+ if (check.result === 'warn') warnings++;
141
151
  }
142
- if (check.result === 'fail') failures++;
143
- if (check.result === 'warn') warnings++;
152
+
153
+ console.log('');
154
+ if (blockers > 0) {
155
+ console.log(`\x1b[31m[doctor] ${blockers} blocker(s) — fix before running npx autopilot run\x1b[0m\n`);
156
+ } else if (warnings > 0) {
157
+ console.log(`\x1b[33m[doctor] ${warnings} warning(s) — pipeline will run but some steps may be skipped\x1b[0m\n`);
158
+ } else {
159
+ console.log(`\x1b[32m[doctor] All checks passed — ready to run\x1b[0m\n`);
160
+ }
161
+
162
+ return { blockers, warnings };
144
163
  }
145
164
 
146
- console.log('');
147
- if (failures > 0) {
148
- console.log(`\x1b[31m[preflight] ${failures} check(s) failed — fix before running /autopilot\x1b[0m\n`);
149
- process.exit(1);
150
- } else if (warnings > 0) {
151
- console.log(`\x1b[33m[preflight] ${warnings} warning(s) — pipeline will run but some steps may be degraded\x1b[0m\n`);
152
- } else {
153
- console.log(`\x1b[32m[preflight] All checks passed\x1b[0m\n`);
165
+ // Run when invoked directly
166
+ const isMain = process.argv[1] !== undefined &&
167
+ fileURLToPath(import.meta.url) === path.resolve(process.argv[1]);
168
+ if (isMain) {
169
+ runDoctor().then(r => process.exit(r.blockers > 0 ? 1 : 0));
154
170
  }
package/src/cli/setup.ts CHANGED
@@ -4,6 +4,7 @@ import * as path from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { detectProject } from './detector.ts';
6
6
  import { runHook } from './hook.ts';
7
+ import { runDoctor } from './preflight.ts';
7
8
 
8
9
  const PASS = '\x1b[32m✓\x1b[0m';
9
10
  const WARN = '\x1b[33m!\x1b[0m';
@@ -77,5 +78,8 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
77
78
  }
78
79
  }
79
80
 
81
+ console.log('\n[setup] Checking prerequisites...');
82
+ await runDoctor();
83
+
80
84
  console.log('\n[setup] Done. Run: npx autopilot run\n');
81
85
  }