@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.
@@ -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
+ }
@@ -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
+ }