ai-nexus 1.3.2 → 1.3.4

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.
@@ -4,6 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>ai-nexus</title>
7
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 120 120'%3E%3Cdefs%3E%3ClinearGradient id='g1' x1='0%25' y1='0%25' x2='100%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%2300e5ff'/%3E%3Cstop offset='100%25' stop-color='%23a855f7'/%3E%3C/linearGradient%3E%3ClinearGradient id='g2' x1='0%25' y1='100%25' x2='100%25' y2='0%25'%3E%3Cstop offset='0%25' stop-color='%2300e5ff'/%3E%3Cstop offset='100%25' stop-color='%23a855f7'/%3E%3C/linearGradient%3E%3C/defs%3E%3Crect width='120' height='120' rx='28' fill='%230a0a0f'/%3E%3Cline x1='60' y1='30' x2='30' y2='80' stroke='url(%23g1)' stroke-width='3' opacity='0.6'/%3E%3Cline x1='60' y1='30' x2='90' y2='80' stroke='url(%23g1)' stroke-width='3' opacity='0.6'/%3E%3Cline x1='30' y1='80' x2='90' y2='80' stroke='url(%23g2)' stroke-width='3' opacity='0.6'/%3E%3Ccircle cx='60' cy='30' r='10' fill='url(%23g1)'/%3E%3Ccircle cx='30' cy='80' r='10' fill='url(%23g1)'/%3E%3Ccircle cx='90' cy='80' r='10' fill='url(%23g2)'/%3E%3Ccircle cx='60' cy='63' r='5' fill='url(%23g1)' opacity='0.4'/%3E%3C/svg%3E">
7
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
8
9
  <style>
