aiexecode 1.0.92 → 1.0.96

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 (54) hide show
  1. package/README.md +210 -87
  2. package/index.js +33 -1
  3. package/package.json +3 -3
  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 +18 -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/commands.js +51 -0
  20. package/src/commands/debug.js +52 -0
  21. package/src/commands/help.js +11 -1
  22. package/src/commands/model.js +43 -7
  23. package/src/commands/skills.js +46 -0
  24. package/src/config/ai_models.js +96 -5
  25. package/src/config/constants.js +71 -0
  26. package/src/frontend/App.js +4 -5
  27. package/src/frontend/components/ConversationItem.js +25 -24
  28. package/src/frontend/components/HelpView.js +106 -2
  29. package/src/frontend/components/SetupWizard.js +53 -8
  30. package/src/frontend/utils/syntaxHighlighter.js +4 -4
  31. package/src/frontend/utils/toolUIFormatter.js +261 -0
  32. package/src/system/agents_loader.js +289 -0
  33. package/src/system/ai_request.js +147 -9
  34. package/src/system/command_parser.js +33 -3
  35. package/src/system/conversation_state.js +265 -0
  36. package/src/system/custom_command_loader.js +386 -0
  37. package/src/system/session.js +59 -35
  38. package/src/system/skill_loader.js +318 -0
  39. package/src/system/tool_approval.js +10 -0
  40. package/src/tools/file_reader.js +49 -9
  41. package/src/tools/glob.js +0 -3
  42. package/src/tools/ripgrep.js +5 -7
  43. package/src/tools/skill_tool.js +122 -0
  44. package/src/tools/web_downloader.js +0 -3
  45. package/src/util/clone.js +174 -0
  46. package/src/util/config.js +38 -2
  47. package/src/util/config_migration.js +174 -0
  48. package/src/util/path_validator.js +178 -0
  49. package/src/util/prompt_loader.js +68 -1
  50. package/src/util/safe_fs.js +43 -3
  51. package/payload_viewer/out/_next/static/chunks/ecd2072ebf41611f.css +0 -3
  52. /package/payload_viewer/out/_next/static/{d0-fu2rgYnshgGFPxr1CR → lHmNygVpv4N1VR0LdnwkJ}/_buildManifest.js +0 -0
  53. /package/payload_viewer/out/_next/static/{d0-fu2rgYnshgGFPxr1CR → lHmNygVpv4N1VR0LdnwkJ}/_clientMiddlewareManifest.json +0 -0
  54. /package/payload_viewer/out/_next/static/{d0-fu2rgYnshgGFPxr1CR → lHmNygVpv4N1VR0LdnwkJ}/_ssgManifest.js +0 -0
@@ -5,7 +5,24 @@
5
5
  import React, { useState, useRef } from 'react';
6
6
  import { Box, Text, useInput } from 'ink';
7
7
  import { theme } from '../design/themeColors.js';
8
- import { AI_MODELS, getAllModelIds, DEFAULT_MODEL } from '../../config/ai_models.js';
8
+ import { AI_MODELS, getAllModelIds, getModelsByProvider, DEFAULT_MODEL } from '../../config/ai_models.js';
9
+
10
+ /**
11
+ * API 키 스타일로 provider 감지
12
+ * @param {string} apiKey - API 키
13
+ * @returns {string|null} provider 이름 또는 null
14
+ */
15
+ function detectProviderFromApiKey(apiKey) {
16
+ if (!apiKey || typeof apiKey !== 'string') return null;
17
+
18
+ if (apiKey.startsWith('sk-proj-')) {
19
+ return 'openai';
20
+ } else if (/^[a-f0-9]{32}\.[A-Za-z0-9]{16}$/.test(apiKey)) {
21
+ // Z.AI API 키 형식: 32자리 hex + '.' + 16자리 영숫자
22
+ return 'zai';
23
+ }
24
+ return null;
25
+ }
9
26
 
