@litmers/cursorflow-orchestrator 0.1.20 → 0.1.28
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 +20 -0
- package/commands/cursorflow-clean.md +19 -0
- package/commands/cursorflow-runs.md +59 -0
- package/commands/cursorflow-stop.md +55 -0
- package/dist/cli/clean.js +171 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/logs.js +83 -42
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.d.ts +7 -0
- package/dist/cli/monitor.js +1007 -189
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/prepare.js +87 -3
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +188 -236
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +125 -3
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/runs.d.ts +5 -0
- package/dist/cli/runs.js +214 -0
- package/dist/cli/runs.js.map +1 -0
- package/dist/cli/setup-commands.js +0 -0
- package/dist/cli/signal.js +1 -1
- package/dist/cli/signal.js.map +1 -1
- package/dist/cli/stop.d.ts +5 -0
- package/dist/cli/stop.js +215 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/cli/tasks.d.ts +10 -0
- package/dist/cli/tasks.js +165 -0
- package/dist/cli/tasks.js.map +1 -0
- package/dist/core/auto-recovery.d.ts +212 -0
- package/dist/core/auto-recovery.js +737 -0
- package/dist/core/auto-recovery.js.map +1 -0
- package/dist/core/failure-policy.d.ts +156 -0
- package/dist/core/failure-policy.js +488 -0
- package/dist/core/failure-policy.js.map +1 -0
- package/dist/core/orchestrator.d.ts +15 -2
- package/dist/core/orchestrator.js +397 -15
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/reviewer.d.ts +2 -0
- package/dist/core/reviewer.js +2 -0
- package/dist/core/reviewer.js.map +1 -1
- package/dist/core/runner.d.ts +33 -10
- package/dist/core/runner.js +321 -146
- package/dist/core/runner.js.map +1 -1
- package/dist/services/logging/buffer.d.ts +67 -0
- package/dist/services/logging/buffer.js +309 -0
- package/dist/services/logging/buffer.js.map +1 -0
- package/dist/services/logging/console.d.ts +89 -0
- package/dist/services/logging/console.js +169 -0
- package/dist/services/logging/console.js.map +1 -0
- package/dist/services/logging/file-writer.d.ts +71 -0
- package/dist/services/logging/file-writer.js +516 -0
- package/dist/services/logging/file-writer.js.map +1 -0
- package/dist/services/logging/formatter.d.ts +39 -0
- package/dist/services/logging/formatter.js +227 -0
- package/dist/services/logging/formatter.js.map +1 -0
- package/dist/services/logging/index.d.ts +11 -0
- package/dist/services/logging/index.js +30 -0
- package/dist/services/logging/index.js.map +1 -0
- package/dist/services/logging/parser.d.ts +31 -0
- package/dist/services/logging/parser.js +222 -0
- package/dist/services/logging/parser.js.map +1 -0
- package/dist/services/process/index.d.ts +59 -0
- package/dist/services/process/index.js +257 -0
- package/dist/services/process/index.js.map +1 -0
- package/dist/types/agent.d.ts +20 -0
- package/dist/types/agent.js +6 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/config.d.ts +65 -0
- package/dist/types/config.js +6 -0
- package/dist/types/config.js.map +1 -0
- package/dist/types/events.d.ts +125 -0
- package/dist/types/events.js +6 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/index.js +37 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/lane.d.ts +43 -0
- package/dist/types/lane.js +6 -0
- package/dist/types/lane.js.map +1 -0
- package/dist/types/logging.d.ts +71 -0
- package/dist/types/logging.js +16 -0
- package/dist/types/logging.js.map +1 -0
- package/dist/types/review.d.ts +17 -0
- package/dist/types/review.js +6 -0
- package/dist/types/review.js.map +1 -0
- package/dist/types/run.d.ts +32 -0
- package/dist/types/run.js +6 -0
- package/dist/types/run.js.map +1 -0
- package/dist/types/task.d.ts +71 -0
- package/dist/types/task.js +6 -0
- package/dist/types/task.js.map +1 -0
- package/dist/ui/components.d.ts +134 -0
- package/dist/ui/components.js +389 -0
- package/dist/ui/components.js.map +1 -0
- package/dist/ui/log-viewer.d.ts +49 -0
- package/dist/ui/log-viewer.js +449 -0
- package/dist/ui/log-viewer.js.map +1 -0
- package/dist/utils/checkpoint.d.ts +87 -0
- package/dist/utils/checkpoint.js +317 -0
- package/dist/utils/checkpoint.js.map +1 -0
- package/dist/utils/config.d.ts +4 -0
- package/dist/utils/config.js +11 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/cursor-agent.js.map +1 -1
- package/dist/utils/dependency.d.ts +74 -0
- package/dist/utils/dependency.js +420 -0
- package/dist/utils/dependency.js.map +1 -0
- package/dist/utils/doctor.js +10 -5
- package/dist/utils/doctor.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +10 -33
- package/dist/utils/enhanced-logger.js +94 -9
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +121 -0
- package/dist/utils/git.js +322 -2
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/health.d.ts +91 -0
- package/dist/utils/health.js +556 -0
- package/dist/utils/health.js.map +1 -0
- package/dist/utils/lock.d.ts +95 -0
- package/dist/utils/lock.js +332 -0
- package/dist/utils/lock.js.map +1 -0
- package/dist/utils/log-buffer.d.ts +17 -0
- package/dist/utils/log-buffer.js +14 -0
- package/dist/utils/log-buffer.js.map +1 -0
- package/dist/utils/log-constants.d.ts +23 -0
- package/dist/utils/log-constants.js +28 -0
- package/dist/utils/log-constants.js.map +1 -0
- package/dist/utils/log-formatter.d.ts +9 -0
- package/dist/utils/log-formatter.js +113 -70
- package/dist/utils/log-formatter.js.map +1 -1
- package/dist/utils/log-service.d.ts +19 -0
- package/dist/utils/log-service.js +47 -0
- package/dist/utils/log-service.js.map +1 -0
- package/dist/utils/logger.d.ts +46 -27
- package/dist/utils/logger.js +82 -60
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/process-manager.d.ts +21 -0
- package/dist/utils/process-manager.js +138 -0
- package/dist/utils/process-manager.js.map +1 -0
- package/dist/utils/retry.d.ts +121 -0
- package/dist/utils/retry.js +374 -0
- package/dist/utils/retry.js.map +1 -0
- package/dist/utils/run-service.d.ts +88 -0
- package/dist/utils/run-service.js +412 -0
- package/dist/utils/run-service.js.map +1 -0
- package/dist/utils/state.d.ts +58 -2
- package/dist/utils/state.js +306 -3
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +82 -0
- package/dist/utils/task-service.js +348 -0
- package/dist/utils/task-service.js.map +1 -0
- package/dist/utils/types.d.ts +2 -272
- package/dist/utils/types.js +16 -0
- package/dist/utils/types.js.map +1 -1
- package/package.json +38 -23
- package/scripts/ai-security-check.js +0 -1
- package/scripts/local-security-gate.sh +0 -0
- package/scripts/monitor-lanes.sh +94 -0
- package/scripts/patches/test-cursor-agent.js +0 -1
- package/scripts/release.sh +0 -0
- package/scripts/setup-security.sh +0 -0
- package/scripts/stream-logs.sh +72 -0
- package/scripts/verify-and-fix.sh +0 -0
- package/src/cli/clean.ts +180 -0
- package/src/cli/index.ts +7 -0
- package/src/cli/init.ts +1 -1
- package/src/cli/logs.ts +79 -42
- package/src/cli/monitor.ts +1815 -899
- package/src/cli/prepare.ts +97 -3
- package/src/cli/resume.ts +220 -277
- package/src/cli/run.ts +154 -3
- package/src/cli/runs.ts +212 -0
- package/src/cli/setup-commands.ts +0 -0
- package/src/cli/signal.ts +1 -1
- package/src/cli/stop.ts +209 -0
- package/src/cli/tasks.ts +154 -0
- package/src/core/auto-recovery.ts +909 -0
- package/src/core/failure-policy.ts +592 -0
- package/src/core/orchestrator.ts +1136 -675
- package/src/core/reviewer.ts +4 -0
- package/src/core/runner.ts +1443 -1217
- package/src/services/logging/buffer.ts +326 -0
- package/src/services/logging/console.ts +193 -0
- package/src/services/logging/file-writer.ts +526 -0
- package/src/services/logging/formatter.ts +268 -0
- package/src/services/logging/index.ts +16 -0
- package/src/services/logging/parser.ts +232 -0
- package/src/services/process/index.ts +261 -0
- package/src/types/agent.ts +24 -0
- package/src/types/config.ts +79 -0
- package/src/types/events.ts +156 -0
- package/src/types/index.ts +29 -0
- package/src/types/lane.ts +56 -0
- package/src/types/logging.ts +96 -0
- package/src/types/review.ts +20 -0
- package/src/types/run.ts +37 -0
- package/src/types/task.ts +79 -0
- package/src/ui/components.ts +430 -0
- package/src/ui/log-viewer.ts +485 -0
- package/src/utils/checkpoint.ts +374 -0
- package/src/utils/config.ts +11 -2
- package/src/utils/cursor-agent.ts +1 -1
- package/src/utils/dependency.ts +482 -0
- package/src/utils/doctor.ts +11 -5
- package/src/utils/enhanced-logger.ts +108 -49
- package/src/utils/git.ts +871 -499
- package/src/utils/health.ts +596 -0
- package/src/utils/lock.ts +346 -0
- package/src/utils/log-buffer.ts +28 -0
- package/src/utils/log-constants.ts +26 -0
- package/src/utils/log-formatter.ts +120 -37
- package/src/utils/log-service.ts +49 -0
- package/src/utils/logger.ts +100 -51
- package/src/utils/process-manager.ts +100 -0
- package/src/utils/retry.ts +413 -0
- package/src/utils/run-service.ts +433 -0
- package/src/utils/state.ts +369 -3
- package/src/utils/task-service.ts +370 -0
- package/src/utils/types.ts +2 -315
package/src/cli/run.ts
CHANGED
|
@@ -10,6 +10,105 @@ import { getLogsDir, loadConfig } from '../utils/config';
|
|
|
10
10
|
import { runDoctor, getDoctorStatus } from '../utils/doctor';
|
|
11
11
|
import { areCommandsInstalled, setupCommands } from './setup-commands';
|
|
12
12
|
import { safeJoin } from '../utils/path';
|
|
13
|
+
import { loadState } from '../utils/state';
|
|
14
|
+
import { LaneState } from '../types';
|
|
15
|
+
|
|
16
|
+
interface IncompleteLaneInfo {
|
|
17
|
+
name: string;
|
|
18
|
+
status: string;
|
|
19
|
+
taskIndex: number;
|
|
20
|
+
totalTasks: number;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface ExistingRunInfo {
|
|
25
|
+
runDir: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
incompleteLanes: IncompleteLaneInfo[];
|
|
28
|
+
completedLanes: string[];
|
|
29
|
+
totalLanes: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Find existing run for a tasks directory
|
|
34
|
+
*/
|
|
35
|
+
function findExistingRunForTasks(logsDir: string, tasksDir: string): ExistingRunInfo | null {
|
|
36
|
+
const runsDir = safeJoin(logsDir, 'runs');
|
|
37
|
+
if (!fs.existsSync(runsDir)) return null;
|
|
38
|
+
|
|
39
|
+
const runs = fs.readdirSync(runsDir)
|
|
40
|
+
.filter(d => d.startsWith('run-'))
|
|
41
|
+
.sort()
|
|
42
|
+
.reverse(); // Latest first
|
|
43
|
+
|
|
44
|
+
for (const runId of runs) {
|
|
45
|
+
const runDir = safeJoin(runsDir, runId);
|
|
46
|
+
const lanesDir = safeJoin(runDir, 'lanes');
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(lanesDir)) continue;
|
|
49
|
+
|
|
50
|
+
const laneDirs = fs.readdirSync(lanesDir)
|
|
51
|
+
.filter(f => fs.statSync(safeJoin(lanesDir, f)).isDirectory());
|
|
52
|
+
|
|
53
|
+
if (laneDirs.length === 0) continue;
|
|
54
|
+
|
|
55
|
+
// Check if any lane belongs to this tasks directory
|
|
56
|
+
let matchesTasksDir = false;
|
|
57
|
+
const incompleteLanes: IncompleteLaneInfo[] = [];
|
|
58
|
+
const completedLanes: string[] = [];
|
|
59
|
+
|
|
60
|
+
for (const laneName of laneDirs) {
|
|
61
|
+
const statePath = safeJoin(lanesDir, laneName, 'state.json');
|
|
62
|
+
if (!fs.existsSync(statePath)) continue;
|
|
63
|
+
|
|
64
|
+
const state = loadState<LaneState>(statePath);
|
|
65
|
+
if (!state) continue;
|
|
66
|
+
|
|
67
|
+
// Check if this lane's tasks file is in the target tasks directory
|
|
68
|
+
if (state.tasksFile) {
|
|
69
|
+
const taskFileDir = path.dirname(state.tasksFile);
|
|
70
|
+
if (path.resolve(taskFileDir) === path.resolve(tasksDir)) {
|
|
71
|
+
matchesTasksDir = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check completion status
|
|
76
|
+
if (state.status === 'completed') {
|
|
77
|
+
completedLanes.push(laneName);
|
|
78
|
+
} else {
|
|
79
|
+
// Check if process is alive (zombie detection)
|
|
80
|
+
let isZombie = false;
|
|
81
|
+
if (state.status === 'running' && state.pid) {
|
|
82
|
+
try {
|
|
83
|
+
process.kill(state.pid, 0);
|
|
84
|
+
} catch {
|
|
85
|
+
isZombie = true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
incompleteLanes.push({
|
|
90
|
+
name: laneName,
|
|
91
|
+
status: isZombie ? 'zombie' : state.status,
|
|
92
|
+
taskIndex: state.currentTaskIndex,
|
|
93
|
+
totalTasks: state.totalTasks,
|
|
94
|
+
error: state.error || undefined,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (matchesTasksDir && incompleteLanes.length > 0) {
|
|
100
|
+
return {
|
|
101
|
+
runDir,
|
|
102
|
+
runId,
|
|
103
|
+
incompleteLanes,
|
|
104
|
+
completedLanes,
|
|
105
|
+
totalLanes: laneDirs.length,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
13
112
|
|
|
14
113
|
interface RunOptions {
|
|
15
114
|
tasksDir?: string;
|
|
@@ -18,6 +117,7 @@ interface RunOptions {
|
|
|
18
117
|
maxConcurrent: number | null;
|
|
19
118
|
skipDoctor: boolean;
|
|
20
119
|
noGit: boolean;
|
|
120
|
+
raw: boolean;
|
|
21
121
|
help: boolean;
|
|
22
122
|
}
|
|
23
123
|
|
|
@@ -27,12 +127,16 @@ Usage: cursorflow run <tasks-dir> [options]
|
|
|
27
127
|
|
|
28
128
|
Run task orchestration based on dependency graph.
|
|
29
129
|
|
|
130
|
+
If an existing run with incomplete lanes is found for the same tasks directory,
|
|
131
|
+
it will automatically resume instead of starting a new run.
|
|
132
|
+
|
|
30
133
|
Options:
|
|
31
134
|
<tasks-dir> Directory containing task JSON files
|
|
32
135
|
--max-concurrent <num> Limit parallel agents (overrides config)
|
|
33
136
|
--executor <type> cursor-agent | cloud
|
|
34
137
|
--skip-doctor Skip environment checks (not recommended)
|
|
35
138
|
--no-git Disable Git operations (worktree, push, commit)
|
|
139
|
+
--raw Save raw logs (absolute raw, no processing)
|
|
36
140
|
--dry-run Show execution plan without starting agents
|
|
37
141
|
--help, -h Show help
|
|
38
142
|
|
|
@@ -54,6 +158,7 @@ function parseArgs(args: string[]): RunOptions {
|
|
|
54
158
|
maxConcurrent: maxConcurrentIdx >= 0 ? parseInt(args[maxConcurrentIdx + 1] || '0') || null : null,
|
|
55
159
|
skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
|
|
56
160
|
noGit: args.includes('--no-git'),
|
|
161
|
+
raw: args.includes('--raw'),
|
|
57
162
|
help: args.includes('--help') || args.includes('-h'),
|
|
58
163
|
};
|
|
59
164
|
}
|
|
@@ -98,6 +203,49 @@ async function run(args: string[]): Promise<void> {
|
|
|
98
203
|
throw new Error(`Tasks directory not found: ${tasksDir}`);
|
|
99
204
|
}
|
|
100
205
|
|
|
206
|
+
// Check for existing incomplete run and auto-resume
|
|
207
|
+
const existingRun = findExistingRunForTasks(logsDir, tasksDir);
|
|
208
|
+
if (existingRun && existingRun.incompleteLanes.length > 0) {
|
|
209
|
+
logger.section('📋 Existing Run Detected');
|
|
210
|
+
logger.info(`Run: ${existingRun.runId}`);
|
|
211
|
+
logger.info(`Completed: ${existingRun.completedLanes.length}/${existingRun.totalLanes} lanes`);
|
|
212
|
+
|
|
213
|
+
console.log('');
|
|
214
|
+
logger.info('Incomplete lanes:');
|
|
215
|
+
for (const lane of existingRun.incompleteLanes) {
|
|
216
|
+
const statusEmoji = lane.status === 'failed' ? '❌' :
|
|
217
|
+
lane.status === 'zombie' ? '🧟' :
|
|
218
|
+
lane.status === 'running' ? '🔄' : '⏸';
|
|
219
|
+
logger.info(` ${statusEmoji} ${lane.name}: ${lane.status} (${lane.taskIndex}/${lane.totalTasks})`);
|
|
220
|
+
if (lane.error) {
|
|
221
|
+
logger.warn(` └─ ${lane.error.substring(0, 60)}${lane.error.length > 60 ? '...' : ''}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
console.log('');
|
|
226
|
+
logger.info('🔄 Auto-resuming from existing run...');
|
|
227
|
+
console.log('');
|
|
228
|
+
|
|
229
|
+
// Call the resume command with --all flag
|
|
230
|
+
const resumeCmd = require('./resume');
|
|
231
|
+
const resumeArgs = [
|
|
232
|
+
'--all',
|
|
233
|
+
'--run-dir', existingRun.runDir,
|
|
234
|
+
];
|
|
235
|
+
|
|
236
|
+
if (options.skipDoctor) resumeArgs.push('--skip-doctor');
|
|
237
|
+
if (options.noGit) resumeArgs.push('--no-git');
|
|
238
|
+
if (options.executor) {
|
|
239
|
+
resumeArgs.push('--executor', options.executor);
|
|
240
|
+
}
|
|
241
|
+
if (options.maxConcurrent) {
|
|
242
|
+
resumeArgs.push('--max-concurrent', String(options.maxConcurrent));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
await resumeCmd(resumeArgs);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
101
249
|
// Check if doctor has been run at least once
|
|
102
250
|
const doctorStatus = getDoctorStatus(config.projectRoot);
|
|
103
251
|
if (!doctorStatus) {
|
|
@@ -120,9 +268,9 @@ async function run(args: string[]): Promise<void> {
|
|
|
120
268
|
for (const issue of report.issues) {
|
|
121
269
|
const header = `${issue.title} (${issue.id})`;
|
|
122
270
|
if (issue.severity === 'error') {
|
|
123
|
-
logger.error(header, '❌');
|
|
271
|
+
logger.error(header, { emoji: '❌' });
|
|
124
272
|
} else {
|
|
125
|
-
logger.warn(header, '⚠️');
|
|
273
|
+
logger.warn(header, { emoji: '⚠️' });
|
|
126
274
|
}
|
|
127
275
|
console.log(` ${issue.message}`);
|
|
128
276
|
if (issue.details) console.log(` Details: ${issue.details}`);
|
|
@@ -143,7 +291,10 @@ async function run(args: string[]): Promise<void> {
|
|
|
143
291
|
runDir: path.join(logsDir, 'runs', `run-${Date.now()}`),
|
|
144
292
|
maxConcurrentLanes: options.maxConcurrent || config.maxConcurrentLanes,
|
|
145
293
|
webhooks: config.webhooks || [],
|
|
146
|
-
enhancedLogging:
|
|
294
|
+
enhancedLogging: {
|
|
295
|
+
...config.enhancedLogging,
|
|
296
|
+
...(options.raw ? { raw: true } : {}),
|
|
297
|
+
},
|
|
147
298
|
noGit: options.noGit,
|
|
148
299
|
});
|
|
149
300
|
} catch (error: any) {
|
package/src/cli/runs.ts
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow runs command - List and view run details
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as logger from '../utils/logger';
|
|
8
|
+
import { loadConfig, getLogsDir } from '../utils/config';
|
|
9
|
+
import { RunService } from '../utils/run-service';
|
|
10
|
+
import { RunStatus, RunInfo } from '../utils/types';
|
|
11
|
+
import { safeJoin } from '../utils/path';
|
|
12
|
+
|
|
13
|
+
interface RunsOptions {
|
|
14
|
+
status?: RunStatus;
|
|
15
|
+
json: boolean;
|
|
16
|
+
help: boolean;
|
|
17
|
+
runId?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printHelp(): void {
|
|
21
|
+
console.log(`
|
|
22
|
+
Usage: cursorflow runs [run-id] [options]
|
|
23
|
+
|
|
24
|
+
List all runs or view details of a specific run.
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
[run-id] View details of a specific run
|
|
28
|
+
--running Filter to show only running runs
|
|
29
|
+
--status <status> Filter by status: running, completed, failed, partial, pending
|
|
30
|
+
--json Output in JSON format
|
|
31
|
+
--help, -h Show help
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
cursorflow runs # List all runs
|
|
35
|
+
cursorflow runs --running # List only running runs
|
|
36
|
+
cursorflow runs run-123 # View details of run-123
|
|
37
|
+
`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parseArgs(args: string[]): RunsOptions {
|
|
41
|
+
const statusIdx = args.indexOf('--status');
|
|
42
|
+
const running = args.includes('--running');
|
|
43
|
+
|
|
44
|
+
// Find run ID (first non-option argument)
|
|
45
|
+
const runId = args.find((arg, i) => {
|
|
46
|
+
if (arg.startsWith('--') || arg.startsWith('-')) return false;
|
|
47
|
+
// Skip values for options
|
|
48
|
+
const prevArg = args[i - 1];
|
|
49
|
+
if (prevArg && ['--status'].includes(prevArg)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
runId,
|
|
57
|
+
status: running ? 'running' : (statusIdx >= 0 ? args[statusIdx + 1] as RunStatus : undefined),
|
|
58
|
+
json: args.includes('--json'),
|
|
59
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Format duration in a human-readable way
|
|
65
|
+
*/
|
|
66
|
+
function formatDuration(ms: number): string {
|
|
67
|
+
if (ms < 1000) return `${ms}ms`;
|
|
68
|
+
const seconds = Math.floor(ms / 1000);
|
|
69
|
+
if (seconds < 60) return `${seconds}s`;
|
|
70
|
+
const minutes = Math.floor(seconds / 60);
|
|
71
|
+
const remainingSeconds = seconds % 60;
|
|
72
|
+
if (minutes < 60) return `${minutes}m ${remainingSeconds}s`;
|
|
73
|
+
const hours = Math.floor(minutes / 60);
|
|
74
|
+
const remainingMinutes = minutes % 60;
|
|
75
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Get status color and emoji
|
|
80
|
+
*/
|
|
81
|
+
function getStatusDisplay(status: RunStatus): string {
|
|
82
|
+
switch (status) {
|
|
83
|
+
case 'running':
|
|
84
|
+
return `${logger.COLORS.blue}🔄 running${logger.COLORS.reset}`;
|
|
85
|
+
case 'completed':
|
|
86
|
+
return `${logger.COLORS.green}✅ done ${logger.COLORS.reset}`;
|
|
87
|
+
case 'failed':
|
|
88
|
+
return `${logger.COLORS.red}❌ failed ${logger.COLORS.reset}`;
|
|
89
|
+
case 'partial':
|
|
90
|
+
return `${logger.COLORS.yellow}⚠️ partial${logger.COLORS.reset}`;
|
|
91
|
+
case 'pending':
|
|
92
|
+
return `${logger.COLORS.gray}⏳ pending${logger.COLORS.reset}`;
|
|
93
|
+
default:
|
|
94
|
+
return status;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Display list of runs
|
|
100
|
+
*/
|
|
101
|
+
function displayRunList(runs: RunInfo[]): void {
|
|
102
|
+
if (runs.length === 0) {
|
|
103
|
+
console.log('No runs found.');
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log(` Run ID Task Status Lanes Duration`);
|
|
108
|
+
console.log(`${logger.COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${logger.COLORS.reset}`);
|
|
109
|
+
|
|
110
|
+
for (const run of runs) {
|
|
111
|
+
const isRunning = run.status === 'running';
|
|
112
|
+
const prefix = isRunning ? `${logger.COLORS.blue} ▶${logger.COLORS.reset}` : ' ';
|
|
113
|
+
|
|
114
|
+
const id = run.id.padEnd(20);
|
|
115
|
+
const task = (run.taskName || 'unnamed').substring(0, 15).padEnd(15);
|
|
116
|
+
const status = getStatusDisplay(run.status);
|
|
117
|
+
|
|
118
|
+
const completedLanes = run.lanes.filter(l => l.status === 'completed').length;
|
|
119
|
+
const totalLanes = run.lanes.length;
|
|
120
|
+
const lanes = `${completedLanes}/${totalLanes}`.padEnd(10);
|
|
121
|
+
|
|
122
|
+
const duration = formatDuration(run.duration);
|
|
123
|
+
|
|
124
|
+
console.log(`${prefix} ${id} ${task} ${status} ${lanes} ${duration}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Display detailed information for a single run
|
|
130
|
+
*/
|
|
131
|
+
function displayRunDetail(run: RunInfo): void {
|
|
132
|
+
logger.section(`Run Details: ${run.id}`);
|
|
133
|
+
|
|
134
|
+
console.log(`${logger.COLORS.bold}Task:${logger.COLORS.reset} ${run.taskName}`);
|
|
135
|
+
console.log(`${logger.COLORS.bold}Status:${logger.COLORS.reset} ${getStatusDisplay(run.status)}`);
|
|
136
|
+
console.log(`${logger.COLORS.bold}Start Time:${logger.COLORS.reset} ${new Date(run.startTime).toLocaleString()}`);
|
|
137
|
+
console.log(`${logger.COLORS.bold}Duration:${logger.COLORS.reset} ${formatDuration(run.duration)}`);
|
|
138
|
+
console.log(`${logger.COLORS.bold}Path:${logger.COLORS.reset} ${run.path}`);
|
|
139
|
+
|
|
140
|
+
console.log(`\n${logger.COLORS.bold}Lanes:${logger.COLORS.reset}`);
|
|
141
|
+
for (const lane of run.lanes) {
|
|
142
|
+
const statusColor = lane.status === 'completed' ? logger.COLORS.green :
|
|
143
|
+
lane.status === 'failed' ? logger.COLORS.red :
|
|
144
|
+
lane.status === 'running' ? logger.COLORS.blue : logger.COLORS.reset;
|
|
145
|
+
|
|
146
|
+
console.log(` - ${lane.name.padEnd(20)} [${statusColor}${lane.status.toUpperCase()}${logger.COLORS.reset}] Task ${lane.currentTask}/${lane.totalTasks} (PID: ${lane.pid || 'N/A'})`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (run.branches.length > 0) {
|
|
150
|
+
console.log(`\n${logger.COLORS.bold}Branches:${logger.COLORS.reset}`);
|
|
151
|
+
for (const branch of run.branches) {
|
|
152
|
+
console.log(` - ${branch}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (run.worktrees.length > 0) {
|
|
157
|
+
console.log(`\n${logger.COLORS.bold}Worktrees:${logger.COLORS.reset}`);
|
|
158
|
+
for (const wt of run.worktrees) {
|
|
159
|
+
console.log(` - ${wt}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
console.log(`\nView logs: ${logger.COLORS.cyan}cursorflow logs ${run.id} --all${logger.COLORS.reset}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runs(args: string[]): Promise<void> {
|
|
167
|
+
const options = parseArgs(args);
|
|
168
|
+
|
|
169
|
+
if (options.help) {
|
|
170
|
+
printHelp();
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const config = loadConfig();
|
|
175
|
+
const logsDir = getLogsDir(config);
|
|
176
|
+
const runsDir = safeJoin(logsDir, 'runs');
|
|
177
|
+
|
|
178
|
+
if (!fs.existsSync(runsDir)) {
|
|
179
|
+
if (options.json) {
|
|
180
|
+
console.log('[]');
|
|
181
|
+
} else {
|
|
182
|
+
console.log('No runs found. (Runs directory does not exist)');
|
|
183
|
+
}
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const runService = new RunService(runsDir);
|
|
188
|
+
|
|
189
|
+
if (options.runId) {
|
|
190
|
+
const run = runService.getRunInfo(options.runId);
|
|
191
|
+
if (!run) {
|
|
192
|
+
throw new Error(`Run not found: ${options.runId}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (options.json) {
|
|
196
|
+
console.log(JSON.stringify(run, null, 2));
|
|
197
|
+
} else {
|
|
198
|
+
displayRunDetail(run);
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const runsList = runService.listRuns({ status: options.status });
|
|
204
|
+
|
|
205
|
+
if (options.json) {
|
|
206
|
+
console.log(JSON.stringify(runsList, null, 2));
|
|
207
|
+
} else {
|
|
208
|
+
displayRunList(runsList);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export = runs;
|
|
File without changes
|
package/src/cli/signal.ts
CHANGED
|
@@ -112,7 +112,7 @@ async function signal(args: string[]): Promise<void> {
|
|
|
112
112
|
fs.writeFileSync(interventionPath, options.message);
|
|
113
113
|
|
|
114
114
|
// 2. Also append to conversation log for visibility and history
|
|
115
|
-
const entry = createConversationEntry('
|
|
115
|
+
const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${options.message}`, {
|
|
116
116
|
task: 'DIRECT_SIGNAL'
|
|
117
117
|
});
|
|
118
118
|
appendLog(convoPath, entry);
|
package/src/cli/stop.ts
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow stop command - Stop running workflows or specific lanes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import * as readline from 'readline';
|
|
6
|
+
import * as logger from '../utils/logger';
|
|
7
|
+
import { loadConfig, getLogsDir } from '../utils/config';
|
|
8
|
+
import { RunService } from '../utils/run-service';
|
|
9
|
+
import { ProcessManager } from '../utils/process-manager';
|
|
10
|
+
import { safeJoin } from '../utils/path';
|
|
11
|
+
|
|
12
|
+
interface StopOptions {
|
|
13
|
+
runId?: string;
|
|
14
|
+
lane?: string;
|
|
15
|
+
force: boolean;
|
|
16
|
+
yes: boolean;
|
|
17
|
+
help: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printHelp(): void {
|
|
21
|
+
console.log(`
|
|
22
|
+
Usage: cursorflow stop [run-id] [options]
|
|
23
|
+
|
|
24
|
+
Stop running workflows or specific lanes.
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
[run-id] Stop a specific run
|
|
28
|
+
--lane <name> Stop only a specific lane
|
|
29
|
+
--force Use SIGKILL instead of SIGTERM
|
|
30
|
+
--yes, -y Skip confirmation prompt
|
|
31
|
+
--help, -h Show help
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
cursorflow stop # Stop all running workflows
|
|
35
|
+
cursorflow stop run-123 # Stop run-123
|
|
36
|
+
cursorflow stop --lane api # Stop only the 'api' lane in the latest run
|
|
37
|
+
cursorflow stop --force # Force stop all workflows
|
|
38
|
+
`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function parseArgs(args: string[]): StopOptions {
|
|
42
|
+
const laneIdx = args.indexOf('--lane');
|
|
43
|
+
|
|
44
|
+
// Find run ID (first non-option argument)
|
|
45
|
+
const runId = args.find((arg, i) => {
|
|
46
|
+
if (arg.startsWith('--') || arg.startsWith('-')) return false;
|
|
47
|
+
// Skip values for options
|
|
48
|
+
const prevArg = args[i - 1];
|
|
49
|
+
if (prevArg && ['--lane'].includes(prevArg)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
runId,
|
|
57
|
+
lane: laneIdx >= 0 ? args[laneIdx + 1] : undefined,
|
|
58
|
+
force: args.includes('--force'),
|
|
59
|
+
yes: args.includes('--yes') || args.includes('-y'),
|
|
60
|
+
help: args.includes('--help') || args.includes('-h'),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Prompt user for confirmation
|
|
66
|
+
*/
|
|
67
|
+
async function confirm(message: string): Promise<boolean> {
|
|
68
|
+
const rl = readline.createInterface({
|
|
69
|
+
input: process.stdin,
|
|
70
|
+
output: process.stdout,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve) => {
|
|
74
|
+
rl.question(`${message} [y/N]: `, (answer) => {
|
|
75
|
+
rl.close();
|
|
76
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function stop(args: string[]): Promise<void> {
|
|
82
|
+
const options = parseArgs(args);
|
|
83
|
+
|
|
84
|
+
if (options.help) {
|
|
85
|
+
printHelp();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const config = loadConfig();
|
|
90
|
+
const logsDir = getLogsDir(config);
|
|
91
|
+
const runsDir = safeJoin(logsDir, 'runs');
|
|
92
|
+
const runService = new RunService(runsDir);
|
|
93
|
+
|
|
94
|
+
const signal = options.force ? 'SIGKILL' : 'SIGTERM';
|
|
95
|
+
|
|
96
|
+
// Case 1: Stop specific lane in specific run (or latest if runId not provided)
|
|
97
|
+
if (options.lane) {
|
|
98
|
+
let runId = options.runId;
|
|
99
|
+
if (!runId) {
|
|
100
|
+
const activeRuns = runService.getActiveRuns();
|
|
101
|
+
if (activeRuns.length === 0) {
|
|
102
|
+
logger.info('No active runs found.');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
runId = activeRuns[0]!.id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const run = runService.getRunInfo(runId);
|
|
109
|
+
if (!run) {
|
|
110
|
+
throw new Error(`Run not found: ${runId}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const lane = run.lanes.find(l => l.name === options.lane);
|
|
114
|
+
if (!lane) {
|
|
115
|
+
throw new Error(`Lane '${options.lane}' not found in run '${runId}'`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!lane.pid) {
|
|
119
|
+
logger.info(`Lane '${options.lane}' is not currently running (no PID).`);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!options.yes) {
|
|
124
|
+
const ok = await confirm(`⚠️ Stop lane '${options.lane}' in run '${runId}'?`);
|
|
125
|
+
if (!ok) return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
logger.info(`🛑 Stopping lane '${options.lane}' (PID ${lane.pid})...`);
|
|
129
|
+
if (ProcessManager.killProcess(lane.pid, signal)) {
|
|
130
|
+
logger.success(`✓ lane '${options.lane}' stopped.`);
|
|
131
|
+
} else {
|
|
132
|
+
logger.error(`Failed to stop lane '${options.lane}'.`);
|
|
133
|
+
}
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Case 2: Stop specific run
|
|
138
|
+
if (options.runId) {
|
|
139
|
+
const run = runService.getRunInfo(options.runId);
|
|
140
|
+
if (!run) {
|
|
141
|
+
throw new Error(`Run not found: ${options.runId}`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (run.status !== 'running') {
|
|
145
|
+
logger.info(`Run '${options.runId}' is not currently running (Status: ${run.status}).`);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!options.yes) {
|
|
150
|
+
const ok = await confirm(`⚠️ Stop run '${options.runId}' (${run.taskName})?`);
|
|
151
|
+
if (!ok) return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
logger.info(`🛑 Stopping run '${options.runId}'...`);
|
|
155
|
+
let stoppedCount = 0;
|
|
156
|
+
for (const lane of run.lanes) {
|
|
157
|
+
if (lane.pid && ProcessManager.killProcess(lane.pid, signal)) {
|
|
158
|
+
logger.info(` ✓ lane '${lane.name}' (PID ${lane.pid}) stopped`);
|
|
159
|
+
stoppedCount++;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (stoppedCount > 0) {
|
|
164
|
+
logger.success(`✅ Run '${options.runId}' stopped.`);
|
|
165
|
+
} else {
|
|
166
|
+
logger.info('No active lanes were stopped.');
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Case 3: Stop all running workflows
|
|
172
|
+
const activeRuns = runService.getActiveRuns();
|
|
173
|
+
if (activeRuns.length === 0) {
|
|
174
|
+
logger.info('No active runs found.');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!options.yes) {
|
|
179
|
+
console.log('\n⚠️ Stop all running workflows?\n');
|
|
180
|
+
console.log('Currently running:');
|
|
181
|
+
for (const run of activeRuns) {
|
|
182
|
+
const activeLanes = run.lanes.filter(l => l.pid).length;
|
|
183
|
+
console.log(` - ${run.id} (${run.taskName}): ${activeLanes} lanes active`);
|
|
184
|
+
}
|
|
185
|
+
console.log('');
|
|
186
|
+
|
|
187
|
+
const ok = await confirm('Continue?');
|
|
188
|
+
if (!ok) return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
logger.info('🛑 Stopping all workflows...');
|
|
192
|
+
let totalStopped = 0;
|
|
193
|
+
for (const run of activeRuns) {
|
|
194
|
+
for (const lane of run.lanes) {
|
|
195
|
+
if (lane.pid && ProcessManager.killProcess(lane.pid, signal)) {
|
|
196
|
+
logger.info(` ✓ lane '${lane.name}' (PID ${lane.pid}) stopped`);
|
|
197
|
+
totalStopped++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (totalStopped > 0) {
|
|
203
|
+
logger.success(`\n✅ All workflows stopped (${totalStopped} lanes).`);
|
|
204
|
+
} else {
|
|
205
|
+
logger.info('\nNo active lanes were stopped.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export = stop;
|