@litmers/cursorflow-orchestrator 0.1.18 → 0.1.26

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 (234) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +25 -7
  3. package/commands/cursorflow-clean.md +19 -0
  4. package/commands/cursorflow-runs.md +59 -0
  5. package/commands/cursorflow-stop.md +55 -0
  6. package/dist/cli/clean.js +178 -6
  7. package/dist/cli/clean.js.map +1 -1
  8. package/dist/cli/index.js +12 -1
  9. package/dist/cli/index.js.map +1 -1
  10. package/dist/cli/init.js +8 -7
  11. package/dist/cli/init.js.map +1 -1
  12. package/dist/cli/logs.js +126 -77
  13. package/dist/cli/logs.js.map +1 -1
  14. package/dist/cli/monitor.d.ts +7 -0
  15. package/dist/cli/monitor.js +1021 -202
  16. package/dist/cli/monitor.js.map +1 -1
  17. package/dist/cli/prepare.js +39 -21
  18. package/dist/cli/prepare.js.map +1 -1
  19. package/dist/cli/resume.js +268 -163
  20. package/dist/cli/resume.js.map +1 -1
  21. package/dist/cli/run.js +11 -5
  22. package/dist/cli/run.js.map +1 -1
  23. package/dist/cli/runs.d.ts +5 -0
  24. package/dist/cli/runs.js +214 -0
  25. package/dist/cli/runs.js.map +1 -0
  26. package/dist/cli/setup-commands.js +0 -0
  27. package/dist/cli/signal.js +8 -8
  28. package/dist/cli/signal.js.map +1 -1
  29. package/dist/cli/stop.d.ts +5 -0
  30. package/dist/cli/stop.js +215 -0
  31. package/dist/cli/stop.js.map +1 -0
  32. package/dist/cli/tasks.d.ts +10 -0
  33. package/dist/cli/tasks.js +165 -0
  34. package/dist/cli/tasks.js.map +1 -0
  35. package/dist/core/auto-recovery.d.ts +212 -0
  36. package/dist/core/auto-recovery.js +737 -0
  37. package/dist/core/auto-recovery.js.map +1 -0
  38. package/dist/core/failure-policy.d.ts +156 -0
  39. package/dist/core/failure-policy.js +488 -0
  40. package/dist/core/failure-policy.js.map +1 -0
  41. package/dist/core/orchestrator.d.ts +16 -2
  42. package/dist/core/orchestrator.js +439 -105
  43. package/dist/core/orchestrator.js.map +1 -1
  44. package/dist/core/reviewer.d.ts +2 -0
  45. package/dist/core/reviewer.js +2 -0
  46. package/dist/core/reviewer.js.map +1 -1
  47. package/dist/core/runner.d.ts +33 -10
  48. package/dist/core/runner.js +374 -164
  49. package/dist/core/runner.js.map +1 -1
  50. package/dist/services/logging/buffer.d.ts +67 -0
  51. package/dist/services/logging/buffer.js +309 -0
  52. package/dist/services/logging/buffer.js.map +1 -0
  53. package/dist/services/logging/console.d.ts +89 -0
  54. package/dist/services/logging/console.js +169 -0
  55. package/dist/services/logging/console.js.map +1 -0
  56. package/dist/services/logging/file-writer.d.ts +71 -0
  57. package/dist/services/logging/file-writer.js +516 -0
  58. package/dist/services/logging/file-writer.js.map +1 -0
  59. package/dist/services/logging/formatter.d.ts +39 -0
  60. package/dist/services/logging/formatter.js +227 -0
  61. package/dist/services/logging/formatter.js.map +1 -0
  62. package/dist/services/logging/index.d.ts +11 -0
  63. package/dist/services/logging/index.js +30 -0
  64. package/dist/services/logging/index.js.map +1 -0
  65. package/dist/services/logging/parser.d.ts +31 -0
  66. package/dist/services/logging/parser.js +222 -0
  67. package/dist/services/logging/parser.js.map +1 -0
  68. package/dist/services/process/index.d.ts +59 -0
  69. package/dist/services/process/index.js +257 -0
  70. package/dist/services/process/index.js.map +1 -0
  71. package/dist/types/agent.d.ts +20 -0
  72. package/dist/types/agent.js +6 -0
  73. package/dist/types/agent.js.map +1 -0
  74. package/dist/types/config.d.ts +65 -0
  75. package/dist/types/config.js +6 -0
  76. package/dist/types/config.js.map +1 -0
  77. package/dist/types/events.d.ts +125 -0
  78. package/dist/types/events.js +6 -0
  79. package/dist/types/events.js.map +1 -0
  80. package/dist/types/index.d.ts +12 -0
  81. package/dist/types/index.js +37 -0
  82. package/dist/types/index.js.map +1 -0
  83. package/dist/types/lane.d.ts +43 -0
  84. package/dist/types/lane.js +6 -0
  85. package/dist/types/lane.js.map +1 -0
  86. package/dist/types/logging.d.ts +71 -0
  87. package/dist/types/logging.js +16 -0
  88. package/dist/types/logging.js.map +1 -0
  89. package/dist/types/review.d.ts +17 -0
  90. package/dist/types/review.js +6 -0
  91. package/dist/types/review.js.map +1 -0
  92. package/dist/types/run.d.ts +32 -0
  93. package/dist/types/run.js +6 -0
  94. package/dist/types/run.js.map +1 -0
  95. package/dist/types/task.d.ts +71 -0
  96. package/dist/types/task.js +6 -0
  97. package/dist/types/task.js.map +1 -0
  98. package/dist/ui/components.d.ts +134 -0
  99. package/dist/ui/components.js +389 -0
  100. package/dist/ui/components.js.map +1 -0
  101. package/dist/ui/log-viewer.d.ts +49 -0
  102. package/dist/ui/log-viewer.js +449 -0
  103. package/dist/ui/log-viewer.js.map +1 -0
  104. package/dist/utils/checkpoint.d.ts +87 -0
  105. package/dist/utils/checkpoint.js +317 -0
  106. package/dist/utils/checkpoint.js.map +1 -0
  107. package/dist/utils/config.d.ts +4 -0
  108. package/dist/utils/config.js +18 -8
  109. package/dist/utils/config.js.map +1 -1
  110. package/dist/utils/cursor-agent.js.map +1 -1
  111. package/dist/utils/dependency.d.ts +74 -0
  112. package/dist/utils/dependency.js +420 -0
  113. package/dist/utils/dependency.js.map +1 -0
  114. package/dist/utils/doctor.js +17 -11
  115. package/dist/utils/doctor.js.map +1 -1
  116. package/dist/utils/enhanced-logger.d.ts +10 -33
  117. package/dist/utils/enhanced-logger.js +108 -20
  118. package/dist/utils/enhanced-logger.js.map +1 -1
  119. package/dist/utils/git.d.ts +121 -0
  120. package/dist/utils/git.js +484 -11
  121. package/dist/utils/git.js.map +1 -1
  122. package/dist/utils/health.d.ts +91 -0
  123. package/dist/utils/health.js +556 -0
  124. package/dist/utils/health.js.map +1 -0
  125. package/dist/utils/lock.d.ts +95 -0
  126. package/dist/utils/lock.js +332 -0
  127. package/dist/utils/lock.js.map +1 -0
  128. package/dist/utils/log-buffer.d.ts +17 -0
  129. package/dist/utils/log-buffer.js +14 -0
  130. package/dist/utils/log-buffer.js.map +1 -0
  131. package/dist/utils/log-constants.d.ts +23 -0
  132. package/dist/utils/log-constants.js +28 -0
  133. package/dist/utils/log-constants.js.map +1 -0
  134. package/dist/utils/log-formatter.d.ts +25 -0
  135. package/dist/utils/log-formatter.js +237 -0
  136. package/dist/utils/log-formatter.js.map +1 -0
  137. package/dist/utils/log-service.d.ts +19 -0
  138. package/dist/utils/log-service.js +47 -0
  139. package/dist/utils/log-service.js.map +1 -0
  140. package/dist/utils/logger.d.ts +46 -27
  141. package/dist/utils/logger.js +82 -60
  142. package/dist/utils/logger.js.map +1 -1
  143. package/dist/utils/path.d.ts +19 -0
  144. package/dist/utils/path.js +77 -0
  145. package/dist/utils/path.js.map +1 -0
  146. package/dist/utils/process-manager.d.ts +21 -0
  147. package/dist/utils/process-manager.js +138 -0
  148. package/dist/utils/process-manager.js.map +1 -0
  149. package/dist/utils/retry.d.ts +121 -0
  150. package/dist/utils/retry.js +374 -0
  151. package/dist/utils/retry.js.map +1 -0
  152. package/dist/utils/run-service.d.ts +88 -0
  153. package/dist/utils/run-service.js +412 -0
  154. package/dist/utils/run-service.js.map +1 -0
  155. package/dist/utils/state.d.ts +62 -3
  156. package/dist/utils/state.js +317 -11
  157. package/dist/utils/state.js.map +1 -1
  158. package/dist/utils/task-service.d.ts +82 -0
  159. package/dist/utils/task-service.js +348 -0
  160. package/dist/utils/task-service.js.map +1 -0
  161. package/dist/utils/template.d.ts +14 -0
  162. package/dist/utils/template.js +122 -0
  163. package/dist/utils/template.js.map +1 -0
  164. package/dist/utils/types.d.ts +2 -271
  165. package/dist/utils/types.js +16 -0
  166. package/dist/utils/types.js.map +1 -1
  167. package/package.json +38 -23
  168. package/scripts/ai-security-check.js +0 -1
  169. package/scripts/local-security-gate.sh +0 -0
  170. package/scripts/monitor-lanes.sh +94 -0
  171. package/scripts/patches/test-cursor-agent.js +0 -1
  172. package/scripts/release.sh +0 -0
  173. package/scripts/setup-security.sh +0 -0
  174. package/scripts/stream-logs.sh +72 -0
  175. package/scripts/verify-and-fix.sh +0 -0
  176. package/src/cli/clean.ts +187 -6
  177. package/src/cli/index.ts +12 -1
  178. package/src/cli/init.ts +8 -7
  179. package/src/cli/logs.ts +124 -77
  180. package/src/cli/monitor.ts +1815 -898
  181. package/src/cli/prepare.ts +41 -21
  182. package/src/cli/resume.ts +753 -626
  183. package/src/cli/run.ts +12 -5
  184. package/src/cli/runs.ts +212 -0
  185. package/src/cli/setup-commands.ts +0 -0
  186. package/src/cli/signal.ts +8 -7
  187. package/src/cli/stop.ts +209 -0
  188. package/src/cli/tasks.ts +154 -0
  189. package/src/core/auto-recovery.ts +909 -0
  190. package/src/core/failure-policy.ts +592 -0
  191. package/src/core/orchestrator.ts +1131 -704
  192. package/src/core/reviewer.ts +4 -0
  193. package/src/core/runner.ts +444 -180
  194. package/src/services/logging/buffer.ts +326 -0
  195. package/src/services/logging/console.ts +193 -0
  196. package/src/services/logging/file-writer.ts +526 -0
  197. package/src/services/logging/formatter.ts +268 -0
  198. package/src/services/logging/index.ts +16 -0
  199. package/src/services/logging/parser.ts +232 -0
  200. package/src/services/process/index.ts +261 -0
  201. package/src/types/agent.ts +24 -0
  202. package/src/types/config.ts +79 -0
  203. package/src/types/events.ts +156 -0
  204. package/src/types/index.ts +29 -0
  205. package/src/types/lane.ts +56 -0
  206. package/src/types/logging.ts +96 -0
  207. package/src/types/review.ts +20 -0
  208. package/src/types/run.ts +37 -0
  209. package/src/types/task.ts +79 -0
  210. package/src/ui/components.ts +430 -0
  211. package/src/ui/log-viewer.ts +485 -0
  212. package/src/utils/checkpoint.ts +374 -0
  213. package/src/utils/config.ts +18 -8
  214. package/src/utils/cursor-agent.ts +1 -1
  215. package/src/utils/dependency.ts +482 -0
  216. package/src/utils/doctor.ts +18 -11
  217. package/src/utils/enhanced-logger.ts +122 -60
  218. package/src/utils/git.ts +517 -11
  219. package/src/utils/health.ts +596 -0
  220. package/src/utils/lock.ts +346 -0
  221. package/src/utils/log-buffer.ts +28 -0
  222. package/src/utils/log-constants.ts +26 -0
  223. package/src/utils/log-formatter.ts +245 -0
  224. package/src/utils/log-service.ts +49 -0
  225. package/src/utils/logger.ts +100 -51
  226. package/src/utils/path.ts +45 -0
  227. package/src/utils/process-manager.ts +100 -0
  228. package/src/utils/retry.ts +413 -0
  229. package/src/utils/run-service.ts +433 -0
  230. package/src/utils/state.ts +385 -11
  231. package/src/utils/task-service.ts +370 -0
  232. package/src/utils/template.ts +92 -0
  233. package/src/utils/types.ts +2 -314
  234. package/templates/basic.json +21 -0
