aiexecode 1.0.66 → 1.0.68

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 (86) hide show
  1. package/config_template/settings.json +1 -3
  2. package/index.js +46 -71
  3. package/package.json +1 -12
  4. package/payload_viewer/out/404/index.html +1 -1
  5. package/payload_viewer/out/404.html +1 -1
  6. package/payload_viewer/out/index.html +1 -1
  7. package/payload_viewer/out/index.txt +1 -1
  8. package/payload_viewer/web_server.js +0 -163
  9. package/src/ai_based/completion_judge.js +96 -5
  10. package/src/ai_based/orchestrator.js +71 -3
  11. package/src/ai_based/pip_package_installer.js +14 -12
  12. package/src/ai_based/pip_package_lookup.js +13 -10
  13. package/src/commands/apikey.js +8 -34
  14. package/src/commands/help.js +3 -4
  15. package/src/commands/model.js +17 -74
  16. package/src/commands/reasoning_effort.js +1 -1
  17. package/src/config/feature_flags.js +0 -12
  18. package/src/{ui → frontend}/App.js +23 -25
  19. package/src/frontend/README.md +81 -0
  20. package/src/{ui/components/SuggestionsDisplay.js → frontend/components/AutocompleteMenu.js} +3 -3
  21. package/src/{ui/components/HistoryItemDisplay.js → frontend/components/ConversationItem.js} +37 -89
  22. package/src/{ui → frontend}/components/CurrentModelView.js +3 -5
  23. package/src/{ui → frontend}/components/Footer.js +4 -6
  24. package/src/{ui → frontend}/components/Header.js +2 -5
  25. package/src/{ui/components/InputPrompt.js → frontend/components/Input.js} +16 -54
  26. package/src/frontend/components/ModelListView.js +106 -0
  27. package/src/{ui → frontend}/components/ModelUpdatedView.js +3 -5
  28. package/src/{ui → frontend}/components/SessionSpinner.js +3 -3
  29. package/src/{ui → frontend}/components/SetupWizard.js +8 -101
  30. package/src/{ui → frontend}/components/ToolApprovalPrompt.js +16 -14
  31. package/src/frontend/design/themeColors.js +42 -0
  32. package/src/{ui → frontend}/index.js +7 -7
  33. package/src/frontend/utils/inputBuffer.js +441 -0
  34. package/src/{ui/utils/markdownRenderer.js → frontend/utils/markdownParser.js} +3 -3
  35. package/src/{ui/utils/ConsolePatcher.js → frontend/utils/outputRedirector.js} +9 -9
  36. package/src/{ui/utils/codeColorizer.js → frontend/utils/syntaxHighlighter.js} +2 -3
  37. package/src/system/ai_request.js +145 -595
  38. package/src/system/code_executer.js +111 -16
  39. package/src/system/file_integrity.js +5 -7
  40. package/src/system/log.js +3 -3
  41. package/src/system/mcp_integration.js +15 -13
  42. package/src/system/output_helper.js +0 -20
  43. package/src/system/session.js +97 -23
  44. package/src/system/session_memory.js +2 -82
  45. package/src/system/system_info.js +1 -1
  46. package/src/system/ui_events.js +0 -43
  47. package/src/tools/code_editor.js +17 -2
  48. package/src/tools/file_reader.js +17 -2
  49. package/src/tools/glob.js +9 -1
  50. package/src/tools/response_message.js +0 -2
  51. package/src/tools/ripgrep.js +9 -1
  52. package/src/tools/web_downloader.js +9 -1
  53. package/src/util/config.js +3 -8
  54. package/src/util/debug_log.js +4 -11
  55. package/src/util/mcp_config_manager.js +3 -5
  56. package/src/util/output_formatter.js +0 -47
  57. package/src/util/prompt_loader.js +3 -4
  58. package/src/util/safe_fs.js +60 -0
  59. package/src/util/setup_wizard.js +1 -3
  60. package/src/util/text_formatter.js +0 -86
  61. package/src/config/claude_models.js +0 -195
  62. package/src/ui/README.md +0 -208
  63. package/src/ui/api.js +0 -167
  64. package/src/ui/components/AgenticProgressDisplay.js +0 -126
  65. package/src/ui/components/Composer.js +0 -55
  66. package/src/ui/components/LoadingIndicator.js +0 -54
  67. package/src/ui/components/ModelListView.js +0 -214
  68. package/src/ui/components/Notifications.js +0 -55
  69. package/src/ui/components/StreamingIndicator.js +0 -36
  70. package/src/ui/contexts/AppContext.js +0 -25
  71. package/src/ui/contexts/StreamingContext.js +0 -20
  72. package/src/ui/contexts/UIStateContext.js +0 -117
  73. package/src/ui/example-usage.js +0 -180
  74. package/src/ui/hooks/useTerminalResize.js +0 -39
  75. package/src/ui/themes/semantic-tokens.js +0 -73
  76. package/src/ui/utils/text-buffer.js +0 -975
  77. /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → Z3AZSKhutj-kS4L8VpcOl}/_buildManifest.js +0 -0
  78. /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → Z3AZSKhutj-kS4L8VpcOl}/_clientMiddlewareManifest.json +0 -0
  79. /package/payload_viewer/out/_next/static/{t0WTsjXST7ISD1Boa6ifx → Z3AZSKhutj-kS4L8VpcOl}/_ssgManifest.js +0 -0
  80. /package/src/{ui → frontend}/components/BlankLine.js +0 -0
  81. /package/src/{ui → frontend}/components/FileDiffViewer.js +0 -0
  82. /package/src/{ui → frontend}/components/HelpView.js +0 -0
  83. /package/src/{ui → frontend}/hooks/useCompletion.js +0 -0
  84. /package/src/{ui → frontend}/hooks/useKeypress.js +0 -0
  85. /package/src/{ui → frontend}/utils/diffUtils.js +0 -0
  86. /package/src/{ui → frontend}/utils/renderInkComponent.js +0 -0
