@litmers/cursorflow-orchestrator 0.1.9 → 0.1.12

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +90 -72
  3. package/commands/cursorflow-clean.md +24 -135
  4. package/commands/cursorflow-doctor.md +66 -38
  5. package/commands/cursorflow-init.md +33 -50
  6. package/commands/cursorflow-models.md +51 -0
  7. package/commands/cursorflow-monitor.md +52 -72
  8. package/commands/cursorflow-prepare.md +426 -147
  9. package/commands/cursorflow-resume.md +51 -159
  10. package/commands/cursorflow-review.md +38 -202
  11. package/commands/cursorflow-run.md +197 -84
  12. package/commands/cursorflow-signal.md +27 -72
  13. package/dist/cli/clean.js +23 -0
  14. package/dist/cli/clean.js.map +1 -1
  15. package/dist/cli/doctor.js +14 -1
  16. package/dist/cli/doctor.js.map +1 -1
  17. package/dist/cli/index.js +14 -3
  18. package/dist/cli/index.js.map +1 -1
  19. package/dist/cli/init.js +5 -4
  20. package/dist/cli/init.js.map +1 -1
  21. package/dist/cli/models.d.ts +7 -0
  22. package/dist/cli/models.js +104 -0
  23. package/dist/cli/models.js.map +1 -0
  24. package/dist/cli/monitor.js +17 -0
  25. package/dist/cli/monitor.js.map +1 -1
  26. package/dist/cli/prepare.d.ts +7 -0
  27. package/dist/cli/prepare.js +748 -0
  28. package/dist/cli/prepare.js.map +1 -0
  29. package/dist/cli/resume.js +56 -0
  30. package/dist/cli/resume.js.map +1 -1
  31. package/dist/cli/run.js +30 -1
  32. package/dist/cli/run.js.map +1 -1
  33. package/dist/cli/signal.js +18 -0
  34. package/dist/cli/signal.js.map +1 -1
  35. package/dist/utils/cursor-agent.d.ts +4 -0
  36. package/dist/utils/cursor-agent.js +58 -10
  37. package/dist/utils/cursor-agent.js.map +1 -1
  38. package/dist/utils/doctor.d.ts +10 -0
  39. package/dist/utils/doctor.js +581 -1
  40. package/dist/utils/doctor.js.map +1 -1
  41. package/dist/utils/types.d.ts +2 -0
  42. package/examples/README.md +114 -59
  43. package/examples/demo-project/README.md +61 -79
  44. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
  45. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
  46. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
  47. package/package.json +1 -1
  48. package/src/cli/clean.ts +27 -0
  49. package/src/cli/doctor.ts +18 -2
  50. package/src/cli/index.ts +15 -3
  51. package/src/cli/init.ts +6 -4
  52. package/src/cli/models.ts +83 -0
  53. package/src/cli/monitor.ts +20 -0
  54. package/src/cli/prepare.ts +844 -0
  55. package/src/cli/resume.ts +66 -0
  56. package/src/cli/run.ts +36 -2
  57. package/src/cli/signal.ts +22 -0
  58. package/src/utils/cursor-agent.ts +62 -10
  59. package/src/utils/doctor.ts +633 -5
  60. package/src/utils/types.ts +2 -0
package/src/cli/resume.ts CHANGED
@@ -9,12 +9,31 @@ import * as logger from '../utils/logger';
9
9
  import { loadConfig, getLogsDir } from '../utils/config';
10
10
  import { loadState } from '../utils/state';
11
11
  import { LaneState } from '../utils/types';
12
+ import { runDoctor } from '../utils/doctor';
12
13
 
13
14
  interface ResumeOptions {
14
15
  lane: string | null;
15
16
  runDir: string | null;
16
17
  clean: boolean;
17
18
  restart: boolean;
19
+ skipDoctor: boolean;
20
+ help: boolean;
21
+ }
22
+
23
+ function printHelp(): void {
24
+ console.log(`
25
+ Usage: cursorflow resume <lane> [options]
26
+
27
+ Resume an interrupted or failed lane.
28
+
29
+ Options:
30
+ <lane> Lane name to resume
31
+ --run-dir <path> Use a specific run directory (default: latest)
32
+ --clean Clean up existing worktree before resuming
33
+ --restart Restart from the first task (index 0)
34
+ --skip-doctor Skip environment/branch checks (not recommended)
35
+ --help, -h Show help
36
+ `);
18
37
  }
