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.
Files changed (103) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +576 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.js +421 -0
  5. package/dist/app.d.ts +2 -0
  6. package/dist/app.js +1406 -0
  7. package/dist/components/AgentProgress.d.ts +33 -0
  8. package/dist/components/AgentProgress.js +97 -0
  9. package/dist/components/Export.d.ts +8 -0
  10. package/dist/components/Export.js +27 -0
  11. package/dist/components/Help.d.ts +2 -0
  12. package/dist/components/Help.js +3 -0
  13. package/dist/components/Input.d.ts +9 -0
  14. package/dist/components/Input.js +89 -0
  15. package/dist/components/Loading.d.ts +9 -0
  16. package/dist/components/Loading.js +31 -0
  17. package/dist/components/Login.d.ts +7 -0
  18. package/dist/components/Login.js +77 -0
  19. package/dist/components/Logo.d.ts +8 -0
  20. package/dist/components/Logo.js +89 -0
  21. package/dist/components/LogoutPicker.d.ts +8 -0
  22. package/dist/components/LogoutPicker.js +61 -0
  23. package/dist/components/Message.d.ts +10 -0
  24. package/dist/components/Message.js +234 -0
  25. package/dist/components/MessageList.d.ts +10 -0
  26. package/dist/components/MessageList.js +8 -0
  27. package/dist/components/ProjectPermission.d.ts +7 -0
  28. package/dist/components/ProjectPermission.js +52 -0
  29. package/dist/components/Search.d.ts +10 -0
  30. package/dist/components/Search.js +30 -0
  31. package/dist/components/SessionPicker.d.ts +9 -0
  32. package/dist/components/SessionPicker.js +88 -0
  33. package/dist/components/Sessions.d.ts +12 -0
  34. package/dist/components/Sessions.js +102 -0
  35. package/dist/components/Settings.d.ts +7 -0
  36. package/dist/components/Settings.js +162 -0
  37. package/dist/components/Status.d.ts +2 -0
  38. package/dist/components/Status.js +12 -0
  39. package/dist/config/config.test.d.ts +1 -0
  40. package/dist/config/config.test.js +157 -0
  41. package/dist/config/index.d.ts +121 -0
  42. package/dist/config/index.js +555 -0
  43. package/dist/config/providers.d.ts +43 -0
  44. package/dist/config/providers.js +82 -0
  45. package/dist/config/providers.test.d.ts +1 -0
  46. package/dist/config/providers.test.js +132 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +38 -0
  49. package/dist/utils/agent.d.ts +37 -0
  50. package/dist/utils/agent.js +627 -0
  51. package/dist/utils/codeReview.d.ts +36 -0
  52. package/dist/utils/codeReview.js +390 -0
  53. package/dist/utils/context.d.ts +49 -0
  54. package/dist/utils/context.js +216 -0
  55. package/dist/utils/diffPreview.d.ts +57 -0
  56. package/dist/utils/diffPreview.js +335 -0
  57. package/dist/utils/export.d.ts +19 -0
  58. package/dist/utils/export.js +94 -0
  59. package/dist/utils/git.d.ts +85 -0
  60. package/dist/utils/git.js +399 -0
  61. package/dist/utils/git.test.d.ts +1 -0
  62. package/dist/utils/git.test.js +193 -0
  63. package/dist/utils/history.d.ts +93 -0
  64. package/dist/utils/history.js +348 -0
  65. package/dist/utils/interactive.d.ts +34 -0
  66. package/dist/utils/interactive.js +206 -0
  67. package/dist/utils/keychain.d.ts +17 -0
  68. package/dist/utils/keychain.js +160 -0
  69. package/dist/utils/learning.d.ts +89 -0
  70. package/dist/utils/learning.js +330 -0
  71. package/dist/utils/logger.d.ts +33 -0
  72. package/dist/utils/logger.js +130 -0
  73. package/dist/utils/project.d.ts +86 -0
  74. package/dist/utils/project.js +415 -0
  75. package/dist/utils/project.test.d.ts +1 -0
  76. package/dist/utils/project.test.js +212 -0
  77. package/dist/utils/ratelimit.d.ts +26 -0
  78. package/dist/utils/ratelimit.js +132 -0
  79. package/dist/utils/ratelimit.test.d.ts +1 -0
  80. package/dist/utils/ratelimit.test.js +131 -0
  81. package/dist/utils/retry.d.ts +28 -0
  82. package/dist/utils/retry.js +109 -0
  83. package/dist/utils/retry.test.d.ts +1 -0
  84. package/dist/utils/retry.test.js +163 -0
  85. package/dist/utils/search.d.ts +11 -0
  86. package/dist/utils/search.js +29 -0
  87. package/dist/utils/shell.d.ts +45 -0
  88. package/dist/utils/shell.js +242 -0
  89. package/dist/utils/skills.d.ts +144 -0
  90. package/dist/utils/skills.js +1137 -0
  91. package/dist/utils/smartContext.d.ts +29 -0
  92. package/dist/utils/smartContext.js +441 -0
  93. package/dist/utils/tools.d.ts +224 -0
  94. package/dist/utils/tools.js +731 -0
  95. package/dist/utils/update.d.ts +22 -0
  96. package/dist/utils/update.js +128 -0
  97. package/dist/utils/validation.d.ts +28 -0
  98. package/dist/utils/validation.js +141 -0
  99. package/dist/utils/validation.test.d.ts +1 -0
  100. package/dist/utils/validation.test.js +164 -0
  101. package/dist/utils/verify.d.ts +78 -0
  102. package/dist/utils/verify.js +464 -0
  103. package/package.json +68 -0
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry';
3
+ describe('retry utilities', () => {
4
+ describe('isNetworkError', () => {
5
+ it('should detect fetch TypeError', () => {
6
+ const error = new TypeError('Failed to fetch');
7
+ expect(isNetworkError(error)).toBe(true);
8
+ });
9
+ it('should detect network TypeError', () => {
10
+ const error = new TypeError('Network request failed');
11
+ expect(isNetworkError(error)).toBe(true);
12
+ });
13
+ it('should detect ECONNREFUSED', () => {
14
+ const error = { code: 'ECONNREFUSED' };
15
+ expect(isNetworkError(error)).toBe(true);
16
+ });
17
+ it('should detect ENOTFOUND', () => {
18
+ const error = { code: 'ENOTFOUND' };
19
+ expect(isNetworkError(error)).toBe(true);
20
+ });
21
+ it('should detect ETIMEDOUT', () => {
22
+ const error = { code: 'ETIMEDOUT' };
23
+ expect(isNetworkError(error)).toBe(true);
24
+ });
25
+ it('should detect ENETUNREACH', () => {
26
+ const error = { code: 'ENETUNREACH' };
27
+ expect(isNetworkError(error)).toBe(true);
28
+ });
29
+ it('should detect ECONNRESET', () => {
30
+ const error = { code: 'ECONNRESET' };
31
+ expect(isNetworkError(error)).toBe(true);
32
+ });
33
+ it('should return false for non-network errors', () => {
34
+ expect(isNetworkError(new Error('Some other error'))).toBe(false);
35
+ expect(isNetworkError({ status: 400 })).toBe(false);
36
+ expect(isNetworkError({ code: 'EPERM' })).toBe(false);
37
+ });
38
+ });
39
+ describe('isTimeoutError', () => {
40
+ it('should detect AbortError', () => {
41
+ const error = new Error('Aborted');
42
+ error.name = 'AbortError';
43
+ expect(isTimeoutError(error)).toBe(true);
44
+ });
45
+ it('should detect ETIMEDOUT', () => {
46
+ const error = { code: 'ETIMEDOUT' };
47
+ expect(isTimeoutError(error)).toBe(true);
48
+ });
49
+ it('should return false for non-timeout errors', () => {
50
+ expect(isTimeoutError(new Error('Some error'))).toBe(false);
51
+ expect(isTimeoutError({ code: 'ECONNREFUSED' })).toBe(false);
52
+ });
53
+ });
54
+ describe('withRetry', () => {
55
+ it('should return result on first success', async () => {
56
+ const fn = vi.fn().mockResolvedValue('success');
57
+ const result = await withRetry(fn);
58
+ expect(result).toBe('success');
59
+ expect(fn).toHaveBeenCalledTimes(1);
60
+ });
61
+ it('should retry on failure and succeed', async () => {
62
+ const fn = vi.fn()
63
+ .mockRejectedValueOnce(new Error('fail 1'))
64
+ .mockRejectedValueOnce(new Error('fail 2'))
65
+ .mockResolvedValue('success');
66
+ const result = await withRetry(fn, { baseDelay: 10 });
67
+ expect(result).toBe('success');
68
+ expect(fn).toHaveBeenCalledTimes(3);
69
+ });
70
+ it('should throw after max attempts', async () => {
71
+ const fn = vi.fn().mockRejectedValue(new Error('always fails'));
72
+ await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
73
+ .rejects.toThrow('always fails');
74
+ expect(fn).toHaveBeenCalledTimes(3);
75
+ });
76
+ it('should not retry on AbortError', async () => {
77
+ const abortError = new Error('Aborted');
78
+ abortError.name = 'AbortError';
79
+ const fn = vi.fn().mockRejectedValue(abortError);
80
+ await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
81
+ .rejects.toThrow('Aborted');
82
+ expect(fn).toHaveBeenCalledTimes(1);
83
+ });
84
+ it('should call onRetry callback', async () => {
85
+ const onRetry = vi.fn();
86
+ const fn = vi.fn()
87
+ .mockRejectedValueOnce(new Error('fail'))
88
+ .mockResolvedValue('success');
89
+ await withRetry(fn, { baseDelay: 10, onRetry });
90
+ expect(onRetry).toHaveBeenCalledTimes(1);
91
+ expect(onRetry).toHaveBeenCalledWith(1, expect.any(Error), expect.any(Number));
92
+ });
93
+ it('should respect shouldRetry option', async () => {
94
+ const shouldRetry = vi.fn().mockReturnValue(false);
95
+ const fn = vi.fn().mockRejectedValue(new Error('fail'));
96
+ await expect(withRetry(fn, { shouldRetry, maxAttempts: 3, baseDelay: 10 }))
97
+ .rejects.toThrow('fail');
98
+ expect(fn).toHaveBeenCalledTimes(1);
99
+ });
100
+ it('should not retry on 4xx errors by default', async () => {
101
+ const error = { status: 400, message: 'Bad Request' };
102
+ const fn = vi.fn().mockRejectedValue(error);
103
+ await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
104
+ .rejects.toEqual(error);
105
+ expect(fn).toHaveBeenCalledTimes(1);
106
+ });
107
+ it('should retry on 5xx errors by default', async () => {
108
+ const error = { status: 500, message: 'Server Error' };
109
+ const fn = vi.fn()
110
+ .mockRejectedValueOnce(error)
111
+ .mockResolvedValue('success');
112
+ const result = await withRetry(fn, { baseDelay: 10 });
113
+ expect(result).toBe('success');
114
+ expect(fn).toHaveBeenCalledTimes(2);
115
+ });
116
+ it('should respect maxDelay', async () => {
117
+ const onRetry = vi.fn();
118
+ const fn = vi.fn()
119
+ .mockRejectedValueOnce(new Error('fail 1'))
120
+ .mockRejectedValueOnce(new Error('fail 2'))
121
+ .mockResolvedValue('success');
122
+ await withRetry(fn, { baseDelay: 1000, maxDelay: 100, onRetry });
123
+ // All delays should be capped at maxDelay
124
+ for (const call of onRetry.mock.calls) {
125
+ expect(call[2]).toBeLessThanOrEqual(100);
126
+ }
127
+ });
128
+ });
129
+ describe('fetchWithTimeout', () => {
130
+ it('should make fetch request', async () => {
131
+ const mockResponse = new Response('ok', { status: 200 });
132
+ global.fetch = vi.fn().mockResolvedValue(mockResponse);
133
+ const response = await fetchWithTimeout('https://example.com');
134
+ expect(response.status).toBe(200);
135
+ expect(global.fetch).toHaveBeenCalledWith('https://example.com', expect.objectContaining({ signal: expect.any(AbortSignal) }));
136
+ });
137
+ it('should abort on timeout', async () => {
138
+ global.fetch = vi.fn().mockImplementation(() => new Promise((_, reject) => {
139
+ setTimeout(() => {
140
+ const error = new Error('Aborted');
141
+ error.name = 'AbortError';
142
+ reject(error);
143
+ }, 100);
144
+ }));
145
+ await expect(fetchWithTimeout('https://example.com', { timeout: 50 }))
146
+ .rejects.toThrow();
147
+ });
148
+ it('should pass through fetch options', async () => {
149
+ const mockResponse = new Response('ok');
150
+ global.fetch = vi.fn().mockResolvedValue(mockResponse);
151
+ await fetchWithTimeout('https://example.com', {
152
+ method: 'POST',
153
+ headers: { 'Content-Type': 'application/json' },
154
+ body: JSON.stringify({ test: true }),
155
+ });
156
+ expect(global.fetch).toHaveBeenCalledWith('https://example.com', expect.objectContaining({
157
+ method: 'POST',
158
+ headers: { 'Content-Type': 'application/json' },
159
+ body: JSON.stringify({ test: true }),
160
+ }));
161
+ });
162
+ });
163
+ });
@@ -0,0 +1,11 @@
1
+ import { Message } from '../config/index';
2
+ export interface SearchResult {
3
+ messageIndex: number;
4
+ role: 'user' | 'assistant';
5
+ content: string;
6
+ matchedText: string;
7
+ }
8
+ /**
9
+ * Search through chat history for a term
10
+ */
11
+ export declare function searchMessages(messages: Message[], searchTerm: string): SearchResult[];
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Search through chat history for a term
3
+ */
4
+ export function searchMessages(messages, searchTerm) {
5
+ const results = [];
6
+ const term = searchTerm.toLowerCase();
7
+ messages.forEach((message, index) => {
8
+ const content = message.content.toLowerCase();
9
+ if (content.includes(term)) {
10
+ // Find the matching snippet with context
11
+ const matchIndex = content.indexOf(term);
12
+ const start = Math.max(0, matchIndex - 50);
13
+ const end = Math.min(content.length, matchIndex + term.length + 50);
14
+ let snippet = message.content.substring(start, end);
15
+ // Add ellipsis if truncated
16
+ if (start > 0)
17
+ snippet = '...' + snippet;
18
+ if (end < content.length)
19
+ snippet = snippet + '...';
20
+ results.push({
21
+ messageIndex: index,
22
+ role: message.role,
23
+ content: message.content,
24
+ matchedText: snippet,
25
+ });
26
+ }
27
+ });
28
+ return results;
29
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Shell command execution utilities with safety checks
3
+ */
4
+ export interface CommandResult {
5
+ success: boolean;
6
+ stdout: string;
7
+ stderr: string;
8
+ exitCode: number;
9
+ duration: number;
10
+ command: string;
11
+ args: string[];
12
+ }
13
+ export interface CommandOptions {
14
+ cwd?: string;
15
+ timeout?: number;
16
+ env?: Record<string, string>;
17
+ projectRoot?: string;
18
+ }
19
+ /**
20
+ * Validate if a command is safe to execute
21
+ */
22
+ export declare function validateCommand(command: string, args: string[], options?: CommandOptions): {
23
+ valid: boolean;
24
+ reason?: string;
25
+ };
26
+ /**
27
+ * Execute a shell command with safety checks
28
+ */
29
+ export declare function executeCommand(command: string, args?: string[], options?: CommandOptions): CommandResult;
30
+ /**
31
+ * Execute a command and return only stdout if successful
32
+ */
33
+ export declare function execSimple(command: string, args?: string[], options?: CommandOptions): string | null;
34
+ /**
35
+ * Check if a command exists in PATH
36
+ */
37
+ export declare function commandExists(command: string): boolean;
38
+ /**
39
+ * Get list of allowed commands
40
+ */
41
+ export declare function getAllowedCommands(): string[];
42
+ /**
43
+ * Format command result for display
44
+ */
45
+ export declare function formatCommandResult(result: CommandResult): string;
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Shell command execution utilities with safety checks
3
+ */
4
+ import { spawnSync } from 'child_process';
5
+ import { resolve, relative, isAbsolute } from 'path';
6
+ import { existsSync } from 'fs';
7
+ // Dangerous command patterns that should never be executed
8
+ const BLOCKED_COMMANDS = new Set([
9
+ 'sudo',
10
+ 'su',
11
+ 'chmod',
12
+ 'chown',
13
+ 'mkfs',
14
+ 'fdisk',
15
+ 'dd',
16
+ 'mount',
17
+ 'umount',
18
+ 'systemctl',
19
+ 'service',
20
+ 'shutdown',
21
+ 'reboot',
22
+ 'init',
23
+ 'kill',
24
+ 'killall',
25
+ 'pkill',
26
+ ]);
27
+ // Dangerous argument patterns
28
+ const BLOCKED_PATTERNS = [
29
+ /rm\s+(-[rf]+\s+)*\/(?![\w])/, // rm -rf / (root)
30
+ /rm\s+(-[rf]+\s+)*~/, // rm home directory
31
+ />\s*\/etc\//, // redirect to /etc
32
+ />\s*\/usr\//, // redirect to /usr
33
+ />\s*\/var\//, // redirect to /var
34
+ />\s*\/bin\//, // redirect to /bin
35
+ />\s*\/sbin\//, // redirect to /sbin
36
+ /curl.*\|\s*(ba)?sh/, // curl pipe to shell
37
+ /wget.*\|\s*(ba)?sh/, // wget pipe to shell
38
+ /eval\s+/, // eval command
39
+ /`.*`/, // command substitution in backticks
40
+ /\$\(.*\)/, // command substitution
41
+ ];
42
+ // Allowed commands for agent mode (whitelist approach for extra safety)
43
+ const ALLOWED_COMMANDS = new Set([
44
+ // Package managers
45
+ 'npm', 'npx', 'yarn', 'pnpm', 'bun',
46
+ 'pip', 'pip3', 'poetry', 'pipenv',
47
+ 'cargo', 'rustup',
48
+ 'go',
49
+ 'composer',
50
+ 'gem', 'bundle',
51
+ 'brew',
52
+ // Build tools
53
+ 'make', 'cmake', 'gradle', 'mvn',
54
+ 'tsc', 'esbuild', 'vite', 'webpack', 'rollup',
55
+ // Version control
56
+ 'git',
57
+ // File operations (safe ones)
58
+ 'ls', 'cat', 'head', 'tail', 'grep', 'find', 'wc',
59
+ 'mkdir', 'touch', 'cp', 'mv', 'rm', 'rmdir',
60
+ // Node.js
61
+ 'node', 'deno',
62
+ // Python
63
+ 'python', 'python3',
64
+ // PHP
65
+ 'php', 'composer', 'phpunit', 'artisan',
66
+ // Testing
67
+ 'jest', 'vitest', 'pytest', 'mocha',
68
+ // Linting/Formatting
69
+ 'eslint', 'prettier', 'black', 'rustfmt',
70
+ // Other common tools
71
+ 'echo', 'pwd', 'which', 'env', 'date',
72
+ 'curl', 'wget', // allowed but patterns checked
73
+ 'tar', 'unzip', 'zip',
74
+ // HTTP tools
75
+ 'http', 'https',
76
+ ]);
77
+ /**
78
+ * Validate if a command is safe to execute
79
+ */
80
+ export function validateCommand(command, args, options) {
81
+ // Check if command is in blocked list
82
+ if (BLOCKED_COMMANDS.has(command)) {
83
+ return { valid: false, reason: `Command '${command}' is not allowed for security reasons` };
84
+ }
85
+ // Check if command is in allowed list (whitelist mode)
86
+ if (!ALLOWED_COMMANDS.has(command)) {
87
+ return { valid: false, reason: `Command '${command}' is not in the allowed list` };
88
+ }
89
+ // Check full command string against dangerous patterns
90
+ const fullCommand = `${command} ${args.join(' ')}`;
91
+ for (const pattern of BLOCKED_PATTERNS) {
92
+ if (pattern.test(fullCommand)) {
93
+ return { valid: false, reason: `Command contains blocked pattern: ${pattern}` };
94
+ }
95
+ }
96
+ // Validate paths in arguments stay within project
97
+ if (options?.projectRoot) {
98
+ for (const arg of args) {
99
+ // Skip flags
100
+ if (arg.startsWith('-'))
101
+ continue;
102
+ // Check if argument looks like a path
103
+ if (arg.includes('/') || arg.includes('\\')) {
104
+ const absolutePath = isAbsolute(arg) ? arg : resolve(options.cwd || options.projectRoot, arg);
105
+ const relativePath = relative(options.projectRoot, absolutePath);
106
+ // Path escapes project root
107
+ if (relativePath.startsWith('..')) {
108
+ return { valid: false, reason: `Path '${arg}' is outside project directory` };
109
+ }
110
+ }
111
+ }
112
+ }
113
+ // Special validation for rm command
114
+ if (command === 'rm') {
115
+ const hasRecursive = args.some(a => a.includes('r'));
116
+ const hasForce = args.some(a => a.includes('f'));
117
+ if (hasRecursive && hasForce) {
118
+ // rm -rf requires extra validation
119
+ const paths = args.filter(a => !a.startsWith('-'));
120
+ if (paths.length === 0) {
121
+ return { valid: false, reason: 'rm -rf without specific paths is not allowed' };
122
+ }
123
+ }
124
+ }
125
+ return { valid: true };
126
+ }
127
+ /**
128
+ * Execute a shell command with safety checks
129
+ */
130
+ export function executeCommand(command, args = [], options) {
131
+ const startTime = Date.now();
132
+ const cwd = options?.cwd || process.cwd();
133
+ const timeout = options?.timeout || 60000; // Default 1 minute
134
+ // Validate command first
135
+ const validation = validateCommand(command, args, options);
136
+ if (!validation.valid) {
137
+ return {
138
+ success: false,
139
+ stdout: '',
140
+ stderr: validation.reason || 'Command validation failed',
141
+ exitCode: -1,
142
+ duration: 0,
143
+ command,
144
+ args,
145
+ };
146
+ }
147
+ // Ensure cwd exists
148
+ if (!existsSync(cwd)) {
149
+ return {
150
+ success: false,
151
+ stdout: '',
152
+ stderr: `Working directory does not exist: ${cwd}`,
153
+ exitCode: -1,
154
+ duration: 0,
155
+ command,
156
+ args,
157
+ };
158
+ }
159
+ const spawnOptions = {
160
+ cwd,
161
+ timeout,
162
+ encoding: 'utf-8',
163
+ env: {
164
+ ...process.env,
165
+ ...options?.env,
166
+ },
167
+ maxBuffer: 10 * 1024 * 1024, // 10MB
168
+ };
169
+ try {
170
+ const result = spawnSync(command, args, spawnOptions);
171
+ const duration = Date.now() - startTime;
172
+ // Handle timeout
173
+ if (result.signal === 'SIGTERM') {
174
+ return {
175
+ success: false,
176
+ stdout: result.stdout?.toString() || '',
177
+ stderr: `Command timed out after ${timeout}ms`,
178
+ exitCode: -1,
179
+ duration,
180
+ command,
181
+ args,
182
+ };
183
+ }
184
+ return {
185
+ success: result.status === 0,
186
+ stdout: result.stdout?.toString() || '',
187
+ stderr: result.stderr?.toString() || '',
188
+ exitCode: result.status ?? -1,
189
+ duration,
190
+ command,
191
+ args,
192
+ };
193
+ }
194
+ catch (error) {
195
+ const duration = Date.now() - startTime;
196
+ const err = error;
197
+ return {
198
+ success: false,
199
+ stdout: '',
200
+ stderr: err.message || 'Unknown error executing command',
201
+ exitCode: -1,
202
+ duration,
203
+ command,
204
+ args,
205
+ };
206
+ }
207
+ }
208
+ /**
209
+ * Execute a command and return only stdout if successful
210
+ */
211
+ export function execSimple(command, args = [], options) {
212
+ const result = executeCommand(command, args, options);
213
+ return result.success ? result.stdout.trim() : null;
214
+ }
215
+ /**
216
+ * Check if a command exists in PATH
217
+ */
218
+ export function commandExists(command) {
219
+ const result = spawnSync('which', [command], { encoding: 'utf-8' });
220
+ return result.status === 0;
221
+ }
222
+ /**
223
+ * Get list of allowed commands
224
+ */
225
+ export function getAllowedCommands() {
226
+ return Array.from(ALLOWED_COMMANDS).sort();
227
+ }
228
+ /**
229
+ * Format command result for display
230
+ */
231
+ export function formatCommandResult(result) {
232
+ const status = result.success ? '✓' : '✗';
233
+ const cmd = `${result.command} ${result.args.join(' ')}`.trim();
234
+ let output = `${status} ${cmd} (${result.duration}ms, exit ${result.exitCode})`;
235
+ if (result.stdout) {
236
+ output += `\n stdout: ${result.stdout.slice(0, 500)}${result.stdout.length > 500 ? '...' : ''}`;
237
+ }
238
+ if (result.stderr && !result.success) {
239
+ output += `\n stderr: ${result.stderr.slice(0, 500)}${result.stderr.length > 500 ? '...' : ''}`;
240
+ }
241
+ return output;
242
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Skills System - predefined workflows and commands
3
+ */
4
+ import { ProjectContext } from './project';
5
+ export interface SkillStep {
6
+ type: 'prompt' | 'command' | 'confirm' | 'notify' | 'agent';
7
+ content: string;
8
+ optional?: boolean;
9
+ }
10
+ export interface SkillParameter {
11
+ name: string;
12
+ description: string;
13
+ required?: boolean;
14
+ default?: string;
15
+ }
16
+ export interface Skill {
17
+ name: string;
18
+ description: string;
19
+ shortcut?: string;
20
+ category: SkillCategory;
21
+ steps: SkillStep[];
22
+ parameters?: SkillParameter[];
23
+ requiresWriteAccess?: boolean;
24
+ requiresGit?: boolean;
25
+ }
26
+ export type SkillCategory = 'git' | 'testing' | 'documentation' | 'refactoring' | 'debugging' | 'deployment' | 'generation' | 'devops' | 'custom';
27
+ export interface SkillExecutionResult {
28
+ success: boolean;
29
+ output: string;
30
+ steps: {
31
+ step: SkillStep;
32
+ result: string;
33
+ success: boolean;
34
+ }[];
35
+ }
36
+ export interface SkillChain {
37
+ skills: string[];
38
+ stopOnError: boolean;
39
+ }
40
+ /**
41
+ * Get all built-in skills
42
+ */
43
+ export declare function getBuiltInSkills(): Skill[];
44
+ /**
45
+ * Load custom skills from disk
46
+ */
47
+ export declare function loadCustomSkills(): Skill[];
48
+ /**
49
+ * Get all skills (built-in + custom)
50
+ */
51
+ export declare function getAllSkills(): Skill[];
52
+ /**
53
+ * Find skill by name or shortcut
54
+ */
55
+ export declare function findSkill(nameOrShortcut: string): Skill | null;
56
+ /**
57
+ * Parse skill chain (e.g., "commit+push" → ["commit", "push"])
58
+ */
59
+ export declare function parseSkillChain(input: string): SkillChain | null;
60
+ /**
61
+ * Parse skill parameters from args string
62
+ * Supports: "value" for first param, key=value, key="value with spaces"
63
+ */
64
+ export declare function parseSkillArgs(args: string, skill: Skill): Record<string, string>;
65
+ /**
66
+ * Interpolate parameters into skill step content
67
+ */
68
+ export declare function interpolateParams(content: string, params: Record<string, string>): string;
69
+ /**
70
+ * Save a custom skill
71
+ */
72
+ export declare function saveCustomSkill(skill: Skill): void;
73
+ /**
74
+ * Delete a custom skill
75
+ */
76
+ export declare function deleteCustomSkill(name: string): boolean;
77
+ /**
78
+ * Generate prompt for a skill with parameters
79
+ */
80
+ export declare function generateSkillPrompt(skill: Skill, context: ProjectContext, additionalContext?: string, params?: Record<string, string>): string;
81
+ /**
82
+ * Get skill steps that need execution
83
+ */
84
+ export declare function getExecutableSteps(skill: Skill): SkillStep[];
85
+ /**
86
+ * Format skills list for display
87
+ */
88
+ export declare function formatSkillsList(skills: Skill[]): string;
89
+ /**
90
+ * Format skill help
91
+ */
92
+ export declare function formatSkillHelp(skill: Skill): string;
93
+ /**
94
+ * Create a custom skill from template
95
+ */
96
+ export declare function createSkillTemplate(name: string): Skill;
97
+ /**
98
+ * Wizard step for creating custom skills
99
+ */
100
+ export interface WizardStep {
101
+ field: 'name' | 'description' | 'shortcut' | 'step_type' | 'step_content' | 'add_another' | 'done';
102
+ prompt: string;
103
+ validate?: (input: string) => string | null;
104
+ }
105
+ export declare const WIZARD_STEPS: WizardStep[];
106
+ /**
107
+ * Parse skill definition from YAML-like string
108
+ */
109
+ export declare function parseSkillDefinition(content: string): Skill | null;
110
+ /**
111
+ * Get skill categories summary
112
+ */
113
+ export declare function getSkillsSummary(): Record<SkillCategory, number>;
114
+ /**
115
+ * Search skills by keyword
116
+ */
117
+ export declare function searchSkills(query: string): Skill[];
118
+ /**
119
+ * Track skill usage
120
+ */
121
+ export declare function trackSkillUsage(skillName: string, success?: boolean): void;
122
+ /**
123
+ * Get recently used skills
124
+ */
125
+ export declare function getRecentSkills(limit?: number): string[];
126
+ /**
127
+ * Get most frequently used skills
128
+ */
129
+ export declare function getMostUsedSkills(limit?: number): Array<{
130
+ name: string;
131
+ count: number;
132
+ }>;
133
+ /**
134
+ * Get skill usage statistics
135
+ */
136
+ export declare function getSkillStats(): {
137
+ totalUsage: number;
138
+ uniqueSkills: number;
139
+ successRate: number;
140
+ };
141
+ /**
142
+ * Clear skill usage history
143
+ */
144
+ export declare function clearSkillHistory(): void;