codeep 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 +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface VersionInfo {
|
|
2
|
+
current: string;
|
|
3
|
+
latest: string | null;
|
|
4
|
+
hasUpdate: boolean;
|
|
5
|
+
error?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Get current version from package.json
|
|
9
|
+
*/
|
|
10
|
+
export declare function getCurrentVersion(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Check for updates from npm registry
|
|
13
|
+
*/
|
|
14
|
+
export declare function checkForUpdates(): Promise<VersionInfo>;
|
|
15
|
+
/**
|
|
16
|
+
* Get update instructions based on installation method
|
|
17
|
+
*/
|
|
18
|
+
export declare function getUpdateInstructions(): string;
|
|
19
|
+
/**
|
|
20
|
+
* Format version info for display
|
|
21
|
+
*/
|
|
22
|
+
export declare function formatVersionInfo(info: VersionInfo): string;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = dirname(__filename);
|
|
6
|
+
/**
|
|
7
|
+
* Get current version from package.json
|
|
8
|
+
*/
|
|
9
|
+
export function getCurrentVersion() {
|
|
10
|
+
try {
|
|
11
|
+
// In built version, package.json is in parent directory
|
|
12
|
+
const packagePath = join(__dirname, '../../package.json');
|
|
13
|
+
const packageJson = JSON.parse(readFileSync(packagePath, 'utf-8'));
|
|
14
|
+
return packageJson.version;
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return 'unknown';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Check for updates from npm registry
|
|
22
|
+
*/
|
|
23
|
+
export async function checkForUpdates() {
|
|
24
|
+
const current = getCurrentVersion();
|
|
25
|
+
if (current === 'unknown') {
|
|
26
|
+
return {
|
|
27
|
+
current,
|
|
28
|
+
latest: null,
|
|
29
|
+
hasUpdate: false,
|
|
30
|
+
error: 'Could not determine current version',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const response = await fetch('https://registry.npmjs.org/codeep/latest', {
|
|
35
|
+
headers: { 'Accept': 'application/json' },
|
|
36
|
+
});
|
|
37
|
+
if (!response.ok) {
|
|
38
|
+
throw new Error(`HTTP ${response.status}`);
|
|
39
|
+
}
|
|
40
|
+
const data = await response.json();
|
|
41
|
+
const latest = data.version;
|
|
42
|
+
const hasUpdate = compareVersions(latest, current) > 0;
|
|
43
|
+
return {
|
|
44
|
+
current,
|
|
45
|
+
latest,
|
|
46
|
+
hasUpdate,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
return {
|
|
51
|
+
current,
|
|
52
|
+
latest: null,
|
|
53
|
+
hasUpdate: false,
|
|
54
|
+
error: error instanceof Error ? error.message : 'Failed to check for updates',
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compare two semver versions
|
|
60
|
+
* Returns: 1 if a > b, -1 if a < b, 0 if equal
|
|
61
|
+
*/
|
|
62
|
+
function compareVersions(a, b) {
|
|
63
|
+
const aParts = a.replace(/^v/, '').split('.').map(Number);
|
|
64
|
+
const bParts = b.replace(/^v/, '').split('.').map(Number);
|
|
65
|
+
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
|
|
66
|
+
const aPart = aParts[i] || 0;
|
|
67
|
+
const bPart = bParts[i] || 0;
|
|
68
|
+
if (aPart > bPart)
|
|
69
|
+
return 1;
|
|
70
|
+
if (aPart < bPart)
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Detect installation method
|
|
77
|
+
*/
|
|
78
|
+
function detectInstallMethod() {
|
|
79
|
+
const execPath = process.execPath;
|
|
80
|
+
const argv0 = process.argv[0];
|
|
81
|
+
// Homebrew detection - installed in Cellar or with homebrew in path
|
|
82
|
+
if (execPath.includes('/Cellar/codeep') ||
|
|
83
|
+
execPath.includes('homebrew') ||
|
|
84
|
+
execPath.includes('/opt/homebrew')) {
|
|
85
|
+
return 'homebrew';
|
|
86
|
+
}
|
|
87
|
+
// npm global detection - running via node with npm in path
|
|
88
|
+
if (process.env.npm_package_name === 'codeep' ||
|
|
89
|
+
execPath.includes('/.npm/') ||
|
|
90
|
+
execPath.includes('/npm/') ||
|
|
91
|
+
argv0.includes('node')) {
|
|
92
|
+
return 'npm';
|
|
93
|
+
}
|
|
94
|
+
// Binary installation (curl install or manual download)
|
|
95
|
+
return 'binary';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Get update instructions based on installation method
|
|
99
|
+
*/
|
|
100
|
+
export function getUpdateInstructions() {
|
|
101
|
+
const method = detectInstallMethod();
|
|
102
|
+
switch (method) {
|
|
103
|
+
case 'homebrew':
|
|
104
|
+
return 'brew update && brew upgrade codeep';
|
|
105
|
+
case 'npm':
|
|
106
|
+
return 'npm update -g codeep';
|
|
107
|
+
case 'binary':
|
|
108
|
+
return 'curl -fsSL https://raw.githubusercontent.com/VladoIvankovic/Codeep/main/install.sh | bash';
|
|
109
|
+
default:
|
|
110
|
+
return 'Visit: https://codeep.dev';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format version info for display
|
|
115
|
+
*/
|
|
116
|
+
export function formatVersionInfo(info) {
|
|
117
|
+
if (info.error) {
|
|
118
|
+
return `Current: ${info.current}\nUpdate check failed: ${info.error}`;
|
|
119
|
+
}
|
|
120
|
+
if (!info.latest) {
|
|
121
|
+
return `Current: ${info.current}\nCould not check for updates`;
|
|
122
|
+
}
|
|
123
|
+
if (info.hasUpdate) {
|
|
124
|
+
const instructions = getUpdateInstructions();
|
|
125
|
+
return `Current: ${info.current}\nLatest: ${info.latest}\n\nUpdate available! Run:\n ${instructions}`;
|
|
126
|
+
}
|
|
127
|
+
return `Current: ${info.current}\nYou're running the latest version! 🎉`;
|
|
128
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and sanitization utilities
|
|
3
|
+
*/
|
|
4
|
+
export interface ValidationResult {
|
|
5
|
+
valid: boolean;
|
|
6
|
+
sanitized?: string;
|
|
7
|
+
error?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Validate and sanitize user input before sending to API
|
|
11
|
+
*/
|
|
12
|
+
export declare function validateInput(input: string): ValidationResult;
|
|
13
|
+
/**
|
|
14
|
+
* Validate API key format
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateApiKey(key: string): ValidationResult;
|
|
17
|
+
/**
|
|
18
|
+
* Validate command arguments
|
|
19
|
+
*/
|
|
20
|
+
export declare function validateCommandArgs(command: string, args: string[]): ValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Validate file path (for file operations)
|
|
23
|
+
*/
|
|
24
|
+
export declare function validateFilePath(path: string): ValidationResult;
|
|
25
|
+
/**
|
|
26
|
+
* Sanitize output before display (prevent terminal escape sequence injection)
|
|
27
|
+
*/
|
|
28
|
+
export declare function sanitizeOutput(output: string): string;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input validation and sanitization utilities
|
|
3
|
+
*/
|
|
4
|
+
const MAX_INPUT_LENGTH = 50000; // ~50KB max input
|
|
5
|
+
const MAX_LINES = 5000;
|
|
6
|
+
/**
|
|
7
|
+
* Validate and sanitize user input before sending to API
|
|
8
|
+
*/
|
|
9
|
+
export function validateInput(input) {
|
|
10
|
+
// Check for empty input
|
|
11
|
+
if (!input || input.trim().length === 0) {
|
|
12
|
+
return {
|
|
13
|
+
valid: false,
|
|
14
|
+
error: 'Input cannot be empty',
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// Check length
|
|
18
|
+
if (input.length > MAX_INPUT_LENGTH) {
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
error: `Input too long (max ${MAX_INPUT_LENGTH} characters)`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Check line count
|
|
25
|
+
const lines = input.split('\n');
|
|
26
|
+
if (lines.length > MAX_LINES) {
|
|
27
|
+
return {
|
|
28
|
+
valid: false,
|
|
29
|
+
error: `Too many lines (max ${MAX_LINES} lines)`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
// Sanitize: remove null bytes and other control characters (except newlines, tabs)
|
|
33
|
+
let sanitized = input
|
|
34
|
+
.replace(/\0/g, '') // Remove null bytes
|
|
35
|
+
.replace(/[\x01-\x08\x0B-\x0C\x0E-\x1F\x7F]/g, ''); // Remove other control chars
|
|
36
|
+
// Trim excessive whitespace (but preserve intentional formatting)
|
|
37
|
+
sanitized = sanitized.replace(/\n{5,}/g, '\n\n\n\n'); // Max 4 consecutive newlines
|
|
38
|
+
return {
|
|
39
|
+
valid: true,
|
|
40
|
+
sanitized,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Validate API key format
|
|
45
|
+
*/
|
|
46
|
+
export function validateApiKey(key) {
|
|
47
|
+
if (!key || key.trim().length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
valid: false,
|
|
50
|
+
error: 'API key cannot be empty',
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
// Basic format check - most API keys are alphanumeric with some special chars
|
|
54
|
+
const keyPattern = /^[a-zA-Z0-9._-]+$/;
|
|
55
|
+
if (!keyPattern.test(key)) {
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
error: 'API key contains invalid characters',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// Length check - most API keys are 20-100 characters
|
|
62
|
+
if (key.length < 10 || key.length > 200) {
|
|
63
|
+
return {
|
|
64
|
+
valid: false,
|
|
65
|
+
error: 'API key length invalid (expected 10-200 characters)',
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
valid: true,
|
|
70
|
+
sanitized: key.trim(),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validate command arguments
|
|
75
|
+
*/
|
|
76
|
+
export function validateCommandArgs(command, args) {
|
|
77
|
+
// Check for command injection attempts
|
|
78
|
+
const dangerousPatterns = [
|
|
79
|
+
/[;&|`$()]/, // Shell metacharacters
|
|
80
|
+
/\.\./, // Path traversal
|
|
81
|
+
/\beval\b/i, // eval() attempts
|
|
82
|
+
/\bexec\b/i, // exec() attempts
|
|
83
|
+
];
|
|
84
|
+
const fullCommand = [command, ...args].join(' ');
|
|
85
|
+
for (const pattern of dangerousPatterns) {
|
|
86
|
+
if (pattern.test(fullCommand)) {
|
|
87
|
+
return {
|
|
88
|
+
valid: false,
|
|
89
|
+
error: 'Command contains potentially dangerous characters',
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
valid: true,
|
|
95
|
+
sanitized: fullCommand,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate file path (for file operations)
|
|
100
|
+
*/
|
|
101
|
+
export function validateFilePath(path) {
|
|
102
|
+
if (!path || path.trim().length === 0) {
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
error: 'File path cannot be empty',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// Check for path traversal
|
|
109
|
+
if (path.includes('..')) {
|
|
110
|
+
return {
|
|
111
|
+
valid: false,
|
|
112
|
+
error: 'Path traversal not allowed',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Check for absolute paths outside project (basic check)
|
|
116
|
+
if (path.startsWith('/etc/') ||
|
|
117
|
+
path.startsWith('/sys/') ||
|
|
118
|
+
path.startsWith('/proc/') ||
|
|
119
|
+
path.startsWith('C:\\Windows\\') ||
|
|
120
|
+
path.startsWith('C:\\System')) {
|
|
121
|
+
return {
|
|
122
|
+
valid: false,
|
|
123
|
+
error: 'Access to system paths not allowed',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
valid: true,
|
|
128
|
+
sanitized: path.trim(),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Sanitize output before display (prevent terminal escape sequence injection)
|
|
133
|
+
*/
|
|
134
|
+
export function sanitizeOutput(output) {
|
|
135
|
+
// Remove ANSI escape sequences that could be malicious
|
|
136
|
+
// Keep basic formatting codes but remove cursor movement, clear screen, etc.
|
|
137
|
+
return output
|
|
138
|
+
.replace(/\x1b\[(\d+;)*\d*[ABCDEFGHJKSTfmsu]/g, '') // Remove cursor control
|
|
139
|
+
.replace(/\x1b\].*?\x07/g, '') // Remove OSC sequences
|
|
140
|
+
.replace(/\x1b\[.*?~/g, ''); // Remove other escape sequences
|
|
141
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { validateInput, validateApiKey, validateCommandArgs, validateFilePath, sanitizeOutput, } from './validation';
|
|
3
|
+
describe('validation utilities', () => {
|
|
4
|
+
describe('validateInput', () => {
|
|
5
|
+
it('should reject empty input', () => {
|
|
6
|
+
expect(validateInput('').valid).toBe(false);
|
|
7
|
+
expect(validateInput(' ').valid).toBe(false);
|
|
8
|
+
});
|
|
9
|
+
it('should accept valid input', () => {
|
|
10
|
+
const result = validateInput('Hello, world!');
|
|
11
|
+
expect(result.valid).toBe(true);
|
|
12
|
+
expect(result.sanitized).toBe('Hello, world!');
|
|
13
|
+
});
|
|
14
|
+
it('should reject input exceeding max length', () => {
|
|
15
|
+
const longInput = 'a'.repeat(60000);
|
|
16
|
+
const result = validateInput(longInput);
|
|
17
|
+
expect(result.valid).toBe(false);
|
|
18
|
+
expect(result.error).toContain('too long');
|
|
19
|
+
});
|
|
20
|
+
it('should reject input with too many lines', () => {
|
|
21
|
+
const manyLines = Array(6000).fill('line').join('\n');
|
|
22
|
+
const result = validateInput(manyLines);
|
|
23
|
+
expect(result.valid).toBe(false);
|
|
24
|
+
expect(result.error).toContain('Too many lines');
|
|
25
|
+
});
|
|
26
|
+
it('should remove null bytes', () => {
|
|
27
|
+
const result = validateInput('hello\0world');
|
|
28
|
+
expect(result.valid).toBe(true);
|
|
29
|
+
expect(result.sanitized).toBe('helloworld');
|
|
30
|
+
});
|
|
31
|
+
it('should remove control characters except newlines and tabs', () => {
|
|
32
|
+
const result = validateInput('hello\x01\x02world\n\ttab');
|
|
33
|
+
expect(result.valid).toBe(true);
|
|
34
|
+
expect(result.sanitized).toBe('helloworld\n\ttab');
|
|
35
|
+
});
|
|
36
|
+
it('should limit consecutive newlines to 4', () => {
|
|
37
|
+
const result = validateInput('hello\n\n\n\n\n\n\nworld');
|
|
38
|
+
expect(result.valid).toBe(true);
|
|
39
|
+
expect(result.sanitized).toBe('hello\n\n\n\nworld');
|
|
40
|
+
});
|
|
41
|
+
it('should preserve normal formatting', () => {
|
|
42
|
+
const input = 'function test() {\n return true;\n}';
|
|
43
|
+
const result = validateInput(input);
|
|
44
|
+
expect(result.valid).toBe(true);
|
|
45
|
+
expect(result.sanitized).toBe(input);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
describe('validateApiKey', () => {
|
|
49
|
+
it('should reject empty key', () => {
|
|
50
|
+
expect(validateApiKey('').valid).toBe(false);
|
|
51
|
+
expect(validateApiKey(' ').valid).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
it('should accept valid API key', () => {
|
|
54
|
+
const result = validateApiKey('sk-abcdef123456789012345');
|
|
55
|
+
expect(result.valid).toBe(true);
|
|
56
|
+
expect(result.sanitized).toBe('sk-abcdef123456789012345');
|
|
57
|
+
});
|
|
58
|
+
it('should reject key with invalid characters', () => {
|
|
59
|
+
const result = validateApiKey('sk-test!@#$%');
|
|
60
|
+
expect(result.valid).toBe(false);
|
|
61
|
+
expect(result.error).toContain('invalid characters');
|
|
62
|
+
});
|
|
63
|
+
it('should reject too short keys', () => {
|
|
64
|
+
const result = validateApiKey('short');
|
|
65
|
+
expect(result.valid).toBe(false);
|
|
66
|
+
expect(result.error).toContain('length invalid');
|
|
67
|
+
});
|
|
68
|
+
it('should reject too long keys', () => {
|
|
69
|
+
const result = validateApiKey('a'.repeat(250));
|
|
70
|
+
expect(result.valid).toBe(false);
|
|
71
|
+
expect(result.error).toContain('length invalid');
|
|
72
|
+
});
|
|
73
|
+
it('should reject keys with whitespace', () => {
|
|
74
|
+
// API keys with leading/trailing spaces contain invalid characters
|
|
75
|
+
const result = validateApiKey(' sk-validkey12345678 ');
|
|
76
|
+
expect(result.valid).toBe(false);
|
|
77
|
+
});
|
|
78
|
+
it('should accept keys with dots, underscores, and dashes', () => {
|
|
79
|
+
const result = validateApiKey('sk_test.key-123456789');
|
|
80
|
+
expect(result.valid).toBe(true);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
describe('validateCommandArgs', () => {
|
|
84
|
+
it('should accept safe commands', () => {
|
|
85
|
+
const result = validateCommandArgs('help', []);
|
|
86
|
+
expect(result.valid).toBe(true);
|
|
87
|
+
});
|
|
88
|
+
it('should reject shell metacharacters', () => {
|
|
89
|
+
expect(validateCommandArgs('test', ['; rm -rf /']).valid).toBe(false);
|
|
90
|
+
expect(validateCommandArgs('test', ['| cat /etc/passwd']).valid).toBe(false);
|
|
91
|
+
expect(validateCommandArgs('test', ['`whoami`']).valid).toBe(false);
|
|
92
|
+
expect(validateCommandArgs('test', ['$(id)']).valid).toBe(false);
|
|
93
|
+
expect(validateCommandArgs('test', ['&& echo']).valid).toBe(false);
|
|
94
|
+
});
|
|
95
|
+
it('should reject path traversal', () => {
|
|
96
|
+
const result = validateCommandArgs('read', ['../../etc/passwd']);
|
|
97
|
+
expect(result.valid).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
it('should reject eval attempts', () => {
|
|
100
|
+
const result = validateCommandArgs('run', ['eval("code")', 'test']);
|
|
101
|
+
expect(result.valid).toBe(false);
|
|
102
|
+
});
|
|
103
|
+
it('should reject exec attempts', () => {
|
|
104
|
+
const result = validateCommandArgs('run', ['exec(cmd)']);
|
|
105
|
+
expect(result.valid).toBe(false);
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('validateFilePath', () => {
|
|
109
|
+
it('should reject empty path', () => {
|
|
110
|
+
expect(validateFilePath('').valid).toBe(false);
|
|
111
|
+
expect(validateFilePath(' ').valid).toBe(false);
|
|
112
|
+
});
|
|
113
|
+
it('should accept valid paths', () => {
|
|
114
|
+
expect(validateFilePath('src/index.ts').valid).toBe(true);
|
|
115
|
+
expect(validateFilePath('./package.json').valid).toBe(true);
|
|
116
|
+
expect(validateFilePath('utils/helper.js').valid).toBe(true);
|
|
117
|
+
});
|
|
118
|
+
it('should reject path traversal', () => {
|
|
119
|
+
expect(validateFilePath('../secret.txt').valid).toBe(false);
|
|
120
|
+
expect(validateFilePath('src/../../etc/passwd').valid).toBe(false);
|
|
121
|
+
expect(validateFilePath('..').valid).toBe(false);
|
|
122
|
+
});
|
|
123
|
+
it('should reject system paths on Unix', () => {
|
|
124
|
+
expect(validateFilePath('/etc/passwd').valid).toBe(false);
|
|
125
|
+
expect(validateFilePath('/sys/kernel').valid).toBe(false);
|
|
126
|
+
expect(validateFilePath('/proc/self').valid).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it('should reject system paths on Windows', () => {
|
|
129
|
+
expect(validateFilePath('C:\\Windows\\System32').valid).toBe(false);
|
|
130
|
+
expect(validateFilePath('C:\\System\\config').valid).toBe(false);
|
|
131
|
+
});
|
|
132
|
+
it('should trim whitespace', () => {
|
|
133
|
+
const result = validateFilePath(' src/index.ts ');
|
|
134
|
+
expect(result.valid).toBe(true);
|
|
135
|
+
expect(result.sanitized).toBe('src/index.ts');
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
describe('sanitizeOutput', () => {
|
|
139
|
+
it('should preserve normal text', () => {
|
|
140
|
+
expect(sanitizeOutput('Hello, world!')).toBe('Hello, world!');
|
|
141
|
+
});
|
|
142
|
+
it('should remove cursor control sequences', () => {
|
|
143
|
+
// Move cursor up
|
|
144
|
+
expect(sanitizeOutput('text\x1b[2Amore')).toBe('textmore');
|
|
145
|
+
// Move cursor down
|
|
146
|
+
expect(sanitizeOutput('text\x1b[5Bmore')).toBe('textmore');
|
|
147
|
+
// Clear screen
|
|
148
|
+
expect(sanitizeOutput('text\x1b[2Jmore')).toBe('textmore');
|
|
149
|
+
});
|
|
150
|
+
it('should remove OSC sequences', () => {
|
|
151
|
+
// Set window title
|
|
152
|
+
expect(sanitizeOutput('text\x1b]0;malicious title\x07more')).toBe('textmore');
|
|
153
|
+
});
|
|
154
|
+
it('should handle multiple escape sequences', () => {
|
|
155
|
+
const malicious = '\x1b[2J\x1b[H\x1b]0;pwned\x07dangerous content';
|
|
156
|
+
const result = sanitizeOutput(malicious);
|
|
157
|
+
expect(result).not.toContain('\x1b');
|
|
158
|
+
expect(result).toContain('dangerous content');
|
|
159
|
+
});
|
|
160
|
+
it('should handle empty string', () => {
|
|
161
|
+
expect(sanitizeOutput('')).toBe('');
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
});
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-verification module for agent
|
|
3
|
+
* Runs build/test and analyzes errors for auto-fixing
|
|
4
|
+
*/
|
|
5
|
+
export interface VerifyResult {
|
|
6
|
+
success: boolean;
|
|
7
|
+
type: 'build' | 'test' | 'lint' | 'typecheck';
|
|
8
|
+
command: string;
|
|
9
|
+
output: string;
|
|
10
|
+
errors: ParsedError[];
|
|
11
|
+
duration: number;
|
|
12
|
+
}
|
|
13
|
+
export interface ParsedError {
|
|
14
|
+
file?: string;
|
|
15
|
+
line?: number;
|
|
16
|
+
column?: number;
|
|
17
|
+
message: string;
|
|
18
|
+
code?: string;
|
|
19
|
+
severity: 'error' | 'warning';
|
|
20
|
+
}
|
|
21
|
+
export interface VerifyOptions {
|
|
22
|
+
runBuild: boolean;
|
|
23
|
+
runTest: boolean;
|
|
24
|
+
runLint: boolean;
|
|
25
|
+
runTypecheck: boolean;
|
|
26
|
+
timeout: number;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Detect project type and available scripts
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectProjectScripts(projectRoot: string): {
|
|
32
|
+
build?: string;
|
|
33
|
+
test?: string;
|
|
34
|
+
lint?: string;
|
|
35
|
+
typecheck?: string;
|
|
36
|
+
packageManager: 'npm' | 'yarn' | 'pnpm' | 'bun';
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Run build verification
|
|
40
|
+
*/
|
|
41
|
+
export declare function runBuildVerification(projectRoot: string, timeout?: number): VerifyResult | null;
|
|
42
|
+
/**
|
|
43
|
+
* Run test verification
|
|
44
|
+
*/
|
|
45
|
+
export declare function runTestVerification(projectRoot: string, timeout?: number): VerifyResult | null;
|
|
46
|
+
/**
|
|
47
|
+
* Run TypeScript type checking
|
|
48
|
+
*/
|
|
49
|
+
export declare function runTypecheckVerification(projectRoot: string, timeout?: number): VerifyResult | null;
|
|
50
|
+
/**
|
|
51
|
+
* Run lint verification
|
|
52
|
+
*/
|
|
53
|
+
export declare function runLintVerification(projectRoot: string, timeout?: number): VerifyResult | null;
|
|
54
|
+
/**
|
|
55
|
+
* Run all verifications
|
|
56
|
+
*/
|
|
57
|
+
export declare function runAllVerifications(projectRoot: string, options?: Partial<VerifyOptions>): VerifyResult[];
|
|
58
|
+
/**
|
|
59
|
+
* Format verification results for display
|
|
60
|
+
*/
|
|
61
|
+
export declare function formatVerifyResults(results: VerifyResult[]): string;
|
|
62
|
+
/**
|
|
63
|
+
* Format errors for agent to fix
|
|
64
|
+
*/
|
|
65
|
+
export declare function formatErrorsForAgent(results: VerifyResult[]): string;
|
|
66
|
+
/**
|
|
67
|
+
* Check if any verification failed
|
|
68
|
+
*/
|
|
69
|
+
export declare function hasVerificationErrors(results: VerifyResult[]): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Get summary of verification
|
|
72
|
+
*/
|
|
73
|
+
export declare function getVerificationSummary(results: VerifyResult[]): {
|
|
74
|
+
passed: number;
|
|
75
|
+
failed: number;
|
|
76
|
+
total: number;
|
|
77
|
+
errors: number;
|
|
78
|
+
};
|