aiexecode 1.0.94 → 1.0.98

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.

Potentially problematic release.


This version of aiexecode might be problematic. Click here for more details.

Files changed (60) hide show
  1. package/README.md +198 -88
  2. package/index.js +43 -9
  3. package/package.json +4 -4
  4. package/payload_viewer/out/404/index.html +1 -1
  5. package/payload_viewer/out/404.html +1 -1
  6. package/payload_viewer/out/_next/static/chunks/{37d0cd2587a38f79.js → b6c0459f3789d25c.js} +1 -1
  7. package/payload_viewer/out/_next/static/chunks/b75131b58f8ca46a.css +3 -0
  8. package/payload_viewer/out/index.html +1 -1
  9. package/payload_viewer/out/index.txt +3 -3
  10. package/payload_viewer/web_server.js +361 -0
  11. package/src/LLMClient/client.js +392 -16
  12. package/src/LLMClient/converters/responses-to-claude.js +67 -18
  13. package/src/LLMClient/converters/responses-to-zai.js +608 -0
  14. package/src/LLMClient/errors.js +30 -4
  15. package/src/LLMClient/index.js +5 -0
  16. package/src/ai_based/completion_judge.js +35 -4
  17. package/src/ai_based/orchestrator.js +146 -35
  18. package/src/commands/agents.js +70 -0
  19. package/src/commands/apikey.js +1 -1
  20. package/src/commands/commands.js +51 -0
  21. package/src/commands/debug.js +52 -0
  22. package/src/commands/help.js +11 -1
  23. package/src/commands/model.js +42 -7
  24. package/src/commands/reasoning_effort.js +2 -2
  25. package/src/commands/skills.js +46 -0
  26. package/src/config/ai_models.js +106 -6
  27. package/src/config/constants.js +71 -0
  28. package/src/frontend/App.js +8 -0
  29. package/src/frontend/components/AutocompleteMenu.js +7 -1
  30. package/src/frontend/components/CurrentModelView.js +2 -2
  31. package/src/frontend/components/HelpView.js +106 -2
  32. package/src/frontend/components/Input.js +33 -11
  33. package/src/frontend/components/ModelListView.js +1 -1
  34. package/src/frontend/components/SetupWizard.js +51 -8
  35. package/src/frontend/hooks/useFileCompletion.js +467 -0
  36. package/src/frontend/utils/toolUIFormatter.js +261 -0
  37. package/src/system/agents_loader.js +289 -0
  38. package/src/system/ai_request.js +175 -12
  39. package/src/system/command_parser.js +33 -3
  40. package/src/system/conversation_state.js +265 -0
  41. package/src/system/custom_command_loader.js +386 -0
  42. package/src/system/session.js +59 -35
  43. package/src/system/skill_loader.js +318 -0
  44. package/src/system/tool_approval.js +10 -0
  45. package/src/tools/file_reader.js +49 -9
  46. package/src/tools/glob.js +0 -3
  47. package/src/tools/ripgrep.js +5 -7
  48. package/src/tools/skill_tool.js +122 -0
  49. package/src/tools/web_downloader.js +0 -3
  50. package/src/util/clone.js +174 -0
  51. package/src/util/config.js +38 -2
  52. package/src/util/config_migration.js +174 -0
  53. package/src/util/file_reference_parser.js +132 -0
  54. package/src/util/path_validator.js +178 -0
  55. package/src/util/prompt_loader.js +68 -1
  56. package/src/util/safe_fs.js +43 -3
  57. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  58. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → WjvWEjPqhHNIE_a6QIZaG}/_buildManifest.js +0 -0
  59. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → WjvWEjPqhHNIE_a6QIZaG}/_clientMiddlewareManifest.json +0 -0
  60. /package/payload_viewer/out/_next/static/{wkEKh6i9XPSyP6rjDRvHn → WjvWEjPqhHNIE_a6QIZaG}/_ssgManifest.js +0 -0
