agileflow 2.90.0 → 2.90.1

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 CHANGED
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.90.1] - 2026-01-16
11
+
12
+ ### Added
13
+ - TUI as CLI command with session fixes
14
+
10
15
  ## [2.90.0] - 2026-01-16
11
16
 
12
17
  ### Added
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  </p>
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
- [![Commands](https://img.shields.io/badge/commands-73-blue)](docs/04-architecture/commands.md)
6
+ [![Commands](https://img.shields.io/badge/commands-72-blue)](docs/04-architecture/commands.md)
7
7
  [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-29-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-dynamic-purple)](docs/04-architecture/skills.md)
9
9
 
@@ -65,7 +65,7 @@ AgileFlow combines three proven methodologies:
65
65
 
66
66
  | Component | Count | Description |
67
67
  |-----------|-------|-------------|
68
- | [Commands](docs/04-architecture/commands.md) | 73 | Slash commands for agile workflows |
68
+ | [Commands](docs/04-architecture/commands.md) | 72 | Slash commands for agile workflows |
69
69
  | [Agents/Experts](docs/04-architecture/subagents.md) | 29 | Specialized agents with self-improving knowledge bases |
70
70
  | [Skills](docs/04-architecture/skills.md) | Dynamic | Generated on-demand with `/agileflow:skill:create` |
71
71
 
@@ -76,7 +76,7 @@ AgileFlow combines three proven methodologies:
76
76
  Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
77
77
 
78
78
  ### Reference
79
- - [Commands](docs/04-architecture/commands.md) - All 73 slash commands
79
+ - [Commands](docs/04-architecture/commands.md) - All 72 slash commands
80
80
  - [Agents/Experts](docs/04-architecture/subagents.md) - 29 specialized agents with self-improving knowledge
81
81
  - [Skills](docs/04-architecture/skills.md) - Dynamic skill generator with MCP integration
82
82
 
@@ -69,28 +69,40 @@ function checkFilePermissions(mode) {
69
69
  if (worldWrite) {
70
70
  return {
71
71
  ok: false,
72
- warning: 'File is world-writable (mode: ' + permissions.toString(8) + '). Security risk - others can modify.',
72
+ warning:
73
+ 'File is world-writable (mode: ' +
74
+ permissions.toString(8) +
75
+ '). Security risk - others can modify.',
73
76
  };
74
77
  }
75
78
 
76
79
  if (worldRead) {
77
80
  return {
78
81
  ok: false,
79
- warning: 'File is world-readable (mode: ' + permissions.toString(8) + '). May expose sensitive config.',
82
+ warning:
83
+ 'File is world-readable (mode: ' +
84
+ permissions.toString(8) +
85
+ '). May expose sensitive config.',
80
86
  };
81
87
  }
82
88
 
83
89
  if (groupWrite) {
84
90
  return {
85
91
  ok: false,
86
- warning: 'File is group-writable (mode: ' + permissions.toString(8) + '). Consider restricting to 0600.',
92
+ warning:
93
+ 'File is group-writable (mode: ' +
94
+ permissions.toString(8) +
95
+ '). Consider restricting to 0600.',
87
96
  };
88
97
  }
89
98
 
90
99
  if (groupRead) {
91
100
  return {
92
101
  ok: false,
93
- warning: 'File is group-readable (mode: ' + permissions.toString(8) + '). Consider restricting to 0600.',
102
+ warning:
103
+ 'File is group-readable (mode: ' +
104
+ permissions.toString(8) +
105
+ '). Consider restricting to 0600.',
94
106
  };
95
107
  }
96
108
 
@@ -113,10 +125,14 @@ function setSecurePermissions(filePath) {
113
125
  debugLog('setSecurePermissions', { filePath, mode: SECURE_FILE_MODE.toString(8) });
114
126
  return { ok: true };
115
127
  } catch (err) {
116
- const error = createTypedError(`Failed to set secure permissions on ${filePath}: ${err.message}`, 'EPERM', {
117
- cause: err,
118
- context: { filePath, mode: SECURE_FILE_MODE },
119
- });
128
+ const error = createTypedError(
129
+ `Failed to set secure permissions on ${filePath}: ${err.message}`,
130
+ 'EPERM',
131
+ {
132
+ cause: err,
133
+ context: { filePath, mode: SECURE_FILE_MODE },
134
+ }
135
+ );
120
136
  return { ok: false, error };
121
137
  }
122
138
  }
@@ -536,7 +552,10 @@ class SmartJsonFile {
536
552
  if (this.secureMode) {
537
553
  const permResult = setSecurePermissions(this.filePath);
538
554
  if (!permResult.ok) {
539
- debugLog('writeSync', { status: 'warning', security: 'failed to set secure permissions' });
555
+ debugLog('writeSync', {
556
+ status: 'warning',
557
+ security: 'failed to set secure permissions',
558
+ });
540
559
  }
541
560
  }
542
561
 
@@ -616,7 +635,11 @@ function cleanupTempFiles(directory, options = {}) {
616
635
  }
617
636
 
618
637
  cleaned.push(filePath);
619
- debugLog('cleanupTempFiles', { action: dryRun ? 'would delete' : 'deleted', filePath, ageHours: Math.round(age / 3600000) });
638
+ debugLog('cleanupTempFiles', {
639
+ action: dryRun ? 'would delete' : 'deleted',
640
+ filePath,
641
+ ageHours: Math.round(age / 3600000),
642
+ });
620
643
  } catch (err) {
621
644
  errors.push(`${filePath}: ${err.message}`);
622
645
  debugLog('cleanupTempFiles', { action: 'error', filePath, error: err.message });
@@ -211,7 +211,9 @@ function formatTable(headers, rows, options = {}) {
211
211
  lines.push(indent + c.bold + headerLine + c.reset);
212
212
 
213
213
  for (const row of stringRows) {
214
- const rowLine = row.map((cell, i) => padString(cell, colWidths[i], align[i] || 'left')).join(' ');
214
+ const rowLine = row
215
+ .map((cell, i) => padString(cell, colWidths[i], align[i] || 'left'))
216
+ .join(' ');
215
217
  lines.push(indent + rowLine);
216
218
  }
217
219
  } else {
@@ -228,9 +230,7 @@ function formatTable(headers, rows, options = {}) {
228
230
 
229
231
  // Top border
230
232
  const topBorder =
231
- box.topLeft +
232
- paddedWidths.map(w => box.horizontal.repeat(w)).join(box.topT) +
233
- box.topRight;
233
+ box.topLeft + paddedWidths.map(w => box.horizontal.repeat(w)).join(box.topT) + box.topRight;
234
234
  lines.push(indent + c.dim + topBorder + c.reset);
235
235
 
236
236
  // Header row
@@ -238,13 +238,20 @@ function formatTable(headers, rows, options = {}) {
238
238
  const padded = padString(h, colWidths[i], align[i] || 'left');
239
239
  return ' ' + c.bold + padded + c.reset + ' ';
240
240
  });
241
- lines.push(indent + c.dim + box.vertical + c.reset + headerCells.join(c.dim + box.vertical + c.reset) + c.dim + box.vertical + c.reset);
241
+ lines.push(
242
+ indent +
243
+ c.dim +
244
+ box.vertical +
245
+ c.reset +
246
+ headerCells.join(c.dim + box.vertical + c.reset) +
247
+ c.dim +
248
+ box.vertical +
249
+ c.reset
250
+ );
242
251
 
243
252
  // Header separator
244
253
  const headerSep =
245
- box.leftT +
246
- paddedWidths.map(w => box.horizontal.repeat(w)).join(box.cross) +
247
- box.rightT;
254
+ box.leftT + paddedWidths.map(w => box.horizontal.repeat(w)).join(box.cross) + box.rightT;
248
255
  lines.push(indent + c.dim + headerSep + c.reset);
249
256
 
250
257
  // Data rows
@@ -253,7 +260,16 @@ function formatTable(headers, rows, options = {}) {
253
260
  const padded = padString(cell, colWidths[i], align[i] || 'left');
254
261
  return ' ' + padded + ' ';
255
262
  });
256
- lines.push(indent + c.dim + box.vertical + c.reset + cells.join(c.dim + box.vertical + c.reset) + c.dim + box.vertical + c.reset);
263
+ lines.push(
264
+ indent +
265
+ c.dim +
266
+ box.vertical +
267
+ c.reset +
268
+ cells.join(c.dim + box.vertical + c.reset) +
269
+ c.dim +
270
+ box.vertical +
271
+ c.reset
272
+ );
257
273
  }
258
274
 
259
275
  // Bottom border
@@ -314,9 +330,7 @@ function formatKeyValue(data, options = {}) {
314
330
  }
315
331
 
316
332
  // Calculate key width for alignment
317
- const maxKeyWidth = alignValues
318
- ? Math.max(...pairs.map(([k]) => visibleWidth(k)))
319
- : 0;
333
+ const maxKeyWidth = alignValues ? Math.max(...pairs.map(([k]) => visibleWidth(k))) : 0;
320
334
 
321
335
  const lines = pairs.map(([key, value]) => {
322
336
  const paddedKey = alignValues ? padString(key, maxKeyWidth, 'left') : key;
@@ -453,7 +467,8 @@ function formatHeader(title, options = {}) {
453
467
  if (!isTTY()) {
454
468
  let header = `\n${title}`;
455
469
  if (subtitle) header += ` - ${subtitle}`;
456
- header += '\n' + '='.repeat(Math.min(visibleWidth(title) + (subtitle ? subtitle.length + 3 : 0), 40));
470
+ header +=
471
+ '\n' + '='.repeat(Math.min(visibleWidth(title) + (subtitle ? subtitle.length + 3 : 0), 40));
457
472
  return header;
458
473
  }
459
474
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.90.0",
3
+ "version": "2.90.1",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -56,12 +56,11 @@
56
56
  "chalk": "^4.1.2",
57
57
  "commander": "^12.1.0",
58
58
  "fs-extra": "^11.2.0",
59
- "ink": "^4.4.1",
60
- "ink-spinner": "^5.0.0",
59
+ "ink": "^3.2.0",
61
60
  "inquirer": "^8.2.6",
62
61
  "js-yaml": "^4.1.0",
63
62
  "ora": "^5.4.1",
64
- "react": "^18.2.0",
63
+ "react": "^17.0.2",
65
64
  "semver": "^7.6.3"
66
65
  },
67
66
  "optionalDependencies": {
@@ -151,7 +151,8 @@ async function fetchLatestVersion() {
151
151
  suggestion: 'Check network connection. If error persists, try: npm cache clean --force',
152
152
  };
153
153
  if (err.code === 'CERT_HAS_EXPIRED' || err.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
154
- errorInfo.suggestion = 'TLS certificate error - check system time or update CA certificates';
154
+ errorInfo.suggestion =
155
+ 'TLS certificate error - check system time or update CA certificates';
155
156
  }
156
157
  debugLog('Network error', errorInfo);
157
158
  resolve(null);
@@ -637,14 +637,10 @@ class SessionRegistry extends EventEmitter {
637
637
 
638
638
  if (checkMain.status === 0) return 'main';
639
639
 
640
- const checkMaster = spawnSync(
641
- 'git',
642
- ['show-ref', '--verify', '--quiet', 'refs/heads/master'],
643
- {
644
- cwd: this.rootDir,
645
- encoding: 'utf8',
646
- }
647
- );
640
+ const checkMaster = spawnSync('git', ['show-ref', '--verify', '--quiet', 'refs/heads/master'], {
641
+ cwd: this.rootDir,
642
+ encoding: 'utf8',
643
+ });
648
644
 
649
645
  if (checkMaster.status === 0) return 'master';
650
646
 
@@ -15,7 +15,7 @@ function App({
15
15
  title = 'AgileFlow TUI',
16
16
  showFooter = true,
17
17
  onAction = null,
18
- bindings = DEFAULT_BINDINGS
18
+ bindings = DEFAULT_BINDINGS,
19
19
  }) {
20
20
  const { exit } = useApp();
21
21
  const [showHelp, setShowHelp] = React.useState(false);
@@ -35,7 +35,7 @@ function App({
35
35
  keyboard.on('help', () => setShowHelp(prev => !prev));
36
36
 
37
37
  // Forward all actions to parent
38
- keyboard.on('action', (action) => {
38
+ keyboard.on('action', action => {
39
39
  setLastAction(action);
40
40
  if (onAction) {
41
41
  onAction(action);
@@ -60,7 +60,7 @@ function App({
60
60
  {
61
61
  flexDirection: 'column',
62
62
  width: '100%',
63
- minHeight: 20
63
+ minHeight: 20,
64
64
  },
65
65
  // Header
66
66
  React.createElement(
@@ -69,18 +69,10 @@ function App({
69
69
  borderStyle: 'round',
70
70
  borderColor: 'cyan',
71
71
  paddingX: 1,
72
- justifyContent: 'center'
72
+ justifyContent: 'center',
73
73
  },
74
- React.createElement(
75
- Text,
76
- { bold: true, color: 'cyan' },
77
- title
78
- ),
79
- lastAction && React.createElement(
80
- Text,
81
- { dimColor: true },
82
- ` [${lastAction.action}]`
83
- )
74
+ React.createElement(Text, { bold: true, color: 'cyan' }, title),
75
+ lastAction && React.createElement(Text, { dimColor: true }, ` [${lastAction.action}]`)
84
76
  ),
85
77
  // Main content area
86
78
  React.createElement(
@@ -89,39 +81,34 @@ function App({
89
81
  flexDirection: 'column',
90
82
  flexGrow: 1,
91
83
  paddingX: 1,
92
- paddingY: 1
84
+ paddingY: 1,
93
85
  },
94
- showHelp
95
- ? React.createElement(HelpPanel, { bindings })
96
- : children
86
+ showHelp ? React.createElement(HelpPanel, { bindings }) : children
97
87
  ),
98
88
  // Footer with key bindings
99
- showFooter && React.createElement(
100
- Box,
101
- {
102
- borderStyle: 'single',
103
- borderColor: 'gray',
104
- paddingX: 1,
105
- justifyContent: 'space-between'
106
- },
89
+ showFooter &&
107
90
  React.createElement(
108
91
  Box,
109
- { flexDirection: 'row' },
110
- footerBindings.map((binding, i) =>
111
- React.createElement(
112
- Text,
113
- { key: `binding-${i}`, dimColor: true },
114
- i > 0 ? ' | ' : '',
115
- binding
92
+ {
93
+ borderStyle: 'single',
94
+ borderColor: 'gray',
95
+ paddingX: 1,
96
+ justifyContent: 'space-between',
97
+ },
98
+ React.createElement(
99
+ Box,
100
+ { flexDirection: 'row' },
101
+ footerBindings.map((binding, i) =>
102
+ React.createElement(
103
+ Text,
104
+ { key: `binding-${i}`, dimColor: true },
105
+ i > 0 ? ' | ' : '',
106
+ binding
107
+ )
116
108
  )
117
- )
118
- ),
119
- React.createElement(
120
- Text,
121
- { dimColor: true },
122
- '1-9:Sessions | AgileFlow v2.89.3'
109
+ ),
110
+ React.createElement(Text, { dimColor: true }, '1-9:Sessions | AgileFlow v2.89.3')
123
111
  )
124
- )
125
112
  );
126
113
  }
127
114
 
@@ -131,29 +118,21 @@ function App({
131
118
  function HelpPanel({ bindings = DEFAULT_BINDINGS }) {
132
119
  const groups = {
133
120
  'Loop Control': ['start', 'pause', 'resume'],
134
- 'View': ['trace', 'help'],
135
- 'Navigation': ['quit'],
136
- 'Sessions': ['session1', 'session2', 'session3']
121
+ View: ['trace', 'help'],
122
+ Navigation: ['quit'],
123
+ Sessions: ['session1', 'session2', 'session3'],
137
124
  };
138
125
 
139
126
  return React.createElement(
140
127
  Box,
141
128
  { flexDirection: 'column', padding: 1 },
142
- React.createElement(
143
- Text,
144
- { bold: true, color: 'cyan' },
145
- 'Key Bindings'
146
- ),
129
+ React.createElement(Text, { bold: true, color: 'cyan' }, 'Key Bindings'),
147
130
  React.createElement(Box, { marginTop: 1 }),
148
131
  Object.entries(groups).map(([groupName, actions]) =>
149
132
  React.createElement(
150
133
  Box,
151
134
  { key: groupName, flexDirection: 'column', marginBottom: 1 },
152
- React.createElement(
153
- Text,
154
- { bold: true },
155
- groupName + ':'
156
- ),
135
+ React.createElement(Text, { bold: true }, groupName + ':'),
157
136
  actions.map(action => {
158
137
  const binding = bindings[action];
159
138
  if (!binding) return null;
@@ -165,11 +144,7 @@ function HelpPanel({ bindings = DEFAULT_BINDINGS }) {
165
144
  })
166
145
  )
167
146
  ),
168
- React.createElement(
169
- Text,
170
- { dimColor: true, marginTop: 1 },
171
- 'Press ? to close help'
172
- )
147
+ React.createElement(Text, { dimColor: true, marginTop: 1 }, 'Press ? to close help')
173
148
  );
174
149
  }
175
150
 
@@ -20,52 +20,8 @@
20
20
  * 1-9 - Switch session focus
21
21
  */
22
22
 
23
- const React = require('react');
24
- const { render, Box, Text } = require('ink');
25
- const { App } = require('./App');
26
- const { SessionPanel } = require('./panels/SessionPanel');
27
- const { OutputPanel } = require('./panels/OutputPanel');
28
-
29
- // Main TUI Layout - split panel view
30
- function MainLayout() {
31
- return React.createElement(
32
- Box,
33
- { flexDirection: 'row', width: '100%', minHeight: 15 },
34
- // Left panel - Sessions (40% width)
35
- React.createElement(
36
- Box,
37
- { flexDirection: 'column', width: '40%', paddingRight: 1 },
38
- React.createElement(SessionPanel, { refreshInterval: 5000 })
39
- ),
40
- // Right panel - Agent Output (60% width)
41
- React.createElement(
42
- Box,
43
- { flexDirection: 'column', width: '60%' },
44
- React.createElement(OutputPanel, {
45
- maxMessages: 100,
46
- showTimestamp: true,
47
- title: 'AGENT OUTPUT'
48
- })
49
- )
50
- );
51
- }
52
-
53
- // Main entry point
54
- function main() {
55
- const instance = render(
56
- React.createElement(
57
- App,
58
- { title: 'AgileFlow TUI' },
59
- React.createElement(MainLayout)
60
- )
61
- );
62
-
63
- // Handle clean exit
64
- instance.waitUntilExit().then(() => {
65
- console.log('AgileFlow TUI closed.');
66
- process.exit(0);
67
- });
68
- }
23
+ // Use the simple TUI implementation (pure Node.js, no React dependencies)
24
+ const { main } = require('./simple-tui');
69
25
 
70
26
  // Run if executed directly
71
27
  if (require.main === module) {
@@ -92,12 +92,12 @@ function createCheckpoint(sessionId = 'default', loopState = null) {
92
92
  coverage_threshold: loopState.coverage_threshold || 80,
93
93
  coverage_current: loopState.coverage_current || 0,
94
94
  started_at: loopState.started_at,
95
- conditions: loopState.conditions || []
95
+ conditions: loopState.conditions || [],
96
96
  },
97
97
  recovery_info: {
98
98
  can_resume: true,
99
- last_checkpoint: new Date().toISOString()
100
- }
99
+ last_checkpoint: new Date().toISOString(),
100
+ },
101
101
  };
102
102
 
103
103
  const result = safeWriteJSON(checkpointPath, checkpoint);
@@ -105,7 +105,7 @@ function createCheckpoint(sessionId = 'default', loopState = null) {
105
105
  return {
106
106
  ok: result.ok,
107
107
  checkpoint,
108
- path: checkpointPath
108
+ path: checkpointPath,
109
109
  };
110
110
  }
111
111
 
@@ -128,7 +128,7 @@ function loadCheckpoint(sessionId = 'default') {
128
128
  return {
129
129
  ok: true,
130
130
  exists: true,
131
- checkpoint: result.data
131
+ checkpoint: result.data,
132
132
  };
133
133
  }
134
134
 
@@ -151,7 +151,9 @@ function checkRecoveryNeeded(sessionId = 'default') {
151
151
  const checkpoint = checkpointResult.checkpoint;
152
152
 
153
153
  // Check if checkpoint is stale (older than 1 hour without update)
154
- const lastCheckpoint = new Date(checkpoint.recovery_info?.last_checkpoint || checkpoint.created_at);
154
+ const lastCheckpoint = new Date(
155
+ checkpoint.recovery_info?.last_checkpoint || checkpoint.created_at
156
+ );
155
157
  const now = new Date();
156
158
  const hoursSinceCheckpoint = (now - lastCheckpoint) / (1000 * 60 * 60);
157
159
 
@@ -186,12 +188,12 @@ function checkRecoveryNeeded(sessionId = 'default') {
186
188
  resume: {
187
189
  iteration: loopState.iteration,
188
190
  story: loopState.current_story,
189
- epic: loopState.epic
191
+ epic: loopState.epic,
190
192
  },
191
193
  fresh: {
192
- message: 'Start fresh from beginning of epic'
193
- }
194
- }
194
+ message: 'Start fresh from beginning of epic',
195
+ },
196
+ },
195
197
  };
196
198
  }
197
199
 
@@ -226,7 +228,7 @@ function resumeFromCheckpoint(sessionId = 'default') {
226
228
  conditions: loopState.conditions,
227
229
  started_at: loopState.started_at,
228
230
  resumed_at: new Date().toISOString(),
229
- resumed_from_checkpoint: true
231
+ resumed_from_checkpoint: true,
230
232
  };
231
233
 
232
234
  safeWriteJSON(statePath, state);
@@ -236,7 +238,7 @@ function resumeFromCheckpoint(sessionId = 'default') {
236
238
  resumed: true,
237
239
  iteration: loopState.iteration,
238
240
  story: loopState.current_story,
239
- epic: loopState.epic
241
+ epic: loopState.epic,
240
242
  };
241
243
  }
242
244
 
@@ -286,7 +288,7 @@ function getRecoveryStatus(sessionId = 'default') {
286
288
  reason: recovery.reason,
287
289
  hasCheckpoint: checkpoint.exists,
288
290
  checkpoint: checkpoint.checkpoint,
289
- options: recovery.recovery_options
291
+ options: recovery.recovery_options,
290
292
  };
291
293
  }
292
294
 
@@ -298,5 +300,5 @@ module.exports = {
298
300
  resumeFromCheckpoint,
299
301
  clearCheckpoint,
300
302
  startFresh,
301
- getRecoveryStatus
303
+ getRecoveryStatus,
302
304
  };
@@ -29,13 +29,8 @@ class EventStream extends EventEmitter {
29
29
 
30
30
  this.options = {
31
31
  // Path to log file (defaults to agent bus)
32
- logPath: options.logPath || path.join(
33
- getProjectRoot(),
34
- 'docs',
35
- '09-agents',
36
- 'bus',
37
- 'log.jsonl'
38
- ),
32
+ logPath:
33
+ options.logPath || path.join(getProjectRoot(), 'docs', '09-agents', 'bus', 'log.jsonl'),
39
34
  // Polling interval in ms (fallback if fs.watch fails)
40
35
  pollInterval: options.pollInterval || 1000,
41
36
  // Maximum events to keep in buffer
@@ -43,7 +38,7 @@ class EventStream extends EventEmitter {
43
38
  // Whether to emit historical events on start
44
39
  emitHistory: options.emitHistory || false,
45
40
  // How many historical events to emit
46
- historyLimit: options.historyLimit || 10
41
+ historyLimit: options.historyLimit || 10,
47
42
  };
48
43
 
49
44
  this.buffer = [];
@@ -75,13 +70,13 @@ class EventStream extends EventEmitter {
75
70
 
76
71
  // Try to use fs.watch (more efficient)
77
72
  try {
78
- this.watcher = fs.watch(this.options.logPath, (eventType) => {
73
+ this.watcher = fs.watch(this.options.logPath, eventType => {
79
74
  if (eventType === 'change') {
80
75
  this._processNewLines();
81
76
  }
82
77
  });
83
78
 
84
- this.watcher.on('error', (err) => {
79
+ this.watcher.on('error', err => {
85
80
  this.emit('error', err);
86
81
  // Fall back to polling
87
82
  this._startPolling();
@@ -271,9 +266,7 @@ function getDefaultStream() {
271
266
  * Format event for display
272
267
  */
273
268
  function formatEvent(event) {
274
- const timestamp = event.timestamp
275
- ? new Date(event.timestamp).toLocaleTimeString()
276
- : '';
269
+ const timestamp = event.timestamp ? new Date(event.timestamp).toLocaleTimeString() : '';
277
270
 
278
271
  const agent = event.agent || 'unknown';
279
272
  const eventType = event.event || event.type || 'unknown';
@@ -305,12 +298,12 @@ function formatEvent(event) {
305
298
  agent,
306
299
  eventType,
307
300
  message,
308
- raw: event
301
+ raw: event,
309
302
  };
310
303
  }
311
304
 
312
305
  module.exports = {
313
306
  EventStream,
314
307
  getDefaultStream,
315
- formatEvent
308
+ formatEvent,
316
309
  };