@litmers/cursorflow-orchestrator 0.1.6 → 0.1.9
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 +31 -0
- package/README.md +97 -321
- package/commands/cursorflow-doctor.md +28 -0
- package/commands/cursorflow-monitor.md +59 -101
- package/commands/cursorflow-prepare.md +25 -2
- package/commands/cursorflow-resume.md +11 -0
- package/commands/cursorflow-run.md +109 -100
- package/commands/cursorflow-signal.md +85 -14
- package/dist/cli/clean.d.ts +3 -1
- package/dist/cli/clean.js +122 -8
- package/dist/cli/clean.js.map +1 -1
- package/dist/cli/index.js +20 -20
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/monitor.d.ts +1 -1
- package/dist/cli/monitor.js +678 -145
- package/dist/cli/monitor.js.map +1 -1
- package/dist/cli/run.js +1 -0
- package/dist/cli/run.js.map +1 -1
- package/dist/core/orchestrator.d.ts +4 -2
- package/dist/core/orchestrator.js +92 -23
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/runner.d.ts +14 -2
- package/dist/core/runner.js +244 -58
- package/dist/core/runner.js.map +1 -1
- package/dist/utils/types.d.ts +11 -0
- package/package.json +1 -1
- package/scripts/patches/test-cursor-agent.js +203 -0
- package/src/cli/clean.ts +129 -9
- package/src/cli/index.ts +20 -20
- package/src/cli/monitor.ts +732 -185
- package/src/cli/run.ts +1 -0
- package/src/core/orchestrator.ts +102 -27
- package/src/core/runner.ts +284 -66
- package/src/utils/types.ts +11 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Direct cursor-agent spawn test script
|
|
4
|
+
*
|
|
5
|
+
* This script tests cursor-agent directly to compare behavior with CursorFlow.
|
|
6
|
+
* It helps identify if the issue is in how CursorFlow spawns the agent.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/patches/test-cursor-agent.js [--stdio-mode <mode>]
|
|
10
|
+
*
|
|
11
|
+
* Modes:
|
|
12
|
+
* - pipe: Full pipe mode (default, same as CursorFlow)
|
|
13
|
+
* - inherit-stdin: Inherit stdin, pipe stdout/stderr
|
|
14
|
+
* - inherit-all: Inherit all (like running directly in terminal)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { spawn, spawnSync } = require('child_process');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
// Parse args
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
const stdioModeIdx = args.indexOf('--stdio-mode');
|
|
23
|
+
const stdioMode = stdioModeIdx >= 0 ? args[stdioModeIdx + 1] : 'pipe';
|
|
24
|
+
|
|
25
|
+
console.log('='.repeat(60));
|
|
26
|
+
console.log('cursor-agent Direct Spawn Test');
|
|
27
|
+
console.log('='.repeat(60));
|
|
28
|
+
console.log(`stdio mode: ${stdioMode}`);
|
|
29
|
+
console.log('');
|
|
30
|
+
|
|
31
|
+
// Step 1: Check cursor-agent is available
|
|
32
|
+
console.log('1. Checking cursor-agent availability...');
|
|
33
|
+
const whichResult = spawnSync('which', ['cursor-agent']);
|
|
34
|
+
if (whichResult.status !== 0) {
|
|
35
|
+
console.error(' ❌ cursor-agent not found in PATH');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
console.log(` ✓ Found at: ${whichResult.stdout.toString().trim()}`);
|
|
39
|
+
|
|
40
|
+
// Step 2: Create a chat session
|
|
41
|
+
console.log('\n2. Creating chat session...');
|
|
42
|
+
const createChatResult = spawnSync('cursor-agent', ['create-chat'], {
|
|
43
|
+
encoding: 'utf8',
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
timeout: 30000,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (createChatResult.status !== 0) {
|
|
49
|
+
console.error(' ❌ Failed to create chat:');
|
|
50
|
+
console.error(createChatResult.stderr || createChatResult.stdout);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const chatIdLines = createChatResult.stdout.trim().split('\n');
|
|
55
|
+
const chatId = chatIdLines[chatIdLines.length - 1];
|
|
56
|
+
console.log(` ✓ Chat ID: ${chatId}`);
|
|
57
|
+
|
|
58
|
+
// Step 3: Configure stdio based on mode
|
|
59
|
+
let stdioConfig;
|
|
60
|
+
switch (stdioMode) {
|
|
61
|
+
case 'inherit-stdin':
|
|
62
|
+
stdioConfig = ['inherit', 'pipe', 'pipe'];
|
|
63
|
+
break;
|
|
64
|
+
case 'inherit-all':
|
|
65
|
+
stdioConfig = 'inherit';
|
|
66
|
+
break;
|
|
67
|
+
case 'ignore-stdin':
|
|
68
|
+
stdioConfig = ['ignore', 'pipe', 'pipe'];
|
|
69
|
+
break;
|
|
70
|
+
case 'pipe':
|
|
71
|
+
default:
|
|
72
|
+
stdioConfig = ['pipe', 'pipe', 'pipe'];
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`\n3. Testing with stdio: ${JSON.stringify(stdioConfig)}`);
|
|
77
|
+
|
|
78
|
+
// Step 4: Send a simple prompt
|
|
79
|
+
const testPrompt = 'Just respond with: Hello World. Nothing else.';
|
|
80
|
+
const workspace = process.cwd();
|
|
81
|
+
|
|
82
|
+
const agentArgs = [
|
|
83
|
+
'--print',
|
|
84
|
+
'--output-format', 'json',
|
|
85
|
+
'--workspace', workspace,
|
|
86
|
+
'--model', 'gemini-3-flash',
|
|
87
|
+
'--resume', chatId,
|
|
88
|
+
testPrompt,
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
console.log(` Workspace: ${workspace}`);
|
|
92
|
+
console.log(` Prompt: "${testPrompt}"`);
|
|
93
|
+
console.log('');
|
|
94
|
+
console.log('4. Spawning cursor-agent...');
|
|
95
|
+
console.log(` Command: cursor-agent ${agentArgs.join(' ').substring(0, 80)}...`);
|
|
96
|
+
console.log('');
|
|
97
|
+
|
|
98
|
+
const startTime = Date.now();
|
|
99
|
+
|
|
100
|
+
const child = spawn('cursor-agent', agentArgs, {
|
|
101
|
+
stdio: stdioConfig,
|
|
102
|
+
env: {
|
|
103
|
+
...process.env,
|
|
104
|
+
// Disable Python buffering if cursor-agent uses Python
|
|
105
|
+
PYTHONUNBUFFERED: '1',
|
|
106
|
+
// Disable Node.js experimental warnings
|
|
107
|
+
NODE_OPTIONS: '',
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
let fullStdout = '';
|
|
112
|
+
let fullStderr = '';
|
|
113
|
+
let gotData = false;
|
|
114
|
+
|
|
115
|
+
// Track first byte time
|
|
116
|
+
let firstByteTime = null;
|
|
117
|
+
|
|
118
|
+
if (child.stdout) {
|
|
119
|
+
child.stdout.on('data', (data) => {
|
|
120
|
+
if (!firstByteTime) {
|
|
121
|
+
firstByteTime = Date.now();
|
|
122
|
+
console.log(` [+${((firstByteTime - startTime) / 1000).toFixed(2)}s] First stdout byte received`);
|
|
123
|
+
}
|
|
124
|
+
gotData = true;
|
|
125
|
+
const str = data.toString();
|
|
126
|
+
fullStdout += str;
|
|
127
|
+
process.stdout.write(` [stdout] ${str}`);
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (child.stderr) {
|
|
132
|
+
child.stderr.on('data', (data) => {
|
|
133
|
+
const str = data.toString();
|
|
134
|
+
fullStderr += str;
|
|
135
|
+
process.stderr.write(` [stderr] ${str}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Set timeout
|
|
140
|
+
const timeout = setTimeout(() => {
|
|
141
|
+
console.log('\n ⏰ TIMEOUT after 60 seconds');
|
|
142
|
+
console.log(` Got any data: ${gotData}`);
|
|
143
|
+
console.log(` Stdout length: ${fullStdout.length}`);
|
|
144
|
+
console.log(` Stderr length: ${fullStderr.length}`);
|
|
145
|
+
child.kill('SIGTERM');
|
|
146
|
+
}, 60000);
|
|
147
|
+
|
|
148
|
+
child.on('close', (code) => {
|
|
149
|
+
clearTimeout(timeout);
|
|
150
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
151
|
+
|
|
152
|
+
console.log('\n' + '-'.repeat(60));
|
|
153
|
+
console.log('RESULT');
|
|
154
|
+
console.log('-'.repeat(60));
|
|
155
|
+
console.log(`Exit code: ${code}`);
|
|
156
|
+
console.log(`Duration: ${elapsed.toFixed(2)}s`);
|
|
157
|
+
console.log(`First byte: ${firstByteTime ? ((firstByteTime - startTime) / 1000).toFixed(2) + 's' : 'never'}`);
|
|
158
|
+
console.log(`Stdout bytes: ${fullStdout.length}`);
|
|
159
|
+
console.log(`Stderr bytes: ${fullStderr.length}`);
|
|
160
|
+
|
|
161
|
+
if (fullStdout.length > 0) {
|
|
162
|
+
console.log('\nStdout content:');
|
|
163
|
+
console.log(fullStdout);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (fullStderr.length > 0) {
|
|
167
|
+
console.log('\nStderr content:');
|
|
168
|
+
console.log(fullStderr);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Try to parse JSON
|
|
172
|
+
const lines = fullStdout.split('\n').filter(Boolean);
|
|
173
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
174
|
+
const line = lines[i].trim();
|
|
175
|
+
if (line.startsWith('{') && line.endsWith('}')) {
|
|
176
|
+
try {
|
|
177
|
+
const json = JSON.parse(line);
|
|
178
|
+
console.log('\nParsed JSON response:');
|
|
179
|
+
console.log(JSON.stringify(json, null, 2));
|
|
180
|
+
break;
|
|
181
|
+
} catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
console.log('\n' + '='.repeat(60));
|
|
188
|
+
if (code === 0 && fullStdout.includes('result')) {
|
|
189
|
+
console.log('✓ SUCCESS - cursor-agent responded correctly');
|
|
190
|
+
} else if (gotData) {
|
|
191
|
+
console.log('⚠ PARTIAL - Got data but may not have completed');
|
|
192
|
+
} else {
|
|
193
|
+
console.log('❌ FAILURE - No data received');
|
|
194
|
+
}
|
|
195
|
+
console.log('='.repeat(60));
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
child.on('error', (err) => {
|
|
199
|
+
clearTimeout(timeout);
|
|
200
|
+
console.error(`\n❌ Spawn error: ${err.message}`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
});
|
|
203
|
+
|
package/src/cli/clean.ts
CHANGED
|
@@ -1,36 +1,156 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CursorFlow clean command
|
|
2
|
+
* CursorFlow clean command
|
|
3
|
+
*
|
|
4
|
+
* Clean up worktrees, branches, and logs created by CursorFlow
|
|
3
5
|
*/
|
|
4
6
|
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
5
9
|
import * as logger from '../utils/logger';
|
|
10
|
+
import * as git from '../utils/git';
|
|
11
|
+
import { loadConfig, getLogsDir } from '../utils/config';
|
|
6
12
|
|
|
7
13
|
interface CleanOptions {
|
|
8
14
|
type?: string;
|
|
9
15
|
pattern: string | null;
|
|
10
16
|
dryRun: boolean;
|
|
11
17
|
force: boolean;
|
|
18
|
+
all: boolean;
|
|
12
19
|
}
|
|
13
20
|
|
|
14
21
|
function parseArgs(args: string[]): CleanOptions {
|
|
15
22
|
return {
|
|
16
|
-
type: args
|
|
23
|
+
type: args.find(a => ['branches', 'worktrees', 'logs', 'all'].includes(a)),
|
|
17
24
|
pattern: null,
|
|
18
25
|
dryRun: args.includes('--dry-run'),
|
|
19
26
|
force: args.includes('--force'),
|
|
27
|
+
all: args.includes('--all'),
|
|
20
28
|
};
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
async function clean(args: string[]): Promise<void> {
|
|
24
|
-
logger.section('🧹 Cleaning CursorFlow Resources');
|
|
25
|
-
|
|
26
32
|
const options = parseArgs(args);
|
|
33
|
+
const config = loadConfig();
|
|
34
|
+
const repoRoot = git.getRepoRoot();
|
|
35
|
+
|
|
36
|
+
logger.section('🧹 Cleaning CursorFlow Resources');
|
|
37
|
+
|
|
38
|
+
const type = options.type || 'all';
|
|
39
|
+
|
|
40
|
+
if (type === 'all') {
|
|
41
|
+
await cleanWorktrees(config, repoRoot, options);
|
|
42
|
+
await cleanBranches(config, repoRoot, options);
|
|
43
|
+
await cleanLogs(config, options);
|
|
44
|
+
} else if (type === 'worktrees') {
|
|
45
|
+
await cleanWorktrees(config, repoRoot, options);
|
|
46
|
+
} else if (type === 'branches') {
|
|
47
|
+
await cleanBranches(config, repoRoot, options);
|
|
48
|
+
} else if (type === 'logs') {
|
|
49
|
+
await cleanLogs(config, options);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.success('\n✨ Cleaning complete!');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function cleanWorktrees(config: any, repoRoot: string, options: CleanOptions) {
|
|
56
|
+
logger.info('\nChecking worktrees...');
|
|
57
|
+
const worktrees = git.listWorktrees(repoRoot);
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
59
|
+
const worktreeRoot = path.join(repoRoot, config.worktreeRoot || '_cursorflow/worktrees');
|
|
60
|
+
const toRemove = worktrees.filter(wt => {
|
|
61
|
+
// Skip main worktree
|
|
62
|
+
if (wt.path === repoRoot) return false;
|
|
63
|
+
|
|
64
|
+
const isInsideRoot = wt.path.startsWith(worktreeRoot);
|
|
65
|
+
const hasPrefix = path.basename(wt.path).startsWith(config.worktreePrefix || 'cursorflow-');
|
|
66
|
+
|
|
67
|
+
return isInsideRoot || hasPrefix;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
if (toRemove.length === 0) {
|
|
71
|
+
logger.info(' No worktrees found to clean.');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
for (const wt of toRemove) {
|
|
76
|
+
if (options.dryRun) {
|
|
77
|
+
logger.info(` [DRY RUN] Would remove worktree: ${wt.path} (${wt.branch || 'no branch'})`);
|
|
78
|
+
} else {
|
|
79
|
+
try {
|
|
80
|
+
logger.info(` Removing worktree: ${wt.path}...`);
|
|
81
|
+
git.removeWorktree(wt.path, { cwd: repoRoot, force: options.force });
|
|
82
|
+
|
|
83
|
+
// Git worktree remove might leave the directory if it has untracked files
|
|
84
|
+
if (fs.existsSync(wt.path)) {
|
|
85
|
+
if (options.force) {
|
|
86
|
+
fs.rmSync(wt.path, { recursive: true, force: true });
|
|
87
|
+
logger.info(` (Forced removal of directory)`);
|
|
88
|
+
} else {
|
|
89
|
+
logger.warn(` Directory still exists: ${wt.path} (contains untracked files). Use --force to delete anyway.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
} catch (e: any) {
|
|
93
|
+
logger.error(` Failed to remove worktree ${wt.path}: ${e.message}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function cleanBranches(config: any, repoRoot: string, options: CleanOptions) {
|
|
100
|
+
logger.info('\nChecking branches...');
|
|
31
101
|
|
|
32
|
-
|
|
33
|
-
|
|
102
|
+
// List all local branches
|
|
103
|
+
const result = git.runGitResult(['branch', '--list'], { cwd: repoRoot });
|
|
104
|
+
if (!result.success) return;
|
|
105
|
+
|
|
106
|
+
const branches = result.stdout
|
|
107
|
+
.split('\n')
|
|
108
|
+
.map(b => b.replace('*', '').trim())
|
|
109
|
+
.filter(b => b && b !== 'main' && b !== 'master');
|
|
110
|
+
|
|
111
|
+
const prefix = config.branchPrefix || 'feature/';
|
|
112
|
+
const toDelete = branches.filter(b => b.startsWith(prefix));
|
|
113
|
+
|
|
114
|
+
if (toDelete.length === 0) {
|
|
115
|
+
logger.info(' No branches found to clean.');
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const branch of toDelete) {
|
|
120
|
+
if (options.dryRun) {
|
|
121
|
+
logger.info(` [DRY RUN] Would delete branch: ${branch}`);
|
|
122
|
+
} else {
|
|
123
|
+
try {
|
|
124
|
+
logger.info(` Deleting branch: ${branch}...`);
|
|
125
|
+
git.deleteBranch(branch, { cwd: repoRoot, force: options.force || options.all });
|
|
126
|
+
} catch (e: any) {
|
|
127
|
+
logger.warn(` Could not delete branch ${branch}: ${e.message}. Use --force if it's not merged.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function cleanLogs(config: any, options: CleanOptions) {
|
|
134
|
+
const logsDir = getLogsDir(config);
|
|
135
|
+
logger.info(`\nChecking logs in ${logsDir}...`);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(logsDir)) {
|
|
138
|
+
logger.info(' Logs directory does not exist.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (options.dryRun) {
|
|
143
|
+
logger.info(` [DRY RUN] Would remove logs directory: ${logsDir}`);
|
|
144
|
+
} else {
|
|
145
|
+
try {
|
|
146
|
+
logger.info(` Removing logs...`);
|
|
147
|
+
fs.rmSync(logsDir, { recursive: true, force: true });
|
|
148
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
149
|
+
logger.info(` Logs cleared.`);
|
|
150
|
+
} catch (e: any) {
|
|
151
|
+
logger.error(` Failed to clean logs: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
34
154
|
}
|
|
35
155
|
|
|
36
156
|
export = clean;
|
package/src/cli/index.ts
CHANGED
|
@@ -20,45 +20,45 @@ const COMMANDS: Record<string, CommandFn> = {
|
|
|
20
20
|
|
|
21
21
|
function printHelp(): void {
|
|
22
22
|
console.log(`
|
|
23
|
-
|
|
23
|
+
\x1b[1m\x1b[36mCursorFlow\x1b[0m - Git worktree-based parallel AI agent orchestration
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
\x1b[1mUSAGE\x1b[0m
|
|
26
|
+
$ \x1b[32mcursorflow\x1b[0m <command> [options]
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
28
|
+
\x1b[1mCOMMANDS\x1b[0m
|
|
29
|
+
\x1b[33minit\x1b[0m [options] Initialize CursorFlow in project
|
|
30
|
+
\x1b[33mrun\x1b[0m <tasks-dir> [options] Run orchestration (DAG-based)
|
|
31
|
+
\x1b[33mmonitor\x1b[0m [run-dir] [options] \x1b[36mInteractive\x1b[0m lane dashboard
|
|
32
|
+
\x1b[33mclean\x1b[0m <type> [options] Clean branches/worktrees/logs
|
|
33
|
+
\x1b[33mresume\x1b[0m <lane> [options] Resume interrupted lane
|
|
34
|
+
\x1b[33mdoctor\x1b[0m [options] Check environment and preflight
|
|
35
|
+
\x1b[33msignal\x1b[0m <lane> <msg> Directly intervene in a running lane
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
\x1b[1mGLOBAL OPTIONS\x1b[0m
|
|
37
38
|
--config <path> Config file path
|
|
38
39
|
--help, -h Show help
|
|
39
40
|
--version, -v Show version
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
cursorflow doctor
|
|
42
|
+
\x1b[1mEXAMPLES\x1b[0m
|
|
43
|
+
$ \x1b[32mcursorflow init --example\x1b[0m
|
|
44
|
+
$ \x1b[32mcursorflow run _cursorflow/tasks/MyFeature/\x1b[0m
|
|
45
|
+
$ \x1b[32mcursorflow monitor latest\x1b[0m
|
|
46
|
+
$ \x1b[32mcursorflow signal lane-1 "Please use pnpm instead of npm"\x1b[0m
|
|
47
47
|
|
|
48
|
-
|
|
48
|
+
\x1b[1mDOCUMENTATION\x1b[0m
|
|
49
49
|
https://github.com/eungjin-cigro/cursorflow#readme
|
|
50
50
|
`);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
function printVersion(): void {
|
|
54
54
|
const pkg = require('../../package.json');
|
|
55
|
-
console.log(
|
|
55
|
+
console.log(`\x1b[1m\x1b[36mCursorFlow\x1b[0m v${pkg.version}`);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
async function main(): Promise<void> {
|
|
59
59
|
const args = process.argv.slice(2);
|
|
60
60
|
|
|
61
|
-
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
61
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h') || args[0] === 'help') {
|
|
62
62
|
printHelp();
|
|
63
63
|
return;
|
|
64
64
|
}
|