@litmers/cursorflow-orchestrator 0.1.3 → 0.1.6

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 (90) hide show
  1. package/CHANGELOG.md +17 -7
  2. package/README.md +33 -2
  3. package/commands/cursorflow-doctor.md +24 -0
  4. package/commands/cursorflow-signal.md +19 -0
  5. package/dist/cli/clean.d.ts +5 -0
  6. package/dist/cli/clean.js +57 -0
  7. package/dist/cli/clean.js.map +1 -0
  8. package/dist/cli/doctor.d.ts +15 -0
  9. package/dist/cli/doctor.js +139 -0
  10. package/dist/cli/doctor.js.map +1 -0
  11. package/dist/cli/index.d.ts +6 -0
  12. package/dist/cli/index.js +125 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/init.d.ts +7 -0
  15. package/dist/cli/init.js +302 -0
  16. package/dist/cli/init.js.map +1 -0
  17. package/dist/cli/monitor.d.ts +8 -0
  18. package/dist/cli/monitor.js +210 -0
  19. package/dist/cli/monitor.js.map +1 -0
  20. package/dist/cli/resume.d.ts +5 -0
  21. package/dist/cli/resume.js +128 -0
  22. package/dist/cli/resume.js.map +1 -0
  23. package/dist/cli/run.d.ts +5 -0
  24. package/dist/cli/run.js +128 -0
  25. package/dist/cli/run.js.map +1 -0
  26. package/dist/cli/setup-commands.d.ts +23 -0
  27. package/dist/cli/setup-commands.js +234 -0
  28. package/dist/cli/setup-commands.js.map +1 -0
  29. package/dist/cli/signal.d.ts +7 -0
  30. package/dist/cli/signal.js +99 -0
  31. package/dist/cli/signal.js.map +1 -0
  32. package/dist/core/orchestrator.d.ts +47 -0
  33. package/dist/core/orchestrator.js +192 -0
  34. package/dist/core/orchestrator.js.map +1 -0
  35. package/dist/core/reviewer.d.ts +60 -0
  36. package/dist/core/reviewer.js +239 -0
  37. package/dist/core/reviewer.js.map +1 -0
  38. package/dist/core/runner.d.ts +51 -0
  39. package/dist/core/runner.js +499 -0
  40. package/dist/core/runner.js.map +1 -0
  41. package/dist/utils/config.d.ts +31 -0
  42. package/dist/utils/config.js +198 -0
  43. package/dist/utils/config.js.map +1 -0
  44. package/dist/utils/cursor-agent.d.ts +61 -0
  45. package/dist/utils/cursor-agent.js +263 -0
  46. package/dist/utils/cursor-agent.js.map +1 -0
  47. package/dist/utils/doctor.d.ts +63 -0
  48. package/dist/utils/doctor.js +280 -0
  49. package/dist/utils/doctor.js.map +1 -0
  50. package/dist/utils/git.d.ts +131 -0
  51. package/dist/utils/git.js +272 -0
  52. package/dist/utils/git.js.map +1 -0
  53. package/dist/utils/logger.d.ts +68 -0
  54. package/dist/utils/logger.js +158 -0
  55. package/dist/utils/logger.js.map +1 -0
  56. package/dist/utils/state.d.ts +65 -0
  57. package/dist/utils/state.js +216 -0
  58. package/dist/utils/state.js.map +1 -0
  59. package/dist/utils/types.d.ts +118 -0
  60. package/dist/utils/types.js +6 -0
  61. package/dist/utils/types.js.map +1 -0
  62. package/examples/README.md +155 -0
  63. package/examples/demo-project/README.md +262 -0
  64. package/examples/demo-project/_cursorflow/tasks/demo-test/01-create-utils.json +18 -0
  65. package/examples/demo-project/_cursorflow/tasks/demo-test/02-add-tests.json +18 -0
  66. package/examples/demo-project/_cursorflow/tasks/demo-test/README.md +109 -0
  67. package/package.json +71 -61
  68. package/scripts/ai-security-check.js +11 -4
  69. package/scripts/local-security-gate.sh +76 -0
  70. package/src/cli/{clean.js → clean.ts} +11 -5
  71. package/src/cli/doctor.ts +127 -0
  72. package/src/cli/{index.js → index.ts} +27 -16
  73. package/src/cli/{init.js → init.ts} +26 -18
  74. package/src/cli/{monitor.js → monitor.ts} +57 -44
  75. package/src/cli/resume.ts +119 -0
  76. package/src/cli/run.ts +109 -0
  77. package/src/cli/{setup-commands.js → setup-commands.ts} +38 -18
  78. package/src/cli/signal.ts +89 -0
  79. package/src/core/{orchestrator.js → orchestrator.ts} +44 -26
  80. package/src/core/{reviewer.js → reviewer.ts} +36 -29
  81. package/src/core/{runner.js → runner.ts} +125 -76
  82. package/src/utils/{config.js → config.ts} +17 -25
  83. package/src/utils/{cursor-agent.js → cursor-agent.ts} +38 -47
  84. package/src/utils/doctor.ts +312 -0
  85. package/src/utils/{git.js → git.ts} +70 -56
  86. package/src/utils/{logger.js → logger.ts} +170 -178
  87. package/src/utils/{state.js → state.ts} +30 -38
  88. package/src/utils/types.ts +134 -0
  89. package/src/cli/resume.js +0 -31
  90. package/src/cli/run.js +0 -51