@@ -3,9 +3,12 @@ import { request, getModelForProvider } from "../system/ai_request.js";
3
3
  import { safeReadFile, safeWriteFile } from '../util/safe_fs.js';
4
4
  import { join } from 'path';
5
5
  import { CONFIG_DIR, ensureConfigDirectory } from "../util/config.js";
6
+ import { createDebugLogger } from "../util/debug_log.js";
7
+
8
+ const debugLog = createDebugLogger('pip_package_lookup.log', 'pip_package_lookup');
9
+
6
10
  dotenv.config({ quiet: true });
7
11
 
8
- function consolelog() { }
9
12
  // 이 파일은 import에 쓰인 이름을 실제 pip 설치 이름과 연결해 줍니다.
10
13
  // pip_package_installer가 정확한 패키지를 설치할 수 있도록 모든 모듈이 같은 이름을 공유하게 만듭니다.
11
14
  // Package name cache file path
@@ -28,9 +31,9 @@ async function savePackageStore(store) {
28
31
  try {
29
32
  await ensureConfigDirectory();
30
33
  await safeWriteFile(CACHE_FILE, JSON.stringify(store, null, 2), 'utf8');
31
- consolelog(`✓ Package cache saved to ${CACHE_FILE}`);
34
+ debugLog(`✓ Package cache saved to ${CACHE_FILE}`);
32
35
  } catch (error) {
33
- consolelog('Failed to save package cache:', error);
36
+ debugLog('Failed to save package cache:', error);
34
37
  }
35
38
  }
36
39
 
