@fractary/faber-cli 1.1.0 → 1.3.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/dist/commands/plan/index.d.ts +11 -0
- package/dist/commands/plan/index.d.ts.map +1 -0
- package/dist/commands/plan/index.js +383 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/anthropic-client.d.ts +69 -0
- package/dist/lib/anthropic-client.d.ts.map +1 -0
- package/dist/lib/anthropic-client.js +225 -0
- package/dist/lib/config.d.ts +30 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +127 -0
- package/dist/lib/repo-client.d.ts +78 -0
- package/dist/lib/repo-client.d.ts.map +1 -0
- package/dist/lib/repo-client.js +151 -0
- package/dist/lib/sdk-config-adapter.d.ts +24 -0
- package/dist/lib/sdk-config-adapter.d.ts.map +1 -0
- package/dist/lib/sdk-config-adapter.js +50 -0
- package/dist/lib/sdk-type-adapter.d.ts +43 -0
- package/dist/lib/sdk-type-adapter.d.ts.map +1 -0
- package/dist/lib/sdk-type-adapter.js +42 -0
- package/dist/types/config.d.ts +34 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +6 -0
- package/dist/utils/prompt.d.ts +8 -0
- package/dist/utils/prompt.d.ts.map +1 -0
- package/dist/utils/prompt.js +19 -0
- package/dist/utils/validation.d.ts +71 -0
- package/dist/utils/validation.d.ts.map +1 -1
- package/dist/utils/validation.js +157 -0
- package/package.json +2 -1
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Configuration Adapter
|
|
3
|
+
*
|
|
4
|
+
* Converts FABER CLI configuration to @fractary/core SDK configuration format
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Create WorkConfig for WorkManager from FaberConfig
|
|
8
|
+
*
|
|
9
|
+
* @param faberConfig - FABER CLI configuration
|
|
10
|
+
* @returns WorkConfig for @fractary/core WorkManager
|
|
11
|
+
* @throws Error if required fields are missing
|
|
12
|
+
*/
|
|
13
|
+
export function createWorkConfig(faberConfig) {
|
|
14
|
+
const token = faberConfig.github?.token;
|
|
15
|
+
const owner = faberConfig.github?.organization;
|
|
16
|
+
const repo = faberConfig.github?.project;
|
|
17
|
+
if (!token) {
|
|
18
|
+
throw new Error('GitHub token not found. Set GITHUB_TOKEN environment variable or configure in .fractary/settings.json');
|
|
19
|
+
}
|
|
20
|
+
if (!owner || !repo) {
|
|
21
|
+
throw new Error('GitHub organization and project must be configured in .fractary/settings.json');
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
platform: 'github',
|
|
25
|
+
owner,
|
|
26
|
+
repo,
|
|
27
|
+
token,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Create RepoConfig for RepoManager from FaberConfig
|
|
32
|
+
*
|
|
33
|
+
* @param faberConfig - FABER CLI configuration
|
|
34
|
+
* @returns RepoConfig for @fractary/core RepoManager
|
|
35
|
+
* @throws Error if required fields are missing
|
|
36
|
+
*/
|
|
37
|
+
export function createRepoConfig(faberConfig) {
|
|
38
|
+
const token = faberConfig.github?.token;
|
|
39
|
+
const owner = faberConfig.github?.organization;
|
|
40
|
+
const repo = faberConfig.github?.project;
|
|
41
|
+
if (!token) {
|
|
42
|
+
throw new Error('GitHub token not found. Set GITHUB_TOKEN environment variable or configure in .fractary/settings.json');
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
platform: 'github',
|
|
46
|
+
owner,
|
|
47
|
+
repo,
|
|
48
|
+
token,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Type Adapter
|
|
3
|
+
*
|
|
4
|
+
* Converts between @fractary/core SDK types and FABER CLI types
|
|
5
|
+
*/
|
|
6
|
+
import type { Issue as SDKIssue, Worktree as SDKWorktree } from '@fractary/core';
|
|
7
|
+
interface CLIIssue {
|
|
8
|
+
id: string;
|
|
9
|
+
number: number;
|
|
10
|
+
title: string;
|
|
11
|
+
description: string;
|
|
12
|
+
labels: string[];
|
|
13
|
+
url: string;
|
|
14
|
+
state: string;
|
|
15
|
+
}
|
|
16
|
+
interface WorktreeResult {
|
|
17
|
+
path: string;
|
|
18
|
+
absolute_path: string;
|
|
19
|
+
branch: string;
|
|
20
|
+
created_at: string;
|
|
21
|
+
organization: string;
|
|
22
|
+
project: string;
|
|
23
|
+
work_id: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Convert SDK Issue to CLI Issue
|
|
27
|
+
*
|
|
28
|
+
* @param sdkIssue - Issue from @fractary/core SDK
|
|
29
|
+
* @returns CLI Issue format
|
|
30
|
+
*/
|
|
31
|
+
export declare function sdkIssueToCLIIssue(sdkIssue: SDKIssue): CLIIssue;
|
|
32
|
+
/**
|
|
33
|
+
* Convert SDK Worktree to CLI WorktreeResult
|
|
34
|
+
*
|
|
35
|
+
* @param sdkWorktree - Worktree from @fractary/core SDK
|
|
36
|
+
* @param organization - GitHub organization
|
|
37
|
+
* @param project - GitHub project/repo name
|
|
38
|
+
* @param workId - Work/issue ID
|
|
39
|
+
* @returns CLI WorktreeResult format
|
|
40
|
+
*/
|
|
41
|
+
export declare function sdkWorktreeToCLIWorktreeResult(sdkWorktree: SDKWorktree, organization: string, project: string, workId: string): WorktreeResult;
|
|
42
|
+
export {};
|
|
43
|
+
//# sourceMappingURL=sdk-type-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sdk-type-adapter.d.ts","sourceRoot":"","sources":["../../src/lib/sdk-type-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,KAAK,IAAI,QAAQ,EAAE,QAAQ,IAAI,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAGjF,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,QAAQ,CAU/D;AAED;;;;;;;;GAQG;AACH,wBAAgB,8BAA8B,CAC5C,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,GACb,cAAc,CAUhB"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK Type Adapter
|
|
3
|
+
*
|
|
4
|
+
* Converts between @fractary/core SDK types and FABER CLI types
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Convert SDK Issue to CLI Issue
|
|
8
|
+
*
|
|
9
|
+
* @param sdkIssue - Issue from @fractary/core SDK
|
|
10
|
+
* @returns CLI Issue format
|
|
11
|
+
*/
|
|
12
|
+
export function sdkIssueToCLIIssue(sdkIssue) {
|
|
13
|
+
return {
|
|
14
|
+
id: sdkIssue.id,
|
|
15
|
+
number: sdkIssue.number,
|
|
16
|
+
title: sdkIssue.title,
|
|
17
|
+
description: sdkIssue.body, // Map 'body' to 'description'
|
|
18
|
+
labels: sdkIssue.labels.map(label => label.name), // Extract label names
|
|
19
|
+
url: sdkIssue.url,
|
|
20
|
+
state: sdkIssue.state,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Convert SDK Worktree to CLI WorktreeResult
|
|
25
|
+
*
|
|
26
|
+
* @param sdkWorktree - Worktree from @fractary/core SDK
|
|
27
|
+
* @param organization - GitHub organization
|
|
28
|
+
* @param project - GitHub project/repo name
|
|
29
|
+
* @param workId - Work/issue ID
|
|
30
|
+
* @returns CLI WorktreeResult format
|
|
31
|
+
*/
|
|
32
|
+
export function sdkWorktreeToCLIWorktreeResult(sdkWorktree, organization, project, workId) {
|
|
33
|
+
return {
|
|
34
|
+
path: sdkWorktree.path,
|
|
35
|
+
absolute_path: sdkWorktree.path, // SDK path is already absolute
|
|
36
|
+
branch: sdkWorktree.branch || '',
|
|
37
|
+
created_at: new Date().toISOString(),
|
|
38
|
+
organization,
|
|
39
|
+
project,
|
|
40
|
+
work_id: workId,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FABER CLI Configuration Types
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for configuration objects
|
|
5
|
+
*/
|
|
6
|
+
export interface AnthropicConfig {
|
|
7
|
+
api_key?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface GitHubConfig {
|
|
10
|
+
token?: string;
|
|
11
|
+
organization?: string;
|
|
12
|
+
project?: string;
|
|
13
|
+
repo?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WorktreeConfig {
|
|
16
|
+
location?: string;
|
|
17
|
+
inherit_from_claude?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface WorkflowConfig {
|
|
20
|
+
default?: string;
|
|
21
|
+
config_path?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface FaberConfig {
|
|
24
|
+
anthropic?: AnthropicConfig;
|
|
25
|
+
github?: GitHubConfig;
|
|
26
|
+
worktree?: WorktreeConfig;
|
|
27
|
+
workflow?: WorkflowConfig;
|
|
28
|
+
}
|
|
29
|
+
export interface ClaudeConfig {
|
|
30
|
+
worktree?: {
|
|
31
|
+
directory?: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/types/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE;QACT,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/utils/prompt.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH;;GAEG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAYxD"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple prompt utility for CLI input
|
|
3
|
+
*/
|
|
4
|
+
import * as readline from 'readline';
|
|
5
|
+
/**
|
|
6
|
+
* Prompt user for input
|
|
7
|
+
*/
|
|
8
|
+
export function prompt(question) {
|
|
9
|
+
const rl = readline.createInterface({
|
|
10
|
+
input: process.stdin,
|
|
11
|
+
output: process.stdout,
|
|
12
|
+
});
|
|
13
|
+
return new Promise((resolve) => {
|
|
14
|
+
rl.question(question, (answer) => {
|
|
15
|
+
rl.close();
|
|
16
|
+
resolve(answer.trim());
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -27,4 +27,75 @@ export declare function parseOptionalInteger(value: string | undefined, fieldNam
|
|
|
27
27
|
* @throws Error if value is not a valid positive integer
|
|
28
28
|
*/
|
|
29
29
|
export declare function parsePositiveInteger(value: string, fieldName: string): number;
|
|
30
|
+
/**
|
|
31
|
+
* Validates work ID format
|
|
32
|
+
* Work IDs must be numeric (GitHub issue numbers)
|
|
33
|
+
*
|
|
34
|
+
* @param workId - Work ID to validate
|
|
35
|
+
* @returns True if valid
|
|
36
|
+
* @throws Error if invalid
|
|
37
|
+
*/
|
|
38
|
+
export declare function validateWorkId(workId: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Validates multiple comma-separated work IDs
|
|
41
|
+
*
|
|
42
|
+
* @param workIds - Comma-separated work IDs
|
|
43
|
+
* @returns Array of validated work IDs
|
|
44
|
+
* @throws Error if any ID is invalid
|
|
45
|
+
*/
|
|
46
|
+
export declare function validateWorkIds(workIds: string): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Validates label format
|
|
49
|
+
* Labels must be alphanumeric with hyphens, colons, and underscores only
|
|
50
|
+
*
|
|
51
|
+
* @param label - Label to validate
|
|
52
|
+
* @returns True if valid
|
|
53
|
+
* @throws Error if invalid
|
|
54
|
+
*/
|
|
55
|
+
export declare function validateLabel(label: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Validates multiple comma-separated labels
|
|
58
|
+
*
|
|
59
|
+
* @param labels - Comma-separated labels
|
|
60
|
+
* @returns Array of validated labels
|
|
61
|
+
* @throws Error if any label is invalid
|
|
62
|
+
*/
|
|
63
|
+
export declare function validateLabels(labels: string): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Validates workflow name format
|
|
66
|
+
* Workflow names must be alphanumeric with hyphens and underscores
|
|
67
|
+
*
|
|
68
|
+
* @param workflowName - Workflow name to validate
|
|
69
|
+
* @returns True if valid
|
|
70
|
+
* @throws Error if invalid
|
|
71
|
+
*/
|
|
72
|
+
export declare function validateWorkflowName(workflowName: string): boolean;
|
|
73
|
+
/**
|
|
74
|
+
* Validates and sanitizes file path to prevent path traversal
|
|
75
|
+
* Ensures path doesn't contain dangerous patterns like ../, absolute paths, etc.
|
|
76
|
+
*
|
|
77
|
+
* @param filePath - File path to validate
|
|
78
|
+
* @param baseDir - Base directory that path must be relative to (optional)
|
|
79
|
+
* @returns Sanitized path
|
|
80
|
+
* @throws Error if path is unsafe
|
|
81
|
+
*/
|
|
82
|
+
export declare function validateSafePath(filePath: string, baseDir?: string): string;
|
|
83
|
+
/**
|
|
84
|
+
* Validates JSON response size to prevent DoS attacks
|
|
85
|
+
*
|
|
86
|
+
* @param jsonString - JSON string to validate
|
|
87
|
+
* @param maxSizeBytes - Maximum allowed size in bytes (default: 1MB)
|
|
88
|
+
* @returns True if valid
|
|
89
|
+
* @throws Error if too large
|
|
90
|
+
*/
|
|
91
|
+
export declare function validateJsonSize(jsonString: string, maxSizeBytes?: number): boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Validates plan ID format
|
|
94
|
+
* Plan IDs follow format: fractary-faber-{work-id}-{YYYYMMDD}-{HHMMSS}
|
|
95
|
+
*
|
|
96
|
+
* @param planId - Plan ID to validate
|
|
97
|
+
* @returns True if valid
|
|
98
|
+
* @throws Error if invalid
|
|
99
|
+
*/
|
|
100
|
+
export declare function validatePlanId(planId: string): boolean;
|
|
30
101
|
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -1 +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"}
|
|
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;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAWtD;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBzD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAYpD;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBvD;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAYlE;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,CAoC3E;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,GAAE,MAAoB,GAAG,OAAO,CAYhG;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAUtD"}
|
package/dist/utils/validation.js
CHANGED
|
@@ -47,3 +47,160 @@ export function parsePositiveInteger(value, fieldName) {
|
|
|
47
47
|
}
|
|
48
48
|
return parsed;
|
|
49
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Validates work ID format
|
|
52
|
+
* Work IDs must be numeric (GitHub issue numbers)
|
|
53
|
+
*
|
|
54
|
+
* @param workId - Work ID to validate
|
|
55
|
+
* @returns True if valid
|
|
56
|
+
* @throws Error if invalid
|
|
57
|
+
*/
|
|
58
|
+
export function validateWorkId(workId) {
|
|
59
|
+
// Work IDs must be numeric (1-8 digits for GitHub issue numbers)
|
|
60
|
+
const workIdPattern = /^\d{1,8}$/;
|
|
61
|
+
if (!workIdPattern.test(workId)) {
|
|
62
|
+
throw new Error(`Invalid work ID format: "${workId}". Work IDs must be numeric (e.g., "123", "456").`);
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Validates multiple comma-separated work IDs
|
|
68
|
+
*
|
|
69
|
+
* @param workIds - Comma-separated work IDs
|
|
70
|
+
* @returns Array of validated work IDs
|
|
71
|
+
* @throws Error if any ID is invalid
|
|
72
|
+
*/
|
|
73
|
+
export function validateWorkIds(workIds) {
|
|
74
|
+
const ids = workIds.split(',').map(id => id.trim()).filter(Boolean);
|
|
75
|
+
if (ids.length === 0) {
|
|
76
|
+
throw new Error('No work IDs provided');
|
|
77
|
+
}
|
|
78
|
+
if (ids.length > 50) {
|
|
79
|
+
throw new Error(`Too many work IDs (${ids.length}). Maximum is 50 to prevent API overload.`);
|
|
80
|
+
}
|
|
81
|
+
// Validate each ID
|
|
82
|
+
ids.forEach(id => validateWorkId(id));
|
|
83
|
+
return ids;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validates label format
|
|
87
|
+
* Labels must be alphanumeric with hyphens, colons, and underscores only
|
|
88
|
+
*
|
|
89
|
+
* @param label - Label to validate
|
|
90
|
+
* @returns True if valid
|
|
91
|
+
* @throws Error if invalid
|
|
92
|
+
*/
|
|
93
|
+
export function validateLabel(label) {
|
|
94
|
+
// Labels: alphanumeric, hyphens, colons, underscores (GitHub label format)
|
|
95
|
+
// Examples: "workflow:etl", "status:approved", "priority-high"
|
|
96
|
+
const labelPattern = /^[a-zA-Z0-9_:-]{1,50}$/;
|
|
97
|
+
if (!labelPattern.test(label)) {
|
|
98
|
+
throw new Error(`Invalid label format: "${label}". Labels must be alphanumeric with hyphens, colons, or underscores (max 50 chars).`);
|
|
99
|
+
}
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Validates multiple comma-separated labels
|
|
104
|
+
*
|
|
105
|
+
* @param labels - Comma-separated labels
|
|
106
|
+
* @returns Array of validated labels
|
|
107
|
+
* @throws Error if any label is invalid
|
|
108
|
+
*/
|
|
109
|
+
export function validateLabels(labels) {
|
|
110
|
+
const labelArray = labels.split(',').map(l => l.trim()).filter(Boolean);
|
|
111
|
+
if (labelArray.length === 0) {
|
|
112
|
+
throw new Error('No labels provided');
|
|
113
|
+
}
|
|
114
|
+
if (labelArray.length > 20) {
|
|
115
|
+
throw new Error(`Too many labels (${labelArray.length}). Maximum is 20.`);
|
|
116
|
+
}
|
|
117
|
+
// Validate each label
|
|
118
|
+
labelArray.forEach(label => validateLabel(label));
|
|
119
|
+
return labelArray;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Validates workflow name format
|
|
123
|
+
* Workflow names must be alphanumeric with hyphens and underscores
|
|
124
|
+
*
|
|
125
|
+
* @param workflowName - Workflow name to validate
|
|
126
|
+
* @returns True if valid
|
|
127
|
+
* @throws Error if invalid
|
|
128
|
+
*/
|
|
129
|
+
export function validateWorkflowName(workflowName) {
|
|
130
|
+
// Workflow names: alphanumeric, hyphens, underscores
|
|
131
|
+
// Examples: "etl", "data-pipeline", "bugfix"
|
|
132
|
+
const workflowPattern = /^[a-zA-Z0-9_-]{1,50}$/;
|
|
133
|
+
if (!workflowPattern.test(workflowName)) {
|
|
134
|
+
throw new Error(`Invalid workflow name: "${workflowName}". Workflow names must be alphanumeric with hyphens or underscores (max 50 chars).`);
|
|
135
|
+
}
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Validates and sanitizes file path to prevent path traversal
|
|
140
|
+
* Ensures path doesn't contain dangerous patterns like ../, absolute paths, etc.
|
|
141
|
+
*
|
|
142
|
+
* @param filePath - File path to validate
|
|
143
|
+
* @param baseDir - Base directory that path must be relative to (optional)
|
|
144
|
+
* @returns Sanitized path
|
|
145
|
+
* @throws Error if path is unsafe
|
|
146
|
+
*/
|
|
147
|
+
export function validateSafePath(filePath, baseDir) {
|
|
148
|
+
// Remove any null bytes
|
|
149
|
+
if (filePath.includes('\0')) {
|
|
150
|
+
throw new Error('Path contains null bytes');
|
|
151
|
+
}
|
|
152
|
+
// Check for path traversal patterns
|
|
153
|
+
const dangerousPatterns = [
|
|
154
|
+
/\.\./, // Parent directory (..)
|
|
155
|
+
/^\//, // Absolute path
|
|
156
|
+
/^[a-zA-Z]:\\/, // Windows absolute path
|
|
157
|
+
/^~\//, // Home directory
|
|
158
|
+
];
|
|
159
|
+
for (const pattern of dangerousPatterns) {
|
|
160
|
+
if (pattern.test(filePath)) {
|
|
161
|
+
throw new Error(`Unsafe path detected: "${filePath}". Paths must be relative and cannot contain "..", absolute paths, or home directory references.`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Normalize path (remove redundant separators, etc.)
|
|
165
|
+
const normalized = filePath.replace(/\/+/g, '/').replace(/\\/g, '/');
|
|
166
|
+
// If base directory provided, ensure path starts with it
|
|
167
|
+
if (baseDir) {
|
|
168
|
+
const normalizedBase = baseDir.replace(/\/+$/, '');
|
|
169
|
+
if (!normalized.startsWith(normalizedBase)) {
|
|
170
|
+
throw new Error(`Path "${filePath}" must be within base directory "${baseDir}"`);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return normalized;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Validates JSON response size to prevent DoS attacks
|
|
177
|
+
*
|
|
178
|
+
* @param jsonString - JSON string to validate
|
|
179
|
+
* @param maxSizeBytes - Maximum allowed size in bytes (default: 1MB)
|
|
180
|
+
* @returns True if valid
|
|
181
|
+
* @throws Error if too large
|
|
182
|
+
*/
|
|
183
|
+
export function validateJsonSize(jsonString, maxSizeBytes = 1024 * 1024) {
|
|
184
|
+
const sizeBytes = Buffer.byteLength(jsonString, 'utf8');
|
|
185
|
+
if (sizeBytes > maxSizeBytes) {
|
|
186
|
+
const sizeMB = (sizeBytes / 1024 / 1024).toFixed(2);
|
|
187
|
+
const maxMB = (maxSizeBytes / 1024 / 1024).toFixed(2);
|
|
188
|
+
throw new Error(`JSON response too large: ${sizeMB}MB (maximum: ${maxMB}MB). This may indicate an API error or DoS attempt.`);
|
|
189
|
+
}
|
|
190
|
+
return true;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Validates plan ID format
|
|
194
|
+
* Plan IDs follow format: fractary-faber-{work-id}-{YYYYMMDD}-{HHMMSS}
|
|
195
|
+
*
|
|
196
|
+
* @param planId - Plan ID to validate
|
|
197
|
+
* @returns True if valid
|
|
198
|
+
* @throws Error if invalid
|
|
199
|
+
*/
|
|
200
|
+
export function validatePlanId(planId) {
|
|
201
|
+
const planIdPattern = /^fractary-faber-\d+-\d{8}-\d{6}$/;
|
|
202
|
+
if (!planIdPattern.test(planId)) {
|
|
203
|
+
throw new Error(`Invalid plan ID format: "${planId}". Expected format: fractary-faber-{work-id}-{YYYYMMDD}-{HHMMSS}`);
|
|
204
|
+
}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fractary/faber-cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "FABER CLI - Command-line interface for FABER development toolkit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"access": "public"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
+
"@fractary/core": "^0.2.0",
|
|
39
40
|
"@fractary/faber": "*",
|
|
40
41
|
"chalk": "^5.0.0",
|
|
41
42
|
"commander": "^12.0.0"
|