10
27
  const STEPS = {
11
28
  API_KEY: 'api_key',
@@ -17,6 +34,8 @@ export function SetupWizard({ onComplete, onCancel }) {
17
34
  const [step, setStep] = useState(STEPS.API_KEY);
18
35
  const [selectedIndex, setSelectedIndex] = useState(0);
19
36
  const [textInput, setTextInput] = useState('');
37
+ const [detectedProvider, setDetectedProvider] = useState(null);
38
+ const [errorMessage, setErrorMessage] = useState('');
20
39
 
21
40
  // settings를 ref로 관리하여 stale closure 문제 방지
22
41
  const settingsRef = useRef({
@@ -25,6 +44,14 @@ export function SetupWizard({ onComplete, onCancel }) {
25
44
  REASONING_EFFORT: 'medium'
26
45
  });
27
46
 
47
+ // 감지된 provider에 따른 모델 목록
48
+ const getAvailableModels = () => {
49
+ if (detectedProvider) {
50
+ return getModelsByProvider(detectedProvider);
51
+ }
52
+ return getAllModelIds();
53
+ };
54
+
28
55
  // 현재 스텝이 텍스트 입력인지 선택지인지 판단
29
56
  const isTextInputStep = step === STEPS.API_KEY;
30
57
 
@@ -41,14 +68,26 @@ export function SetupWizard({ onComplete, onCancel }) {
41
68
  if (!textInput.trim()) {
42
69
  return;
43
70
  }
44
- settingsRef.current.API_KEY = textInput.trim();
71
+ const apiKey = textInput.trim();
72
+ // API 키 스타일로 provider 감지
73
+ const provider = detectProviderFromApiKey(apiKey);
74
+ if (!provider) {
75
+ // 유효하지 않은 API 키 형식
76
+ setErrorMessage('Invalid API key. Please use OpenAI (sk-proj-...) or Z.AI format.');
77
+ setTextInput('');
78
+ return;
79
+ }
80
+ // 유효한 API 키
81
+ setErrorMessage('');
82
+ settingsRef.current.API_KEY = apiKey;
83
+ setDetectedProvider(provider);
45
84
  setStep(STEPS.MODEL);
46
85
  setTextInput('');
47
86
  setSelectedIndex(0);
48
87
  break;
49
88
 
50
89
  case STEPS.MODEL:
51
- const models = getAllModelIds();
90
+ const models = getAvailableModels();
52
91
  const selectedModel = models[selectedIndex];
53
92
  settingsRef.current.MODEL = selectedModel;
54
93
 
@@ -97,6 +136,10 @@ export function SetupWizard({ onComplete, onCancel }) {
97
136
 
98
137
  if (input && !key.ctrl && !key.meta) {
99
138
  setTextInput(prev => prev + input);
139
+ // 입력 시 에러 메시지 클리어
140
+ if (errorMessage) {
141
+ setErrorMessage('');
142
+ }
100
143
  }
101
144
  return;
102
145
  }
@@ -118,7 +161,7 @@ export function SetupWizard({ onComplete, onCancel }) {
118
161
  const getMaxIndexForStep = (currentStep) => {
119
162
  switch (currentStep) {
120
163
  case STEPS.MODEL:
121
- return getAllModelIds().length - 1;
164
+ return getAvailableModels().length - 1;
122
165
  case STEPS.REASONING_EFFORT:
123
166
  return 3; // 4 options
124
167
  default:
@@ -145,16 +188,18 @@ export function SetupWizard({ onComplete, onCancel }) {
145
188
  case STEPS.API_KEY:
146
189
  return React.createElement(Box, { flexDirection: 'column' },
147
190
  React.createElement(Text, { bold: true, color: theme.text.accent }, '1. API Key:'),
148
- React.createElement(Text, { color: theme.text.secondary }, ' Get your API key from: https://platform.openai.com/account/api-keys or https://console.anthropic.com/settings/keys'),
191
+ React.createElement(Text, { color: theme.text.secondary }, ' Get your API key from: https://platform.openai.com/account/api-keys or https://z.ai/manage-apikey/apikey-list'),
149
192
  React.createElement(Text, null),
150
193
  React.createElement(Box, {
151
194
  borderStyle: 'round',
152
- borderColor: theme.border.focused,
195
+ borderColor: errorMessage ? theme.status.error : theme.border.focused,
153
196
  paddingX: 1
154
197
  },
155
198
  React.createElement(Text, null, textInput ? '*'.repeat(textInput.length) : ' ')
156
199
  ),
157
- React.createElement(Text, null),
200
+ errorMessage
201
+ ? React.createElement(Text, { color: theme.status.error }, errorMessage)
202
+ : React.createElement(Text, null),
158
203
  React.createElement(Text, { dimColor: true }, 'Type your API key and press Enter')
159
204
  );
160
205
 
@@ -163,7 +208,7 @@ export function SetupWizard({ onComplete, onCancel }) {
163
208
  React.createElement(Text, { bold: true, color: theme.text.accent }, '2. Choose Model:'),
164
209
  React.createElement(Text, null),
165
210
  renderOptions(
166
- getAllModelIds().map(modelId => {
211
+ getAvailableModels().map(modelId => {
167
212
  const model = AI_MODELS[modelId];
168
213
  return `${modelId} (${model.name})`;
169
214
  })
@@ -114,11 +114,11 @@ export function colorizeCode(code, language, showLineNumbers = true) {
114
114
  const lines = codeToHighlight.split('\n');
115
115
  const padWidth = String(lines.length).length;
116
116
 
117
- return React.createElement(Box, { flexDirection: 'column' },
117
+ return React.createElement(Box, { flexDirection: 'column', width: '100%' },
118
118
  lines.map((line, index) => {
119
119
  const contentToRender = highlightLine(line, language);
120
120
 
121
- return React.createElement(Box, { key: index },
121
+ return React.createElement(Box, { key: index, width: '100%' },
122
122
  showLineNumbers && React.createElement(Text, { color: 'gray' },
123
123
  `${String(index + 1).padStart(padWidth, ' ')} `
124
124
  ),
@@ -135,9 +135,9 @@ export function colorizeCode(code, language, showLineNumbers = true) {
135
135
  const lines = codeToHighlight.split('\n');
136
136
  const padWidth = String(lines.length).length;
137
137
 
138
- return React.createElement(Box, { flexDirection: 'column' },
138
+ return React.createElement(Box, { flexDirection: 'column', width: '100%' },
139
139
  lines.map((line, index) =>
140
- React.createElement(Box, { key: index },
140
+ React.createElement(Box, { key: index, width: '100%' },
141
141
  showLineNumbers && React.createElement(Text, { color: 'gray' },
142
142
  `${String(index + 1).padStart(padWidth, ' ')} `
143
143
  ),
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Tool UI Formatter
3
+ * 도구 스키마에서 UI 포맷팅 로직을 분리하여 tools → frontend 의존성을 제거합니다.
4
+ */
5
+
6
+ import { theme } from '../design/themeColors.js';
7
+ import { toDisplayPath } from '../../util/path_helper.js';
8
+
9
+ /**
10
+ * 도구별 UI 포맷터 정의
11
+ * 각 도구의 ui_display 설정을 중앙에서 관리합니다.
12
+ */
13
+ export const toolUIFormatters = {
14
+ // ============================================
15
+ // File Reader
16
+ // ============================================
17
+ read_file: {
18
+ show_tool_call: true,
19
+ show_tool_result: true,
20
+ display_name: 'Read',
21
+ format_tool_call: (args) => {
22
+ return `(${toDisplayPath(args.filePath)})`;
23
+ },
24
+ format_tool_result: (result) => {
25
+ if (result.operation_successful) {
26
+ const lines = result.total_line_count || 0;
27
+ return {
28
+ type: 'formatted',
29
+ parts: [
30
+ { text: 'Read ', style: {} },
31
+ { text: String(lines), style: { color: theme.brand.light, bold: true } },
32
+ { text: ` line${lines !== 1 ? 's' : ''}`, style: {} }
33
+ ]
34
+ };
35
+ }
36
+ return result.error_message || 'Error reading file';
37
+ }
38
+ },
39
+
40
+ read_file_range: {
41
+ show_tool_call: true,
42
+ show_tool_result: true,
43
+ display_name: 'Read',
44
+ format_tool_call: (args) => {
45
+ return `(${toDisplayPath(args.filePath)}, lines ${args.startLine}-${args.endLine})`;
46
+ },
47
+ format_tool_result: (result) => {
48
+ if (result.operation_successful) {
49
+ const lineCount = result.file_content ? result.file_content.split('\n').length : 0;
50
+ return {
51
+ type: 'formatted',
52
+ parts: [
53
+ { text: 'Read ', style: {} },
54
+ { text: String(lineCount), style: { color: theme.brand.light, bold: true } },
55
+ { text: ` line${lineCount !== 1 ? 's' : ''}`, style: {} }
56
+ ]
57
+ };
58
+ }
59
+ return result.error_message || 'Error reading file';
60
+ }
61
+ },
62
+
63
+ // ============================================
64
+ // Code Editor
65
+ // ============================================
66
+ write_file: {
67
+ show_tool_call: true,
68
+ show_tool_result: true,
69
+ display_name: 'Write',
70
+ format_tool_call: (args) => {
71
+ return `(${toDisplayPath(args.file_path)})`;
72
+ },
73
+ format_tool_result: (result) => {
74
+ if (result.operation_successful) {
75
+ const lines = result.total_line_count || 0;
76
+ const action = result.file_existed ? 'Overwrote' : 'Created';
77
+ return {
78
+ type: 'formatted',
79
+ parts: [
80
+ { text: `${action} `, style: {} },
81
+ { text: String(lines), style: { color: theme.brand.light, bold: true } },
82
+ { text: ` line${lines !== 1 ? 's' : ''}`, style: {} }
83
+ ]
84
+ };
85
+ }
86
+ return result.error_message || 'Error writing file';
87
+ }
88
+ },
89
+
90
+ edit_file_replace: {
91
+ show_tool_call: true,
92
+ show_tool_result: true,
93
+ display_name: 'Replace',
94
+ format_tool_call: (args) => {
95
+ return `(${toDisplayPath(args.file_path)})`;
96
+ },
97
+ format_tool_result: (result) => {
98
+ if (result.operation_successful) {
99
+ const count = result.replacement_count || 0;
100
+ return {
101
+ type: 'formatted',
102
+ parts: [
103
+ { text: 'Replaced ', style: {} },
104
+ { text: String(count), style: { color: theme.brand.light, bold: true } },
105
+ { text: ` occurrence${count !== 1 ? 's' : ''}`, style: {} }
106
+ ]
107
+ };
108
+ }
109
+ return result.error_message || 'Error replacing string';
110
+ }
111
+ },
112
+
113
+ edit_file_range: {
114
+ show_tool_call: true,
115
+ show_tool_result: true,
116
+ display_name: 'Edit',
117
+ format_tool_call: (args) => {
118
+ return `(${toDisplayPath(args.file_path)}, lines ${args.start_line}-${args.end_line})`;
119
+ },
120
+ format_tool_result: (result) => {
121
+ if (result.operation_successful) {
122
+ const op = result.operation_type;
123
+ if (op === 'delete') {
124
+ return `Deleted lines`;
125
+ } else if (op === 'insert') {
126
+ return `Inserted lines`;
127
+ } else {
128
+ return `Replaced lines`;
129
+ }
130
+ }
131
+ return result.error_message || 'Error editing file';
132
+ }
133
+ },
134
+
135
+ // ============================================
136
+ // Search Tools
137
+ // ============================================
138
+ glob_search: {
139
+ show_tool_call: true,
140
+ show_tool_result: true,
141
+ display_name: 'Search',
142
+ format_tool_call: (args) => {
143
+ const pattern = args.pattern || '';
144
+ return `(${pattern})`;
145
+ },
146
+ format_tool_result: (result) => {
147
+ if (result.operation_successful) {
148
+ const matches = result.total_matches || 0;
149
+ return {
150
+ type: 'formatted',
151
+ parts: [
152
+ { text: 'Found ', style: {} },
153
+ { text: String(matches), style: { color: theme.brand.light, bold: true } },
154
+ { text: ` match${matches !== 1 ? 'es' : ''}`, style: {} }
155
+ ]
156
+ };
157
+ }
158
+ return result.error_message || 'Search failed';
159
+ }
160
+ },
161
+
162
+ ripgrep: {
163
+ show_tool_call: true,
164
+ show_tool_result: true,
165
+ display_name: 'Grep',
166
+ format_tool_call: (args) => {
167
+ const pattern = args.pattern || '';
168
+ const shortened = pattern.length > 30 ? pattern.substring(0, 27) + '...' : pattern;
169
+ return `(${shortened})`;
170
+ },
171
+ format_tool_result: (result) => {
172
+ if (result.operation_successful) {
173
+ const matches = result.totalMatches || 0;
174
+ return {
175
+ type: 'formatted',
176
+ parts: [
177
+ { text: 'Found ', style: {} },
178
+ { text: String(matches), style: { color: theme.brand.light, bold: true } },
179
+ { text: ` match${matches !== 1 ? 'es' : ''}`, style: {} }
180
+ ]
181
+ };
182
+ }
183
+ return result.error_message || 'Search failed';
184
+ }
185
+ },
186
+
187
+ // ============================================
188
+ // Web Downloader
189
+ // ============================================
190
+ fetch_web_page: {
191
+ show_tool_call: true,
192
+ show_tool_result: true,
193
+ display_name: 'Fetch',
194
+ format_tool_call: (args) => {
195
+ const url = args.url || '';
196
+ const shortened = url.length > 50 ? url.substring(0, 47) + '...' : url;
197
+ return `(${shortened})`;
198
+ },
199
+ format_tool_result: (result) => {
200
+ if (result.operation_successful) {
201
+ const contentLength = result.content?.length || 0;
202
+ return {
203
+ type: 'formatted',
204
+ parts: [
205
+ { text: 'Fetched ', style: {} },
206
+ { text: String(contentLength), style: { color: theme.brand.light, bold: true } },
207
+ { text: ' characters', style: {} }
208
+ ]
209
+ };
210
+ }
211
+ return result.error_message || 'Fetch failed';
212
+ }
213
+ }
214
+ };
215
+
216
+ /**
217
+ * 도구 이름으로 UI 포맷터를 가져옵니다.
218
+ * @param {string} toolName - 도구 이름
219
+ * @returns {Object|null} UI 포맷터 또는 null
220
+ */
221
+ export function getToolUIFormatter(toolName) {
222
+ return toolUIFormatters[toolName] || null;
223
+ }
224
+
225
+ /**
226
+ * 도구 호출을 포맷팅합니다.
227
+ * @param {string} toolName - 도구 이름
228
+ * @param {Object} args - 도구 인자
229
+ * @returns {string} 포맷팅된 문자열
230
+ */
231
+ export function formatToolCall(toolName, args) {
232
+ const formatter = getToolUIFormatter(toolName);
233
+ if (formatter && formatter.format_tool_call) {
234
+ return formatter.format_tool_call(args);
235
+ }
236
+ return `(${JSON.stringify(args)})`;
237
+ }
238
+
239
+ /**
240
+ * 도구 결과를 포맷팅합니다.
241
+ * @param {string} toolName - 도구 이름
242
+ * @param {Object} result - 도구 실행 결과
243
+ * @returns {string|Object} 포맷팅된 결과
244
+ */
245
+ export function formatToolResult(toolName, result) {
246
+ const formatter = getToolUIFormatter(toolName);
247
+ if (formatter && formatter.format_tool_result) {
248
+ return formatter.format_tool_result(result);
249
+ }
250
+ return result.operation_successful ? 'Success' : (result.error_message || 'Error');
251
+ }
252
+
253
+ /**
254
+ * 도구의 표시 이름을 가져옵니다.
255
+ * @param {string} toolName - 도구 이름
256
+ * @returns {string} 표시 이름
257
+ */
258
+ export function getToolDisplayName(toolName) {
259
+ const formatter = getToolUIFormatter(toolName);
260
+ return formatter?.display_name || toolName;
261
+ }