@@ -0,0 +1,467 @@
1
+ /**
2
+ * File Completion Hook
3
+ *
4
+ * @ 문자 감지 시 파일/디렉토리 자동완성을 제공합니다.
5
+ * - 경로 모드: @src/ → src 디렉토리 내용 표시
6
+ * - 검색 모드: @glob → 프로젝트 전체에서 "glob" 포함 파일 검색
7
+ */
8
+
9
+ import { useState, useEffect, useCallback, useRef } from 'react';
10
+ import { resolve, dirname, basename, join, relative } from 'path';
11
+ import { safeReaddir, safeStat, safeExists } from '../../util/safe_fs.js';
12
+
13
+ // 검색에서 제외할 디렉토리 (성능 최적화)
14
+ const EXCLUDED_DIRS = new Set([
15
+ 'node_modules',
16
+ '.git',
17
+ '.svn',
18
+ '.hg',
19
+ 'dist',
20
+ 'build',
21
+ 'out',
22
+ 'output',
23
+ 'target',
24
+ '.next',
25
+ '.nuxt',
26
+ '.svelte-kit',
27
+ '__pycache__',
28
+ '.pytest_cache',
29
+ '.mypy_cache',
30
+ '.tox',
31
+ 'venv',
32
+ '.venv',
33
+ 'env',
34
+ '.virtualenv',
35
+ '.cache',
36
+ '.temp',
37
+ '.tmp',
38
+ 'coverage',
39
+ '.nyc_output',
40
+ '.turbo',
41
+ '.parcel-cache',
42
+ '.sass-cache',
43
+ '.idea',
44
+ '.vscode',
45
+ '.vs',
46
+ 'vendor',
47
+ 'Pods',
48
+ '.bundle',
49
+ 'bower_components',
50
+ 'jspm_packages',
51
+ 'bin',
52
+ 'obj',
53
+ 'logs'
54
+ ]);
55
+
56
+ // 검색 최대 깊이
57
+ const MAX_SEARCH_DEPTH = 5;
58
+
59
+ // 검색 최대 결과 수
60
+ const MAX_SEARCH_RESULTS = 20;
61
+
62
+ // 디바운스 시간 (ms)
63
+ const SEARCH_DEBOUNCE_MS = 150;
64
+
65
+ /**
66
+ * 텍스트에서 @ 참조를 파싱합니다.
67
+ * @param {string} text - 전체 텍스트
68
+ * @param {number} cursorPosition - 커서 위치
69
+ * @returns {Object} { isActive, prefix, startIndex, isSearchMode }
70
+ */
71
+ function parseAtReference(text, cursorPosition) {
72
+ const textBeforeCursor = text.slice(0, cursorPosition);
73
+ const match = textBeforeCursor.match(/@([^\s@]*)$/);
74
+
75
+ if (!match) {
76
+ return { isActive: false, prefix: '', startIndex: -1, isSearchMode: false };
77
+ }
78
+
79
+ const prefix = match[1];
80
+
81
+ // 검색 모드 판단: /가 없고 2글자 이상이면 검색 모드
82
+ const isSearchMode = prefix.length >= 2 && !prefix.includes('/');
83
+
84
+ return {
85
+ isActive: true,
86
+ prefix,
87
+ startIndex: match.index,
88
+ isSearchMode
89
+ };
90
+ }
91
+
92
+ /**
93
+ * 재귀적으로 파일을 검색합니다.
94
+ * @param {string} dirPath - 검색할 디렉토리
95
+ * @param {string} searchTerm - 검색어 (소문자)
96
+ * @param {string} basePath - 기준 경로 (상대 경로 계산용)
97
+ * @param {number} depth - 현재 깊이
98
+ * @param {Array} results - 결과 배열 (mutation)
99
+ * @param {number} maxResults - 최대 결과 수
100
+ */
101
+ async function searchFilesRecursive(dirPath, searchTerm, basePath, depth, results, maxResults) {
102
+ // 깊이 제한 또는 결과 수 제한 도달
103
+ if (depth > MAX_SEARCH_DEPTH || results.length >= maxResults) {
104
+ return;
105
+ }
106
+
107
+ try {
108
+ const entries = await safeReaddir(dirPath, { withFileTypes: true });
109
+
110
+ for (const entry of entries) {
111
+ if (results.length >= maxResults) break;
112
+
113
+ const name = entry.name;
114
+
115
+ // 숨김 파일/폴더 제외
116
+ if (name.startsWith('.')) continue;
117
+
118
+ // 제외 디렉토리 체크
119
+ if (entry.isDirectory() && EXCLUDED_DIRS.has(name)) continue;
120
+
121
+ const fullPath = join(dirPath, name);
122
+ const relativePath = relative(basePath, fullPath);
123
+ const nameLower = name.toLowerCase();
124
+
125
+ // 이름에 검색어가 포함되면 결과에 추가
126
+ if (nameLower.includes(searchTerm)) {
127
+ const isDirectory = entry.isDirectory();
128
+ results.push({
129
+ value: '@' + relativePath + (isDirectory ? '/' : ''),
130
+ displayValue: relativePath,
131
+ icon: isDirectory ? '📁' : '📄',
132
+ isDirectory,
133
+ description: isDirectory ? 'Directory' : 'File',
134
+ // 정렬용 점수: 이름이 검색어로 시작하면 높은 점수
135
+ score: nameLower.startsWith(searchTerm) ? 2 : (nameLower === searchTerm ? 3 : 1)
136
+ });
137
+ }
138
+
139
+ // 디렉토리면 재귀 탐색
140
+ if (entry.isDirectory() && results.length < maxResults) {
141
+ await searchFilesRecursive(fullPath, searchTerm, basePath, depth + 1, results, maxResults);
142
+ }
143
+ }
144
+ } catch (error) {
145
+ // 접근 불가능한 디렉토리는 무시
146
+ }
147
+ }
148
+
149
+ /**
150
+ * 프로젝트 전체에서 파일을 검색합니다.
151
+ * @param {string} searchTerm - 검색어
152
+ * @returns {Promise<Array>} 검색 결과
153
+ */
154
+ async function searchFiles(searchTerm) {
155
+ const basePath = process.cwd();
156
+ const searchTermLower = searchTerm.toLowerCase();
157
+ const results = [];
158
+
159
+ await searchFilesRecursive(basePath, searchTermLower, basePath, 0, results, MAX_SEARCH_RESULTS);
160
+
161
+ // 점수순 정렬 (높은 점수 우선), 같은 점수면 경로 길이 짧은 것 우선
162
+ results.sort((a, b) => {
163
+ if (b.score !== a.score) return b.score - a.score;
164
+ if (a.displayValue.length !== b.displayValue.length) {
165
+ return a.displayValue.length - b.displayValue.length;
166
+ }
167
+ return a.displayValue.localeCompare(b.displayValue);
168
+ });
169
+
170
+ return results.slice(0, MAX_SEARCH_RESULTS);
171
+ }
172
+
173
+ /**
174
+ * 디렉토리 내용을 가져와 필터링합니다.
175
+ * @param {string} prefix - @ 뒤의 경로 prefix
176
+ * @returns {Promise<Array>} 자동완성 항목 목록
177
+ */
178
+ async function fetchDirectoryContents(prefix) {
179
+ try {
180
+ let dirPath;
181
+ let filterPrefix;
182
+
183
+ if (!prefix) {
184
+ dirPath = process.cwd();
185
+ filterPrefix = '';
186
+ } else if (prefix.endsWith('/')) {
187
+ dirPath = resolve(process.cwd(), prefix);
188
+ filterPrefix = '';
189
+ } else {
190
+ const parentDir = dirname(prefix);
191
+ filterPrefix = basename(prefix).toLowerCase();
192
+
193
+ if (parentDir === '.' || parentDir === '') {
194
+ dirPath = process.cwd();
195
+ } else {
196
+ dirPath = resolve(process.cwd(), parentDir);
197
+ }
198
+ }
199
+
200
+ const exists = await safeExists(dirPath);
201
+ if (!exists) return [];
202
+
203
+ const stat = await safeStat(dirPath);
204
+ if (!stat.isDirectory()) return [];
205
+
206
+ const entries = await safeReaddir(dirPath, { withFileTypes: true });
207
+ const results = [];
208
+
209
+ for (const entry of entries) {
210
+ const name = entry.name;
211
+
212
+ if (name.startsWith('.')) continue;
213
+
214
+ if (filterPrefix && !name.toLowerCase().startsWith(filterPrefix)) continue;
215
+
216
+ const isDirectory = entry.isDirectory();
217
+
218
+ let completePath;
219
+ if (!prefix) {
220
+ completePath = name;
221
+ } else if (prefix.endsWith('/')) {
222
+ completePath = prefix + name;
223
+ } else {
224
+ const parentDir = dirname(prefix);
225
+ if (parentDir === '.' || parentDir === '') {
226
+ completePath = name;
227
+ } else {
228
+ completePath = join(parentDir, name);
229
+ }
230
+ }
231
+
232
+ results.push({
233
+ value: '@' + completePath + (isDirectory ? '/' : ''),
234
+ displayValue: name,
235
+ icon: isDirectory ? '📁' : '📄',
236
+ isDirectory,
237
+ description: isDirectory ? 'Directory' : 'File'
238
+ });
239
+ }
240
+
241
+ results.sort((a, b) => {
242
+ if (a.isDirectory && !b.isDirectory) return -1;
243
+ if (!a.isDirectory && b.isDirectory) return 1;
244
+ return a.displayValue.localeCompare(b.displayValue);
245
+ });
246
+
247
+ return results.slice(0, 20);
248
+
249
+ } catch (error) {
250
+ return [];
251
+ }
252
+ }
253
+
254
+ /**
255
+ * 파일 자동완성 훅
256
+ * @param {Object} buffer - Input buffer 객체
257
+ * @returns {Object} 자동완성 상태 및 핸들러
258
+ */
259
+ export function useFileCompletion(buffer) {
260
+ const [suggestions, setSuggestions] = useState([]);
261
+ const [activeSuggestionIndex, setActiveSuggestionIndex] = useState(-1);
262
+ const [showSuggestions, setShowSuggestions] = useState(false);
263
+ const [atReference, setAtReference] = useState({ isActive: false, prefix: '', startIndex: -1, isSearchMode: false });
264
+ const lastTextRef = useRef('');
265
+ const fetchingRef = useRef(false);
266
+ const debounceTimerRef = useRef(null);
267
+ const lastSearchTermRef = useRef('');
268
+
269
+ useEffect(() => {
270
+ const text = buffer.text;
271
+
272
+ if (text === lastTextRef.current) {
273
+ return;
274
+ }
275
+ lastTextRef.current = text;
276
+
277
+ // 커서 위치 계산
278
+ const [row, col] = buffer.cursor;
279
+ const lines = buffer.lines;
280
+ let cursorPosition = 0;
281
+ for (let i = 0; i < row; i++) {
282
+ cursorPosition += lines[i].length + 1;
283
+ }
284
+ cursorPosition += col;
285
+
286
+ const ref = parseAtReference(text, cursorPosition);
287
+ setAtReference(ref);
288
+
289
+ if (!ref.isActive) {
290
+ if (showSuggestions) {
291
+ setSuggestions([]);
292
+ setShowSuggestions(false);
293
+ setActiveSuggestionIndex(-1);
294
+ }
295
+ // 디바운스 타이머 정리
296
+ if (debounceTimerRef.current) {
297
+ clearTimeout(debounceTimerRef.current);
298
+ debounceTimerRef.current = null;
299
+ }
300
+ return;
301
+ }
302
+
303
+ // 검색 모드
304
+ if (ref.isSearchMode) {
305
+ // 같은 검색어면 스킵
306
+ if (ref.prefix === lastSearchTermRef.current) {
307
+ return;
308
+ }
309
+
310
+ // 기존 타이머 취소
311
+ if (debounceTimerRef.current) {
312
+ clearTimeout(debounceTimerRef.current);
313
+ }
314
+
315
+ // 디바운스 적용
316
+ debounceTimerRef.current = setTimeout(async () => {
317
+ if (fetchingRef.current) return;
318
+
319
+ fetchingRef.current = true;
320
+ lastSearchTermRef.current = ref.prefix;
321
+
322
+ try {
323
+ const results = await searchFiles(ref.prefix);
324
+
325
+ // 텍스트가 변경되었으면 무시
326
+ if (buffer.text !== lastTextRef.current) {
327
+ fetchingRef.current = false;
328
+ return;
329
+ }
330
+
331
+ setSuggestions(results);
332
+ setShowSuggestions(results.length > 0);
333
+ setActiveSuggestionIndex(results.length > 0 ? 0 : -1);
334
+ } catch (error) {
335
+ // 에러 무시
336
+ } finally {
337
+ fetchingRef.current = false;
338
+ }
339
+ }, SEARCH_DEBOUNCE_MS);
340
+
341
+ return;
342
+ }
343
+
344
+ // 경로 모드 (기존 로직)
345
+ lastSearchTermRef.current = '';
346
+
347
+ if (fetchingRef.current) {
348
+ return;
349
+ }
350
+
351
+ fetchingRef.current = true;
352
+ fetchDirectoryContents(ref.prefix).then(results => {
353
+ fetchingRef.current = false;
354
+
355
+ if (buffer.text !== lastTextRef.current) {
356
+ return;
357
+ }
358
+
359
+ setSuggestions(results);
360
+ setShowSuggestions(results.length > 0);
361
+ setActiveSuggestionIndex(results.length > 0 ? 0 : -1);
362
+ }).catch(() => {
363
+ fetchingRef.current = false;
364
+ });
365
+
366
+ }, [buffer.text, buffer.cursor, buffer.lines, showSuggestions]);
367
+
368
+ // 컴포넌트 언마운트 시 타이머 정리
369
+ useEffect(() => {
370
+ return () => {
371
+ if (debounceTimerRef.current) {
372
+ clearTimeout(debounceTimerRef.current);
373
+ }
374
+ };
375
+ }, []);
376
+
377
+ const navigateUp = useCallback(() => {
378
+ setActiveSuggestionIndex(prev =>
379
+ prev > 0 ? prev - 1 : suggestions.length - 1
380
+ );
381
+ }, [suggestions.length]);
382
+
383
+ const navigateDown = useCallback(() => {
384
+ setActiveSuggestionIndex(prev =>
385
+ prev < suggestions.length - 1 ? prev + 1 : 0
386
+ );
387
+ }, [suggestions.length]);
388
+
389
+ const handleAutocomplete = useCallback((index) => {
390
+ if (index >= 0 && index < suggestions.length) {
391
+ const suggestion = suggestions[index];
392
+ const text = buffer.text;
393
+
394
+ const beforeAt = text.slice(0, atReference.startIndex);
395
+
396
+ const [row, col] = buffer.cursor;
397
+ const lines = buffer.lines;
398
+ let cursorPosition = 0;
399
+ for (let i = 0; i < row; i++) {
400
+ cursorPosition += lines[i].length + 1;
401
+ }
402
+ cursorPosition += col;
403
+ const afterCursor = text.slice(cursorPosition);
404
+
405
+ let newValue = suggestion.value;
406
+
407
+ if (!suggestion.isDirectory) {
408
+ newValue += ' ';
409
+ }
410
+
411
+ const newText = beforeAt + newValue + afterCursor;
412
+ buffer.setText(newText);
413
+
414
+ const newCursorPos = beforeAt.length + newValue.length;
415
+
416
+ let pos = 0;
417
+ let targetRow = 0;
418
+ let targetCol = 0;
419
+ const newLines = newText.split('\n');
420
+ for (let i = 0; i < newLines.length; i++) {
421
+ if (pos + newLines[i].length >= newCursorPos) {
422
+ targetRow = i;
423
+ targetCol = newCursorPos - pos;
424
+ break;
425
+ }
426
+ pos += newLines[i].length + 1;
427
+ }
428
+
429
+ if (typeof buffer.setCursor === 'function') {
430
+ buffer.setCursor(targetRow, targetCol);
431
+ } else {
432
+ buffer.move('end');
433
+ }
434
+
435
+ if (suggestion.isDirectory) {
436
+ // 디렉토리 선택 시 검색 모드 해제
437
+ lastSearchTermRef.current = '';
438
+ } else {
439
+ setSuggestions([]);
440
+ setShowSuggestions(false);
441
+ setActiveSuggestionIndex(-1);
442
+ }
443
+ }
444
+ }, [buffer, suggestions, atReference]);
445
+
446
+ const resetCompletionState = useCallback(() => {
447
+ setSuggestions([]);
448
+ setShowSuggestions(false);
449
+ setActiveSuggestionIndex(-1);
450
+ setAtReference({ isActive: false, prefix: '', startIndex: -1, isSearchMode: false });
451
+ lastSearchTermRef.current = '';
452
+ if (debounceTimerRef.current) {
453
+ clearTimeout(debounceTimerRef.current);
454
+ debounceTimerRef.current = null;
455
+ }
456
+ }, []);
457
+
458
+ return {
459
+ suggestions,
460
+ activeSuggestionIndex,
461
+ showSuggestions,
462
+ navigateUp,
463
+ navigateDown,
464
+ handleAutocomplete,
465
+ resetCompletionState
466
+ };
467
+ }