package/src/cli/run.ts CHANGED
@@ -9,6 +9,7 @@ import { orchestrate } from '../core/orchestrator';
9
9
  import { getLogsDir, loadConfig } from '../utils/config';
10
10
  import { runDoctor, getDoctorStatus } from '../utils/doctor';
11
11
  import { areCommandsInstalled, setupCommands } from './setup-commands';
12
+ import { safeJoin } from '../utils/path';
12
13
 
13
14
  interface RunOptions {
14
15
  tasksDir?: string;
@@ -17,6 +18,7 @@ interface RunOptions {
17
18
  maxConcurrent: number | null;
18
19
  skipDoctor: boolean;
19
20
  noGit: boolean;
21
+ raw: boolean;
20
22
  help: boolean;
21
23
  }
22
24
 
@@ -32,6 +34,7 @@ Options:
32
34
  --executor <type> cursor-agent | cloud
33
35
  --skip-doctor Skip environment checks (not recommended)
34
36
  --no-git Disable Git operations (worktree, push, commit)
37
+ --raw Save raw logs (absolute raw, no processing)
35
38
  --dry-run Show execution plan without starting agents
36
39
  --help, -h Show help
37
40
 
@@ -53,6 +56,7 @@ function parseArgs(args: string[]): RunOptions {
53
56
  maxConcurrent: maxConcurrentIdx >= 0 ? parseInt(args[maxConcurrentIdx + 1] || '0') || null : null,
54
57
  skipDoctor: args.includes('--skip-doctor') || args.includes('--no-doctor'),
55
58
  noGit: args.includes('--no-git'),
59
+ raw: args.includes('--raw'),
56
60
  help: args.includes('--help') || args.includes('-h'),
57
61
  };
58
62
  }
