@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
@@ -0,0 +1,268 @@
1
+ /**
2
+ * Log Formatter - Human-readable log formatting
3
+ *
4
+ * Formats log messages for console display with various styles.
5
+ *
6
+ * Rules:
7
+ * - Box format only for: user, assistant, system, result
8
+ * - Compact format for: tool, tool_result, thinking (gray/dim)
9
+ * - Tool names simplified: ShellToolCall → Shell
10
+ */
11
+
12
+ import { COLORS } from './console';
13
+ import { ParsedMessage, MessageType } from '../../types/logging';
14
+
15
+ // Types that should use box format
16
+ const BOX_TYPES = new Set(['user', 'assistant', 'system', 'result']);
17
+
18
+ /**
19
+ * Strip ANSI escape sequences from text
20
+ */
21
+ export function stripAnsi(text: string): string {
22
+ const EXTENDED_ANSI_REGEX = /(?:\x1B[@-Z\\-_]|\x1B\[[0-?]*[ -/]*[@-~]|\x1B\][^\x07]*(?:\x07|\x1B\\)|\x1B[PX^_][^\x1B]*\x1B\\|\x1B.)/g;
23
+ const ANSI_REGEX = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g;
24
+
25
+ return text
26
+ .replace(EXTENDED_ANSI_REGEX, '')
27
+ .replace(ANSI_REGEX, '')
28
+ .replace(/\r[^\n]/g, '\n')
29
+ .replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
30
+ }
31
+
32
+ /**
33
+ * Simplify tool names (ShellToolCall → Shell, etc.)
34
+ */
35
+ function simplifyToolName(name: string): string {
36
+ return name
37
+ .replace(/ToolCall$/i, '')
38
+ .replace(/Tool$/i, '')
39
+ .replace(/^run_terminal_cmd$/i, 'shell')
40
+ .replace(/^search_replace$/i, 'edit');
41
+ }
42
+
43
+ /**
44
+ * Format options for console display
45
+ */
46
+ export interface FormatOptions {
47
+ includeTimestamp?: boolean;
48
+ laneLabel?: string;
49
+ compact?: boolean;
50
+ showBorders?: boolean;
51
+ }
52
+
53
+ /**
54
+ * Format a parsed message for console display
55
+ */
56
+ export function formatMessageForConsole(
57
+ msg: ParsedMessage,
58
+ options: FormatOptions = {}
59
+ ): string {
60
+ const { includeTimestamp = true, laneLabel = '', compact = false, showBorders = true } = options;
61
+
62
+ const ts = includeTimestamp
63
+ ? new Date(msg.timestamp).toLocaleTimeString('en-US', { hour12: false })
64
+ : '';
65
+ const tsPrefix = ts ? `${COLORS.gray}[${ts}]${COLORS.reset} ` : '';
66
+
67
+ // Lane label max 16 chars
68
+ const truncatedLabel = laneLabel.length > 16 ? laneLabel.substring(0, 16) : laneLabel;
69
+ const labelPrefix = truncatedLabel
70
+ ? `${COLORS.magenta}${truncatedLabel.padEnd(16)}${COLORS.reset} `
71
+ : '';
72
+
73
+ // Determine if should use box format
74
+ const useBox = !compact && showBorders && BOX_TYPES.has(msg.type);
75
+ const { typePrefix, formattedContent } = formatMessageContent(msg, !useBox);
76
+
77
+ if (!typePrefix) return `${tsPrefix}${labelPrefix}${formattedContent}`;
78
+
79
+ if (!useBox) {
80
+ return `${tsPrefix}${labelPrefix}${typePrefix.padEnd(12)} ${formattedContent}`;
81
+ }
82
+
83
+ // Multi-line box format (only for user, assistant, system, result)
84
+ // Emoji width is 2, so we need to account for that in indent calculation
85
+ const lines = formattedContent.split('\n');
86
+ const fullPrefix = `${tsPrefix}${labelPrefix}`;
87
+ const strippedPrefix = stripAnsi(typePrefix);
88
+ // Count emojis (they take 2 terminal columns but 1-2 chars in string)
89
+ const emojiCount = (strippedPrefix.match(/[\u{1F300}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]|[\u{1F600}-\u{1F64F}]|[\u{1F680}-\u{1F6FF}]|[\u{1F1E0}-\u{1F1FF}]|[\u{2300}-\u{23FF}]|[\u{2B50}-\u{2B55}]|[\u{231A}-\u{231B}]|[\u{23E9}-\u{23F3}]|[\u{23F8}-\u{23FA}]|✅|❌|⚙️|ℹ️|⚠️|🔧|📄|🤔|🧑|🤖/gu) || []).length;
90
+ const visualWidth = strippedPrefix.length + emojiCount; // emoji adds 1 extra width
91
+
92
+ const boxWidth = 60;
93
+ const header = `${typePrefix}┌${'─'.repeat(boxWidth)}`;
94
+ let result = `${fullPrefix}${header}\n`;
95
+
96
+ const indent = ' '.repeat(visualWidth);
97
+ for (const line of lines) {
98
+ result += `${fullPrefix}${indent}│ ${line}\n`;
99
+ }
100
+ result += `${fullPrefix}${indent}└${'─'.repeat(boxWidth)}`;
101
+
102
+ return result;
103
+ }
104
+
105
+ function formatMessageContent(msg: ParsedMessage, forceCompact: boolean): { typePrefix: string; formattedContent: string } {
106
+ let typePrefix = '';
107
+ let formattedContent = msg.content;
108
+
109
+ // For thinking: collapse multiple newlines
110
+ if (msg.type === 'thinking') {
111
+ formattedContent = formattedContent.replace(/\n\s*\n/g, ' ').replace(/\n/g, ' ').trim();
112
+ }
113
+
114
+ switch (msg.type) {
115
+ case 'user':
116
+ typePrefix = `${COLORS.cyan}🧑 USER${COLORS.reset}`;
117
+ if (forceCompact) formattedContent = truncate(formattedContent.replace(/\n/g, ' '), 100);
118
+ break;
119
+
120
+ case 'assistant':
121
+ typePrefix = `${COLORS.green}🤖 ASST${COLORS.reset}`;
122
+ if (forceCompact) formattedContent = truncate(formattedContent.replace(/\n/g, ' '), 100);
123
+ break;
124
+
125
+ case 'tool':
126
+ // Tool calls are always gray
127
+ typePrefix = `${COLORS.gray}🔧 TOOL${COLORS.reset}`;
128
+ formattedContent = formatToolCall(formattedContent);
129
+ break;
130
+
131
+ case 'tool_result':
132
+ // Tool results are always gray
133
+ typePrefix = `${COLORS.gray}📄 RESL${COLORS.reset}`;
134
+ const resMatch = formattedContent.match(/\[Tool Result: ([^\]]+)\]/);
135
+ if (resMatch) {
136
+ const simpleName = simplifyToolName(resMatch[1]!);
137
+ formattedContent = `${COLORS.gray}${simpleName} OK${COLORS.reset}`;
138
+ } else {
139
+ formattedContent = `${COLORS.gray}result${COLORS.reset}`;
140
+ }
141
+ break;
142
+
143
+ case 'result':
144
+ typePrefix = `${COLORS.green}✅ DONE${COLORS.reset}`;
145
+ break;
146
+
147
+ case 'system':
148
+ typePrefix = `${COLORS.gray}⚙️ SYS${COLORS.reset}`;
149
+ break;
150
+
151
+ case 'thinking':
152
+ // Thinking is always gray and compact
153
+ typePrefix = `${COLORS.gray}🤔 THNK${COLORS.reset}`;
154
+ formattedContent = `${COLORS.gray}${truncate(formattedContent, 100)}${COLORS.reset}`;
155
+ break;
156
+ }
157
+
158
+ return { typePrefix, formattedContent };
159
+ }
160
+
161
+ function formatToolCall(content: string): string {
162
+ const toolMatch = content.match(/\[Tool: ([^\]]+)\] (.*)/);
163
+ if (!toolMatch) return content;
164
+
165
+ const [, rawName, args] = toolMatch;
166
+ const name = simplifyToolName(rawName!);
167
+
168
+ try {
169
+ const parsedArgs = JSON.parse(args!);
170
+ let argStr = '';
171
+
172
+ if (rawName === 'read_file' && parsedArgs.target_file) {
173
+ argStr = parsedArgs.target_file;
174
+ } else if (rawName === 'run_terminal_cmd' && parsedArgs.command) {
175
+ argStr = parsedArgs.command;
176
+ } else if (rawName === 'write' && parsedArgs.file_path) {
177
+ argStr = parsedArgs.file_path;
178
+ } else if (rawName === 'search_replace' && parsedArgs.file_path) {
179
+ argStr = parsedArgs.file_path;
180
+ } else {
181
+ const keys = Object.keys(parsedArgs);
182
+ if (keys.length > 0) {
183
+ argStr = String(parsedArgs[keys[0]]).substring(0, 50);
184
+ }
185
+ }
186
+
187
+ return `${COLORS.gray}${name}${COLORS.reset}(${COLORS.gray}${argStr}${COLORS.reset})`;
188
+ } catch {
189
+ return `${COLORS.gray}${name}${COLORS.reset}: ${args}`;
190
+ }
191
+ }
192
+
193
+ function truncate(str: string, maxLength: number): string {
194
+ if (str.length <= maxLength) return str;
195
+ return str.substring(0, maxLength) + '...';
196
+ }
197
+
198
+ /**
199
+ * Format a readable log entry for display
200
+ */
201
+ export function formatReadableEntry(
202
+ timestamp: Date,
203
+ laneName: string,
204
+ type: MessageType,
205
+ content: string,
206
+ options: { showLane?: boolean; maxWidth?: number } = {}
207
+ ): string {
208
+ const { showLane = true, maxWidth = 100 } = options;
209
+
210
+ const ts = timestamp.toLocaleTimeString('en-US', { hour12: false });
211
+ const laneStr = showLane ? `[${laneName.padEnd(12)}] ` : '';
212
+
213
+ const typeInfo = getTypeInfo(type);
214
+ const truncatedContent = content.length > maxWidth
215
+ ? content.substring(0, maxWidth - 3) + '...'
216
+ : content;
217
+
218
+ return `${COLORS.gray}[${ts}]${COLORS.reset} ${laneStr}${typeInfo.color}[${typeInfo.label}]${COLORS.reset} ${truncatedContent}`;
219
+ }
220
+
221
+ function getTypeInfo(type: MessageType): { label: string; color: string } {
222
+ const typeMap: Record<MessageType, { label: string; color: string }> = {
223
+ user: { label: 'USER ', color: COLORS.cyan },
224
+ assistant: { label: 'ASST ', color: COLORS.green },
225
+ tool: { label: 'TOOL ', color: COLORS.yellow },
226
+ tool_result: { label: 'RESULT', color: COLORS.gray },
227
+ result: { label: 'DONE ', color: COLORS.green },
228
+ system: { label: 'SYSTEM', color: COLORS.gray },
229
+ thinking: { label: 'THINK ', color: COLORS.gray },
230
+ success: { label: 'OK ', color: COLORS.green },
231
+ info: { label: 'INFO ', color: COLORS.cyan },
232
+ warn: { label: 'WARN ', color: COLORS.yellow },
233
+ error: { label: 'ERROR ', color: COLORS.red },
234
+ stdout: { label: 'STDOUT', color: COLORS.white },
235
+ stderr: { label: 'STDERR', color: COLORS.red },
236
+ };
237
+
238
+ return typeMap[type] || { label: type.toUpperCase().padEnd(6), color: COLORS.white };
239
+ }
240
+
241
+ /**
242
+ * Format a timestamp for log files
243
+ */
244
+ export function formatTimestampISO(format: 'iso' | 'relative' | 'short', startTime?: number): string {
245
+ const now = Date.now();
246
+
247
+ switch (format) {
248
+ case 'iso':
249
+ return new Date(now).toISOString();
250
+ case 'relative':
251
+ if (startTime) {
252
+ const elapsed = now - startTime;
253
+ const seconds = Math.floor(elapsed / 1000);
254
+ const minutes = Math.floor(seconds / 60);
255
+ const hours = Math.floor(minutes / 60);
256
+
257
+ if (hours > 0) return `+${hours}h${minutes % 60}m${seconds % 60}s`;
258
+ if (minutes > 0) return `+${minutes}m${seconds % 60}s`;
259
+ return `+${seconds}s`;
260
+ }
261
+ return new Date(now).toISOString();
262
+ case 'short':
263
+ return new Date(now).toLocaleTimeString('en-US', { hour12: false });
264
+ default:
265
+ return new Date(now).toISOString();
266
+ }
267
+ }
268
+
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Logging Service - Unified logging module for CursorFlow
3
+ *
4
+ * Re-exports all logging-related functionality from a single entry point.
5
+ */
6
+
7
+ // Core logging utilities
8
+ export * from './console';
9
+ export * from './formatter';
10
+ export * from './parser';
11
+ export * from './buffer';
12
+ export * from './file-writer';
13
+
14
+ // Re-export types
15
+ export * from '../../types/logging';
16
+
@@ -0,0 +1,232 @@
1
+ /**
2
+ * Log Parser - Parse cursor-agent streaming output
3
+ *
4
+ * Parses JSON output from cursor-agent and combines tokens into readable messages.
5
+ */
6
+
7
+ import { ParsedMessage, MessageType } from '../../types/logging';
8
+
9
+ /**
10
+ * Streaming message parser for cursor-agent output
11
+ */
12
+ export class StreamingMessageParser {
13
+ private currentMessage = '';
14
+ private currentRole = '';
15
+ private messageStartTime = 0;
16
+ private onMessage: (msg: ParsedMessage) => void;
17
+
18
+ constructor(onMessage: (msg: ParsedMessage) => void) {
19
+ this.onMessage = onMessage;
20
+ }
21
+
22
+ /**
23
+ * Parse a line of JSON output
24
+ */
25
+ parseLine(line: string): void {
26
+ const trimmed = line.trim();
27
+ if (!trimmed || !trimmed.startsWith('{')) return;
28
+
29
+ try {
30
+ const json = JSON.parse(trimmed);
31
+ this.handleJsonMessage(json);
32
+ } catch {
33
+ // Not valid JSON, ignore
34
+ }
35
+ }
36
+
37
+ private handleJsonMessage(json: any): void {
38
+ const type = json.type;
39
+
40
+ switch (type) {
41
+ case 'system':
42
+ this.emitMessage({
43
+ type: 'system',
44
+ role: 'system',
45
+ content: `[System] Model: ${json.model || 'unknown'}, Mode: ${json.permissionMode || 'default'}`,
46
+ timestamp: json.timestamp_ms || Date.now(),
47
+ });
48
+ break;
49
+
50
+ case 'user':
51
+ if (json.message?.content) {
52
+ const textContent = this.extractTextContent(json.message.content);
53
+ this.emitMessage({
54
+ type: 'user',
55
+ role: 'user',
56
+ content: textContent,
57
+ timestamp: json.timestamp_ms || Date.now(),
58
+ });
59
+ }
60
+ break;
61
+
62
+ case 'assistant':
63
+ if (json.message?.content) {
64
+ const textContent = this.extractTextContent(json.message.content);
65
+
66
+ if (this.currentRole !== 'assistant') {
67
+ this.flush();
68
+ this.currentRole = 'assistant';
69
+ this.messageStartTime = json.timestamp_ms || Date.now();
70
+ }
71
+
72
+ this.currentMessage += textContent;
73
+ }
74
+ break;
75
+
76
+ case 'tool_call':
77
+ if (json.subtype === 'started' && json.tool_call) {
78
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
79
+ const toolArgs = json.tool_call[toolName]?.args || {};
80
+
81
+ this.flush();
82
+
83
+ this.emitMessage({
84
+ type: 'tool',
85
+ role: 'tool',
86
+ content: `[Tool: ${toolName}] ${JSON.stringify(toolArgs)}`,
87
+ timestamp: json.timestamp_ms || Date.now(),
88
+ metadata: { callId: json.call_id, toolName },
89
+ });
90
+ } else if (json.subtype === 'completed' && json.tool_call) {
91
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
92
+ const result = json.tool_call[toolName]?.result;
93
+
94
+ if (result?.success) {
95
+ const content = result.success.content || '';
96
+ const truncated = content.length > 500
97
+ ? content.substring(0, 500) + '... (truncated)'
98
+ : content;
99
+
100
+ this.emitMessage({
101
+ type: 'tool_result',
102
+ role: 'tool',
103
+ content: `[Tool Result: ${toolName}] ${truncated}`,
104
+ timestamp: json.timestamp_ms || Date.now(),
105
+ metadata: { callId: json.call_id, toolName, lines: result.success.totalLines },
106
+ });
107
+ }
108
+ }
109
+ break;
110
+
111
+ case 'result':
112
+ this.flush();
113
+ this.emitMessage({
114
+ type: 'result',
115
+ role: 'assistant',
116
+ content: json.result || '',
117
+ timestamp: json.timestamp_ms || Date.now(),
118
+ metadata: {
119
+ duration_ms: json.duration_ms,
120
+ is_error: json.is_error,
121
+ subtype: json.subtype,
122
+ },
123
+ });
124
+ break;
125
+
126
+ case 'thinking':
127
+ if (json.subtype === 'delta' && json.text) {
128
+ if (this.currentRole !== 'thinking') {
129
+ this.flush();
130
+ this.currentRole = 'thinking';
131
+ this.messageStartTime = json.timestamp_ms || Date.now();
132
+ }
133
+ this.currentMessage += json.text;
134
+ } else if (json.subtype === 'completed') {
135
+ this.flush();
136
+ }
137
+ break;
138
+ }
139
+ }
140
+
141
+ private extractTextContent(content: any[]): string {
142
+ return content
143
+ .filter((c: any) => c.type === 'text')
144
+ .map((c: any) => c.text)
145
+ .join('');
146
+ }
147
+
148
+ /**
149
+ * Flush accumulated message
150
+ */
151
+ flush(): void {
152
+ if (this.currentMessage && this.currentRole) {
153
+ this.emitMessage({
154
+ type: this.currentRole as MessageType,
155
+ role: this.currentRole,
156
+ content: this.currentMessage,
157
+ timestamp: this.messageStartTime,
158
+ });
159
+ }
160
+ this.currentMessage = '';
161
+ this.currentRole = '';
162
+ this.messageStartTime = 0;
163
+ }
164
+
165
+ private emitMessage(msg: ParsedMessage): void {
166
+ if (msg.content.trim()) {
167
+ this.onMessage(msg);
168
+ }
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Parse a raw JSON log file line by line
174
+ */
175
+ export function parseJsonLogLine(line: string): ParsedMessage | null {
176
+ const trimmed = line.trim();
177
+ if (!trimmed || !trimmed.startsWith('{')) return null;
178
+
179
+ try {
180
+ const json = JSON.parse(trimmed);
181
+ return parseJsonToMessage(json);
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ function parseJsonToMessage(json: any): ParsedMessage | null {
188
+ const type = json.type;
189
+ if (!type) return null;
190
+
191
+ let messageType: MessageType;
192
+ let content = '';
193
+
194
+ if (type === 'thinking' && (json.text || json.thought)) {
195
+ content = json.text || json.thought;
196
+ messageType = 'thinking';
197
+ } else if (type === 'assistant' && json.message?.content) {
198
+ content = json.message.content
199
+ .filter((c: any) => c.type === 'text')
200
+ .map((c: any) => c.text)
201
+ .join('');
202
+ messageType = 'assistant';
203
+ } else if (type === 'user' && json.message?.content) {
204
+ content = json.message.content
205
+ .filter((c: any) => c.type === 'text')
206
+ .map((c: any) => c.text)
207
+ .join('');
208
+ messageType = 'user';
209
+ } else if (type === 'tool_call' && json.subtype === 'started') {
210
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
211
+ const args = json.tool_call[toolName]?.args || {};
212
+ content = `[Tool: ${toolName}] ${JSON.stringify(args)}`;
213
+ messageType = 'tool';
214
+ } else if (type === 'tool_call' && json.subtype === 'completed') {
215
+ const toolName = Object.keys(json.tool_call)[0] || 'unknown';
216
+ content = `[Tool Result: ${toolName}]`;
217
+ messageType = 'tool_result';
218
+ } else if (type === 'result') {
219
+ content = json.result || 'Task completed';
220
+ messageType = 'result';
221
+ } else {
222
+ return null;
223
+ }
224
+
225
+ return {
226
+ type: messageType,
227
+ role: messageType,
228
+ content,
229
+ timestamp: json.timestamp_ms || Date.now(),
230
+ };
231
+ }
232
+