@@ -107,7 +110,7 @@ export async function checkValidPackageName(pkgList) {
107
110
 
108
111
  for (const pkg of pkgList) {
109
112
  if (packageStore.hasOwnProperty(pkg)) {
110
- consolelog(`✓ Found in cache: ${pkg} -> ${packageStore[pkg]}`);
113
+ debugLog(`✓ Found in cache: ${pkg} -> ${packageStore[pkg]}`);
111
114
  // 캐시된 값이 null이 아닌 경우만 결과에 추가 (표준 라이브러리는 null로 저장됨)
112
115
  if (packageStore[pkg] !== null) {
113
116
  cachedResults.push(packageStore[pkg]);
@@ -119,12 +122,12 @@ export async function checkValidPackageName(pkgList) {
119
122
 
120
123
  // 3. 캐시되지 않은 패키지가 없으면 캐시 결과만 반환
121
124
  if (uncachedPackages.length === 0) {
122
- consolelog("All packages found in cache, no AI request needed");
125
+ debugLog("All packages found in cache, no AI request needed");
123
126
  return cachedResults;
124
127
  }
125
128
 
126
- consolelog(`Cache miss for packages: ${uncachedPackages.join(', ')}`);
127
- consolelog("Making AI request for uncached packages...");
129
+ debugLog(`Cache miss for packages: ${uncachedPackages.join(', ')}`);
130
+ debugLog("Making AI request for uncached packages...");
128
131
 
129
132
  // 4. AI에게 캐시되지 않은 패키지들만 요청
130
133
  const codeGenerationTemplate = [
@@ -169,14 +172,14 @@ export async function checkValidPackageName(pkgList) {
169
172
  packageStore[import_name] = pip_name;
170
173
  aiResults.push(pip_name);
171
174
  receivedMappings.add(import_name);
172
- consolelog(`✓ Cached: ${import_name} -> ${pip_name}`);
175
+ debugLog(`✓ Cached: ${import_name} -> ${pip_name}`);
173
176
  }
174
177
 
175
178
  // 6. AI 응답에 포함되지 않은 패키지들은 표준 라이브러리로 간주하여 null로 캐시
176
179
  for (const pkg of uncachedPackages) {
177
180
  if (!receivedMappings.has(pkg)) {
178
181
  packageStore[pkg] = null;
179
- consolelog(`✓ Cached as standard library: ${pkg} -> null`);
182
+ debugLog(`✓ Cached as standard library: ${pkg} -> null`);
180
183
  }
181
184
  }
182
185
 
@@ -187,7 +190,7 @@ export async function checkValidPackageName(pkgList) {
187
190
  return [...cachedResults, ...aiResults];
188
191
 
189
192
  } catch (error) {
190
- consolelog("Error validating package names:", error);
193
+ debugLog("Error validating package names:", error);
191
194
  // 에러 발생 시 캐시 결과와 원본 패키지명 반환
192
195
  return [...cachedResults, ...uncachedPackages];
193
196
  }
@@ -1,30 +1,21 @@
1
1
  import { uiEvents } from '../system/ui_events.js';
2
2
  import { loadSettings, saveSettings, SETTINGS_FILE } from '../util/config.js';
3
3
  import { resetAIClients } from '../system/ai_request.js';
4
- import { ENABLE_ANTHROPIC_PROVIDER } from '../config/feature_flags.js';
5
4
 
6
5
  /**
7
6
  * /apikey 커맨드 - AI Provider 및 API 키 설정
8
7
  */
9
8
  export default {
10
9
  name: 'apikey',
11
- description: ENABLE_ANTHROPIC_PROVIDER
12
- ? 'Set AI provider and API key (openai or anthropic)'
13
- : 'Set AI provider and API key (openai)',
14
- usage: ENABLE_ANTHROPIC_PROVIDER
15
- ? '/apikey <provider> <api-key>'
16
- : '/apikey openai <api-key>',
10
+ description: 'Set AI provider and API key (openai)',
11
+ usage: '/apikey openai <api-key>',
17
12
  handler: async (args, context) => {
18
13
  // 인자 확인
19
14
  if (!args || args.length < 2) {
20
- const usageExamples = ENABLE_ANTHROPIC_PROVIDER
21
- ? ` /apikey openai sk-proj-...\n /apikey anthropic sk-ant-...`
22
- : ` /apikey openai sk-proj-...`;
23
-
24
15
  uiEvents.addSystemMessage(
25
16
  `Please enter provider and API key.\n\n` +
26
17
  `Usage:\n` +
27
- usageExamples
18
+ ` /apikey openai sk-proj-...`
28
19
  );
29
20
  return;
30
21
  }
@@ -36,36 +27,19 @@ export default {
36
27
  const providerArg = args[0].toLowerCase();
37
28
 
38
29
  // Provider 검증
39
- const validProviders = ENABLE_ANTHROPIC_PROVIDER
40
- ? ['openai', 'anthropic']
41
- : ['openai'];
42
-
43
- if (!validProviders.includes(providerArg)) {
44
- const validProvidersText = ENABLE_ANTHROPIC_PROVIDER
45
- ? 'openai, anthropic'
46
- : 'openai';
47
-
30
+ if (providerArg !== 'openai') {
48
31
  uiEvents.addSystemMessage(
49
32
  `Invalid provider: ${providerArg}\n\n` +
50
- `Valid providers: ${validProvidersText}`
33
+ `Valid providers: openai`
51
34
  );
52
35
  return;
53
36
  }
54
37
 
55
- const provider = providerArg;
56
38
  const apiKey = args[1];
57
39
 
58
- // Provider 설정
59
- settings.AI_PROVIDER = provider;
60
-
61
40
  // API 키 업데이트
62
- if (provider === 'openai') {
63
- settings.OPENAI_API_KEY = apiKey;
64
- process.env.OPENAI_API_KEY = apiKey;
65
- } else if (provider === 'anthropic') {
66
- settings.ANTHROPIC_API_KEY = apiKey;
67
- process.env.ANTHROPIC_API_KEY = apiKey;
68
- }
41
+ settings.OPENAI_API_KEY = apiKey;
42
+ process.env.OPENAI_API_KEY = apiKey;
69
43
 
70
44
  // 설정 저장
71
45
  await saveSettings(settings);
@@ -80,7 +54,7 @@ export default {
80
54
 
81
55
  uiEvents.addSystemMessage(
82
56
  `API configuration complete\n\n` +
83
- `Provider: ${provider.toUpperCase()}\n` +
57
+ `Provider: OPENAI\n` +
84
58
  `API Key: ${maskedKey}\n` +
85
59
  `Saved to: ${SETTINGS_FILE}`
86
60
  );
@@ -1,5 +1,7 @@
1
+ import React from 'react';
1
2
  import { uiEvents } from '../system/ui_events.js';
2
- import { renderInkComponent } from '../ui/utils/renderInkComponent.js';
3
+ import { renderInkComponent } from '../frontend/utils/renderInkComponent.js';
4
+ import { HelpView } from '../frontend/components/HelpView.js';
3
5
 
4
6
  /**
5
7
  * /help 커맨드 - 사용 가능한 커맨드 목록 표시
@@ -11,9 +13,6 @@ export default {
11
13
  handler: async (args, context) => {
12
14
  const { commandRegistry } = context;
13
15
 
14
- const React = await import('react');
15
- const { HelpView } = await import('../ui/components/HelpView.js');
16
-
17
16
  const commands = commandRegistry.getCommands();
18
17
 
19
18
  const component = React.createElement(HelpView, {
@@ -1,65 +1,37 @@
1
+ import React from 'react';
1
2
  import { uiEvents } from '../system/ui_events.js';
2
3
  import { loadSettings, saveSettings, SETTINGS_FILE } from '../util/config.js';
3
4
  import { resetAIClients } from '../system/ai_request.js';
4
- import { CLAUDE_MODELS, getClaude4Models, getClaude3Models, DEFAULT_CLAUDE_MODEL } from '../config/claude_models.js';
5
5
  import { OPENAI_MODELS, getGPT5Models, DEFAULT_OPENAI_MODEL } from '../config/openai_models.js';
6
- import { renderInkComponent } from '../ui/utils/renderInkComponent.js';
7
- import { ENABLE_ANTHROPIC_PROVIDER } from '../config/feature_flags.js';
6
+ import { renderInkComponent } from '../frontend/utils/renderInkComponent.js';
7
+ import { ModelListView } from '../frontend/components/ModelListView.js';
8
+ import { CurrentModelView } from '../frontend/components/CurrentModelView.js';
9
+ import { ModelUpdatedView } from '../frontend/components/ModelUpdatedView.js';
8
10
 
9
11
  // 지원하는 모델 목록
10
12
  // OpenAI: https://platform.openai.com/docs/pricing
11
- // Anthropic: https://docs.claude.com/en/docs/about-claude/models/overview
12
13
  const MODELS = {
13
- openai: OPENAI_MODELS,
14
- anthropic: CLAUDE_MODELS
14
+ openai: OPENAI_MODELS
15
15
  };
16
16
 
17
17
  // 모델 ID로 provider 찾기
18
18
  function getProviderForModel(modelId) {
19
- for (const [provider, models] of Object.entries(MODELS)) {
20
- // Feature flag가 비활성화된 경우 anthropic provider 제외
21
- if (!ENABLE_ANTHROPIC_PROVIDER && provider === 'anthropic') {
22
- continue;
23
- }
24
- if (models[modelId]) {
25
- return provider;
26
- }
19
+ if (MODELS.openai[modelId]) {
20
+ return 'openai';
27
21
  }
28
22
  return null;
29
23
  }
30
24
 
31
25
  // 모든 모델 목록 표시
32
26
  async function listAllModels() {
33
- const React = await import('react');
34
- const { ModelListView } = await import('../ui/components/ModelListView.js');
35
-
36
27
  const gpt5Models = getGPT5Models();
37
28
  const openaiModels = gpt5Models.map(id => ({
38
29
  id,
39
30
  ...MODELS.openai[id]
40
31
  }));
41
32
 
42
- // Feature flag에 따라 Claude 모델 표시 여부 결정
43
- let claudeModels = null;
44
- if (ENABLE_ANTHROPIC_PROVIDER) {
45
- const claude4Models = getClaude4Models();
46
- const claude3Models = getClaude3Models();
47
-
48
- claudeModels = {
49
- claude4: claude4Models.map(id => ({
50
- id,
51
- ...CLAUDE_MODELS[id]
52
- })),
53
- claude3: claude3Models.map(id => ({
54
- id,
55
- ...CLAUDE_MODELS[id]
56
- }))
57
- };
58
- }
59
-
60
33
  const component = React.createElement(ModelListView, {
61
- openaiModels,
62
- claudeModels
34
+ openaiModels
63
35
  });
64
36
 
65
37
  const output = await renderInkComponent(component);
@@ -69,23 +41,12 @@ async function listAllModels() {
69
41
  // 현재 설정된 모델 표시
70
42
  async function showCurrentModel() {
71
43
  try {
72
- const React = await import('react');
73
- const { CurrentModelView } = await import('../ui/components/CurrentModelView.js');
74
-
75
44
  const settings = await loadSettings();
76
- const provider = settings?.AI_PROVIDER || 'openai';
77
-
78
- let currentModel;
79
- if (provider === 'anthropic') {
80
- currentModel = settings?.ANTHROPIC_MODEL || DEFAULT_CLAUDE_MODEL;
81
- } else {
82
- currentModel = settings?.OPENAI_MODEL || DEFAULT_OPENAI_MODEL;
83
- }
84
-
85
- const modelInfo = MODELS[provider]?.[currentModel];
45
+ const currentModel = settings?.OPENAI_MODEL || DEFAULT_OPENAI_MODEL;
46
+ const modelInfo = MODELS.openai?.[currentModel];
86
47
 
87
48
  const component = React.createElement(CurrentModelView, {
88
- provider,
49
+ provider: 'openai',
89
50
  modelId: currentModel,
90
51
  modelInfo
91
52
  });
@@ -102,9 +63,7 @@ async function showCurrentModel() {
102
63
  */
103
64
  export default {
104
65
  name: 'model',
105
- description: ENABLE_ANTHROPIC_PROVIDER
106
- ? 'Select AI model (OpenAI or Anthropic)'
107
- : 'Select AI model (OpenAI)',
66
+ description: 'Select AI model (OpenAI)',
108
67
  usage: '/model [model-id] or /model list',
109
68
  handler: async (args, context) => {
110
69
  // 인자가 없으면 현재 모델 표시
@@ -139,17 +98,9 @@ export default {
139
98
  // 현재 설정 로드
140
99
  const settings = await loadSettings();
141
100
 
142
- // Provider와 모델 업데이트
143
- settings.AI_PROVIDER = provider;
144
- process.env.AI_PROVIDER = provider;
145
-
146
- if (provider === 'openai') {
147
- settings.OPENAI_MODEL = modelId;
148
- process.env.OPENAI_MODEL = modelId;
149
- } else if (provider === 'anthropic') {
150
- settings.ANTHROPIC_MODEL = modelId;
151
- process.env.ANTHROPIC_MODEL = modelId;
152
- }
101
+ // 모델 업데이트
102
+ settings.OPENAI_MODEL = modelId;
103
+ process.env.OPENAI_MODEL = modelId;
153
104
 
154
105
  // 설정 저장
155
106
  await saveSettings(settings);
@@ -161,22 +112,14 @@ export default {
161
112
  uiEvents.emit('model:changed', { model: modelId });
162
113
 
163
114
  // 성공 메시지
164
- const React = await import('react');
165
- const { ModelUpdatedView } = await import('../ui/components/ModelUpdatedView.js');
166
-
167
115
  const modelInfo = MODELS[provider][modelId];
168
116
 
169
117
  let warning = null;
170
- if (provider === 'openai' && !settings.OPENAI_API_KEY) {
118
+ if (!settings.OPENAI_API_KEY) {
171
119
  warning = {
172
120
  message: 'OpenAI API key is not configured.',
173
121
  hint: 'Set your API key with: `/apikey openai sk-proj-...`'
174
122
  };
175
- } else if (provider === 'anthropic' && !settings.ANTHROPIC_API_KEY) {
176
- warning = {
177
- message: 'Anthropic API key is not configured.',
178
- hint: 'Set your API key with: `/apikey anthropic sk-ant-...`'
179
- };
180
123
  }
181
124
 
182
125
  const component = React.createElement(ModelUpdatedView, {
@@ -38,7 +38,7 @@ async function checkModelSupport() {
38
38
  const provider = settings?.AI_PROVIDER || 'openai';
39
39
 
40
40
  if (provider !== 'openai') {
41
- return { supported: false, reason: 'Anthropic models do not support reasoning_effort.' };
41
+ return { supported: false, reason: 'Only OpenAI models support reasoning_effort.' };
42
42
  }
43
43
 
44
44
  const modelInfo = OPENAI_MODELS[currentModel];
@@ -3,17 +3,6 @@
3
3
  * 기능 활성화/비활성화를 제어하는 플래그들을 관리합니다.
4
4
  */
5
5
 
6
- /**
7
- * Anthropic 제공자 활성화 여부
8
- *
9
- * true: Anthropic(Claude) 모델을 사용자 인터페이스에 표시하고 사용 가능
10
- * false: Anthropic 관련 기능을 사용자 인터페이스에서 숨김 (구현은 보존됨)
11
- *
12
- * 이 플래그는 사용자가 접하는 UI, 명령어, 설정 화면 등에만 영향을 미치며,
13
- * 실제 API 호출 및 내부 구현 코드는 모두 보존됩니다.
14
- */
15
- export const ENABLE_ANTHROPIC_PROVIDER = false;
16
-
17
6
  /**
18
7
  * 에러 메시지 상세도 설정
19
8
  *
@@ -23,6 +12,5 @@ export const ENABLE_ANTHROPIC_PROVIDER = false;
23
12
  export const ERROR_VERBOSITY = 'concise';
24
13
 
25
14
  export default {
26
- ENABLE_ANTHROPIC_PROVIDER,
27
15
  ERROR_VERBOSITY
28
16
  };
@@ -7,11 +7,11 @@ import React, { useState, useEffect, useMemo, useCallback, memo, useRef } from '
7
7
  import { Box, Text, Newline, useStdout, Static } from 'ink';
8
8
  import { Header } from './components/Header.js';
9
9
  import { Footer } from './components/Footer.js';
10
- import { InputPrompt } from './components/InputPrompt.js';
10
+ import { Input } from './components/Input.js';
11
11
  import { SessionSpinner } from './components/SessionSpinner.js';
12
- import { HistoryItemDisplay } from './components/HistoryItemDisplay.js';
12
+ import { ConversationItem } from './components/ConversationItem.js';
13
13
  import { BlankLine } from './components/BlankLine.js';
14
- import { useTextBuffer } from './utils/text-buffer.js';
14
+ import { useTextBuffer } from './utils/inputBuffer.js';
15
15
  import { uiEvents } from '../system/ui_events.js';
16
16
  import { getToolDisplayConfig, extractMessageFromArgs, formatToolCall, formatToolResult, getToolDisplayName } from '../system/tool_registry.js';
17
17
  import { ToolApprovalPrompt } from './components/ToolApprovalPrompt.js';
@@ -20,9 +20,6 @@ import { createDebugLogger } from '../util/debug_log.js';
20
20
 
21
21
  const debugLog = createDebugLogger('ui_app.log', 'App');
22
22
 
23
- // pendingHistory 렌더링 활성화 여부
24
- const RENDER_PENDING_HISTORY = false;
25
-
26
23
  /**
27
24
  * 빈 줄 추가 여부를 결정하는 함수
28
25
  *
@@ -179,13 +176,13 @@ function shouldAddBlankLineAfter(item, nextItem) {
179
176
  *
180
177
  * ## 기능
181
178
  *
182
- * 히스토리 아이템 배열을 받아서 HistoryItemDisplay와 BlankLine 컴포넌트를
179
+ * 히스토리 아이템 배열을 받아서 ConversationItem과 BlankLine 컴포넌트를
183
180
  * 적절히 조합한 React 엘리먼트 배열을 생성합니다.
184
181
  *
185
182
  * ## 처리 과정
186
183
  *
187
184
  * 각 아이템마다:
188
- * 1. HistoryItemDisplay 컴포넌트 생성
185
+ * 1. ConversationItem 컴포넌트 생성
189
186
  * 2. shouldAddBlankLineAfter()로 빈 줄 필요 여부 확인
190
187
  * 3. 필요하면 BlankLine 컴포넌트 추가
191
188
  *
@@ -193,16 +190,16 @@ function shouldAddBlankLineAfter(item, nextItem) {
193
190
  *
194
191
  * 입력: [user, tool_start, tool_result]
195
192
  * 출력:
196
- * - HistoryItemDisplay(user)
193
+ * - ConversationItem(user)
197
194
  * - BlankLine (user 뒤)
198
- * - HistoryItemDisplay(tool_start)
199
- * - HistoryItemDisplay(tool_result) <- tool_start와 붙음, 빈 줄 없음
195
+ * - ConversationItem(tool_start)
196
+ * - ConversationItem(tool_result) <- tool_start와 붙음, 빈 줄 없음
200
197
  * - BlankLine (tool_result 뒤)
201
198
  *
202
199
  * ## 키 관리
203
200
  *
204
201
  * - startKey부터 시작하여 순차적으로 증가
205
- * - HistoryItemDisplay: `${keyPrefix}-${n}`
202
+ * - ConversationItem: `${keyPrefix}-${n}`
206
203
  * - BlankLine: `${keyPrefix}-blank-${n}`
207
204
  * - 반환값의 nextKey: 다음에 사용할 키 번호
208
205
  *
@@ -234,7 +231,7 @@ function createItemsWithBlankLines(items, terminalWidth, startKey, keyPrefix = '
234
231
 
235
232
  // 1. 아이템 추가
236
233
  elements.push(
237
- React.createElement(HistoryItemDisplay, {
234
+ React.createElement(ConversationItem, {
238
235
  key: `${keyPrefix}-${currentKey}`,
239
236
  item,
240
237
  terminalWidth,
@@ -242,7 +239,7 @@ function createItemsWithBlankLines(items, terminalWidth, startKey, keyPrefix = '
242
239
  isLastInBatch
243
240
  })
244
241
  );
245
- debugLog(`[createItemsWithBlankLines] Added HistoryItemDisplay with key: ${keyPrefix}-${currentKey}`);
242
+ debugLog(`[createItemsWithBlankLines] Added ConversationItem with key: ${keyPrefix}-${currentKey}`);
246
243
  currentKey++;
247
244
 
248
245
  // 2. 빈 줄 추가 여부 확인 (shouldAddBlankLineAfter 함수 참조)
@@ -591,7 +588,8 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
591
588
 
592
589
  debugLog(`[Transform] Transformed type: ${transformed.type}`);
593
590
  debugLog(`[Transform] Transformed toolName: ${transformed.toolName || 'N/A'}`);
594
- debugLog(`[Transform] Transformed text (first 100 chars): ${transformed.text?.substring(0, 100) || 'N/A'}...`);
591
+ const textPreview1 = transformed.text ? (typeof transformed.text === 'string' ? transformed.text.substring(0, 100) : JSON.stringify(transformed.text).substring(0, 100)) : 'N/A';
592
+ debugLog(`[Transform] Transformed text (first 100 chars): ${textPreview1}...`);
595
593
  debugLog(`[Session] isSessionRunning: ${isSessionRunning}`);
596
594
 
597
595
  if (isSessionRunning) {
@@ -643,7 +641,8 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
643
641
  if (prev.length > 0) {
644
642
  debugLog(`[Pair Matching] Pending items:`);
645
643
  prev.forEach((p, idx) => {
646
- debugLog(` [${idx}] type=${p.type}, toolName=${p.toolName || 'N/A'}, text=${p.text?.substring(0, 50) || 'N/A'}...`);
644
+ const textPreview2 = p.text ? (typeof p.text === 'string' ? p.text.substring(0, 50) : JSON.stringify(p.text).substring(0, 50)) : 'N/A';
645
+ debugLog(` [${idx}] type=${p.type}, toolName=${p.toolName || 'N/A'}, text=${textPreview2}...`);
647
646
  });
648
647
  } else {
649
648
  debugLog(`[Pair Matching] WARNING: Pending array is EMPTY - no tool_start to match!`);
@@ -746,7 +745,8 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
746
745
 
747
746
  // 쌍의 시작 부분 (tool_start, code_execution)은 pending에 추가
748
747
  debugLog(`[Pending Add] Adding to pending (waiting for pair): ${newType}, toolName=${transformed.toolName || 'N/A'}`);
749
- debugLog(`[Pending Add] Text: ${transformed.text?.substring(0, 80) || 'N/A'}...`);
748
+ const textPreview3 = transformed.text ? (typeof transformed.text === 'string' ? transformed.text.substring(0, 80) : JSON.stringify(transformed.text).substring(0, 80)) : 'N/A';
749
+ debugLog(`[Pending Add] Text: ${textPreview3}...`);
750
750
  debugLog(`[Pending Add] Current pending items BEFORE add:`);
751
751
  if (prev.length > 0) {
752
752
  prev.forEach((p, idx) => {
@@ -779,9 +779,9 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
779
779
  }, [isSessionRunning, transformEvent, terminalWidth]);
780
780
 
781
781
  // Render a single history item (Memoized)
782
- const MemoizedHistoryItem = React.useMemo(() =>
783
- React.memo(function MemoizedHistoryItem({ item, terminalWidth, nextItem, itemKey }) {
784
- return React.createElement(HistoryItemDisplay, {
782
+ const MemoizedConversationItem = React.useMemo(() =>
783
+ React.memo(function MemoizedConversationItem({ item, terminalWidth, nextItem, itemKey }) {
784
+ return React.createElement(ConversationItem, {
785
785
  item,
786
786
  terminalWidth,
787
787
  nextItem
@@ -791,14 +791,14 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
791
791
 
792
792
  const renderHistoryItem = useCallback((item, index, prefix = 'history', allItems = []) => {
793
793
  const nextItem = allItems[index + 1];
794
- return React.createElement(MemoizedHistoryItem, {
794
+ return React.createElement(MemoizedConversationItem, {
795
795
  key: `${prefix}-${index}`,
796
796
  itemKey: `${prefix}-${index}`,
797
797
  item,
798
798
  terminalWidth,
799
799
  nextItem
800
800
  });
801
- }, [terminalWidth, MemoizedHistoryItem]);
801
+ }, [terminalWidth, MemoizedConversationItem]);
802
802
 
803
803
  // Handle session state transitions
804
804
  const handleSessionTransition = useCallback((wasRunning, isRunning) => {
@@ -914,8 +914,6 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
914
914
  process.env.OPENAI_API_KEY = settings.OPENAI_API_KEY;
915
915
  process.env.OPENAI_MODEL = settings.OPENAI_MODEL;
916
916
  process.env.OPENAI_REASONING_EFFORT = settings.OPENAI_REASONING_EFFORT;
917
- process.env.ANTHROPIC_API_KEY = settings.ANTHROPIC_API_KEY;
918
- process.env.ANTHROPIC_MODEL = settings.ANTHROPIC_MODEL;
919
917
 
920
918
  // 클라이언트 리셋
921
919
  resetAIClients();
@@ -981,7 +979,7 @@ export function App({ onSubmit, onClearScreen, onExit, commands = [], model, ver
981
979
  isRunning: isSessionRunning,
982
980
  message: sessionMessage
983
981
  }),
984
- !approvalRequest && React.createElement(InputPrompt, {
982
+ !approvalRequest && React.createElement(Input, {
985
983
  buffer,
986
984
  onSubmit: handleSubmit,
987
985
  onClearScreen: handleClearScreen,
@@ -0,0 +1,81 @@
1
+ # UI System Documentation
2
+
3
+ 이 디렉토리는 Ink 기반의 터미널 UI 시스템을 포함합니다.
4
+
5
+ ## 구조
6
+
7
+ ```
8
+ ui/
9
+ ├── App.js # 메인 앱 진입점
10
+ ├── index.js # UI 렌더링 진입점
11
+ ├── components/ # UI 컴포넌트
12
+ │ ├── Header.js # 헤더 (로고)
13
+ │ ├── Footer.js # 푸터 (상태 정보)
14
+ │ ├── ConversationItem.js # 개별 대화 아이템
15
+ │ ├── Input.js # 입력 컴포넌트
16
+ │ ├── SessionSpinner.js # 세션 처리 스피너
17
+ │ ├── AutocompleteMenu.js # 자동완성 메뉴
18
+ │ ├── BlankLine.js # 빈 줄 컴포넌트
19
+ │ ├── FileDiffViewer.js # 파일 diff 뷰어
20
+ │ ├── ToolApprovalPrompt.js # 도구 승인 프롬프트
21
+ │ ├── SetupWizard.js # 초기 설정 마법사
22
+ │ ├── HelpView.js # 도움말 뷰
23
+ │ ├── CurrentModelView.js # 현재 모델 뷰
24
+ │ ├── ModelListView.js # 모델 목록 뷰
25
+ │ └── ModelUpdatedView.js # 모델 변경 확인 뷰
26
+ ├── hooks/ # Custom hooks
27
+ │ ├── useKeypress.js # 키보드 이벤트
28
+ │ └── useCompletion.js # 자동완성
29
+ ├── design/ # 디자인 시스템
30
+ │ └── themeColors.js # 테마 색상
31
+ └── utils/ # 유틸리티
32
+ ├── inputBuffer.js # 입력 버퍼 (멀티라인)
33
+ ├── outputRedirector.js # 출력 리다이렉터
34
+ ├── syntaxHighlighter.js # 문법 하이라이터
35
+ ├── markdownParser.js # 마크다운 파서
36
+ ├── diffUtils.js # Diff 유틸리티
37
+ └── renderInkComponent.js # Ink 컴포넌트 렌더러
38
+ ```
39
+
40
+ ## 아키텍처
41
+
42
+ ### 레이아웃 구조
43
+
44
+ ```
45
+ ┌──────────────────────────────────┐
46
+ │ App (Main Layout) │
47
+ │ ├─ Header (Static) │
48
+ │ ├─ History (Static) │
49
+ │ ├─ SessionSpinner (Dynamic) │
50
+ │ ├─ Input │
51
+ │ └─ Footer │
52
+ └──────────────────────────────────┘
53
+ ```
54
+
55
+ ## UI 상태 관리
56
+
57
+ UI 상태는 `uiEvents` 모듈을 통해 관리됩니다. 자세한 내용은 `src/system/ui_events.js`를 참조하세요.
58
+
59
+
60
+ ## 메시지 타입
61
+
62
+ | Type | Icon | Color | Usage |
63
+ |------|------|-------|-------|
64
+ | `user` | `>` | Accent | 사용자 입력 |
65
+ | `assistant` | `◆` | Info | AI 응답 |
66
+ | `system` | `ℹ` | Secondary | 시스템 메시지 |
67
+ | `error` | `✗` | Error | 에러 메시지 |
68
+ | `tool` | `⚙` | Success | 도구 실행 결과 |
69
+ | `thinking` | `💭` | Link | AI 사고 과정 |
70
+
71
+ ## 애니메이션
72
+
73
+ ### SessionSpinner
74
+ - ink-spinner 라이브러리 사용
75
+ - 경과 시간 표시 (초/분)
76
+
77
+ ## 성능 최적화
78
+
79
+ 1. **Static 컴포넌트**: 불변 히스토리는 Static으로 렌더링하여 리렌더링 방지
80
+ 2. **Dynamic 영역**: 진행 중인 작업만 동적으로 업데이트
81
+ 3. **조건부 렌더링**: 필요한 컴포넌트만 표시
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Suggestions display component for autocompletion
2
+ * Autocomplete menu component for command suggestions
3
3
  */
4
4
 
5
5
  import React from 'react';
6
6
  import { Box, Text } from 'ink';
7
- import { theme } from '../themes/semantic-tokens.js';
7
+ import { theme } from '../design/themeColors.js';
8
8
 
9
- export function SuggestionsDisplay({ suggestions = [], activeIndex = 0 }) {
9
+ export function AutocompleteMenu({ suggestions = [], activeIndex = 0 }) {
10
10
  if (suggestions.length === 0) {
11
11
  return null;
12
12
  }