@@ -90,8 +94,8 @@ async function run(args: string[]): Promise<void> {
90
94
  path.isAbsolute(options.tasksDir)
91
95
  ? options.tasksDir
92
96
  : (fs.existsSync(options.tasksDir)
93
- ? path.resolve(process.cwd(), options.tasksDir)
94
- : path.join(config.projectRoot, options.tasksDir));
97
+ ? path.resolve(process.cwd(), options.tasksDir) // nosemgrep
98
+ : safeJoin(config.projectRoot, options.tasksDir));
95
99
 
96
100
  if (!fs.existsSync(tasksDir)) {
97
101
  throw new Error(`Tasks directory not found: ${tasksDir}`);
@@ -119,9 +123,9 @@ async function run(args: string[]): Promise<void> {
119
123
  for (const issue of report.issues) {
120
124
  const header = `${issue.title} (${issue.id})`;
121
125
  if (issue.severity === 'error') {
122
- logger.error(header, '❌');
126
+ logger.error(header, { emoji: '❌' });
123
127
  } else {
124
- logger.warn(header, '⚠️');
128
+ logger.warn(header, { emoji: '⚠️' });
125
129
  }
126
130
  console.log(` ${issue.message}`);
127
131
  if (issue.details) console.log(` Details: ${issue.details}`);
@@ -142,7 +146,10 @@ async function run(args: string[]): Promise<void> {
142
146
  runDir: path.join(logsDir, 'runs', `run-${Date.now()}`),
143
147
  maxConcurrentLanes: options.maxConcurrent || config.maxConcurrentLanes,
144
148
  webhooks: config.webhooks || [],
145
- enhancedLogging: config.enhancedLogging,
149
+ enhancedLogging: {
150
+ ...config.enhancedLogging,
151
+ ...(options.raw ? { raw: true } : {}),
152
+ },
146
153
  noGit: options.noGit,
147
154
  });
148
155
  } catch (error: any) {
@@ -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
@@ -9,6 +9,7 @@ import * as fs from 'fs';
9
9
  import * as logger from '../utils/logger';
10
10
  import { loadConfig, getLogsDir } from '../utils/config';
11
11
  import { appendLog, createConversationEntry } from '../utils/state';
12
+ import { safeJoin } from '../utils/path';
12
13
 
13
14
  interface SignalOptions {
14
15
  lane: string | null;
@@ -51,7 +52,7 @@ function parseArgs(args: string[]): SignalOptions {
51
52
  }
52
53
 
53
54
  function findLatestRunDir(logsDir: string): string | null {
54
- const runsDir = path.join(logsDir, 'runs');
55
+ const runsDir = safeJoin(logsDir, 'runs');
55
56
  if (!fs.existsSync(runsDir)) return null;
56
57
 
57
58
  const runs = fs.readdirSync(runsDir)
@@ -59,7 +60,7 @@ function findLatestRunDir(logsDir: string): string | null {
59
60
  .sort()
60
61
  .reverse();
61
62
 
62
- return runs.length > 0 ? path.join(runsDir, runs[0]!) : null;
63
+ return runs.length > 0 ? safeJoin(runsDir, runs[0]!) : null;
63
64
  }
64
65
 
65
66
  async function signal(args: string[]): Promise<void> {
@@ -86,14 +87,14 @@ async function signal(args: string[]): Promise<void> {
86
87
  throw new Error(`Run directory not found: ${runDir || 'latest'}`);
87
88
  }
88
89
 
89
- const laneDir = path.join(runDir, 'lanes', options.lane);
90
+ const laneDir = safeJoin(runDir, 'lanes', options.lane);
90
91
  if (!fs.existsSync(laneDir)) {
91
92
  throw new Error(`Lane directory not found: ${laneDir}`);
92
93
  }
93
94
 
94
95
  // Case 1: Timeout update
95
96
  if (options.timeout !== null) {
96
- const timeoutPath = path.join(laneDir, 'timeout.txt');
97
+ const timeoutPath = safeJoin(laneDir, 'timeout.txt');
97
98
  fs.writeFileSync(timeoutPath, String(options.timeout));
98
99
  logger.success(`Timeout update signal sent to ${options.lane}: ${options.timeout}ms`);
99
100
  return;
@@ -101,8 +102,8 @@ async function signal(args: string[]): Promise<void> {
101
102
 
102
103
  // Case 2: Intervention message
103
104
  if (options.message) {
104
- const interventionPath = path.join(laneDir, 'intervention.txt');
105
- const convoPath = path.join(laneDir, 'conversation.jsonl');
105
+ const interventionPath = safeJoin(laneDir, 'intervention.txt');
106
+ const convoPath = safeJoin(laneDir, 'conversation.jsonl');
106
107
 
107
108
  logger.info(`Sending signal to lane: ${options.lane}`);
108
109
  logger.info(`Message: "${options.message}"`);
@@ -111,7 +112,7 @@ async function signal(args: string[]): Promise<void> {
111
112
  fs.writeFileSync(interventionPath, options.message);
112
113
 
113
114
  // 2. Also append to conversation log for visibility and history
114
- const entry = createConversationEntry('system', `[COMMANDER INTERVENTION]\n${options.message}`, {
115
+ const entry = createConversationEntry('intervention', `[HUMAN INTERVENTION]: ${options.message}`, {
115
116
  task: 'DIRECT_SIGNAL'
116
117
  });
117
118
  appendLog(convoPath, entry);
@@ -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;
@@ -0,0 +1,154 @@
1
+ /**
2
+ * CursorFlow tasks command
3
+ *
4
+ * Usage:
5
+ * cursorflow tasks # List all tasks
6
+ * cursorflow tasks --validate # List all tasks with validation
7
+ * cursorflow tasks <name> # Show detailed task info
8
+ */
9
+
10
+ import * as logger from '../utils/logger';
11
+ import { TaskService, TaskDirInfo, ValidationStatus } from '../utils/task-service';
12
+ import { findProjectRoot, loadConfig, getTasksDir } from '../utils/config';
13
+
14
+ const COLORS = logger.COLORS;
15
+
16
+ interface TasksCliOptions {
17
+ validate: boolean;
18
+ taskName: string | null;
19
+ }
20
+
21
+ function printHelp(): void {
22
+ console.log(`
23
+ Usage: cursorflow tasks [options] [task-name]
24
+
25
+ Manage and view prepared tasks in _cursorflow/tasks/.
26
+
27
+ Options:
28
+ --validate Run validation on all tasks before listing
29
+ --help, -h Show help
30
+
31
+ Examples:
32
+ cursorflow tasks
33
+ cursorflow tasks --validate
34
+ cursorflow tasks 2412221530_AuthSystem
35
+ `);
36
+ }
37
+
38
+ function parseArgs(args: string[]): TasksCliOptions {
39
+ const options: TasksCliOptions = {
40
+ validate: args.includes('--validate'),
41
+ taskName: null,
42
+ };
43
+
44
+ const nameArg = args.find(arg => !arg.startsWith('-'));
45
+ if (nameArg) {
46
+ options.taskName = nameArg;
47
+ }
48
+
49
+ if (args.includes('--help') || args.includes('-h')) {
50
+ printHelp();
51
+ process.exit(0);
52
+ }
53
+
54
+ return options;
55
+ }
56
+
57
+ function formatDate(date: Date): string {
58
+ const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
59
+ return `${months[date.getMonth()]} ${date.getDate()}`;
60
+ }
61
+
62
+ function getStatusLabel(info: TaskDirInfo): string {
63
+ const status = info.validationStatus;
64
+ const icon = status === 'valid' ? '✅' : status === 'warnings' ? '⚠️' : status === 'errors' ? '❌' : '❓';
65
+
66
+ if (status === 'valid') return `${icon} Valid`;
67
+ if (status === 'warnings') return `${icon} Warnings`;
68
+ if (status === 'errors') return `${icon} Errors`;
69
+
70
+ return `${icon} Unknown`;
71
+ }
72
+
73
+ function printTasksList(tasks: TaskDirInfo[]): void {
74
+ if (tasks.length === 0) {
75
+ logger.info('No tasks found in _cursorflow/tasks/');
76
+ return;
77
+ }
78
+
79
+ console.log(`${COLORS.bold}Prepared Tasks:${COLORS.reset}`);
80
+
81
+ for (let i = 0; i < tasks.length; i++) {
82
+ const task = tasks[i];
83
+ const prefix = i === 0 ? ' ▶' : ' ';
84
+ const name = task.name.padEnd(30);
85
+ const lanes = `${task.lanes.length} lane${task.lanes.length > 1 ? 's' : ''}`.padEnd(10);
86
+ const status = getStatusLabel(task).padEnd(14);
87
+ const date = formatDate(task.timestamp);
88
+
89
+ let color = COLORS.reset;
90
+ if (task.validationStatus === 'errors') color = COLORS.red;
91
+ else if (task.validationStatus === 'warnings') color = COLORS.yellow;
92
+ else if (task.validationStatus === 'valid') color = COLORS.green;
93
+
94
+ console.log(`${color}${prefix} ${name} ${lanes} ${status} ${date}${COLORS.reset}`);
95
+ }
96
+ }
97
+
98
+ function printTaskDetail(info: TaskDirInfo): void {
99
+ console.log(`${COLORS.bold}Task: ${info.name}${COLORS.reset}`);
100
+ console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
101
+
102
+ if (info.lanes.length === 0) {
103
+ console.log('No lanes defined.');
104
+ } else {
105
+ console.log(`${COLORS.bold}Lanes:${COLORS.reset}`);
106
+ for (const lane of info.lanes) {
107
+ const fileName = lane.fileName.padEnd(18);
108
+ const preset = `[${lane.preset}]`.padEnd(10);
109
+ const flow = lane.taskFlow;
110
+ const depends = lane.dependsOn.length > 0 ? ` ${COLORS.gray}(depends: ${lane.dependsOn.join(', ')})${COLORS.reset}` : '';
111
+
112
+ console.log(` ${fileName} ${COLORS.blue}${preset}${COLORS.reset} ${flow}${depends}`);
113
+ }
114
+ }
115
+
116
+ }
117
+
118
+ async function tasks(args: string[]): Promise<void> {
119
+ const options = parseArgs(args);
120
+ const projectRoot = findProjectRoot();
121
+ const config = loadConfig(projectRoot);
122
+ const tasksDir = getTasksDir(config);
123
+ const taskService = new TaskService(tasksDir);
124
+
125
+ if (options.taskName) {
126
+ const info = taskService.getTaskDirInfo(options.taskName);
127
+ if (!info) {
128
+ logger.error(`Task not found: ${options.taskName}`);
129
+ process.exit(1);
130
+ }
131
+
132
+ // Always validate for detail view to have report
133
+ taskService.validateTaskDir(options.taskName);
134
+ const updatedInfo = taskService.getTaskDirInfo(options.taskName)!;
135
+
136
+ printTaskDetail(updatedInfo);
137
+ } else {
138
+ let taskList = taskService.listTaskDirs();
139
+
140
+ if (options.validate) {
141
+ const spinner = logger.createSpinner('Validating tasks...');
142
+ spinner.start();
143
+ for (const task of taskList) {
144
+ taskService.validateTaskDir(task.name);
145
+ }
146
+ spinner.succeed('Validation complete');
147
+ taskList = taskService.listTaskDirs();
148
+ }
149
+
150
+ printTasksList(taskList);
151
+ }
152
+ }
153
+
154
+ export = tasks;