9
10
  :root{--bg:#050508;--card:rgba(255,255,255,0.02);--card2:rgba(255,255,255,0.04);--cyan:#00e5ff;--purple:#a855f7;--pink:#ec4899;--green:#22c55e;--yellow:#eab308;--red:#ef4444;--text:#eeeef0;--text2:#7a7a90;--text3:#44445a;--border:rgba(255,255,255,0.05);--r:20px;--rs:12px;--font:'Inter',system-ui,sans-serif;--mono:'JetBrains Mono',monospace}
@@ -22,7 +23,7 @@ body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:1
22
23
  .hdr{position:sticky;top:0;z-index:100;padding:0 2.5rem;background:rgba(5,5,8,0.7);backdrop-filter:blur(24px) saturate(1.2);border-bottom:1px solid var(--border)}
23
24
  .hdr-inner{max-width:1280px;margin:0 auto;height:60px;display:flex;align-items:center;justify-content:space-between}
24
25
  .logo{display:flex;align-items:center;gap:0.7rem;font-weight:800;font-size:1rem;letter-spacing:-0.03em}
25
- .logo-mark{width:30px;height:30px;border-radius:10px;background:linear-gradient(135deg,var(--cyan),var(--purple));display:grid;place-items:center;font-size:0.55rem;font-weight:900;color:#000;letter-spacing:0}
26
+ .logo-mark{width:30px;height:30px;border-radius:8px;display:grid;place-items:center;overflow:hidden}
26
27
  .logo-txt span{font-weight:400;color:var(--text2)}
27
28
  .hdr-r{display:flex;align-items:center;gap:1.5rem}
28
29
  .live{display:flex;align-items:center;gap:6px;font-size:0.7rem;color:var(--text2);letter-spacing:0.08em;text-transform:uppercase;font-weight:600}
@@ -136,7 +137,7 @@ body{background:var(--bg);color:var(--text);font-family:var(--font);min-height:1
136
137
  <div class="noise"></div>
137
138
 
138
139
  <header class="hdr"><div class="hdr-inner">
139
- <div class="logo"><div class="logo-mark">nx</div><div class="logo-txt">ai-nexus <span>browse</span></div></div>
140
+ <div class="logo"><div class="logo-mark"><svg viewBox="0 0 120 120" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:100%"><defs><linearGradient id="g1" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#00e5ff"/><stop offset="100%" stop-color="#a855f7"/></linearGradient><linearGradient id="g2" x1="0%" y1="100%" x2="100%" y2="0%"><stop offset="0%" stop-color="#00e5ff"/><stop offset="100%" stop-color="#a855f7"/></linearGradient></defs><line x1="60" y1="28" x2="28" y2="82" stroke="url(#g1)" stroke-width="4" opacity="0.6"/><line x1="60" y1="28" x2="92" y2="82" stroke="url(#g1)" stroke-width="4" opacity="0.6"/><line x1="28" y1="82" x2="92" y2="82" stroke="url(#g2)" stroke-width="4" opacity="0.6"/><circle cx="60" cy="28" r="12" fill="url(#g1)"/><circle cx="28" cy="82" r="12" fill="url(#g1)"/><circle cx="92" cy="82" r="12" fill="url(#g2)"/><circle cx="60" cy="64" r="6" fill="url(#g1)" opacity="0.4"/></svg></div><div class="logo-txt">ai-nexus <span>browse</span></div></div>
140
141
  <div class="hdr-r">
141
142
  <div class="live"><span class="live-dot"></span>LIVE</div>
142
143
  <span class="clock" id="clock"></span>
@@ -29,65 +29,65 @@ const require = createRequire(import.meta.url);
29
29
  const TEMPLATES = [
30
30
  { name: '🚀 React/Next.js', value: 'react-nextjs' },
31
31
  { name: '🖥️ Node/Express', value: 'node-express' },
32
- { name: '📝 기본 (최소 설정)', value: 'basic' },
33
- { name: '⏭️ 건너뛰기', value: null },
32
+ { name: '📝 Basic (minimal)', value: 'basic' },
33
+ { name: '⏭️ Skip', value: null },
34
34
  ];
35
35
  export async function initInteractive() {
36
36
  console.clear();
37
37
  printHeader();
38
38
  const builtinConfigDir = path.join(PACKAGE_ROOT, 'config');
39
39
  const configInfo = scanConfigDir(builtinConfigDir);
40
- // Step 1: 설치 범위 선택
40
+ // Step 1: Select scope
41
41
  const { scope } = await inquirer.prompt([
42
42
  {
43
43
  type: 'list',
44
44
  name: 'scope',
45
- message: '설치 범위를 선택하세요',
45
+ message: 'Select installation scope',
46
46
  choices: [
47
- { name: '📁 현재 프로젝트 (.claude/, .codex/)', value: 'project' },
48
- { name: '🏠 전역 설치 (~/.claude/, ~/.codex/)', value: 'global' },
47
+ { name: '📁 Current project (.claude/, .codex/)', value: 'project' },
48
+ { name: '🏠 Global (~/.claude/, ~/.codex/)', value: 'global' },
49
49
  ],
50
50
  },
51
51
  ]);
52
- // Step 2: 도구 선택
52
+ // Step 2: Select tools
53
53
  const { tools } = await inquirer.prompt([
54
54
  {
55
55
  type: 'checkbox',
56
56
  name: 'tools',
57
- message: '설치할 도구를 선택하세요',
57
+ message: 'Select tools to install',
58
58
  choices: [
59
59
  { name: 'Claude Code (.claude/)', value: 'claude', checked: true },
60
60
  { name: 'Codex (.codex/)', value: 'codex', checked: false },
61
61
  { name: 'Cursor (.cursor/rules/)', value: 'cursor', checked: false },
62
62
  ],
63
- validate: (input) => input.length > 0 || '최소 하나 이상 선택해주세요',
63
+ validate: (input) => input.length > 0 || 'Select at least one tool',
64
64
  },
65
65
  ]);
66
- // Step 3: 카테고리 선택
66
+ // Step 3: Select categories
67
67
  const categoryChoices = configInfo.map(cat => ({
68
- name: `${cat.name}/ (${cat.label}) - ${cat.files.length} 파일`,
68
+ name: `${cat.name}/ (${cat.label}) - ${cat.files.length} files`,
69
69
  value: cat.name,
70
70
  checked: ['rules', 'commands'].includes(cat.name),
71
71
  }));
72
72
  // Add hooks and settings options for Claude Code
73
73
  if (tools.includes('claude')) {
74
- categoryChoices.push({ name: 'hooks/ (Semantic Router) - Claude Code hook', value: 'hooks', checked: true }, { name: 'settings.json (Claude Code 설정)', value: 'settings', checked: true });
74
+ categoryChoices.push({ name: 'hooks/ (Semantic Router) - Claude Code hook', value: 'hooks', checked: true }, { name: 'settings.json (Claude Code settings)', value: 'settings', checked: true });
75
75
  }
76
76
  const { categories } = await inquirer.prompt([
77
77
  {
78
78
  type: 'checkbox',
79
79
  name: 'categories',
80
- message: '설치할 항목을 선택하세요 (Space 선택, Enter 확인)',
80
+ message: 'Select categories to install (Space to select, Enter to confirm)',
81
81
  choices: categoryChoices,
82
- validate: (input) => input.length > 0 || '최소 하나 이상 선택해주세요',
82
+ validate: (input) => input.length > 0 || 'Select at least one category',
83
83
  },
84
84
  ]);
85
- // Step 4: 상세 선택 (파일별)
85
+ // Step 4: Detailed file selection
86
86
  const { detailSelect } = await inquirer.prompt([
87
87
  {
88
88
  type: 'confirm',
89
89
  name: 'detailSelect',
90
- message: '파일별로 상세 선택하시겠습니까?',
90
+ message: 'Select individual files?',
91
91
  default: false,
92
92
  },
93
93
  ]);
@@ -109,7 +109,7 @@ export async function initInteractive() {
109
109
  {
110
110
  type: 'checkbox',
111
111
  name: 'files',
112
- message: `설치할 파일을 선택하세요`,
112
+ message: `Select files to install`,
113
113
  choices: fileChoices,
114
114
  pageSize: 15,
115
115
  },
@@ -118,7 +118,7 @@ export async function initInteractive() {
118
118
  }
119
119
  }
120
120
  else {
121
- // 전체 선택
121
+ // Select all
122
122
  for (const category of categories) {
123
123
  const catInfo = configInfo.find(c => c.name === category);
124
124
  if (catInfo) {
@@ -126,63 +126,63 @@ export async function initInteractive() {
126
126
  }
127
127
  }
128
128
  }
129
- // Step 5: 템플릿 선택
129
+ // Step 5: Select template
130
130
  const { template } = await inquirer.prompt([
131
131
  {
132
132
  type: 'list',
133
133
  name: 'template',
134
- message: '프로젝트 템플릿을 선택하세요 (CLAUDE.md 생성)',
134
+ message: 'Select project template (generates CLAUDE.md)',
135
135
  choices: TEMPLATES,
136
136
  },
137
137
  ]);
138
- // Step 6: 설치 방식 선택
138
+ // Step 6: Select install method
139
139
  const { method } = await inquirer.prompt([
140
140
  {
141
141
  type: 'list',
142
142
  name: 'method',
143
- message: '설치 방식을 선택하세요',
143
+ message: 'Select install method',
144
144
  choices: [
145
145
  {
146
- name: '🔗 symlink (ai-nexus update로 자동 업데이트)',
146
+ name: '🔗 symlink (auto-update via ai-nexus update)',
147
147
  value: 'symlink',
148
148
  },
149
149
  {
150
- name: '📄 copy (독립적인 복사본)',
150
+ name: '📄 copy (independent copy)',
151
151
  value: 'copy',
152
152
  },
153
153
  ],
154
154
  },
155
155
  ]);
156
- // Step 7: 확인
157
- console.log(chalk.cyan('\n📋 설치 요약\n'));
156
+ // Step 7: Confirmation
157
+ console.log(chalk.cyan('\n📋 Installation Summary\n'));
158
158
  console.log(chalk.gray('─'.repeat(40)));
159
- console.log(` 범위: ${scope === 'global' ? '전역 (~/)' : '프로젝트 (./)'}`);
160
- console.log(` 도구: ${tools.join(', ')}`);
161
- console.log(` 방식: ${method === 'symlink' ? 'symlink' : 'copy'}`);
162
- console.log(` 템플릿: ${template || '없음'}`);
163
- console.log(` 항목:`);
159
+ console.log(` Scope: ${scope === 'global' ? 'Global (~/)' : 'Project (./)'}`);
160
+ console.log(` Tools: ${tools.join(', ')}`);
161
+ console.log(` Method: ${method === 'symlink' ? 'symlink' : 'copy'}`);
162
+ console.log(` Template: ${template || 'None'}`);
163
+ console.log(` Categories:`);
164
164
  let totalFiles = 0;
165
165
  for (const category of categories) {
166
166
  const count = selectedFiles[category]?.length || 0;
167
167
  totalFiles += count;
168
- console.log(` • ${category}/ (${count})`);
168
+ console.log(` • ${category}/ (${count} files)`);
169
169
  }
170
170
  console.log(chalk.gray('─'.repeat(40)));
171
- console.log(` ${totalFiles} 파일\n`);
171
+ console.log(` Total: ${totalFiles} files\n`);
172
172
  const { confirmed } = await inquirer.prompt([
173
173
  {
174
174
  type: 'confirm',
175
175
  name: 'confirmed',
176
- message: '설치를 진행하시겠습니까?',
176
+ message: 'Proceed with installation?',
177
177
  default: true,
178
178
  },
179
179
  ]);
180
180
  if (!confirmed) {
181
- console.log(chalk.yellow('\n취소되었습니다.\n'));
181
+ console.log(chalk.yellow('\nCancelled.\n'));
182
182
  return;
183
183
  }
184
- // 설치 진행
185
- const spinner = ora('설치 중...').start();
184
+ // Install
185
+ const spinner = ora('Installing...').start();
186
186
  try {
187
187
  await install({
188
188
  scope,
@@ -192,16 +192,16 @@ export async function initInteractive() {
192
192
  template,
193
193
  method,
194
194
  });
195
- spinner.succeed('설치 완료!');
195
+ spinner.succeed('Installation complete!');
196
196
  }
197
197
  catch (error) {
198
- spinner.fail('설치 실패');
198
+ spinner.fail('Installation failed');
199
199
  console.error(chalk.red(error instanceof Error ? error.message : String(error)));
200
200
  return;
201
201
  }
202
- // 완료 메시지
202
+ // Completion message
203
203
  const targetDir = getTargetDir(scope);
204
- console.log(chalk.green('\n✅ ai-nexus 설치 완료!\n'));
204
+ console.log(chalk.green('\n✅ ai-nexus installed successfully!\n'));
205
205
  console.log(chalk.gray('─'.repeat(40)));
206
206
  if (tools.includes('claude')) {
207
207
  console.log(` Claude: ${path.join(targetDir, '.claude')}`);
@@ -212,13 +212,13 @@ export async function initInteractive() {
212
212
  if (tools.includes('cursor')) {
213
213
  console.log(` Cursor: ${path.join(targetDir, '.cursor/rules')}`);
214
214
  }
215
- console.log(` 모드: ${method}`);
215
+ console.log(` Mode: ${method}`);
216
216
  if (template) {
217
- console.log(` 템플릿: ${template}`);
217
+ console.log(` Template: ${template}`);
218
218
  }
219
219
  console.log(chalk.gray('─'.repeat(40)));
220
220
  if (method === 'symlink') {
221
- console.log(chalk.cyan('\n💡 팁: ai-nexus update 최신 규칙을 동기화하세요\n'));
221
+ console.log(chalk.cyan('\n💡 Tip: Run "ai-nexus update" to sync latest rules\n'));
222
222
  }
223
223
  }
224
224
  async function install(selections) {
@@ -392,7 +392,7 @@ function printHeader() {
392
392
  console.log(chalk.cyan(`
393
393
  ╭─────────────────────────────────╮
394
394
  │ │
395
- │ ${chalk.bold('ai-nexus')} 설치 마법사
395
+ │ ${chalk.bold('ai-nexus')} Setup Wizard
396
396
  │ │
397
397
  ╰─────────────────────────────────╯
398
398
  `));
@@ -5,47 +5,44 @@ import { detectInstall } from '../utils/files.js';
5
5
  export async function test(input, options = {}) {
6
6
  const install = detectInstall();
7
7
  if (!install) {
8
- console.log(chalk.yellow('ai-nexus 설치되지 않았습니다.'));
9
- console.log(chalk.gray('먼저 ai-nexus init 또는 ai-nexus install 실행하세요.'));
8
+ console.log(chalk.yellow('ai-nexus is not installed.'));
9
+ console.log(chalk.gray('Run "ai-nexus init" or "ai-nexus install" first.'));
10
10
  return;
11
11
  }
12
- console.log(chalk.cyan('\n🔍 규칙 라우팅 테스트\n'));
13
- console.log(chalk.gray(`입력: "${input}"`));
12
+ console.log(chalk.cyan('\nRule Routing Test\n'));
13
+ console.log(chalk.gray(`Input: "${input}"`));
14
14
  console.log();
15
- const spinner = ora('규칙 선택 중...').start();
15
+ const spinner = ora('Selecting rules...').start();
16
16
  try {
17
17
  let result;
18
18
  if (options.keyword) {
19
- // 키워드 방식만 사용
20
19
  const files = selectFilesWithKeywords(input);
21
20
  result = { files, method: 'keyword' };
22
21
  }
23
22
  else {
24
- // Semantic Router 사용 (가능한 경우)
25
23
  result = await selectFiles(input);
26
24
  }
27
25
  spinner.stop();
28
- // 사용된 방식 표시
29
26
  const methodLabel = result.method === 'semantic'
30
27
  ? chalk.magenta('AI (Semantic Router)')
31
- : chalk.blue('키워드 매칭');
32
- console.log(chalk.gray(`방식: ${methodLabel}`));
28
+ : chalk.blue('Keyword matching');
29
+ console.log(chalk.gray(`Method: ${methodLabel}`));
33
30
  if (result.method === 'keyword' && isSemanticRouterEnabled()) {
34
- console.log(chalk.gray('(AI 선택 실패, 키워드로 폴백)'));
31
+ console.log(chalk.gray('(AI selection failed, keyword fallback)'));
35
32
  }
36
33
  else if (result.method === 'keyword' && !options.keyword) {
37
- console.log(chalk.gray('(SEMANTIC_ROUTER_ENABLED=true API 키 필요)'));
34
+ console.log(chalk.gray('(Requires SEMANTIC_ROUTER_ENABLED=true and API key)'));
38
35
  }
39
36
  console.log();
40
37
  if (result.files.length === 0) {
41
- console.log(chalk.yellow('선택된 규칙 파일이 없습니다.'));
38
+ console.log(chalk.yellow('No rule files selected.'));
42
39
  console.log();
43
- console.log(chalk.gray('키워드 매칭에 사용되는 키워드:'));
40
+ console.log(chalk.gray('Available keywords:'));
44
41
  const keywords = Object.keys(getKeywordMap()).slice(0, 10);
45
42
  console.log(chalk.gray(` ${keywords.join(', ')} ...`));
46
43
  }
47
44
  else {
48
- console.log(chalk.green(`선택된 파일 (${result.files.length}):`));
45
+ console.log(chalk.green(`Selected files (${result.files.length}):`));
49
46
  for (const file of result.files) {
50
47
  console.log(chalk.white(` • ${file}`));
51
48
  }
@@ -53,15 +50,14 @@ export async function test(input, options = {}) {
53
50
  console.log();
54
51
  }
55
52
  catch (error) {
56
- spinner.fail('오류 발생');
53
+ spinner.fail('Error occurred');
57
54
  console.error(chalk.red(error instanceof Error ? error.message : String(error)));
58
55
  }
59
56
  }
60
57
  export async function testKeywords() {
61
- console.log(chalk.cyan('\n📚 등록된 키워드 목록\n'));
58
+ console.log(chalk.cyan('\nRegistered Keywords\n'));
62
59
  const keywordMap = getKeywordMap();
63
60
  const categories = ['rules', 'commands', 'skills', 'agents', 'contexts'];
64
- // 카테고리별로 그룹화
65
61
  const byCategory = {};
66
62
  for (const [keyword, files] of Object.entries(keywordMap)) {
67
63
  for (const category of categories) {
@@ -76,19 +72,18 @@ export async function testKeywords() {
76
72
  if (byCategory[category]?.length) {
77
73
  console.log(chalk.yellow(`${category}/`));
78
74
  const unique = [...new Set(byCategory[category])];
79
- console.log(chalk.gray(` 키워드: ${unique.join(', ')}`));
75
+ console.log(chalk.gray(` Keywords: ${unique.join(', ')}`));
80
76
  console.log();
81
77
  }
82
78
  }
83
- // Semantic Router 상태
84
79
  console.log(chalk.gray('─'.repeat(40)));
85
80
  if (isSemanticRouterEnabled()) {
86
- console.log(chalk.green('Semantic Router 활성화됨'));
81
+ console.log(chalk.green('Semantic Router enabled'));
87
82
  }
88
83
  else {
89
- console.log(chalk.yellow('Semantic Router 비활성화'));
90
- console.log(chalk.gray(' 활성화: SEMANTIC_ROUTER_ENABLED=true'));
91
- console.log(chalk.gray(' API 키: ANTHROPIC_API_KEY 또는 OPENAI_API_KEY'));
84
+ console.log(chalk.yellow('Semantic Router disabled'));
85
+ console.log(chalk.gray(' Enable: SEMANTIC_ROUTER_ENABLED=true'));
86
+ console.log(chalk.gray(' API key: ANTHROPIC_API_KEY or OPENAI_API_KEY'));
92
87
  }
93
88
  console.log();
94
89
  }
@@ -2,15 +2,15 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import https from 'https';
4
4
  import { detectInstall } from './files.js';
5
- // 환경변수
5
+ // Environment variables
6
6
  const ANTHROPIC_API_KEY = process.env.ANTHROPIC_API_KEY;
7
7
  const OPENAI_API_KEY = process.env.OPENAI_API_KEY;
8
8
  const SEMANTIC_ROUTER_ENABLED = process.env.SEMANTIC_ROUTER_ENABLED === 'true';
9
- // 모델 설정
9
+ // Model configuration
10
10
  const ANTHROPIC_MODEL = process.env.ANTHROPIC_MODEL || 'claude-3-haiku-20240307';
11
11
  const OPENAI_MODEL = process.env.OPENAI_MODEL || 'gpt-4o-mini';
12
12
  // ─────────────────────────────────────────────
13
- // 파일 목록 설명 수집
13
+ // File list and description collection
14
14
  // ─────────────────────────────────────────────
15
15
  export function getFileList(configDir) {
16
16
  const categories = ['rules', 'commands', 'skills', 'agents', 'contexts'];
@@ -22,7 +22,7 @@ export function getFileList(configDir) {
22
22
  const entries = fs.readdirSync(dir, { withFileTypes: true });
23
23
  for (const entry of entries) {
24
24
  if (entry.isDirectory()) {
25
- // 하위 디렉토리 처리 (예: affaan-m/)
25
+ // Handle subdirectories (e.g., affaan-m/)
26
26
  const subDir = path.join(dir, entry.name);
27
27
  const subFiles = fs.readdirSync(subDir).filter(f => f.endsWith('.md'));
28
28
  for (const file of subFiles) {
@@ -45,7 +45,7 @@ export function getFileList(configDir) {
45
45
  function parseFileInfo(filePath, category, fileName) {
46
46
  try {
47
47
  const content = fs.readFileSync(filePath, 'utf8');
48
- // frontmatter에서 description, keywords 추출
48
+ // Extract description and keywords from frontmatter
49
49
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
50
50
  let description = '';
51
51
  let keywords = [];
@@ -59,7 +59,7 @@ function parseFileInfo(filePath, category, fileName) {
59
59
  keywords = keywordsMatch[1].split(',').map(k => k.trim().replace(/['"]/g, ''));
60
60
  }
61
61
  }
62
- // description이 없으면 H1에서 추출
62
+ // Fall back to first H1 heading if no description
63
63
  if (!description) {
64
64
  const firstLine = content.split('\n').find(l => l.startsWith('#'));
65
65
  if (firstLine)
@@ -78,7 +78,7 @@ function parseFileInfo(filePath, category, fileName) {
78
78
  }
79
79
  }
80
80
  // ─────────────────────────────────────────────
81
- // Claude API 호출
81
+ // Claude API call
82
82
  // ─────────────────────────────────────────────
83
83
  async function callClaude(prompt) {
84
84
  return new Promise((resolve, reject) => {
@@ -121,7 +121,7 @@ async function callClaude(prompt) {
121
121
  });
122
122
  }
123
123
  // ─────────────────────────────────────────────
124
- // OpenAI API 호출
124
+ // OpenAI API call
125
125
  // ─────────────────────────────────────────────
126
126
  async function callOpenAI(prompt) {
127
127
  return new Promise((resolve, reject) => {
@@ -163,23 +163,23 @@ async function callOpenAI(prompt) {
163
163
  });
164
164
  }
165
165
  // ─────────────────────────────────────────────
166
- // Semantic Router - AI 기반 파일 선택
166
+ // Semantic Router - AI-based file selection
167
167
  // ─────────────────────────────────────────────
168
168
  async function selectFilesWithAI(userInput, fileList) {
169
169
  const fileListText = fileList.map(f => `- ${f.path}: ${f.description}`).join('\n');
170
- const prompt = `사용자가 AI 코딩 어시스턴트에게 다음과 같이 요청했습니다:
170
+ const prompt = `A user made the following request to an AI coding assistant:
171
171
  "${userInput}"
172
172
 
173
- 다음은 사용 가능한 규칙/스킬 파일 목록입니다:
173
+ Here are the available rule/skill files:
174
174
  ${fileListText}
175
175
 
176
- 요청을 처리하는 필요한 파일만 선택해주세요.
177
- 관련 없는 파일은 절대 선택하지 마세요.
176
+ Select only the files that are essential for handling this request.
177
+ Do not select unrelated files.
178
178
 
179
- 응답 형식 (JSON 배열만, 다른 텍스트 없이):
179
+ Response format (JSON array only, no other text):
180
180
  ["rules/commit.md", "commands/commit.md"]
181
181
 
182
- 필요한 파일이 없으면 배열을 반환하세요: []`;
182
+ If no files are needed, return an empty array: []`;
183
183
  let response;
184
184
  try {
185
185
  if (ANTHROPIC_API_KEY) {
@@ -191,7 +191,7 @@ ${fileListText}
191
191
  else {
192
192
  return null;
193
193
  }
194
- // JSON 배열 추출
194
+ // Extract JSON array
195
195
  const match = response.match(/\[[\s\S]*?\]/);
196
196
  if (match) {
197
197
  return JSON.parse(match[0]);
@@ -259,7 +259,7 @@ export function selectFilesWithKeywords(userInput) {
259
259
  return selected;
260
260
  }
261
261
  // ─────────────────────────────────────────────
262
- // 파일 내용 로드
262
+ // Load file contents
263
263
  // ─────────────────────────────────────────────
264
264
  export function loadFile(configDir, filePath) {
265
265
  const fullPath = path.join(configDir, filePath);
@@ -270,12 +270,12 @@ export function loadFile(configDir, filePath) {
270
270
  }
271
271
  }
272
272
  catch {
273
- // 무시
273
+ // Ignore
274
274
  }
275
275
  return '';
276
276
  }
277
277
  // ─────────────────────────────────────────────
278
- // 메인 라우터 함수
278
+ // Main router function
279
279
  // ─────────────────────────────────────────────
280
280
  export async function selectFiles(userInput) {
281
281
  const install = detectInstall();
@@ -283,7 +283,7 @@ export async function selectFiles(userInput) {
283
283
  return { files: [], method: 'keyword' };
284
284
  }
285
285
  const configDir = path.join(install.configPath, 'config');
286
- // Semantic Router 활성화 시 AI 사용
286
+ // Use AI when Semantic Router is enabled
287
287
  if (SEMANTIC_ROUTER_ENABLED && (ANTHROPIC_API_KEY || OPENAI_API_KEY)) {
288
288
  const fileList = getFileList(configDir);
289
289
  const aiSelected = await selectFilesWithAI(userInput, fileList);
@@ -291,7 +291,7 @@ export async function selectFiles(userInput) {
291
291
  return { files: aiSelected, method: 'semantic' };
292
292
  }
293
293
  }
294
- // 폴백: 키워드 방식
294
+ // Fallback: keyword matching
295
295
  return { files: selectFilesWithKeywords(userInput), method: 'keyword' };
296
296
  }
297
297
  export function loadSelectedFiles(files) {
@@ -299,7 +299,7 @@ export function loadSelectedFiles(files) {
299
299
  if (!install)
300
300
  return '';
301
301
  const configDir = path.join(install.configPath, 'config');
302
- // 항상 essential.md 먼저 로드
302
+ // Always load essential.md first
303
303
  let output = loadFile(configDir, 'rules/essential.md');
304
304
  for (const filePath of files) {
305
305
  if (filePath !== 'rules/essential.md') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-nexus",
3
- "version": "1.3.2",
3
+ "version": "1.3.4",
4
4
  "description": "AI coding assistant rule manager for Claude Code, Codex, and Cursor",
5
5
  "main": "dist/index.js",
6
6
  "bin": {