@litmers/cursorflow-orchestrator 0.1.30 → 0.1.34
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/README.md +144 -52
- package/commands/cursorflow-add.md +159 -0
- package/commands/cursorflow-monitor.md +23 -2
- package/commands/cursorflow-new.md +87 -0
- package/dist/cli/add.d.ts +7 -0
- package/dist/cli/add.js +377 -0
- package/dist/cli/add.js.map +1 -0
- package/dist/cli/clean.js +1 -0
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/config.d.ts +7 -0
- package/dist/cli/config.js +181 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/index.js +34 -30
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/logs.js +7 -33
- package/dist/cli/logs.js.map +1 -1
- package/dist/cli/monitor.js +51 -62
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/new.d.ts +7 -0
- package/dist/cli/new.js +232 -0
- package/dist/cli/new.js.map +1 -0
- package/dist/cli/prepare.js +95 -193
- package/dist/cli/prepare.js.map +1 -1
- package/dist/cli/resume.js +11 -47
- package/dist/cli/resume.js.map +1 -1
- package/dist/cli/run.js +27 -22
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/tasks.js +1 -2
- package/dist/cli/tasks.js.map +1 -1
- package/dist/core/failure-policy.d.ts +9 -0
- package/dist/core/failure-policy.js +9 -0
- package/dist/core/failure-policy.js.map +1 -1
- package/dist/core/orchestrator.d.ts +20 -6
- package/dist/core/orchestrator.js +217 -331
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner/agent.d.ts +27 -0
- package/dist/core/runner/agent.js +294 -0
- package/dist/core/runner/agent.js.map +1 -0
- package/dist/core/runner/index.d.ts +5 -0
- package/dist/core/runner/index.js +22 -0
- package/dist/core/runner/index.js.map +1 -0
- package/dist/core/runner/pipeline.d.ts +9 -0
- package/dist/core/runner/pipeline.js +539 -0
- package/dist/core/runner/pipeline.js.map +1 -0
- package/dist/core/runner/prompt.d.ts +25 -0
- package/dist/core/runner/prompt.js +175 -0
- package/dist/core/runner/prompt.js.map +1 -0
- package/dist/core/runner/task.d.ts +26 -0
- package/dist/core/runner/task.js +283 -0
- package/dist/core/runner/task.js.map +1 -0
- package/dist/core/runner/utils.d.ts +37 -0
- package/dist/core/runner/utils.js +161 -0
- package/dist/core/runner/utils.js.map +1 -0
- package/dist/core/runner.d.ts +2 -96
- package/dist/core/runner.js +11 -1136
- package/dist/core/runner.js.map +1 -1
- package/dist/core/stall-detection.d.ts +326 -0
- package/dist/core/stall-detection.js +781 -0
- package/dist/core/stall-detection.js.map +1 -0
- package/dist/types/config.d.ts +6 -6
- package/dist/types/flow.d.ts +84 -0
- package/dist/types/flow.js +10 -0
- package/dist/types/flow.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +3 -3
- package/dist/types/index.js.map +1 -1
- package/dist/types/lane.d.ts +0 -2
- package/dist/types/logging.d.ts +5 -1
- package/dist/types/task.d.ts +7 -11
- package/dist/utils/config.js +7 -15
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/dependency.d.ts +36 -1
- package/dist/utils/dependency.js +256 -1
- package/dist/utils/dependency.js.map +1 -1
- package/dist/utils/enhanced-logger.d.ts +45 -82
- package/dist/utils/enhanced-logger.js +238 -844
- package/dist/utils/enhanced-logger.js.map +1 -1
- package/dist/utils/git.d.ts +29 -0
- package/dist/utils/git.js +115 -5
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/state.js +0 -2
- package/dist/utils/state.js.map +1 -1
- package/dist/utils/task-service.d.ts +2 -2
- package/dist/utils/task-service.js +40 -31
- package/dist/utils/task-service.js.map +1 -1
- package/package.json +4 -3
- package/src/cli/add.ts +397 -0
- package/src/cli/clean.ts +1 -0
- package/src/cli/config.ts +177 -0
- package/src/cli/index.ts +36 -32
- package/src/cli/logs.ts +7 -31
- package/src/cli/monitor.ts +55 -71
- package/src/cli/new.ts +235 -0
- package/src/cli/prepare.ts +98 -205
- package/src/cli/resume.ts +13 -56
- package/src/cli/run.ts +311 -306
- package/src/cli/tasks.ts +1 -2
- package/src/core/failure-policy.ts +9 -0
- package/src/core/orchestrator.ts +281 -375
- package/src/core/runner/agent.ts +314 -0
- package/src/core/runner/index.ts +6 -0
- package/src/core/runner/pipeline.ts +567 -0
- package/src/core/runner/prompt.ts +174 -0
- package/src/core/runner/task.ts +320 -0
- package/src/core/runner/utils.ts +142 -0
- package/src/core/runner.ts +8 -1347
- package/src/core/stall-detection.ts +936 -0
- package/src/types/config.ts +6 -6
- package/src/types/flow.ts +91 -0
- package/src/types/index.ts +15 -3
- package/src/types/lane.ts +0 -2
- package/src/types/logging.ts +5 -1
- package/src/types/task.ts +7 -11
- package/src/utils/config.ts +8 -16
- package/src/utils/dependency.ts +311 -2
- package/src/utils/enhanced-logger.ts +263 -927
- package/src/utils/git.ts +145 -5
- package/src/utils/state.ts +0 -2
- package/src/utils/task-service.ts +48 -40
- package/commands/cursorflow-review.md +0 -56
- package/commands/cursorflow-runs.md +0 -59
- package/dist/cli/runs.d.ts +0 -5
- package/dist/cli/runs.js +0 -214
- package/dist/cli/runs.js.map +0 -1
- package/dist/core/reviewer.d.ts +0 -66
- package/dist/core/reviewer.js +0 -265
- package/dist/core/reviewer.js.map +0 -1
- package/src/cli/runs.ts +0 -212
- package/src/core/reviewer.ts +0 -285
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CursorFlow 'config' command
|
|
3
|
+
*
|
|
4
|
+
* View and set configuration values
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
import * as logger from '../utils/logger';
|
|
10
|
+
import { loadConfig, findProjectRoot } from '../utils/config';
|
|
11
|
+
import { safeJoin } from '../utils/path';
|
|
12
|
+
|
|
13
|
+
interface ConfigOptions {
|
|
14
|
+
key: string | null;
|
|
15
|
+
value: string | null;
|
|
16
|
+
list: boolean;
|
|
17
|
+
help: boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function printHelp(): void {
|
|
21
|
+
console.log(`
|
|
22
|
+
\x1b[1mcursorflow config\x1b[0m - 설정 조회 및 변경
|
|
23
|
+
|
|
24
|
+
\x1b[1m사용법:\x1b[0m
|
|
25
|
+
cursorflow config # 현재 설정 보기
|
|
26
|
+
cursorflow config <key> # 특정 설정 값 보기
|
|
27
|
+
cursorflow config <key> <value> # 설정 값 변경
|
|
28
|
+
|
|
29
|
+
\x1b[1m주요 설정 키:\x1b[0m
|
|
30
|
+
defaultModel 기본 AI 모델 (예: gemini-3-flash)
|
|
31
|
+
branchPrefix 브랜치 접두사 (예: feature/)
|
|
32
|
+
executor 실행기 (cursor-agent | cloud)
|
|
33
|
+
|
|
34
|
+
\x1b[1m예시:\x1b[0m
|
|
35
|
+
# 현재 설정 보기
|
|
36
|
+
cursorflow config
|
|
37
|
+
|
|
38
|
+
# 기본 모델 확인
|
|
39
|
+
cursorflow config defaultModel
|
|
40
|
+
|
|
41
|
+
# 기본 모델 변경
|
|
42
|
+
cursorflow config defaultModel opus-4.5-thinking
|
|
43
|
+
|
|
44
|
+
\x1b[1m참고:\x1b[0m
|
|
45
|
+
설정 파일 위치: cursorflow.config.js
|
|
46
|
+
설정을 영구 저장하려면 cursorflow.config.js 파일을 직접 수정하세요.
|
|
47
|
+
`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseArgs(args: string[]): ConfigOptions {
|
|
51
|
+
const result: ConfigOptions = {
|
|
52
|
+
key: null,
|
|
53
|
+
value: null,
|
|
54
|
+
list: false,
|
|
55
|
+
help: false,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
let positionalCount = 0;
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < args.length; i++) {
|
|
61
|
+
const arg = args[i];
|
|
62
|
+
|
|
63
|
+
if (arg === '--help' || arg === '-h') {
|
|
64
|
+
result.help = true;
|
|
65
|
+
} else if (arg === '--list' || arg === '-l') {
|
|
66
|
+
result.list = true;
|
|
67
|
+
} else if (!arg.startsWith('--')) {
|
|
68
|
+
if (positionalCount === 0) {
|
|
69
|
+
result.key = arg;
|
|
70
|
+
} else if (positionalCount === 1) {
|
|
71
|
+
result.value = arg;
|
|
72
|
+
}
|
|
73
|
+
positionalCount++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function config(args: string[]): Promise<void> {
|
|
81
|
+
const options = parseArgs(args);
|
|
82
|
+
|
|
83
|
+
if (options.help) {
|
|
84
|
+
printHelp();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const projectRoot = findProjectRoot();
|
|
89
|
+
const currentConfig = loadConfig(projectRoot);
|
|
90
|
+
const configPath = safeJoin(projectRoot, 'cursorflow.config.js');
|
|
91
|
+
|
|
92
|
+
// If setting a value
|
|
93
|
+
if (options.key && options.value) {
|
|
94
|
+
const key = options.key;
|
|
95
|
+
const value = options.value;
|
|
96
|
+
|
|
97
|
+
// Validate key exists
|
|
98
|
+
if (!(key in currentConfig)) {
|
|
99
|
+
logger.error(`알 수 없는 설정 키: ${key}`);
|
|
100
|
+
console.log('\n사용 가능한 키: defaultModel, branchPrefix, executor, ...');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Update or create config file
|
|
105
|
+
let configContent: string;
|
|
106
|
+
|
|
107
|
+
if (fs.existsSync(configPath)) {
|
|
108
|
+
// Read existing config
|
|
109
|
+
const existingContent = fs.readFileSync(configPath, 'utf-8');
|
|
110
|
+
|
|
111
|
+
// Simple regex replace for the key
|
|
112
|
+
const keyRegex = new RegExp(`(${key}\\s*:\\s*)['"]?[^'",\\n]+['"]?`, 'g');
|
|
113
|
+
if (keyRegex.test(existingContent)) {
|
|
114
|
+
configContent = existingContent.replace(keyRegex, `$1'${value}'`);
|
|
115
|
+
} else {
|
|
116
|
+
// Add the key to the config
|
|
117
|
+
configContent = existingContent.replace(
|
|
118
|
+
/module\.exports\s*=\s*\{/,
|
|
119
|
+
`module.exports = {\n ${key}: '${value}',`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
} else {
|
|
123
|
+
// Create new config file
|
|
124
|
+
configContent = `module.exports = {
|
|
125
|
+
${key}: '${value}',
|
|
126
|
+
};
|
|
127
|
+
`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fs.writeFileSync(configPath, configContent);
|
|
131
|
+
logger.success(`✅ ${key} = '${value}' 설정됨`);
|
|
132
|
+
console.log(`\n설정 파일: ${configPath}`);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// If viewing a specific key
|
|
137
|
+
if (options.key) {
|
|
138
|
+
const key = options.key;
|
|
139
|
+
|
|
140
|
+
if (!(key in currentConfig)) {
|
|
141
|
+
logger.error(`알 수 없는 설정 키: ${key}`);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const value = (currentConfig as any)[key];
|
|
146
|
+
console.log(`${key} = ${JSON.stringify(value)}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// List all config
|
|
151
|
+
logger.section('CursorFlow 설정');
|
|
152
|
+
console.log('');
|
|
153
|
+
|
|
154
|
+
const importantKeys = [
|
|
155
|
+
'defaultModel',
|
|
156
|
+
'branchPrefix',
|
|
157
|
+
'executor',
|
|
158
|
+
'flowsDir',
|
|
159
|
+
'tasksDir',
|
|
160
|
+
'logsDir',
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
console.log('\x1b[1m주요 설정:\x1b[0m');
|
|
164
|
+
for (const key of importantKeys) {
|
|
165
|
+
const value = (currentConfig as any)[key];
|
|
166
|
+
console.log(` ${key.padEnd(20)} ${JSON.stringify(value)}`);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log('');
|
|
170
|
+
console.log(`설정 파일: ${configPath}`);
|
|
171
|
+
console.log('');
|
|
172
|
+
console.log('설정 변경: cursorflow config <key> <value>');
|
|
173
|
+
console.log('예: cursorflow config defaultModel opus-4.5-thinking');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export = config;
|
|
177
|
+
|
package/src/cli/index.ts
CHANGED
|
@@ -12,6 +12,11 @@ type CommandFn = (args: string[]) => Promise<void>;
|
|
|
12
12
|
*/
|
|
13
13
|
const COMMANDS: Record<string, CommandFn> = {
|
|
14
14
|
init: require('./init'),
|
|
15
|
+
// New Flow architecture commands
|
|
16
|
+
new: require('./new'),
|
|
17
|
+
add: require('./add'),
|
|
18
|
+
config: require('./config'),
|
|
19
|
+
// Legacy prepare command (deprecated)
|
|
15
20
|
prepare: require('./prepare'),
|
|
16
21
|
run: require('./run'),
|
|
17
22
|
monitor: require('./monitor'),
|
|
@@ -21,7 +26,6 @@ const COMMANDS: Record<string, CommandFn> = {
|
|
|
21
26
|
signal: require('./signal'),
|
|
22
27
|
models: require('./models'),
|
|
23
28
|
logs: require('./logs'),
|
|
24
|
-
runs: require('./runs'),
|
|
25
29
|
tasks: require('./tasks'),
|
|
26
30
|
stop: require('./stop'),
|
|
27
31
|
setup: require('./setup-commands').main,
|
|
@@ -35,37 +39,37 @@ function printHelp(): void {
|
|
|
35
39
|
\x1b[1mUSAGE\x1b[0m
|
|
36
40
|
$ \x1b[32mcursorflow\x1b[0m <command> [options]
|
|
37
41
|
|
|
38
|
-
\x1b[
|
|
39
|
-
\x1b[
|
|
40
|
-
\x1b[
|
|
41
|
-
\x1b[
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
\x1b[
|
|
45
|
-
\x1b[
|
|
46
|
-
\x1b[33mstop\x1b[0m [run-id] [options]
|
|
47
|
-
\x1b[
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
\x1b[
|
|
51
|
-
\x1b[
|
|
52
|
-
\x1b[
|
|
53
|
-
|
|
54
|
-
\x1b[
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
\x1b[
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
$ \x1b[32mcursorflow
|
|
66
|
-
$ \x1b[32mcursorflow
|
|
67
|
-
$ \x1b[32mcursorflow
|
|
68
|
-
$ \x1b[32mcursorflow
|
|
42
|
+
\x1b[1mFLOW COMMANDS (New)\x1b[0m
|
|
43
|
+
\x1b[33mnew\x1b[0m <flow> --lanes "..." Create a new Flow with Lanes
|
|
44
|
+
\x1b[33madd\x1b[0m <flow> <lane> --task Add Tasks to a Lane
|
|
45
|
+
\x1b[33mconfig\x1b[0m [key] [value] View or set config (e.g., defaultModel)
|
|
46
|
+
|
|
47
|
+
\x1b[1mEXECUTION\x1b[0m
|
|
48
|
+
\x1b[33mrun\x1b[0m <flow> [options] Run orchestration (DAG-based)
|
|
49
|
+
\x1b[33mmonitor\x1b[0m [run-dir] [options] \x1b[36mInteractive\x1b[0m lane dashboard
|
|
50
|
+
\x1b[33mstop\x1b[0m [run-id] [options] Stop running workflows
|
|
51
|
+
\x1b[33mresume\x1b[0m [lane] [options] Resume lane(s)
|
|
52
|
+
|
|
53
|
+
\x1b[1mINSPECTION\x1b[0m
|
|
54
|
+
\x1b[33mtasks\x1b[0m [name] [options] Browse and validate prepared tasks
|
|
55
|
+
\x1b[33mlogs\x1b[0m [run-dir] [options] View, export, and follow logs
|
|
56
|
+
\x1b[33mdoctor\x1b[0m [options] Check environment and preflight
|
|
57
|
+
|
|
58
|
+
\x1b[1mUTILITY\x1b[0m
|
|
59
|
+
\x1b[33minit\x1b[0m [options] Initialize CursorFlow in project
|
|
60
|
+
\x1b[33msetup\x1b[0m [options] Install Cursor IDE commands
|
|
61
|
+
\x1b[33mclean\x1b[0m <type> [options] Clean branches/worktrees/logs/tasks
|
|
62
|
+
\x1b[33msignal\x1b[0m <lane> <msg> Directly intervene in a running lane
|
|
63
|
+
\x1b[33mmodels\x1b[0m [options] List available AI models
|
|
64
|
+
|
|
65
|
+
\x1b[1mLEGACY\x1b[0m
|
|
66
|
+
\x1b[33mprepare\x1b[0m <feature> [opts] (deprecated) Use 'new' + 'add' instead
|
|
67
|
+
|
|
68
|
+
\x1b[1mQUICK START\x1b[0m
|
|
69
|
+
$ \x1b[32mcursorflow new MyFeature --lanes "backend,frontend"\x1b[0m
|
|
70
|
+
$ \x1b[32mcursorflow add MyFeature backend --task "name=impl|model=sonnet-4.5|prompt=API 구현"\x1b[0m
|
|
71
|
+
$ \x1b[32mcursorflow add MyFeature frontend --task "name=ui|model=sonnet-4.5|prompt=UI 구현" --after "backend"\x1b[0m
|
|
72
|
+
$ \x1b[32mcursorflow run MyFeature\x1b[0m
|
|
69
73
|
|
|
70
74
|
\x1b[1mDOCUMENTATION\x1b[0m
|
|
71
75
|
https://github.com/eungjin-cigro/cursorflow#readme
|
package/src/cli/logs.ts
CHANGED
|
@@ -160,17 +160,12 @@ function displayTextLogs(
|
|
|
160
160
|
let logFile: string;
|
|
161
161
|
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
162
162
|
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
163
|
-
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
164
163
|
|
|
165
164
|
if (options.raw) {
|
|
166
165
|
logFile = rawLog;
|
|
167
|
-
} else if (options.clean) {
|
|
168
|
-
logFile = cleanLog;
|
|
169
|
-
} else if (options.readable && fs.existsSync(readableLog)) {
|
|
170
|
-
logFile = readableLog;
|
|
171
166
|
} else {
|
|
172
|
-
// Default
|
|
173
|
-
logFile =
|
|
167
|
+
// Default to readable log (clean option also uses readable now)
|
|
168
|
+
logFile = readableLog;
|
|
174
169
|
}
|
|
175
170
|
|
|
176
171
|
if (!fs.existsSync(logFile)) {
|
|
@@ -686,17 +681,12 @@ function followLogs(laneDir: string, options: LogsOptions): void {
|
|
|
686
681
|
let logFile: string;
|
|
687
682
|
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
688
683
|
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
689
|
-
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
690
684
|
|
|
691
685
|
if (options.raw) {
|
|
692
686
|
logFile = rawLog;
|
|
693
|
-
} else if (options.clean) {
|
|
694
|
-
logFile = cleanLog;
|
|
695
|
-
} else if (options.readable && fs.existsSync(readableLog)) {
|
|
696
|
-
logFile = readableLog;
|
|
697
687
|
} else {
|
|
698
|
-
// Default
|
|
699
|
-
logFile =
|
|
688
|
+
// Default to readable log
|
|
689
|
+
logFile = readableLog;
|
|
700
690
|
}
|
|
701
691
|
|
|
702
692
|
if (!fs.existsSync(logFile)) {
|
|
@@ -778,16 +768,14 @@ function displaySummary(runDir: string): void {
|
|
|
778
768
|
|
|
779
769
|
for (const lane of lanes) {
|
|
780
770
|
const laneDir = safeJoin(runDir, 'lanes', lane);
|
|
781
|
-
const cleanLog = safeJoin(laneDir, 'terminal.log');
|
|
782
771
|
const rawLog = safeJoin(laneDir, 'terminal-raw.log');
|
|
783
|
-
const jsonLog = safeJoin(laneDir, 'terminal.jsonl');
|
|
784
772
|
const readableLog = safeJoin(laneDir, 'terminal-readable.log');
|
|
785
773
|
|
|
786
774
|
console.log(` ${logger.COLORS.green}📁 ${lane}${logger.COLORS.reset}`);
|
|
787
775
|
|
|
788
|
-
if (fs.existsSync(
|
|
789
|
-
const stats = fs.statSync(
|
|
790
|
-
console.log(` └─ terminal.log
|
|
776
|
+
if (fs.existsSync(readableLog)) {
|
|
777
|
+
const stats = fs.statSync(readableLog);
|
|
778
|
+
console.log(` └─ terminal-readable.log ${formatSize(stats.size)} ${logger.COLORS.yellow}(default)${logger.COLORS.reset}`);
|
|
791
779
|
}
|
|
792
780
|
|
|
793
781
|
if (fs.existsSync(rawLog)) {
|
|
@@ -795,18 +783,6 @@ function displaySummary(runDir: string): void {
|
|
|
795
783
|
console.log(` └─ terminal-raw.log ${formatSize(stats.size)}`);
|
|
796
784
|
}
|
|
797
785
|
|
|
798
|
-
if (fs.existsSync(readableLog)) {
|
|
799
|
-
const stats = fs.statSync(readableLog);
|
|
800
|
-
console.log(` └─ terminal-readable.log ${formatSize(stats.size)} ${logger.COLORS.yellow}(parsed AI output)${logger.COLORS.reset}`);
|
|
801
|
-
}
|
|
802
|
-
|
|
803
|
-
if (fs.existsSync(jsonLog)) {
|
|
804
|
-
const stats = fs.statSync(jsonLog);
|
|
805
|
-
const entries = readJsonLog(jsonLog);
|
|
806
|
-
const errors = entries.filter(e => e.level === 'error' || e.level === 'stderr').length;
|
|
807
|
-
console.log(` └─ terminal.jsonl ${formatSize(stats.size)} (${entries.length} entries, ${errors} errors)`);
|
|
808
|
-
}
|
|
809
|
-
|
|
810
786
|
console.log('');
|
|
811
787
|
}
|
|
812
788
|
}
|
package/src/cli/monitor.ts
CHANGED
|
@@ -56,13 +56,13 @@ const UI = {
|
|
|
56
56
|
interface LaneWithDeps {
|
|
57
57
|
name: string;
|
|
58
58
|
path: string;
|
|
59
|
-
dependsOn: string[];
|
|
60
59
|
}
|
|
61
60
|
|
|
62
61
|
interface MonitorOptions {
|
|
63
62
|
runDir?: string;
|
|
64
63
|
interval: number;
|
|
65
64
|
help: boolean;
|
|
65
|
+
list: boolean;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
function printHelp(): void {
|
|
@@ -73,8 +73,14 @@ Interactive lane dashboard to track progress and dependencies.
|
|
|
73
73
|
|
|
74
74
|
Options:
|
|
75
75
|
[run-dir] Run directory to monitor (default: latest)
|
|
76
|
+
--list, -l List all runs (multiple flows dashboard)
|
|
76
77
|
--interval <seconds> Refresh interval (default: 2)
|
|
77
78
|
--help, -h Show help
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
cursorflow monitor # Monitor latest run
|
|
82
|
+
cursorflow monitor --list # Show all runs dashboard
|
|
83
|
+
cursorflow monitor run-123 # Monitor specific run
|
|
78
84
|
`);
|
|
79
85
|
}
|
|
80
86
|
|
|
@@ -135,9 +141,10 @@ class InteractiveMonitor {
|
|
|
135
141
|
return process.stdout.rows || 24;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
|
-
constructor(runDir: string, interval: number, logsDir?: string) {
|
|
144
|
+
constructor(runDir: string, interval: number, logsDir?: string, initialView: View = View.LIST) {
|
|
139
145
|
this.runDir = runDir;
|
|
140
146
|
this.interval = interval;
|
|
147
|
+
this.view = initialView;
|
|
141
148
|
|
|
142
149
|
// Set logs directory for multiple flows discovery
|
|
143
150
|
if (logsDir) {
|
|
@@ -152,6 +159,21 @@ class InteractiveMonitor {
|
|
|
152
159
|
}
|
|
153
160
|
|
|
154
161
|
public async start() {
|
|
162
|
+
// Non-interactive mode for CI/pipes
|
|
163
|
+
if (!process.stdout.isTTY || !process.stdin.isTTY) {
|
|
164
|
+
this.discoverFlows();
|
|
165
|
+
this.refresh();
|
|
166
|
+
// Print summary and exit
|
|
167
|
+
if (this.view === View.FLOWS_DASHBOARD) {
|
|
168
|
+
console.log(`\nFound ${this.allFlows.length} flows`);
|
|
169
|
+
} else {
|
|
170
|
+
console.log(`\nMonitoring run: ${path.basename(this.runDir)}`);
|
|
171
|
+
const flowSummary = getFlowSummary(this.runDir);
|
|
172
|
+
console.log(`Status: ${flowSummary.running} running, ${flowSummary.completed} completed, ${flowSummary.failed} failed`);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
155
177
|
this.setupTerminal();
|
|
156
178
|
|
|
157
179
|
// Start unified log streaming
|
|
@@ -999,14 +1021,12 @@ class InteractiveMonitor {
|
|
|
999
1021
|
// Next action
|
|
1000
1022
|
let nextAction = '-';
|
|
1001
1023
|
if (status.status === 'completed') {
|
|
1002
|
-
|
|
1003
|
-
nextAction = dependents.length > 0 ? `→ ${dependents.map(d => d.name).join(', ')}` : '✓ Done';
|
|
1024
|
+
nextAction = '✓ Done';
|
|
1004
1025
|
} else if (status.status === 'waiting') {
|
|
1005
1026
|
if (status.waitingFor?.length > 0) {
|
|
1006
1027
|
nextAction = `⏳ ${status.waitingFor.join(', ')}`;
|
|
1007
1028
|
} else {
|
|
1008
|
-
|
|
1009
|
-
nextAction = missingDeps.length > 0 ? `⏳ ${missingDeps.join(', ')}` : '⏳ waiting';
|
|
1029
|
+
nextAction = '⏳ waiting';
|
|
1010
1030
|
}
|
|
1011
1031
|
} else if (processStatus?.actualStatus === 'running') {
|
|
1012
1032
|
nextAction = '🚀 working...';
|
|
@@ -1063,9 +1083,6 @@ class InteractiveMonitor {
|
|
|
1063
1083
|
process.stdout.write(` ${UI.COLORS.dim}Duration${UI.COLORS.reset} ${this.formatDuration(processStatus?.duration || status.duration)}\n`);
|
|
1064
1084
|
process.stdout.write(` ${UI.COLORS.dim}Branch${UI.COLORS.reset} ${status.pipelineBranch}\n`);
|
|
1065
1085
|
|
|
1066
|
-
if (status.dependsOn && status.dependsOn.length > 0) {
|
|
1067
|
-
process.stdout.write(` ${UI.COLORS.dim}Depends${UI.COLORS.reset} ${status.dependsOn.join(', ')}\n`);
|
|
1068
|
-
}
|
|
1069
1086
|
if (status.waitingFor && status.waitingFor.length > 0) {
|
|
1070
1087
|
process.stdout.write(` ${UI.COLORS.yellow}Waiting${UI.COLORS.reset} ${status.waitingFor.join(', ')}\n`);
|
|
1071
1088
|
}
|
|
@@ -1075,7 +1092,7 @@ class InteractiveMonitor {
|
|
|
1075
1092
|
|
|
1076
1093
|
// Live terminal preview
|
|
1077
1094
|
this.renderSectionTitle('Live Terminal', 'last 10 lines');
|
|
1078
|
-
const logPath = safeJoin(lane.path, 'terminal.log');
|
|
1095
|
+
const logPath = safeJoin(lane.path, 'terminal-readable.log');
|
|
1079
1096
|
if (fs.existsSync(logPath)) {
|
|
1080
1097
|
const content = fs.readFileSync(logPath, 'utf8');
|
|
1081
1098
|
const lines = content.split('\n').slice(-10);
|
|
@@ -1132,7 +1149,6 @@ class InteractiveMonitor {
|
|
|
1132
1149
|
const colors: Record<string, string> = {
|
|
1133
1150
|
user: UI.COLORS.yellow,
|
|
1134
1151
|
assistant: UI.COLORS.green,
|
|
1135
|
-
reviewer: UI.COLORS.magenta,
|
|
1136
1152
|
intervention: UI.COLORS.red,
|
|
1137
1153
|
system: UI.COLORS.cyan,
|
|
1138
1154
|
};
|
|
@@ -1243,9 +1259,9 @@ class InteractiveMonitor {
|
|
|
1243
1259
|
const nodeText = `${statusIcon} ${laneName}`;
|
|
1244
1260
|
process.stdout.write(` ${statusColor}${nodeText.padEnd(20)}${UI.COLORS.reset}`);
|
|
1245
1261
|
|
|
1246
|
-
//
|
|
1247
|
-
if (status?.
|
|
1248
|
-
process.stdout.write(` ${UI.COLORS.dim}←${UI.COLORS.reset} ${UI.COLORS.yellow}${status.
|
|
1262
|
+
// Show task-level dependencies if waiting
|
|
1263
|
+
if (status?.waitingFor?.length > 0) {
|
|
1264
|
+
process.stdout.write(` ${UI.COLORS.dim}←${UI.COLORS.reset} ${UI.COLORS.yellow}${status.waitingFor.join(', ')}${UI.COLORS.reset}`);
|
|
1249
1265
|
}
|
|
1250
1266
|
process.stdout.write('\n');
|
|
1251
1267
|
}
|
|
@@ -1256,54 +1272,21 @@ class InteractiveMonitor {
|
|
|
1256
1272
|
}
|
|
1257
1273
|
}
|
|
1258
1274
|
|
|
1259
|
-
process.stdout.write(`\n ${UI.COLORS.dim}
|
|
1275
|
+
process.stdout.write(`\n ${UI.COLORS.dim}Tasks can wait for other tasks using task-level dependencies${UI.COLORS.reset}\n`);
|
|
1260
1276
|
|
|
1261
1277
|
this.renderFooter(['[←/Esc] Back']);
|
|
1262
1278
|
}
|
|
1263
1279
|
|
|
1264
1280
|
/**
|
|
1265
|
-
* Calculate
|
|
1281
|
+
* Calculate levels for visualization (all lanes run in parallel now)
|
|
1266
1282
|
*/
|
|
1267
1283
|
private calculateDependencyLevels(): string[][] {
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
const noDeps = this.lanes.filter(l => !l.dependsOn || l.dependsOn.length === 0);
|
|
1273
|
-
if (noDeps.length > 0) {
|
|
1274
|
-
levels.push(noDeps.map(l => l.name));
|
|
1275
|
-
noDeps.forEach(l => assigned.add(l.name));
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// Then assign remaining lanes by dependency completion
|
|
1279
|
-
let maxIterations = 10;
|
|
1280
|
-
while (assigned.size < this.lanes.length && maxIterations-- > 0) {
|
|
1281
|
-
const nextLevel: string[] = [];
|
|
1282
|
-
|
|
1283
|
-
for (const lane of this.lanes) {
|
|
1284
|
-
if (assigned.has(lane.name)) continue;
|
|
1285
|
-
|
|
1286
|
-
// Check if all dependencies are assigned
|
|
1287
|
-
const allDepsAssigned = lane.dependsOn.every(d => assigned.has(d));
|
|
1288
|
-
if (allDepsAssigned) {
|
|
1289
|
-
nextLevel.push(lane.name);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
if (nextLevel.length === 0) {
|
|
1294
|
-
// Remaining lanes have circular deps or missing deps
|
|
1295
|
-
const remaining = this.lanes.filter(l => !assigned.has(l.name)).map(l => l.name);
|
|
1296
|
-
if (remaining.length > 0) {
|
|
1297
|
-
levels.push(remaining);
|
|
1298
|
-
}
|
|
1299
|
-
break;
|
|
1300
|
-
}
|
|
1301
|
-
|
|
1302
|
-
levels.push(nextLevel);
|
|
1303
|
-
nextLevel.forEach(n => assigned.add(n));
|
|
1284
|
+
// Since lane-level dependencies are removed, all lanes can run in parallel
|
|
1285
|
+
// Group them into a single level
|
|
1286
|
+
if (this.lanes.length === 0) {
|
|
1287
|
+
return [];
|
|
1304
1288
|
}
|
|
1305
|
-
|
|
1306
|
-
return levels;
|
|
1289
|
+
return [this.lanes.map(l => l.name)];
|
|
1307
1290
|
}
|
|
1308
1291
|
|
|
1309
1292
|
private renderTerminal() {
|
|
@@ -1326,8 +1309,8 @@ class InteractiveMonitor {
|
|
|
1326
1309
|
logLines = this.getReadableLogLines(jsonlPath, lane.name);
|
|
1327
1310
|
totalLines = logLines.length;
|
|
1328
1311
|
} else {
|
|
1329
|
-
// Use
|
|
1330
|
-
const logPath = safeJoin(lane.path, 'terminal.log');
|
|
1312
|
+
// Use readable log
|
|
1313
|
+
const logPath = safeJoin(lane.path, 'terminal-readable.log');
|
|
1331
1314
|
if (fs.existsSync(logPath)) {
|
|
1332
1315
|
const content = fs.readFileSync(logPath, 'utf8');
|
|
1333
1316
|
logLines = content.split('\n');
|
|
@@ -1689,39 +1672,33 @@ class InteractiveMonitor {
|
|
|
1689
1672
|
return fs.readdirSync(lanesDir)
|
|
1690
1673
|
.filter(d => fs.statSync(safeJoin(lanesDir, d)).isDirectory())
|
|
1691
1674
|
.map(name => {
|
|
1692
|
-
const config = laneConfigs.find(c => c.name === name);
|
|
1693
1675
|
return {
|
|
1694
1676
|
name,
|
|
1695
1677
|
path: safeJoin(lanesDir, name),
|
|
1696
|
-
dependsOn: config?.dependsOn || [],
|
|
1697
1678
|
};
|
|
1698
1679
|
});
|
|
1699
1680
|
}
|
|
1700
1681
|
|
|
1701
|
-
private listLaneFilesFromDir(tasksDir: string): { name: string
|
|
1682
|
+
private listLaneFilesFromDir(tasksDir: string): { name: string }[] {
|
|
1702
1683
|
if (!fs.existsSync(tasksDir)) return [];
|
|
1703
1684
|
return fs.readdirSync(tasksDir)
|
|
1704
1685
|
.filter(f => f.endsWith('.json'))
|
|
1705
1686
|
.map(f => {
|
|
1706
1687
|
const filePath = safeJoin(tasksDir, f);
|
|
1707
1688
|
try {
|
|
1708
|
-
|
|
1709
|
-
return { name: path.basename(f, '.json'), dependsOn: config.dependsOn || [] };
|
|
1689
|
+
return { name: path.basename(f, '.json') };
|
|
1710
1690
|
} catch {
|
|
1711
|
-
return { name: path.basename(f, '.json')
|
|
1691
|
+
return { name: path.basename(f, '.json') };
|
|
1712
1692
|
}
|
|
1713
1693
|
});
|
|
1714
1694
|
}
|
|
1715
1695
|
|
|
1716
|
-
private getLaneStatus(lanePath: string,
|
|
1696
|
+
private getLaneStatus(lanePath: string, _laneName: string) {
|
|
1717
1697
|
const statePath = safeJoin(lanePath, 'state.json');
|
|
1718
1698
|
const state = loadState<LaneState & { chatId?: string }>(statePath);
|
|
1719
1699
|
|
|
1720
|
-
const laneInfo = this.lanes.find(l => l.name === laneName);
|
|
1721
|
-
const dependsOn = state?.dependsOn || laneInfo?.dependsOn || [];
|
|
1722
|
-
|
|
1723
1700
|
if (!state) {
|
|
1724
|
-
return { status: 'pending', currentTask: 0, totalTasks: '?', progress: '0%',
|
|
1701
|
+
return { status: 'pending', currentTask: 0, totalTasks: '?', progress: '0%', duration: 0, pipelineBranch: '-', chatId: '-' };
|
|
1725
1702
|
}
|
|
1726
1703
|
|
|
1727
1704
|
const progress = state.totalTasks > 0 ? Math.round((state.currentTaskIndex / state.totalTasks) * 100) : 0;
|
|
@@ -1737,7 +1714,6 @@ class InteractiveMonitor {
|
|
|
1737
1714
|
progress: `${progress}%`,
|
|
1738
1715
|
pipelineBranch: state.pipelineBranch || '-',
|
|
1739
1716
|
chatId: state.chatId || '-',
|
|
1740
|
-
dependsOn,
|
|
1741
1717
|
duration,
|
|
1742
1718
|
error: state.error,
|
|
1743
1719
|
pid: state.pid,
|
|
@@ -1788,6 +1764,8 @@ function findLatestRunDir(logsDir: string): string | null {
|
|
|
1788
1764
|
*/
|
|
1789
1765
|
async function monitor(args: string[]): Promise<void> {
|
|
1790
1766
|
const help = args.includes('--help') || args.includes('-h');
|
|
1767
|
+
const list = args.includes('--list') || args.includes('-l');
|
|
1768
|
+
|
|
1791
1769
|
if (help) {
|
|
1792
1770
|
printHelp();
|
|
1793
1771
|
return;
|
|
@@ -1802,12 +1780,18 @@ async function monitor(args: string[]): Promise<void> {
|
|
|
1802
1780
|
let runDir = runDirArg;
|
|
1803
1781
|
if (!runDir || runDir === 'latest') {
|
|
1804
1782
|
runDir = findLatestRunDir(config.logsDir) || undefined;
|
|
1805
|
-
if (!runDir) throw new Error('No run directories found');
|
|
1783
|
+
if (!runDir && !list) throw new Error('No run directories found');
|
|
1784
|
+
if (!runDir && list) {
|
|
1785
|
+
// Create a dummy runDir if none exists but we want to see the list (dashboard will handle empty list)
|
|
1786
|
+
runDir = path.join(config.logsDir, 'runs', 'empty');
|
|
1787
|
+
}
|
|
1806
1788
|
}
|
|
1807
1789
|
|
|
1808
|
-
if (!fs.existsSync(runDir)
|
|
1790
|
+
if (runDir && !fs.existsSync(runDir) && !list) {
|
|
1791
|
+
throw new Error(`Run directory not found: ${runDir}`);
|
|
1792
|
+
}
|
|
1809
1793
|
|
|
1810
|
-
const monitor = new InteractiveMonitor(runDir,
|
|
1794
|
+
const monitor = new InteractiveMonitor(runDir!, interval, undefined, list ? View.FLOWS_DASHBOARD : View.LIST);
|
|
1811
1795
|
await monitor.start();
|
|
1812
1796
|
}
|
|
1813
1797
|
|