@fractary/faber-cli 1.0.0
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/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +163 -0
- package/dist/commands/logs/index.d.ts +11 -0
- package/dist/commands/logs/index.d.ts.map +1 -0
- package/dist/commands/logs/index.js +330 -0
- package/dist/commands/repo/index.d.ts +11 -0
- package/dist/commands/repo/index.d.ts.map +1 -0
- package/dist/commands/repo/index.js +612 -0
- package/dist/commands/spec/index.d.ts +11 -0
- package/dist/commands/spec/index.d.ts.map +1 -0
- package/dist/commands/spec/index.js +280 -0
- package/dist/commands/work/index.d.ts +11 -0
- package/dist/commands/work/index.d.ts.map +1 -0
- package/dist/commands/work/index.js +748 -0
- package/dist/commands/workflow/index.d.ts +31 -0
- package/dist/commands/workflow/index.d.ts.map +1 -0
- package/dist/commands/workflow/index.js +298 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -0
- package/dist/utils/errors.d.ts +14 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +44 -0
- package/dist/utils/output.d.ts +18 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +39 -0
- package/dist/utils/validation.d.ts +30 -0
- package/dist/utils/validation.d.ts.map +1 -0
- package/dist/utils/validation.js +49 -0
- package/package.json +66 -0
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow commands - FABER workflow execution
|
|
3
|
+
*
|
|
4
|
+
* Provides run, status, resume, pause commands via FaberWorkflow SDK.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
/**
|
|
8
|
+
* Create the run command
|
|
9
|
+
*/
|
|
10
|
+
export declare function createRunCommand(): Command;
|
|
11
|
+
/**
|
|
12
|
+
* Create the status command
|
|
13
|
+
*/
|
|
14
|
+
export declare function createStatusCommand(): Command;
|
|
15
|
+
/**
|
|
16
|
+
* Create the resume command
|
|
17
|
+
*/
|
|
18
|
+
export declare function createResumeCommand(): Command;
|
|
19
|
+
/**
|
|
20
|
+
* Create the pause command
|
|
21
|
+
*/
|
|
22
|
+
export declare function createPauseCommand(): Command;
|
|
23
|
+
/**
|
|
24
|
+
* Create the recover command
|
|
25
|
+
*/
|
|
26
|
+
export declare function createRecoverCommand(): Command;
|
|
27
|
+
/**
|
|
28
|
+
* Create the cleanup command
|
|
29
|
+
*/
|
|
30
|
+
export declare function createCleanupCommand(): Command;
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/workflow/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAkD1C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAiF7C;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAyB7C;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAoB5C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CA2B9C;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CA8B9C"}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow commands - FABER workflow execution
|
|
3
|
+
*
|
|
4
|
+
* Provides run, status, resume, pause commands via FaberWorkflow SDK.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from 'commander';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { FaberWorkflow, StateManager } from '@fractary/faber';
|
|
9
|
+
import { parsePositiveInteger } from '../../utils/validation.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create the run command
|
|
12
|
+
*/
|
|
13
|
+
export function createRunCommand() {
|
|
14
|
+
return new Command('run')
|
|
15
|
+
.description('Run FABER workflow')
|
|
16
|
+
.requiredOption('--work-id <id>', 'Work item ID to process')
|
|
17
|
+
.option('--autonomy <level>', 'Autonomy level: supervised|assisted|autonomous', 'supervised')
|
|
18
|
+
.option('--json', 'Output as JSON')
|
|
19
|
+
.action(async (options) => {
|
|
20
|
+
try {
|
|
21
|
+
const workflow = new FaberWorkflow();
|
|
22
|
+
if (!options.json) {
|
|
23
|
+
console.log(chalk.blue(`Starting FABER workflow for work item #${options.workId}`));
|
|
24
|
+
console.log(chalk.gray(`Autonomy: ${options.autonomy}`));
|
|
25
|
+
}
|
|
26
|
+
// Add event listener for progress updates
|
|
27
|
+
workflow.addEventListener((event, data) => {
|
|
28
|
+
if (options.json)
|
|
29
|
+
return;
|
|
30
|
+
switch (event) {
|
|
31
|
+
case 'phase:start':
|
|
32
|
+
console.log(chalk.cyan(`\n→ Starting phase: ${String(data.phase || '').toUpperCase()}`));
|
|
33
|
+
break;
|
|
34
|
+
case 'phase:complete':
|
|
35
|
+
console.log(chalk.green(` ✓ Completed phase: ${data.phase}`));
|
|
36
|
+
break;
|
|
37
|
+
case 'workflow:fail':
|
|
38
|
+
case 'phase:fail':
|
|
39
|
+
console.error(chalk.red(` ✗ Error: ${data.error || 'Unknown error'}`));
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
const result = await workflow.run({
|
|
44
|
+
workId: options.workId,
|
|
45
|
+
autonomy: options.autonomy,
|
|
46
|
+
});
|
|
47
|
+
if (options.json) {
|
|
48
|
+
console.log(JSON.stringify({ status: 'success', data: result }, null, 2));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
console.log(chalk.green(`\n✓ Workflow ${result.status}`));
|
|
52
|
+
console.log(chalk.gray(` Workflow ID: ${result.workflow_id}`));
|
|
53
|
+
console.log(chalk.gray(` Duration: ${result.duration_ms}ms`));
|
|
54
|
+
console.log(chalk.gray(` Phases: ${result.phases.map((p) => p.phase).join(' → ')}`));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
handleWorkflowError(error, options);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create the status command
|
|
64
|
+
*/
|
|
65
|
+
export function createStatusCommand() {
|
|
66
|
+
return new Command('status')
|
|
67
|
+
.description('Show workflow status')
|
|
68
|
+
.option('--work-id <id>', 'Work item ID to check')
|
|
69
|
+
.option('--workflow-id <id>', 'Workflow ID to check')
|
|
70
|
+
.option('--verbose', 'Show detailed status')
|
|
71
|
+
.option('--json', 'Output as JSON')
|
|
72
|
+
.action(async (options) => {
|
|
73
|
+
try {
|
|
74
|
+
const stateManager = new StateManager();
|
|
75
|
+
if (options.workflowId) {
|
|
76
|
+
// Status for specific workflow by ID
|
|
77
|
+
const workflow = new FaberWorkflow();
|
|
78
|
+
const status = workflow.getStatus(options.workflowId);
|
|
79
|
+
if (options.json) {
|
|
80
|
+
console.log(JSON.stringify({ status: 'success', data: status }, null, 2));
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
console.log(chalk.bold(`Workflow Status: ${options.workflowId}`));
|
|
84
|
+
console.log(` Current Phase: ${status.currentPhase || 'N/A'}`);
|
|
85
|
+
console.log(` Progress: ${status.progress}%`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
else if (options.workId) {
|
|
89
|
+
// Status for work item's active workflow
|
|
90
|
+
const state = stateManager.getActiveWorkflow(options.workId);
|
|
91
|
+
if (!state) {
|
|
92
|
+
if (options.json) {
|
|
93
|
+
console.log(JSON.stringify({
|
|
94
|
+
status: 'success',
|
|
95
|
+
data: { active: false, message: 'No active workflow' },
|
|
96
|
+
}, null, 2));
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
console.log(chalk.yellow(`No active workflow for work item #${options.workId}`));
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (options.json) {
|
|
104
|
+
console.log(JSON.stringify({ status: 'success', data: state }, null, 2));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
console.log(chalk.bold(`Workflow Status: Work Item #${options.workId}`));
|
|
108
|
+
console.log(` Workflow ID: ${state.workflow_id}`);
|
|
109
|
+
console.log(` State: ${getStateColor(state.status)(state.status)}`);
|
|
110
|
+
console.log(` Current Phase: ${state.current_phase || 'N/A'}`);
|
|
111
|
+
console.log(` Started: ${state.started_at || 'N/A'}`);
|
|
112
|
+
if (options.verbose && state.phase_states) {
|
|
113
|
+
console.log(chalk.yellow('\nPhase Details:'));
|
|
114
|
+
Object.entries(state.phase_states).forEach(([phase, phaseState]) => {
|
|
115
|
+
const ps = phaseState;
|
|
116
|
+
const icon = ps.status === 'completed' ? chalk.green('✓') :
|
|
117
|
+
ps.status === 'in_progress' ? chalk.cyan('→') :
|
|
118
|
+
ps.status === 'failed' ? chalk.red('✗') : chalk.gray('○');
|
|
119
|
+
console.log(` ${icon} ${phase}`);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// List all workflows
|
|
126
|
+
const workflows = stateManager.listWorkflows();
|
|
127
|
+
if (options.json) {
|
|
128
|
+
console.log(JSON.stringify({ status: 'success', data: workflows }, null, 2));
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
if (workflows.length === 0) {
|
|
132
|
+
console.log(chalk.yellow('No workflows found'));
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
console.log(chalk.bold('Workflows:'));
|
|
136
|
+
workflows.forEach((wf) => {
|
|
137
|
+
const stateColor = getStateColor(wf.status);
|
|
138
|
+
console.log(` ${wf.workflow_id}: work #${wf.work_id} - ${wf.current_phase || 'N/A'} [${stateColor(wf.status)}]`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
handleWorkflowError(error, options);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Create the resume command
|
|
151
|
+
*/
|
|
152
|
+
export function createResumeCommand() {
|
|
153
|
+
return new Command('resume')
|
|
154
|
+
.description('Resume a paused workflow')
|
|
155
|
+
.argument('<workflow_id>', 'Workflow ID to resume')
|
|
156
|
+
.option('--json', 'Output as JSON')
|
|
157
|
+
.action(async (workflowId, options) => {
|
|
158
|
+
try {
|
|
159
|
+
const workflow = new FaberWorkflow();
|
|
160
|
+
if (!options.json) {
|
|
161
|
+
console.log(chalk.blue(`Resuming workflow: ${workflowId}`));
|
|
162
|
+
}
|
|
163
|
+
const result = await workflow.resume(workflowId);
|
|
164
|
+
if (options.json) {
|
|
165
|
+
console.log(JSON.stringify({ status: 'success', data: result }, null, 2));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.log(chalk.green(`\n✓ Workflow ${result.status}`));
|
|
169
|
+
console.log(chalk.gray(` Duration: ${result.duration_ms}ms`));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
handleWorkflowError(error, options);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Create the pause command
|
|
179
|
+
*/
|
|
180
|
+
export function createPauseCommand() {
|
|
181
|
+
return new Command('pause')
|
|
182
|
+
.description('Pause a running workflow')
|
|
183
|
+
.argument('<workflow_id>', 'Workflow ID to pause')
|
|
184
|
+
.option('--json', 'Output as JSON')
|
|
185
|
+
.action(async (workflowId, options) => {
|
|
186
|
+
try {
|
|
187
|
+
const workflow = new FaberWorkflow();
|
|
188
|
+
workflow.pause(workflowId);
|
|
189
|
+
if (options.json) {
|
|
190
|
+
console.log(JSON.stringify({ status: 'success', data: { paused: workflowId } }, null, 2));
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.log(chalk.green(`✓ Paused workflow: ${workflowId}`));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
handleWorkflowError(error, options);
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Create the recover command
|
|
203
|
+
*/
|
|
204
|
+
export function createRecoverCommand() {
|
|
205
|
+
return new Command('recover')
|
|
206
|
+
.description('Recover a workflow from checkpoint')
|
|
207
|
+
.argument('<workflow_id>', 'Workflow ID to recover')
|
|
208
|
+
.option('--checkpoint <id>', 'Specific checkpoint ID to recover from')
|
|
209
|
+
.option('--phase <phase>', 'Recover to specific phase')
|
|
210
|
+
.option('--json', 'Output as JSON')
|
|
211
|
+
.action(async (workflowId, options) => {
|
|
212
|
+
try {
|
|
213
|
+
const stateManager = new StateManager();
|
|
214
|
+
const state = stateManager.recoverWorkflow(workflowId, {
|
|
215
|
+
checkpointId: options.checkpoint,
|
|
216
|
+
fromPhase: options.phase,
|
|
217
|
+
});
|
|
218
|
+
if (options.json) {
|
|
219
|
+
console.log(JSON.stringify({ status: 'success', data: state }, null, 2));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.log(chalk.green(`✓ Recovered workflow: ${workflowId}`));
|
|
223
|
+
console.log(chalk.gray(` Current phase: ${state.current_phase}`));
|
|
224
|
+
console.log(chalk.gray(` Status: ${state.status}`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
handleWorkflowError(error, options);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Create the cleanup command
|
|
234
|
+
*/
|
|
235
|
+
export function createCleanupCommand() {
|
|
236
|
+
return new Command('cleanup')
|
|
237
|
+
.description('Clean up old workflow states')
|
|
238
|
+
.option('--max-age <days>', 'Delete workflows older than N days', '30')
|
|
239
|
+
.option('--json', 'Output as JSON')
|
|
240
|
+
.action(async (options) => {
|
|
241
|
+
try {
|
|
242
|
+
const stateManager = new StateManager();
|
|
243
|
+
const result = stateManager.cleanup(parsePositiveInteger(options.maxAge, 'max age (days)'));
|
|
244
|
+
if (options.json) {
|
|
245
|
+
console.log(JSON.stringify({ status: 'success', data: result }, null, 2));
|
|
246
|
+
}
|
|
247
|
+
else {
|
|
248
|
+
if (result.deleted === 0) {
|
|
249
|
+
console.log(chalk.yellow('No workflows to clean up'));
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
console.log(chalk.green(`✓ Cleaned up ${result.deleted} workflow(s)`));
|
|
253
|
+
}
|
|
254
|
+
if (result.errors.length > 0) {
|
|
255
|
+
console.log(chalk.yellow(`\nErrors (${result.errors.length}):`));
|
|
256
|
+
result.errors.forEach((err) => {
|
|
257
|
+
console.log(chalk.red(` - ${err}`));
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
handleWorkflowError(error, options);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
// Helper functions
|
|
268
|
+
function getStateColor(state) {
|
|
269
|
+
switch (state) {
|
|
270
|
+
case 'running':
|
|
271
|
+
return chalk.cyan;
|
|
272
|
+
case 'completed':
|
|
273
|
+
return chalk.green;
|
|
274
|
+
case 'failed':
|
|
275
|
+
return chalk.red;
|
|
276
|
+
case 'paused':
|
|
277
|
+
return chalk.yellow;
|
|
278
|
+
case 'idle':
|
|
279
|
+
case 'pending':
|
|
280
|
+
return chalk.gray;
|
|
281
|
+
default:
|
|
282
|
+
return chalk.white;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Error handling
|
|
286
|
+
function handleWorkflowError(error, options) {
|
|
287
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
288
|
+
if (options.json) {
|
|
289
|
+
console.error(JSON.stringify({
|
|
290
|
+
status: 'error',
|
|
291
|
+
error: { code: 'WORKFLOW_ERROR', message },
|
|
292
|
+
}));
|
|
293
|
+
}
|
|
294
|
+
else {
|
|
295
|
+
console.error(chalk.red('Error:'), message);
|
|
296
|
+
}
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FABER CLI - Command-line interface for FABER development toolkit
|
|
4
|
+
*
|
|
5
|
+
* Binary: fractary-faber
|
|
6
|
+
* Entry point for the FABER command-line interface
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
/**
|
|
10
|
+
* Create and configure the main CLI program
|
|
11
|
+
*/
|
|
12
|
+
export declare function createFaberCLI(): Command;
|
|
13
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC;;GAEG;AACH,wBAAgB,cAAc,IAAI,OAAO,CA0CxC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* FABER CLI - Command-line interface for FABER development toolkit
|
|
4
|
+
*
|
|
5
|
+
* Binary: fractary-faber
|
|
6
|
+
* Entry point for the FABER command-line interface
|
|
7
|
+
*/
|
|
8
|
+
import { Command } from 'commander';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import { createRunCommand, createStatusCommand, createResumeCommand, createPauseCommand, createRecoverCommand, createCleanupCommand } from './commands/workflow/index.js';
|
|
11
|
+
import { createWorkCommand } from './commands/work/index.js';
|
|
12
|
+
import { createRepoCommand } from './commands/repo/index.js';
|
|
13
|
+
import { createSpecCommand } from './commands/spec/index.js';
|
|
14
|
+
import { createLogsCommand } from './commands/logs/index.js';
|
|
15
|
+
import { createInitCommand } from './commands/init.js';
|
|
16
|
+
const version = '1.0.0';
|
|
17
|
+
/**
|
|
18
|
+
* Create and configure the main CLI program
|
|
19
|
+
*/
|
|
20
|
+
export function createFaberCLI() {
|
|
21
|
+
const program = new Command('fractary-faber');
|
|
22
|
+
program
|
|
23
|
+
.description('FABER development toolkit (workflow, work, repo, spec, logs)')
|
|
24
|
+
.version(version)
|
|
25
|
+
.enablePositionalOptions();
|
|
26
|
+
// Global options
|
|
27
|
+
program.option('--debug', 'Enable debug output');
|
|
28
|
+
// Workflow commands (top-level)
|
|
29
|
+
program.addCommand(createInitCommand());
|
|
30
|
+
program.addCommand(createRunCommand());
|
|
31
|
+
program.addCommand(createStatusCommand());
|
|
32
|
+
program.addCommand(createResumeCommand());
|
|
33
|
+
program.addCommand(createPauseCommand());
|
|
34
|
+
program.addCommand(createRecoverCommand());
|
|
35
|
+
program.addCommand(createCleanupCommand());
|
|
36
|
+
// Subcommand trees
|
|
37
|
+
program.addCommand(createWorkCommand());
|
|
38
|
+
program.addCommand(createRepoCommand());
|
|
39
|
+
program.addCommand(createSpecCommand());
|
|
40
|
+
program.addCommand(createLogsCommand());
|
|
41
|
+
// Help command
|
|
42
|
+
program.addCommand(new Command('help')
|
|
43
|
+
.description('Show help for all commands')
|
|
44
|
+
.action(() => {
|
|
45
|
+
program.help();
|
|
46
|
+
}));
|
|
47
|
+
// Error handling
|
|
48
|
+
program.on('error', (error) => {
|
|
49
|
+
console.error(chalk.red('✖'), error.message);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
return program;
|
|
53
|
+
}
|
|
54
|
+
// Main execution - ESM compatible entry point detection
|
|
55
|
+
const isMainModule = import.meta.url === `file://${process.argv[1]}` ||
|
|
56
|
+
process.argv[1]?.endsWith('fractary-faber');
|
|
57
|
+
if (isMainModule) {
|
|
58
|
+
const program = createFaberCLI();
|
|
59
|
+
program.parse(process.argv);
|
|
60
|
+
// Show help if no command provided
|
|
61
|
+
if (!process.argv.slice(2).length) {
|
|
62
|
+
program.outputHelp();
|
|
63
|
+
process.exit(0);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ErrorOutput {
|
|
2
|
+
status: 'error';
|
|
3
|
+
error: {
|
|
4
|
+
code: string;
|
|
5
|
+
message: string;
|
|
6
|
+
details?: unknown;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export declare function formatError(code: string, message: string, details?: unknown): ErrorOutput;
|
|
10
|
+
export declare function printError(message: string, details?: string): void;
|
|
11
|
+
export declare function printSuccess(message: string, details?: string): void;
|
|
12
|
+
export declare function printInfo(message: string, details?: string): void;
|
|
13
|
+
export declare function printWarning(message: string, details?: string): void;
|
|
14
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,OAAO,GAChB,WAAW,CAmBb;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAKlE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAKpE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAKjE;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAKpE"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
export function formatError(code, message, details) {
|
|
3
|
+
if (details !== undefined) {
|
|
4
|
+
return {
|
|
5
|
+
status: 'error',
|
|
6
|
+
error: {
|
|
7
|
+
code,
|
|
8
|
+
message,
|
|
9
|
+
details,
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
status: 'error',
|
|
15
|
+
error: {
|
|
16
|
+
code,
|
|
17
|
+
message,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export function printError(message, details) {
|
|
22
|
+
console.error(chalk.red('✖'), message);
|
|
23
|
+
if (details) {
|
|
24
|
+
console.error(chalk.gray(details));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function printSuccess(message, details) {
|
|
28
|
+
console.log(chalk.green('✓'), message);
|
|
29
|
+
if (details) {
|
|
30
|
+
console.log(chalk.gray(details));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function printInfo(message, details) {
|
|
34
|
+
console.log(chalk.blue('ℹ'), message);
|
|
35
|
+
if (details) {
|
|
36
|
+
console.log(chalk.gray(details));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function printWarning(message, details) {
|
|
40
|
+
console.log(chalk.yellow('⚠'), message);
|
|
41
|
+
if (details) {
|
|
42
|
+
console.log(chalk.gray(details));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for CLI commands
|
|
3
|
+
*/
|
|
4
|
+
export interface JSONOutput<T = unknown> {
|
|
5
|
+
status: 'success' | 'error';
|
|
6
|
+
data?: T;
|
|
7
|
+
error?: {
|
|
8
|
+
code: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare function formatJSON<T>(data: T, error?: {
|
|
13
|
+
code: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}): JSONOutput<T>;
|
|
16
|
+
export declare function outputJSON<T>(data: JSONOutput<T>): void;
|
|
17
|
+
export declare function formatTable<T extends Record<string, unknown>>(rows: T[]): string;
|
|
18
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,UAAU,CAAC,CAAC,GAAG,OAAO;IACrC,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAY/F;AAED,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAEvD;AAED,wBAAgB,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CA6BhF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Output formatting utilities for CLI commands
|
|
3
|
+
*/
|
|
4
|
+
export function formatJSON(data, error) {
|
|
5
|
+
if (error) {
|
|
6
|
+
return {
|
|
7
|
+
status: 'error',
|
|
8
|
+
error,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
status: 'success',
|
|
13
|
+
data,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function outputJSON(data) {
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
}
|
|
19
|
+
export function formatTable(rows) {
|
|
20
|
+
if (rows.length === 0) {
|
|
21
|
+
return '';
|
|
22
|
+
}
|
|
23
|
+
const keys = Object.keys(rows[0]);
|
|
24
|
+
const columnWidths = keys.map((key) => {
|
|
25
|
+
const headerWidth = String(key).length;
|
|
26
|
+
const maxContentWidth = Math.max(...rows.map((row) => String(row[key] ?? '').length));
|
|
27
|
+
return Math.max(headerWidth, maxContentWidth);
|
|
28
|
+
});
|
|
29
|
+
const separator = columnWidths
|
|
30
|
+
.map((width) => '─'.repeat(width + 2))
|
|
31
|
+
.join('┼');
|
|
32
|
+
const header = keys
|
|
33
|
+
.map((key, i) => ` ${String(key).padEnd(columnWidths[i])} `)
|
|
34
|
+
.join('┃');
|
|
35
|
+
const lines = rows.map((row) => keys
|
|
36
|
+
.map((key, i) => ` ${String(row[key] ?? '').padEnd(columnWidths[i])} `)
|
|
37
|
+
.join('┃'));
|
|
38
|
+
return [header, separator, ...lines].join('\n');
|
|
39
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FABER CLI - Input Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common validation functions for CLI inputs
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse and validate an integer string
|
|
8
|
+
* @param value - String value to parse
|
|
9
|
+
* @param fieldName - Field name for error messages
|
|
10
|
+
* @returns Validated integer
|
|
11
|
+
* @throws Error if value is not a valid integer
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseValidInteger(value: string, fieldName: string): number;
|
|
14
|
+
/**
|
|
15
|
+
* Parse and validate an optional integer string
|
|
16
|
+
* @param value - String value to parse (may be undefined)
|
|
17
|
+
* @param fieldName - Field name for error messages
|
|
18
|
+
* @returns Validated integer or undefined
|
|
19
|
+
* @throws Error if value is provided but not a valid integer
|
|
20
|
+
*/
|
|
21
|
+
export declare function parseOptionalInteger(value: string | undefined, fieldName: string): number | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Parse and validate a positive integer string
|
|
24
|
+
* @param value - String value to parse
|
|
25
|
+
* @param fieldName - Field name for error messages
|
|
26
|
+
* @returns Validated positive integer
|
|
27
|
+
* @throws Error if value is not a valid positive integer
|
|
28
|
+
*/
|
|
29
|
+
export declare function parsePositiveInteger(value: string, fieldName: string): number;
|
|
30
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAY1E;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAMrG;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAQ7E"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FABER CLI - Input Validation Utilities
|
|
3
|
+
*
|
|
4
|
+
* Common validation functions for CLI inputs
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Parse and validate an integer string
|
|
8
|
+
* @param value - String value to parse
|
|
9
|
+
* @param fieldName - Field name for error messages
|
|
10
|
+
* @returns Validated integer
|
|
11
|
+
* @throws Error if value is not a valid integer
|
|
12
|
+
*/
|
|
13
|
+
export function parseValidInteger(value, fieldName) {
|
|
14
|
+
const parsed = parseInt(value, 10);
|
|
15
|
+
if (isNaN(parsed)) {
|
|
16
|
+
throw new Error(`Invalid ${fieldName}: "${value}" is not a valid integer`);
|
|
17
|
+
}
|
|
18
|
+
if (!Number.isFinite(parsed)) {
|
|
19
|
+
throw new Error(`Invalid ${fieldName}: value is not finite`);
|
|
20
|
+
}
|
|
21
|
+
return parsed;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Parse and validate an optional integer string
|
|
25
|
+
* @param value - String value to parse (may be undefined)
|
|
26
|
+
* @param fieldName - Field name for error messages
|
|
27
|
+
* @returns Validated integer or undefined
|
|
28
|
+
* @throws Error if value is provided but not a valid integer
|
|
29
|
+
*/
|
|
30
|
+
export function parseOptionalInteger(value, fieldName) {
|
|
31
|
+
if (!value) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
return parseValidInteger(value, fieldName);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse and validate a positive integer string
|
|
38
|
+
* @param value - String value to parse
|
|
39
|
+
* @param fieldName - Field name for error messages
|
|
40
|
+
* @returns Validated positive integer
|
|
41
|
+
* @throws Error if value is not a valid positive integer
|
|
42
|
+
*/
|
|
43
|
+
export function parsePositiveInteger(value, fieldName) {
|
|
44
|
+
const parsed = parseValidInteger(value, fieldName);
|
|
45
|
+
if (parsed <= 0) {
|
|
46
|
+
throw new Error(`Invalid ${fieldName}: must be a positive integer (got ${parsed})`);
|
|
47
|
+
}
|
|
48
|
+
return parsed;
|
|
49
|
+
}
|