19
38
 
20
39
  function parseArgs(args: string[]): ResumeOptions {
@@ -25,6 +44,8 @@ function parseArgs(args: string[]): ResumeOptions {
25
44
  runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
26
45
  clean: args.includes('--clean'),
27
46
  restart: args.includes('--restart'),
47
+ skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
48
+ help: args.includes('--help') || args.includes('-h'),
28
49
  };
29
50
  }
30
51
 
@@ -45,6 +66,12 @@ function findLatestRunDir(logsDir: string): string | null {
45
66
 
46
67
  async function resume(args: string[]): Promise<void> {
47
68
  const options = parseArgs(args);
69
+
70
+ if (options.help) {
71
+ printHelp();
72
+ return;
73
+ }
74
+
48
75
  const config = loadConfig();
49
76
  const logsDir = getLogsDir(config);
50
77
 
@@ -77,6 +104,45 @@ async function resume(args: string[]): Promise<void> {
77
104
  throw new Error(`Original tasks file not found: ${state.tasksFile}. Resume impossible without task definition.`);
78
105
  }
79
106
 
107
+ // Run doctor check before resuming (check branches, etc.)
108
+ if (!options.skipDoctor) {
109
+ const tasksDir = path.dirname(state.tasksFile);
110
+ logger.info('Running pre-flight checks...');
111
+
112
+ const report = runDoctor({
113
+ cwd: process.cwd(),
114
+ tasksDir,
115
+ includeCursorAgentChecks: false, // Skip agent checks for resume
116
+ });
117
+
118
+ // Only show blocking errors for resume
119
+ const blockingIssues = report.issues.filter(i =>
120
+ i.severity === 'error' &&
121
+ (i.id.startsWith('branch.') || i.id.startsWith('git.'))
122
+ );
123
+
124
+ if (blockingIssues.length > 0) {
125
+ logger.section('🛑 Pre-resume check found issues');
126
+ for (const issue of blockingIssues) {
127
+ logger.error(`${issue.title} (${issue.id})`, '❌');
128
+ console.log(` ${issue.message}`);
129
+ if (issue.details) console.log(` Details: ${issue.details}`);
130
+ if (issue.fixes?.length) {
131
+ console.log(' Fix:');
132
+ for (const fix of issue.fixes) console.log(` - ${fix}`);
133
+ }
134
+ console.log('');
135
+ }
136
+ throw new Error('Pre-resume checks failed. Use --skip-doctor to bypass (not recommended).');
137
+ }
138
+
139
+ // Show warnings but don't block
140
+ const warnings = report.issues.filter(i => i.severity === 'warn' && i.id.startsWith('branch.'));
141
+ if (warnings.length > 0) {
142
+ logger.warn(`${warnings.length} warning(s) found. Run 'cursorflow doctor' for details.`);
143
+ }
144
+ }
145
+
80
146
  logger.section(`🔁 Resuming Lane: ${options.lane}`);
81
147
  logger.info(`Run: ${path.basename(runDir)}`);
82
148
  logger.info(`Tasks: ${state.tasksFile}`);
package/src/cli/run.ts CHANGED
@@ -7,31 +7,57 @@ import * as fs from 'fs';
7
7
  import * as logger from '../utils/logger';
8
8
  import { orchestrate } from '../core/orchestrator';
9
9
  import { getLogsDir, loadConfig } from '../utils/config';
10
- import { runDoctor } from '../utils/doctor';
10
+ import { runDoctor, getDoctorStatus } from '../utils/doctor';
11
11
  import { areCommandsInstalled, setupCommands } from './setup-commands';
12
12
 
13
13
  interface RunOptions {
14
14
  tasksDir?: string;
15
15
  dryRun: boolean;
16
16
  executor: string | null;
17
+ maxConcurrent: number | null;
17
18
  skipDoctor: boolean;
19
+ help: boolean;
20
+ }
21
+
22
+ function printHelp(): void {
23
+ console.log(`
24
+ Usage: cursorflow run <tasks-dir> [options]
25
+
26
+ Run task orchestration based on dependency graph.
27
+
28
+ Options:
29
+ <tasks-dir> Directory containing task JSON files
30
+ --max-concurrent <num> Limit parallel agents (overrides config)
31
+ --executor <type> cursor-agent | cloud
32
+ --skip-doctor Skip environment checks (not recommended)
33
+ --dry-run Show execution plan without starting agents
34
+ --help, -h Show help
35
+ `);
18
36
  }
19
37
 
20
38
  function parseArgs(args: string[]): RunOptions {
21
39
  const tasksDir = args.find(a => !a.startsWith('--'));
22
40
  const executorIdx = args.indexOf('--executor');
41
+ const maxConcurrentIdx = args.indexOf('--max-concurrent');
23
42
 
24
43
  return {
25
44
  tasksDir,
26
45
  dryRun: args.includes('--dry-run'),
27
46
  executor: executorIdx >= 0 ? args[executorIdx + 1] || null : null,
47
+ maxConcurrent: maxConcurrentIdx >= 0 ? parseInt(args[maxConcurrentIdx + 1] || '0') || null : null,
28
48
  skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
49
+ help: args.includes('--help') || args.includes('-h'),
29
50
  };
30
51
  }
31
52
 
32
53
  async function run(args: string[]): Promise<void> {
33
54
  const options = parseArgs(args);
34
55
 
56
+ if (options.help) {
57
+ printHelp();
58
+ return;
59
+ }
60
+
35
61
  // Auto-setup Cursor commands if missing or outdated
36
62
  if (!areCommandsInstalled()) {
37
63
  logger.info('Installing missing or outdated Cursor IDE commands...');
@@ -64,6 +90,14 @@ async function run(args: string[]): Promise<void> {
64
90
  throw new Error(`Tasks directory not found: ${tasksDir}`);
65
91
  }
66
92
 
93
+ // Check if doctor has been run at least once
94
+ const doctorStatus = getDoctorStatus(config.projectRoot);
95
+ if (!doctorStatus) {
96
+ logger.warn('It looks like you haven\'t run `cursorflow doctor` yet.');
97
+ logger.warn('Running doctor is highly recommended to catch environment issues early.');
98
+ console.log(' Run: cursorflow doctor\n');
99
+ }
100
+
67
101
  // Preflight checks (doctor)
68
102
  if (!options.skipDoctor) {
69
103
  const report = runDoctor({
@@ -99,7 +133,7 @@ async function run(args: string[]): Promise<void> {
99
133
  executor: options.executor || config.executor,
100
134
  pollInterval: config.pollInterval * 1000,
101
135
  runDir: path.join(logsDir, 'runs', `run-${Date.now()}`),
102
- maxConcurrentLanes: config.maxConcurrentLanes,
136
+ maxConcurrentLanes: options.maxConcurrent || config.maxConcurrentLanes,
103
137
  });
104
138
  } catch (error: any) {
105
139
  // Re-throw to be handled by the main entry point
package/src/cli/signal.ts CHANGED
@@ -14,6 +14,21 @@ interface SignalOptions {
14
14
  lane: string | null;
15
15
  message: string | null;
16
16
  runDir: string | null;
17
+ help: boolean;
18
+ }
19
+
20
+ function printHelp(): void {
21
+ console.log(`
22
+ Usage: cursorflow signal <lane> "<message>" [options]
23
+
24
+ Directly intervene in a running lane by sending a message to the agent.
25
+
26
+ Options:
27
+ <lane> Lane name to signal
28
+ "<message>" Message text to send
29
+ --run-dir <path> Use a specific run directory (default: latest)
30
+ --help, -h Show help
31
+ `);
17
32
  }
18
33
 
19
34
  function parseArgs(args: string[]): SignalOptions {
@@ -26,6 +41,7 @@ function parseArgs(args: string[]): SignalOptions {
26
41
  lane: nonOptions[0] || null,
27
42
  message: nonOptions.slice(1).join(' ') || null,
28
43
  runDir: runDirIdx >= 0 ? args[runDirIdx + 1] || null : null,
44
+ help: args.includes('--help') || args.includes('-h'),
29
45
  };
30
46
  }
31
47
 
@@ -43,6 +59,12 @@ function findLatestRunDir(logsDir: string): string | null {
43
59
 
44
60
  async function signal(args: string[]): Promise<void> {
45
61
  const options = parseArgs(args);
62
+
63
+ if (options.help) {
64
+ printHelp();
65
+ return;
66
+ }
67
+
46
68
  const config = loadConfig();
47
69
  const logsDir = getLogsDir(config);
48
70
 
@@ -114,19 +114,35 @@ export function validateSetup(executor = 'cursor-agent'): { valid: boolean; erro
114
114
  * Get available models (if cursor-agent supports it)
115
115
  */
116
116
  export function getAvailableModels(): string[] {
117
+ // Known models in the current version of cursor-agent
118
+ const knownModels = [
119
+ 'sonnet-4.5',
120
+ 'sonnet-4.5-thinking',
121
+ 'opus-4.5',
122
+ 'opus-4.5-thinking',
123
+ 'gpt-5.2',
124
+ 'gpt-5.2-high',
125
+ ];
126
+
117
127
  try {
118
- // This is a placeholder - actual implementation depends on cursor-agent API
119
- // execSync('cursor-agent --model invalid "test"', {
120
- // encoding: 'utf8',
121
- // stdio: 'pipe',
122
- // });
128
+ // Try to trigger a model list by using an invalid model with --print
129
+ // Some versions of cursor-agent output valid models when an invalid one is used.
130
+ const result = spawnSync('cursor-agent', ['--print', '--model', 'list-available-models', 'test'], {
131
+ encoding: 'utf8',
132
+ stdio: 'pipe',
133
+ timeout: 5000,
134
+ });
135
+
136
+ const output = (result.stderr || result.stdout || '').toString();
137
+ const discoveredModels = parseModelsFromOutput(output);
123
138
 
124
- return [];
139
+ if (discoveredModels.length > 0) {
140
+ return [...new Set([...knownModels, ...discoveredModels])];
141
+ }
142
+
143
+ return knownModels;
125
144
  } catch (error: any) {
126
- // Parse from error message
127
- const output = (error.stderr || error.stdout || '').toString();
128
- // Extract model names from output
129
- return parseModelsFromOutput(output);
145
+ return knownModels;
130
146
  }
131
147
  }
132
148
 
@@ -175,6 +191,42 @@ export function testCursorAgent(): { success: boolean; output?: string; error?:
175
191
  }
176
192
  }
177
193
 
194
+ /**
195
+ * Run interactive agent test to prime permissions (MCP, user approval, etc.)
196
+ */
197
+ export function runInteractiveAgentTest(): boolean {
198
+ const { spawnSync } = require('child_process');
199
+
200
+ console.log('\n' + '━'.repeat(60));
201
+ console.log('🤖 Interactive Agent Priming Test');
202
+ console.log('━'.repeat(60));
203
+ console.log('\nThis will start cursor-agent in interactive mode (NOT --print).');
204
+ console.log('Use this to approve MCP permissions or initial setup requests.\n');
205
+ console.log('MISSION: Just say hello and confirm MCP connectivity.');
206
+ console.log('ACTION: Once the agent responds and finishes, you can exit.');
207
+ console.log('\n' + '─'.repeat(60) + '\n');
208
+
209
+ try {
210
+ // Run WITHOUT --print to allow interactive user input and UI popups
211
+ const result = spawnSync('cursor-agent', ['Hello, verify MCP and system access.'], {
212
+ stdio: 'inherit', // Crucial for interactivity
213
+ env: process.env,
214
+ });
215
+
216
+ console.log('\n' + '─'.repeat(60));
217
+ if (result.status === 0) {
218
+ console.log('✅ Interactive test completed successfully!');
219
+ return true;
220
+ } else {
221
+ console.log('❌ Interactive test exited with code: ' + result.status);
222
+ return false;
223
+ }
224
+ } catch (error: any) {
225
+ console.log('❌ Failed to run interactive test: ' + error.message);
226
+ return false;
227
+ }
228
+ }
229
+
178
230
  export interface AuthCheckResult {
179
231
  authenticated: boolean;
180
232
  message: string;