@claude-flow/cli 3.0.0-alpha.7 → 3.0.0-alpha.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/.agentic-flow/intelligence.json +4 -4
- package/.claude-flow/daemon-state.json +42 -35
- package/.claude-flow/daemon-test.log +0 -0
- package/.claude-flow/daemon.log +0 -0
- package/.claude-flow/daemon2.log +0 -0
- package/.claude-flow/daemon3.log +0 -0
- package/.claude-flow/metrics/codebase-map.json +2 -2
- package/.claude-flow/metrics/consolidation.json +1 -1
- package/.claude-flow/metrics/performance.json +12 -84
- package/.claude-flow/metrics/security-audit.json +1 -1
- package/.claude-flow/metrics/task-metrics.json +3 -3
- package/.claude-flow/metrics/test-gaps.json +1 -1
- package/agents/architect.yaml +1 -1
- package/agents/coder.yaml +1 -1
- package/agents/reviewer.yaml +1 -1
- package/agents/security-architect.yaml +1 -1
- package/agents/tester.yaml +1 -1
- package/dist/src/commands/daemon.d.ts.map +1 -1
- package/dist/src/commands/daemon.js +182 -8
- package/dist/src/commands/daemon.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +21 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/output.d.ts +16 -0
- package/dist/src/output.d.ts.map +1 -1
- package/dist/src/output.js +42 -0
- package/dist/src/output.js.map +1 -1
- package/dist/src/services/worker-daemon.d.ts +29 -2
- package/dist/src/services/worker-daemon.d.ts.map +1 -1
- package/dist/src/services/worker-daemon.js +123 -20
- package/dist/src/services/worker-daemon.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/commands/daemon.ts +211 -8
- package/src/index.ts +25 -1
- package/src/output.ts +47 -1
- package/src/services/worker-daemon.ts +153 -21
package/src/commands/daemon.ts
CHANGED
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
import type { Command, CommandContext, CommandResult } from '../types.js';
|
|
7
7
|
import { output } from '../output.js';
|
|
8
8
|
import { WorkerDaemon, getDaemon, startDaemon, stopDaemon, type WorkerType } from '../services/worker-daemon.js';
|
|
9
|
+
import { spawn } from 'child_process';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import { dirname, join } from 'path';
|
|
12
|
+
import * as fs from 'fs';
|
|
9
13
|
|
|
10
14
|
// Start daemon subcommand
|
|
11
15
|
const startCommand: Command = {
|
|
@@ -14,15 +18,37 @@ const startCommand: Command = {
|
|
|
14
18
|
options: [
|
|
15
19
|
{ name: 'workers', short: 'w', type: 'string', description: 'Comma-separated list of workers to enable (default: map,audit,optimize,consolidate,testgaps)' },
|
|
16
20
|
{ name: 'quiet', short: 'q', type: 'boolean', description: 'Suppress output' },
|
|
21
|
+
{ name: 'background', short: 'b', type: 'boolean', description: 'Run daemon in background (detached process)', default: true },
|
|
22
|
+
{ name: 'foreground', short: 'f', type: 'boolean', description: 'Run daemon in foreground (blocks terminal)' },
|
|
17
23
|
],
|
|
18
24
|
examples: [
|
|
19
|
-
{ command: 'claude-flow daemon start', description: 'Start daemon
|
|
25
|
+
{ command: 'claude-flow daemon start', description: 'Start daemon in background (default)' },
|
|
26
|
+
{ command: 'claude-flow daemon start --foreground', description: 'Start in foreground (blocks terminal)' },
|
|
20
27
|
{ command: 'claude-flow daemon start -w map,audit,optimize', description: 'Start with specific workers' },
|
|
21
28
|
],
|
|
22
29
|
action: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
23
30
|
const quiet = ctx.flags.quiet as boolean;
|
|
31
|
+
const foreground = ctx.flags.foreground as boolean;
|
|
24
32
|
const projectRoot = process.cwd();
|
|
33
|
+
const isDaemonProcess = process.env.CLAUDE_FLOW_DAEMON === '1';
|
|
34
|
+
|
|
35
|
+
// Check if background daemon already running (skip if we ARE the daemon process)
|
|
36
|
+
if (!isDaemonProcess) {
|
|
37
|
+
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
38
|
+
if (bgPid && isProcessRunning(bgPid)) {
|
|
39
|
+
if (!quiet) {
|
|
40
|
+
output.printWarning(`Daemon already running in background (PID: ${bgPid})`);
|
|
41
|
+
}
|
|
42
|
+
return { success: true };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
25
45
|
|
|
46
|
+
// Background mode (default): fork a detached process
|
|
47
|
+
if (!foreground) {
|
|
48
|
+
return startBackgroundDaemon(projectRoot, quiet);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Foreground mode: run in current process (blocks terminal)
|
|
26
52
|
try {
|
|
27
53
|
if (!quiet) {
|
|
28
54
|
const spinner = output.createSpinner({ text: 'Starting worker daemon...', spinner: 'dots' });
|
|
@@ -31,7 +57,7 @@ const startCommand: Command = {
|
|
|
31
57
|
const daemon = await startDaemon(projectRoot);
|
|
32
58
|
const status = daemon.getStatus();
|
|
33
59
|
|
|
34
|
-
spinner.succeed('Worker daemon started');
|
|
60
|
+
spinner.succeed('Worker daemon started (foreground mode)');
|
|
35
61
|
|
|
36
62
|
output.writeln();
|
|
37
63
|
output.printBox(
|
|
@@ -65,6 +91,9 @@ const startCommand: Command = {
|
|
|
65
91
|
})),
|
|
66
92
|
});
|
|
67
93
|
|
|
94
|
+
output.writeln();
|
|
95
|
+
output.writeln(output.dim('Press Ctrl+C to stop daemon'));
|
|
96
|
+
|
|
68
97
|
// Listen for worker events
|
|
69
98
|
daemon.on('worker:start', ({ type }: { type: string }) => {
|
|
70
99
|
output.writeln(output.dim(`[daemon] Worker starting: ${type}`));
|
|
@@ -77,8 +106,12 @@ const startCommand: Command = {
|
|
|
77
106
|
daemon.on('worker:error', ({ type, error }: { type: string; error: string }) => {
|
|
78
107
|
output.writeln(output.error(`[daemon] Worker failed: ${type} - ${error}`));
|
|
79
108
|
});
|
|
109
|
+
|
|
110
|
+
// Keep process alive
|
|
111
|
+
await new Promise(() => {}); // Never resolves - daemon runs until killed
|
|
80
112
|
} else {
|
|
81
113
|
await startDaemon(projectRoot);
|
|
114
|
+
await new Promise(() => {}); // Keep alive
|
|
82
115
|
}
|
|
83
116
|
|
|
84
117
|
return { success: true };
|
|
@@ -89,6 +122,75 @@ const startCommand: Command = {
|
|
|
89
122
|
},
|
|
90
123
|
};
|
|
91
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Start daemon as a detached background process
|
|
127
|
+
*/
|
|
128
|
+
async function startBackgroundDaemon(projectRoot: string, quiet: boolean): Promise<CommandResult> {
|
|
129
|
+
const stateDir = join(projectRoot, '.claude-flow');
|
|
130
|
+
const pidFile = join(stateDir, 'daemon.pid');
|
|
131
|
+
const logFile = join(stateDir, 'daemon.log');
|
|
132
|
+
|
|
133
|
+
// Ensure state directory exists
|
|
134
|
+
if (!fs.existsSync(stateDir)) {
|
|
135
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Get path to CLI (from dist/src/commands/daemon.js -> bin/cli.js)
|
|
139
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
140
|
+
const __dirname = dirname(__filename);
|
|
141
|
+
// dist/src/commands -> dist/src -> dist -> package root -> bin/cli.js
|
|
142
|
+
const cliPath = join(__dirname, '..', '..', '..', 'bin', 'cli.js');
|
|
143
|
+
|
|
144
|
+
// Verify CLI path exists
|
|
145
|
+
if (!fs.existsSync(cliPath)) {
|
|
146
|
+
output.printError(`CLI not found at: ${cliPath}`);
|
|
147
|
+
return { success: false, exitCode: 1 };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Use shell to spawn daemon with proper output redirection
|
|
151
|
+
// This ensures the log file stays open even after parent exits
|
|
152
|
+
const shellCmd = `"${process.execPath}" "${cliPath}" daemon start --foreground --quiet >> "${logFile}" 2>&1 & echo $!`;
|
|
153
|
+
|
|
154
|
+
const child = spawn('sh', ['-c', shellCmd], {
|
|
155
|
+
cwd: projectRoot,
|
|
156
|
+
detached: true,
|
|
157
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
158
|
+
env: { ...process.env, CLAUDE_FLOW_DAEMON: '1' },
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Wait for the PID to be echoed back
|
|
162
|
+
return new Promise((resolve) => {
|
|
163
|
+
let pidStr = '';
|
|
164
|
+
child.stdout?.on('data', (data: Buffer) => {
|
|
165
|
+
pidStr += data.toString();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
child.on('close', () => {
|
|
169
|
+
const pid = parseInt(pidStr.trim(), 10);
|
|
170
|
+
|
|
171
|
+
if (isNaN(pid) || pid <= 0) {
|
|
172
|
+
output.printError('Failed to get daemon PID');
|
|
173
|
+
resolve({ success: false, exitCode: 1 });
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Save PID
|
|
178
|
+
fs.writeFileSync(pidFile, String(pid));
|
|
179
|
+
|
|
180
|
+
if (!quiet) {
|
|
181
|
+
output.printSuccess(`Daemon started in background (PID: ${pid})`);
|
|
182
|
+
output.printInfo(`Logs: ${logFile}`);
|
|
183
|
+
output.printInfo(`Stop with: claude-flow daemon stop`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
resolve({ success: true });
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Unref so parent can exit immediately
|
|
190
|
+
child.unref();
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
92
194
|
// Stop daemon subcommand
|
|
93
195
|
const stopCommand: Command = {
|
|
94
196
|
name: 'stop',
|
|
@@ -101,17 +203,23 @@ const stopCommand: Command = {
|
|
|
101
203
|
],
|
|
102
204
|
action: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
103
205
|
const quiet = ctx.flags.quiet as boolean;
|
|
206
|
+
const projectRoot = process.cwd();
|
|
104
207
|
|
|
105
208
|
try {
|
|
106
209
|
if (!quiet) {
|
|
107
210
|
const spinner = output.createSpinner({ text: 'Stopping worker daemon...', spinner: 'dots' });
|
|
108
211
|
spinner.start();
|
|
109
212
|
|
|
213
|
+
// Try to stop in-process daemon first
|
|
110
214
|
await stopDaemon();
|
|
111
215
|
|
|
112
|
-
|
|
216
|
+
// Also kill any background daemon by PID
|
|
217
|
+
const killed = await killBackgroundDaemon(projectRoot);
|
|
218
|
+
|
|
219
|
+
spinner.succeed(killed ? 'Worker daemon stopped' : 'Worker daemon was not running');
|
|
113
220
|
} else {
|
|
114
221
|
await stopDaemon();
|
|
222
|
+
await killBackgroundDaemon(projectRoot);
|
|
115
223
|
}
|
|
116
224
|
|
|
117
225
|
return { success: true };
|
|
@@ -122,6 +230,92 @@ const stopCommand: Command = {
|
|
|
122
230
|
},
|
|
123
231
|
};
|
|
124
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Kill background daemon process using PID file
|
|
235
|
+
*/
|
|
236
|
+
async function killBackgroundDaemon(projectRoot: string): Promise<boolean> {
|
|
237
|
+
const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
|
|
238
|
+
|
|
239
|
+
if (!fs.existsSync(pidFile)) {
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
245
|
+
|
|
246
|
+
if (isNaN(pid)) {
|
|
247
|
+
fs.unlinkSync(pidFile);
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Check if process is running
|
|
252
|
+
try {
|
|
253
|
+
process.kill(pid, 0); // Signal 0 = check if alive
|
|
254
|
+
} catch {
|
|
255
|
+
// Process not running, clean up stale PID file
|
|
256
|
+
fs.unlinkSync(pidFile);
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Kill the process
|
|
261
|
+
process.kill(pid, 'SIGTERM');
|
|
262
|
+
|
|
263
|
+
// Wait a moment then force kill if needed
|
|
264
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
process.kill(pid, 0);
|
|
268
|
+
// Still alive, force kill
|
|
269
|
+
process.kill(pid, 'SIGKILL');
|
|
270
|
+
} catch {
|
|
271
|
+
// Process terminated
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Clean up PID file
|
|
275
|
+
if (fs.existsSync(pidFile)) {
|
|
276
|
+
fs.unlinkSync(pidFile);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return true;
|
|
280
|
+
} catch (error) {
|
|
281
|
+
// Clean up PID file on any error
|
|
282
|
+
if (fs.existsSync(pidFile)) {
|
|
283
|
+
fs.unlinkSync(pidFile);
|
|
284
|
+
}
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get PID of background daemon from PID file
|
|
291
|
+
*/
|
|
292
|
+
function getBackgroundDaemonPid(projectRoot: string): number | null {
|
|
293
|
+
const pidFile = join(projectRoot, '.claude-flow', 'daemon.pid');
|
|
294
|
+
|
|
295
|
+
if (!fs.existsSync(pidFile)) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf-8').trim(), 10);
|
|
301
|
+
return isNaN(pid) ? null : pid;
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Check if a process is running
|
|
309
|
+
*/
|
|
310
|
+
function isProcessRunning(pid: number): boolean {
|
|
311
|
+
try {
|
|
312
|
+
process.kill(pid, 0); // Signal 0 = check if alive
|
|
313
|
+
return true;
|
|
314
|
+
} catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
125
319
|
// Status subcommand
|
|
126
320
|
const statusCommand: Command = {
|
|
127
321
|
name: 'status',
|
|
@@ -135,21 +329,30 @@ const statusCommand: Command = {
|
|
|
135
329
|
],
|
|
136
330
|
action: async (ctx: CommandContext): Promise<CommandResult> => {
|
|
137
331
|
const verbose = ctx.flags.verbose as boolean;
|
|
332
|
+
const projectRoot = process.cwd();
|
|
138
333
|
|
|
139
334
|
try {
|
|
140
|
-
const daemon = getDaemon(
|
|
335
|
+
const daemon = getDaemon(projectRoot);
|
|
141
336
|
const status = daemon.getStatus();
|
|
142
337
|
|
|
338
|
+
// Also check for background daemon
|
|
339
|
+
const bgPid = getBackgroundDaemonPid(projectRoot);
|
|
340
|
+
const bgRunning = bgPid ? isProcessRunning(bgPid) : false;
|
|
341
|
+
|
|
342
|
+
const isRunning = status.running || bgRunning;
|
|
343
|
+
const displayPid = bgPid || status.pid;
|
|
344
|
+
|
|
143
345
|
output.writeln();
|
|
144
346
|
|
|
145
347
|
// Daemon status box
|
|
146
|
-
const statusIcon =
|
|
147
|
-
const statusText =
|
|
348
|
+
const statusIcon = isRunning ? output.success('●') : output.error('○');
|
|
349
|
+
const statusText = isRunning ? output.success('RUNNING') : output.error('STOPPED');
|
|
350
|
+
const mode = bgRunning ? output.dim(' (background)') : status.running ? output.dim(' (foreground)') : '';
|
|
148
351
|
|
|
149
352
|
output.printBox(
|
|
150
353
|
[
|
|
151
|
-
`Status: ${statusIcon} ${statusText}`,
|
|
152
|
-
`PID: ${
|
|
354
|
+
`Status: ${statusIcon} ${statusText}${mode}`,
|
|
355
|
+
`PID: ${displayPid}`,
|
|
153
356
|
status.startedAt ? `Started: ${status.startedAt.toISOString()}` : '',
|
|
154
357
|
`Workers Enabled: ${status.config.workers.filter(w => w.enabled).length}`,
|
|
155
358
|
`Max Concurrent: ${status.config.maxConcurrent}`,
|
package/src/index.ts
CHANGED
|
@@ -78,6 +78,21 @@ export class CLI {
|
|
|
78
78
|
this.output.setColorEnabled(false);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
// Set verbosity level based on flags
|
|
82
|
+
if (flags.quiet) {
|
|
83
|
+
this.output.setVerbosity('quiet');
|
|
84
|
+
} else if (flags.verbose) {
|
|
85
|
+
this.output.setVerbosity(process.env.DEBUG ? 'debug' : 'verbose');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Verbose mode: show parsed arguments
|
|
89
|
+
if (this.output.isVerbose()) {
|
|
90
|
+
this.output.printDebug(`Command: ${commandPath.join(' ') || '(none)'}`);
|
|
91
|
+
this.output.printDebug(`Positional: [${positional.join(', ')}]`);
|
|
92
|
+
this.output.printDebug(`Flags: ${JSON.stringify(Object.fromEntries(Object.entries(flags).filter(([k]) => k !== '_')))}`);
|
|
93
|
+
this.output.printDebug(`CWD: ${process.cwd()}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
// No command - show help
|
|
82
97
|
if (commandPath.length === 0 || flags.help || flags.h) {
|
|
83
98
|
if (commandPath.length > 0) {
|
|
@@ -176,8 +191,17 @@ export class CLI {
|
|
|
176
191
|
|
|
177
192
|
// Execute command
|
|
178
193
|
if (targetCommand.action) {
|
|
194
|
+
if (this.output.isVerbose()) {
|
|
195
|
+
this.output.printDebug(`Executing: ${targetCommand.name}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const startTime = Date.now();
|
|
179
199
|
const result = await targetCommand.action(ctx);
|
|
180
200
|
|
|
201
|
+
if (this.output.isVerbose()) {
|
|
202
|
+
this.output.printDebug(`Completed in ${Date.now() - startTime}ms`);
|
|
203
|
+
}
|
|
204
|
+
|
|
181
205
|
if (result && !result.success) {
|
|
182
206
|
process.exit(result.exitCode || 1);
|
|
183
207
|
}
|
|
@@ -379,7 +403,7 @@ export * from './types.js';
|
|
|
379
403
|
export { CommandParser, commandParser } from './parser.js';
|
|
380
404
|
|
|
381
405
|
// Output
|
|
382
|
-
export { OutputFormatter, output, Progress, Spinner } from './output.js';
|
|
406
|
+
export { OutputFormatter, output, Progress, Spinner, type VerbosityLevel } from './output.js';
|
|
383
407
|
|
|
384
408
|
// Prompt
|
|
385
409
|
export * from './prompt.js';
|
package/src/output.ts
CHANGED
|
@@ -50,15 +50,46 @@ const COLORS = {
|
|
|
50
50
|
|
|
51
51
|
type ColorName = keyof typeof COLORS;
|
|
52
52
|
|
|
53
|
+
export type VerbosityLevel = 'quiet' | 'normal' | 'verbose' | 'debug';
|
|
54
|
+
|
|
53
55
|
export class OutputFormatter {
|
|
54
56
|
private colorEnabled: boolean;
|
|
55
57
|
private outputStream: NodeJS.WriteStream;
|
|
56
58
|
private errorStream: NodeJS.WriteStream;
|
|
59
|
+
private verbosity: VerbosityLevel;
|
|
57
60
|
|
|
58
|
-
constructor(options: { color?: boolean } = {}) {
|
|
61
|
+
constructor(options: { color?: boolean; verbosity?: VerbosityLevel } = {}) {
|
|
59
62
|
this.colorEnabled = options.color ?? this.supportsColor();
|
|
60
63
|
this.outputStream = process.stdout;
|
|
61
64
|
this.errorStream = process.stderr;
|
|
65
|
+
this.verbosity = options.verbosity ?? 'normal';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Set verbosity level
|
|
70
|
+
* - quiet: Only errors and direct results
|
|
71
|
+
* - normal: Errors, warnings, info, and results
|
|
72
|
+
* - verbose: All of normal + debug messages
|
|
73
|
+
* - debug: All output including trace
|
|
74
|
+
*/
|
|
75
|
+
setVerbosity(level: VerbosityLevel): void {
|
|
76
|
+
this.verbosity = level;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
getVerbosity(): VerbosityLevel {
|
|
80
|
+
return this.verbosity;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
isQuiet(): boolean {
|
|
84
|
+
return this.verbosity === 'quiet';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
isVerbose(): boolean {
|
|
88
|
+
return this.verbosity === 'verbose' || this.verbosity === 'debug';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
isDebug(): boolean {
|
|
92
|
+
return this.verbosity === 'debug';
|
|
62
93
|
}
|
|
63
94
|
|
|
64
95
|
private supportsColor(): boolean {
|
|
@@ -136,11 +167,13 @@ export class OutputFormatter {
|
|
|
136
167
|
// ============================================
|
|
137
168
|
|
|
138
169
|
printSuccess(message: string): void {
|
|
170
|
+
// Success always shows (result output)
|
|
139
171
|
const icon = this.color('[OK]', 'green', 'bold');
|
|
140
172
|
this.writeln(`${icon} ${message}`);
|
|
141
173
|
}
|
|
142
174
|
|
|
143
175
|
printError(message: string, details?: string): void {
|
|
176
|
+
// Errors always show
|
|
144
177
|
const icon = this.color('[ERROR]', 'red', 'bold');
|
|
145
178
|
this.writeErrorln(`${icon} ${message}`);
|
|
146
179
|
if (details) {
|
|
@@ -149,20 +182,33 @@ export class OutputFormatter {
|
|
|
149
182
|
}
|
|
150
183
|
|
|
151
184
|
printWarning(message: string): void {
|
|
185
|
+
// Warnings suppressed in quiet mode
|
|
186
|
+
if (this.verbosity === 'quiet') return;
|
|
152
187
|
const icon = this.color('[WARN]', 'yellow', 'bold');
|
|
153
188
|
this.writeln(`${icon} ${message}`);
|
|
154
189
|
}
|
|
155
190
|
|
|
156
191
|
printInfo(message: string): void {
|
|
192
|
+
// Info suppressed in quiet mode
|
|
193
|
+
if (this.verbosity === 'quiet') return;
|
|
157
194
|
const icon = this.color('[INFO]', 'blue', 'bold');
|
|
158
195
|
this.writeln(`${icon} ${message}`);
|
|
159
196
|
}
|
|
160
197
|
|
|
161
198
|
printDebug(message: string): void {
|
|
199
|
+
// Debug only shows in verbose/debug mode
|
|
200
|
+
if (this.verbosity !== 'verbose' && this.verbosity !== 'debug') return;
|
|
162
201
|
const icon = this.color('[DEBUG]', 'gray');
|
|
163
202
|
this.writeln(`${icon} ${this.dim(message)}`);
|
|
164
203
|
}
|
|
165
204
|
|
|
205
|
+
printTrace(message: string): void {
|
|
206
|
+
// Trace only shows in debug mode
|
|
207
|
+
if (this.verbosity !== 'debug') return;
|
|
208
|
+
const icon = this.color('[TRACE]', 'gray', 'dim');
|
|
209
|
+
this.writeln(`${icon} ${this.dim(message)}`);
|
|
210
|
+
}
|
|
211
|
+
|
|
166
212
|
// ============================================
|
|
167
213
|
// Table Formatting
|
|
168
214
|
// ============================================
|