@litmers/cursorflow-orchestrator 0.1.9 → 0.1.13
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 +30 -0
- package/README.md +90 -72
- package/commands/cursorflow-clean.md +24 -135
- package/commands/cursorflow-doctor.md +66 -38
- package/commands/cursorflow-init.md +33 -50
- package/commands/cursorflow-models.md +51 -0
- package/commands/cursorflow-monitor.md +52 -72
- package/commands/cursorflow-prepare.md +426 -147
- package/commands/cursorflow-resume.md +51 -159
- package/commands/cursorflow-review.md +38 -202
- package/commands/cursorflow-run.md +197 -84
- package/commands/cursorflow-signal.md +27 -72
- package/dist/cli/clean.js +23 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/doctor.js +14 -1
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.js +14 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +5 -4
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/models.d.ts +7 -0
- package/dist/cli/models.js +104 -0
- package/dist/cli/models.js.map +1 -0
- package/dist/cli/monitor.js +17 -0
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.d.ts +7 -0
- package/dist/cli/prepare.js +748 -0
- package/dist/cli/prepare.js.map +1 -0
- package/dist/cli/resume.js +56 -0
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +30 -1
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/signal.js +18 -0
- package/dist/cli/signal.js.map +1 -1
- package/dist/utils/cursor-agent.d.ts +4 -0
- package/dist/utils/cursor-agent.js +58 -10
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/doctor.d.ts +10 -0
- package/dist/utils/doctor.js +588 -1
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/types.d.ts +2 -0
- package/examples/README.md +114 -59
- package/examples/demo-project/README.md +61 -79
- package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +17 -6
- package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +66 -25
- package/package.json +1 -1
- package/src/cli/clean.ts +27 -0
- package/src/cli/doctor.ts +18 -2
- package/src/cli/index.ts +15 -3
- package/src/cli/init.ts +6 -4
- package/src/cli/models.ts +83 -0
- package/src/cli/monitor.ts +20 -0
- package/src/cli/prepare.ts +844 -0
- package/src/cli/resume.ts +66 -0
- package/src/cli/run.ts +36 -2
- package/src/cli/signal.ts +22 -0
- package/src/utils/cursor-agent.ts +62 -10
- package/src/utils/doctor.ts +643 -5
- 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
|
-
//
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
139
|
+
if (discoveredModels.length > 0) {
|
|
140
|
+
return [...new Set([...knownModels, ...discoveredModels])];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return knownModels;
|
|
125
144
|
} catch (error: any) {
|
|
126
|
-
|
|
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;
|