@@ -1,24 +1,59 @@
1
- #!/usr/bin/env node
2
1
  /**
3
2
  * Git utilities for CursorFlow
4
3
  */
5
4
 
6
- const { execSync, spawnSync } = require('child_process');
7
- const path = require('path');
5
+ import { execSync, spawnSync } from 'child_process';
6
+
7
+ export interface GitRunOptions {
8
+ cwd?: string;
9
+ silent?: boolean;
10
+ }
11
+
12
+ export interface GitResult {
13
+ exitCode: number;
14
+ stdout: string;
15
+ stderr: string;
16
+ success: boolean;
17
+ }
18
+
19
+ export interface WorktreeInfo {
20
+ path: string;
21
+ branch?: string;
22
+ head?: string;
23
+ }
24
+
25
+ export interface ChangedFile {
26
+ status: string;
27
+ file: string;
28
+ }
29
+
30
+ export interface CommitInfo {
31
+ hash: string;
32
+ shortHash: string;
33
+ author: string;
34
+ authorEmail: string;
35
+ timestamp: number;
36
+ subject: string;
37
+ }
8
38
 
9
39
  /**
10
40
  * Run git command and return output
11
41
  */
12
- function runGit(args, options = {}) {
42
+ export function runGit(args: string[], options: GitRunOptions = {}): string {
13
43
  const { cwd, silent = false } = options;
14
44
 
15
45
  try {
16
- const result = execSync(`git ${args.join(' ')}`, {
46
+ const result = spawnSync('git', args, {
17
47
  cwd: cwd || process.cwd(),
18
48
  encoding: 'utf8',
19
49
  stdio: silent ? 'pipe' : 'inherit',
20
50
  });
21
- return result ? result.trim() : '';
51
+
52
+ if (result.status !== 0 && !silent) {
53
+ throw new Error(`Git command failed: git ${args.join(' ')}\n${result.stderr || ''}`);
54
+ }
55
+
56
+ return result.stdout ? result.stdout.trim() : '';
22
57
  } catch (error) {
23
58
  if (silent) {
24
59
  return '';
@@ -30,7 +65,7 @@ function runGit(args, options = {}) {
30
65
  /**
31
66
  * Run git command and return result object
32
67
  */
33
- function runGitResult(args, options = {}) {
68
+ export function runGitResult(args: string[], options: GitRunOptions = {}): GitResult {
34
69
  const { cwd } = options;
35
70
 
36
71
  const result = spawnSync('git', args, {
@@ -41,8 +76,8 @@ function runGitResult(args, options = {}) {
41
76
 
42
77
  return {
43
78
  exitCode: result.status ?? 1,
44
- stdout: (result.stdout || '').trim(),
45
- stderr: (result.stderr || '').trim(),
79
+ stdout: (result.stdout || '').toString().trim(),
80
+ stderr: (result.stderr || '').toString().trim(),
46
81
  success: result.status === 0,
47
82
  };
48
83
  }
@@ -50,21 +85,21 @@ function runGitResult(args, options = {}) {
50
85
  /**
51
86
  * Get current branch name
52
87
  */
53
- function getCurrentBranch(cwd) {
88
+ export function getCurrentBranch(cwd?: string): string {
54
89
  return runGit(['rev-parse', '--abbrev-ref', 'HEAD'], { cwd, silent: true });
55
90
  }
56
91
 
57
92
  /**
58
93
  * Get repository root directory
59
94
  */
60
- function getRepoRoot(cwd) {
95
+ export function getRepoRoot(cwd?: string): string {
61
96
  return runGit(['rev-parse', '--show-toplevel'], { cwd, silent: true });
62
97
  }
63
98
 
64
99
  /**
65
100
  * Check if directory is a git repository
66
101
  */
67
- function isGitRepo(cwd) {
102
+ export function isGitRepo(cwd?: string): boolean {
68
103
  const result = runGitResult(['rev-parse', '--git-dir'], { cwd });
69
104
  return result.success;
70
105
  }
@@ -72,7 +107,7 @@ function isGitRepo(cwd) {
72
107
  /**
73
108
  * Check if worktree exists
74
109
  */
75
- function worktreeExists(worktreePath, cwd) {
110
+ export function worktreeExists(worktreePath: string, cwd?: string): boolean {
76
111
  const result = runGitResult(['worktree', 'list'], { cwd });
77
112
  if (!result.success) return false;
78
113
 
@@ -82,7 +117,7 @@ function worktreeExists(worktreePath, cwd) {
82
117
  /**
83
118
  * Create worktree
84
119
  */
85
- function createWorktree(worktreePath, branchName, options = {}) {
120
+ export function createWorktree(worktreePath: string, branchName: string, options: { cwd?: string; baseBranch?: string } = {}): string {
86
121
  const { cwd, baseBranch = 'main' } = options;
87
122
 
88
123
  // Check if branch already exists
@@ -102,7 +137,7 @@ function createWorktree(worktreePath, branchName, options = {}) {
102
137
  /**
103
138
  * Remove worktree
104
139
  */
105
- function removeWorktree(worktreePath, options = {}) {
140
+ export function removeWorktree(worktreePath: string, options: { cwd?: string; force?: boolean } = {}): void {
106
141
  const { cwd, force = false } = options;
107
142
 
108
143
  const args = ['worktree', 'remove', worktreePath];
@@ -116,18 +151,18 @@ function removeWorktree(worktreePath, options = {}) {
116
151
  /**
117
152
  * List all worktrees
118
153
  */
119
- function listWorktrees(cwd) {
154
+ export function listWorktrees(cwd?: string): WorktreeInfo[] {
120
155
  const result = runGitResult(['worktree', 'list', '--porcelain'], { cwd });
121
156
  if (!result.success) return [];
122
157
 
123
- const worktrees = [];
158
+ const worktrees: WorktreeInfo[] = [];
124
159
  const lines = result.stdout.split('\n');
125
- let current = {};
160
+ let current: Partial<WorktreeInfo> = {};
126
161
 
127
162
  for (const line of lines) {
128
163
  if (line.startsWith('worktree ')) {
129
164
  if (current.path) {
130
- worktrees.push(current);
165
+ worktrees.push(current as WorktreeInfo);
131
166
  }
132
167
  current = { path: line.slice(9) };
133
168
  } else if (line.startsWith('branch ')) {
@@ -138,7 +173,7 @@ function listWorktrees(cwd) {
138
173
  }
139
174
 
140
175
  if (current.path) {
141
- worktrees.push(current);
176
+ worktrees.push(current as WorktreeInfo);
142
177
  }
143
178
 
144
179
  return worktrees;
@@ -147,7 +182,7 @@ function listWorktrees(cwd) {
147
182
  /**
148
183
  * Check if there are uncommitted changes
149
184
  */
150
- function hasUncommittedChanges(cwd) {
185
+ export function hasUncommittedChanges(cwd?: string): boolean {
151
186
  const result = runGitResult(['status', '--porcelain'], { cwd });
152
187
  return result.success && result.stdout.length > 0;
153
188
  }
@@ -155,7 +190,7 @@ function hasUncommittedChanges(cwd) {
155
190
  /**
156
191
  * Get list of changed files
157
192
  */
158
- function getChangedFiles(cwd) {
193
+ export function getChangedFiles(cwd?: string): ChangedFile[] {
159
194
  const result = runGitResult(['status', '--porcelain'], { cwd });
160
195
  if (!result.success) return [];
161
196
 
@@ -172,7 +207,7 @@ function getChangedFiles(cwd) {
172
207
  /**
173
208
  * Create commit
174
209
  */
175
- function commit(message, options = {}) {
210
+ export function commit(message: string, options: { cwd?: string; addAll?: boolean } = {}): void {
176
211
  const { cwd, addAll = true } = options;
177
212
 
178
213
  if (addAll) {
@@ -185,7 +220,7 @@ function commit(message, options = {}) {
185
220
  /**
186
221
  * Push to remote
187
222
  */
188
- function push(branchName, options = {}) {
223
+ export function push(branchName: string, options: { cwd?: string; force?: boolean; setUpstream?: boolean } = {}): void {
189
224
  const { cwd, force = false, setUpstream = false } = options;
190
225
 
191
226
  const args = ['push'];
@@ -206,7 +241,7 @@ function push(branchName, options = {}) {
206
241
  /**
207
242
  * Fetch from remote
208
243
  */
209
- function fetch(options = {}) {
244
+ export function fetch(options: { cwd?: string; prune?: boolean } = {}): void {
210
245
  const { cwd, prune = true } = options;
211
246
 
212
247
  const args = ['fetch', 'origin'];
@@ -220,7 +255,7 @@ function fetch(options = {}) {
220
255
  /**
221
256
  * Check if branch exists (local or remote)
222
257
  */
223
- function branchExists(branchName, options = {}) {
258
+ export function branchExists(branchName: string, options: { cwd?: string; remote?: boolean } = {}): boolean {
224
259
  const { cwd, remote = false } = options;
225
260
 
226
261
  if (remote) {
@@ -235,7 +270,7 @@ function branchExists(branchName, options = {}) {
235
270
  /**
236
271
  * Delete branch
237
272
  */
238
- function deleteBranch(branchName, options = {}) {
273
+ export function deleteBranch(branchName: string, options: { cwd?: string; force?: boolean; remote?: boolean } = {}): void {
239
274
  const { cwd, force = false, remote = false } = options;
240
275
 
241
276
  if (remote) {
@@ -249,7 +284,7 @@ function deleteBranch(branchName, options = {}) {
249
284
  /**
250
285
  * Merge branch
251
286
  */
252
- function merge(branchName, options = {}) {
287
+ export function merge(branchName: string, options: { cwd?: string; noFf?: boolean; message?: string | null } = {}): void {
253
288
  const { cwd, noFf = false, message = null } = options;
254
289
 
255
290
  const args = ['merge'];
@@ -270,7 +305,7 @@ function merge(branchName, options = {}) {
270
305
  /**
271
306
  * Get commit info
272
307
  */
273
- function getCommitInfo(commitHash, options = {}) {
308
+ export function getCommitInfo(commitHash: string, options: { cwd?: string } = {}): CommitInfo | null {
274
309
  const { cwd } = options;
275
310
 
276
311
  const format = '--format=%H%n%h%n%an%n%ae%n%at%n%s';
@@ -280,32 +315,11 @@ function getCommitInfo(commitHash, options = {}) {
280
315
 
281
316
  const lines = result.stdout.split('\n');
282
317
  return {
283
- hash: lines[0],
284
- shortHash: lines[1],
285
- author: lines[2],
286
- authorEmail: lines[3],
287
- timestamp: parseInt(lines[4]),
288
- subject: lines[5],
318
+ hash: lines[0] || '',
319
+ shortHash: lines[1] || '',
320
+ author: lines[2] || '',
321
+ authorEmail: lines[3] || '',
322
+ timestamp: parseInt(lines[4] || '0'),
323
+ subject: lines[5] || '',
289
324
  };
290
325
  }
291
-
292
- module.exports = {
293
- runGit,
294
- runGitResult,
295
- getCurrentBranch,
296
- getRepoRoot,
297
- isGitRepo,
298
- worktreeExists,
299
- createWorktree,
300
- removeWorktree,
301
- listWorktrees,
302
- hasUncommittedChanges,
303
- getChangedFiles,
304
- commit,
305
- push,
306
- fetch,
307
- branchExists,
308
- deleteBranch,
309
- merge,
310
- getCommitInfo,
311
- };
@@ -1,178 +1,170 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Logging utilities for CursorFlow
4
- */
5
-
6
- const LOG_LEVELS = {
7
- error: 0,
8
- warn: 1,
9
- info: 2,
10
- debug: 3,
11
- };
12
-
13
- const COLORS = {
14
- reset: '\x1b[0m',
15
- red: '\x1b[31m',
16
- yellow: '\x1b[33m',
17
- green: '\x1b[32m',
18
- blue: '\x1b[34m',
19
- cyan: '\x1b[36m',
20
- gray: '\x1b[90m',
21
- };
22
-
23
- let currentLogLevel = LOG_LEVELS.info;
24
-
25
- /**
26
- * Set log level
27
- */
28
- function setLogLevel(level) {
29
- if (typeof level === 'string') {
30
- currentLogLevel = LOG_LEVELS[level] ?? LOG_LEVELS.info;
31
- } else {
32
- currentLogLevel = level;
33
- }
34
- }
35
-
36
- /**
37
- * Format message with timestamp
38
- */
39
- function formatMessage(level, message, emoji = '') {
40
- const timestamp = new Date().toISOString();
41
- const prefix = emoji ? `${emoji} ` : '';
42
- return `[${timestamp}] [${level.toUpperCase()}] ${prefix}${message}`;
43
- }
44
-
45
- /**
46
- * Log with color
47
- */
48
- function logWithColor(color, level, message, emoji = '') {
49
- if (LOG_LEVELS[level] > currentLogLevel) {
50
- return;
51
- }
52
-
53
- const formatted = formatMessage(level, message, emoji);
54
- console.log(`${color}${formatted}${COLORS.reset}`);
55
- }
56
-
57
- /**
58
- * Error log
59
- */
60
- function error(message, emoji = '❌') {
61
- logWithColor(COLORS.red, 'error', message, emoji);
62
- }
63
-
64
- /**
65
- * Warning log
66
- */
67
- function warn(message, emoji = '⚠️') {
68
- logWithColor(COLORS.yellow, 'warn', message, emoji);
69
- }
70
-
71
- /**
72
- * Info log
73
- */
74
- function info(message, emoji = 'ℹ️') {
75
- logWithColor(COLORS.cyan, 'info', message, emoji);
76
- }
77
-
78
- /**
79
- * Success log
80
- */
81
- function success(message, emoji = '✅') {
82
- logWithColor(COLORS.green, 'info', message, emoji);
83
- }
84
-
85
- /**
86
- * Debug log
87
- */
88
- function debug(message, emoji = '🔍') {
89
- logWithColor(COLORS.gray, 'debug', message, emoji);
90
- }
91
-
92
- /**
93
- * Progress log
94
- */
95
- function progress(message, emoji = '🔄') {
96
- logWithColor(COLORS.blue, 'info', message, emoji);
97
- }
98
-
99
- /**
100
- * Section header
101
- */
102
- function section(message) {
103
- console.log('');
104
- console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
105
- console.log(`${COLORS.cyan} ${message}${COLORS.reset}`);
106
- console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
107
- console.log('');
108
- }
109
-
110
- /**
111
- * Simple log without formatting
112
- */
113
- function log(message) {
114
- console.log(message);
115
- }
116
-
117
- /**
118
- * Log JSON data (pretty print in debug mode)
119
- */
120
- function json(data) {
121
- if (currentLogLevel >= LOG_LEVELS.debug) {
122
- console.log(JSON.stringify(data, null, 2));
123
- }
124
- }
125
-
126
- /**
127
- * Create spinner (simple implementation)
128
- */
129
- function createSpinner(message) {
130
- const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
131
- let i = 0;
132
- let interval = null;
133
-
134
- return {
135
- start() {
136
- process.stdout.write(`${message} ${frames[0]}`);
137
- interval = setInterval(() => {
138
- i = (i + 1) % frames.length;
139
- process.stdout.write(`\r${message} ${frames[i]}`);
140
- }, 80);
141
- },
142
-
143
- stop(finalMessage = null) {
144
- if (interval) {
145
- clearInterval(interval);
146
- interval = null;
147
- }
148
- process.stdout.write('\r\x1b[K'); // Clear line
149
- if (finalMessage) {
150
- console.log(finalMessage);
151
- }
152
- },
153
-
154
- succeed(message) {
155
- this.stop(`${COLORS.green}✓${COLORS.reset} ${message}`);
156
- },
157
-
158
- fail(message) {
159
- this.stop(`${COLORS.red}✗${COLORS.reset} ${message}`);
160
- },
161
- };
162
- }
163
-
164
- module.exports = {
165
- setLogLevel,
166
- error,
167
- warn,
168
- info,
169
- success,
170
- debug,
171
- progress,
172
- section,
173
- log,
174
- json,
175
- createSpinner,
176
- COLORS,
177
- LOG_LEVELS,
178
- };
1
+ /**
2
+ * Logging utilities for CursorFlow
3
+ */
4
+
5
+ export enum LogLevel {
6
+ error = 0,
7
+ warn = 1,
8
+ info = 2,
9
+ debug = 3,
10
+ }
11
+
12
+ export const COLORS = {
13
+ reset: '\x1b[0m',
14
+ red: '\x1b[31m',
15
+ yellow: '\x1b[33m',
16
+ green: '\x1b[32m',
17
+ blue: '\x1b[34m',
18
+ cyan: '\x1b[36m',
19
+ gray: '\x1b[90m',
20
+ };
21
+
22
+ let currentLogLevel: number = LogLevel.info;
23
+
24
+ /**
25
+ * Set log level
26
+ */
27
+ export function setLogLevel(level: string | number): void {
28
+ if (typeof level === 'string') {
29
+ currentLogLevel = (LogLevel as any)[level] ?? LogLevel.info;
30
+ } else {
31
+ currentLogLevel = level;
32
+ }
33
+ }
34
+
35
+ /**
36
+ * Format message with timestamp
37
+ */
38
+ function formatMessage(level: string, message: string, emoji = ''): string {
39
+ const timestamp = new Date().toISOString();
40
+ const prefix = emoji ? `${emoji} ` : '';
41
+ return `[${timestamp}] [${level.toUpperCase()}] ${prefix}${message}`;
42
+ }
43
+
44
+ /**
45
+ * Log with color
46
+ */
47
+ function logWithColor(color: string, level: keyof typeof LogLevel, message: string, emoji = ''): void {
48
+ if (LogLevel[level] > currentLogLevel) {
49
+ return;
50
+ }
51
+
52
+ const formatted = formatMessage(level, message, emoji);
53
+ console.log(`${color}${formatted}${COLORS.reset}`);
54
+ }
55
+
56
+ /**
57
+ * Error log
58
+ */
59
+ export function error(message: string, emoji = '❌'): void {
60
+ logWithColor(COLORS.red, 'error', message, emoji);
61
+ }
62
+
63
+ /**
64
+ * Warning log
65
+ */
66
+ export function warn(message: string, emoji = '⚠️'): void {
67
+ logWithColor(COLORS.yellow, 'warn', message, emoji);
68
+ }
69
+
70
+ /**
71
+ * Info log
72
+ */
73
+ export function info(message: string, emoji = 'ℹ️'): void {
74
+ logWithColor(COLORS.cyan, 'info', message, emoji);
75
+ }
76
+
77
+ /**
78
+ * Success log
79
+ */
80
+ export function success(message: string, emoji = '✅'): void {
81
+ logWithColor(COLORS.green, 'info', message, emoji);
82
+ }
83
+
84
+ /**
85
+ * Debug log
86
+ */
87
+ export function debug(message: string, emoji = '🔍'): void {
88
+ logWithColor(COLORS.gray, 'debug', message, emoji);
89
+ }
90
+
91
+ /**
92
+ * Progress log
93
+ */
94
+ export function progress(message: string, emoji = '🔄'): void {
95
+ logWithColor(COLORS.blue, 'info', message, emoji);
96
+ }
97
+
98
+ /**
99
+ * Section header
100
+ */
101
+ export function section(message: string): void {
102
+ console.log('');
103
+ console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
104
+ console.log(`${COLORS.cyan} ${message}${COLORS.reset}`);
105
+ console.log(`${COLORS.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${COLORS.reset}`);
106
+ console.log('');
107
+ }
108
+
109
+ /**
110
+ * Simple log without formatting
111
+ */
112
+ export function log(message: string | any): void {
113
+ console.log(message);
114
+ }
115
+
116
+ /**
117
+ * Log JSON data (pretty print in debug mode)
118
+ */
119
+ export function json(data: any): void {
120
+ if (currentLogLevel >= LogLevel.debug) {
121
+ console.log(JSON.stringify(data, null, 2));
122
+ }
123
+ }
124
+
125
+ export interface Spinner {
126
+ start(): void;
127
+ stop(finalMessage?: string | null): void;
128
+ succeed(message: string): void;
129
+ fail(message: string): void;
130
+ }
131
+
132
+ /**
133
+ * Create spinner (simple implementation)
134
+ */
135
+ export function createSpinner(message: string): Spinner {
136
+ const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
137
+ let i = 0;
138
+ let interval: NodeJS.Timeout | null = null;
139
+
140
+ const spinner: Spinner = {
141
+ start() {
142
+ process.stdout.write(`${message} ${frames[0]}`);
143
+ interval = setInterval(() => {
144
+ i = (i + 1) % frames.length;
145
+ process.stdout.write(`\r${message} ${frames[i]}`);
146
+ }, 80);
147
+ },
148
+
149
+ stop(finalMessage: string | null = null) {
150
+ if (interval) {
151
+ clearInterval(interval);
152
+ interval = null;
153
+ }
154
+ process.stdout.write('\r\x1b[K'); // Clear line
155
+ if (finalMessage) {
156
+ console.log(finalMessage);
157
+ }
158
+ },
159
+
160
+ succeed(message: string) {
161
+ this.stop(`${COLORS.green}✓${COLORS.reset} ${message}`);
162
+ },
163
+
164
+ fail(message: string) {
165
+ this.stop(`${COLORS.red}✗${COLORS.reset} ${message}`);
166
+ },
167
+ };
168
+
169
+ return spinner;
170
+ }