cx-skills 1.3.0 → 1.7.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.
- package/README.ko.md +35 -29
- package/README.md +34 -28
- package/bin/cx-skills.mjs +307 -24
- package/hud/hud-qos-status.mjs +253 -106
- package/package.json +3 -2
- package/scripts/cli-route.sh +189 -22
- package/scripts/mcp-check.mjs +88 -0
- package/scripts/setup.mjs +48 -2
- package/skills/cx-codex/SKILL.md +53 -0
- package/skills/cx-gemini/SKILL.md +70 -0
package/README.ko.md
CHANGED
|
@@ -39,8 +39,11 @@ npm install -g cx-skills
|
|
|
39
39
|
# 수동 모드 — agent 직접 지정
|
|
40
40
|
/cx-auto 3:codex "src/api, src/auth, src/payment 각각 리뷰"
|
|
41
41
|
|
|
42
|
-
#
|
|
43
|
-
/cx-
|
|
42
|
+
# Codex-only (Gemini 불필요)
|
|
43
|
+
/cx-codex "리팩터링 + 리뷰"
|
|
44
|
+
|
|
45
|
+
# Gemini-only (Codex 불필요)
|
|
46
|
+
/cx-gemini "구현 + 문서화"
|
|
44
47
|
```
|
|
45
48
|
|
|
46
49
|
끝입니다. `cli-route.sh`는 세션 시작 시 자동 설치됩니다.
|
|
@@ -64,10 +67,8 @@ npm install -g cx-skills
|
|
|
64
67
|
|------|---------|------|
|
|
65
68
|
| **cx-auto** (자동) | N (AI 결정) | 복합 작업 — AI가 분류 + 분해 |
|
|
66
69
|
| **cx-auto** (수동) | N:agent | 직접 제어 — agent와 워커 수 지정 |
|
|
67
|
-
| **cx-
|
|
68
|
-
| **
|
|
69
|
-
| **hteam** | Codex + Gemini + Claude | 혼합 작업 (코드 + UI + 테스트) |
|
|
70
|
-
| **kteam** | N (Windows 안정화) | Windows 환경 Gemini 안정화 적용 |
|
|
70
|
+
| **cx-codex** | N (Codex-only) | Gemini CLI 없는 환경 |
|
|
71
|
+
| **cx-gemini** | N (Gemini-only) | Codex CLI 없는 환경 |
|
|
71
72
|
|
|
72
73
|
---
|
|
73
74
|
|
|
@@ -78,9 +79,10 @@ npm install -g cx-skills
|
|
|
78
79
|
| 스킬 | 설명 | 예시 |
|
|
79
80
|
|------|------|------|
|
|
80
81
|
| **cx-auto** | 통합 병렬 오케스트레이터 | `/cx-auto "분석 + 구현 + 테스트"` |
|
|
81
|
-
| **cx-
|
|
82
|
+
| **cx-codex** | Codex-only 오케스트레이터 (Gemini 불필요) | `/cx-codex "리팩터링 + 리뷰"` |
|
|
83
|
+
| **cx-gemini** | Gemini-only 오케스트레이터 (Codex 불필요) | `/cx-gemini "구현 + 문서화"` |
|
|
82
84
|
|
|
83
|
-
### CLI 커맨드 (
|
|
85
|
+
### CLI 커맨드 (cx-auto 숏컷)
|
|
84
86
|
|
|
85
87
|
| 커맨드 | 라우팅 대상 | 용도 |
|
|
86
88
|
|--------|-----------|------|
|
|
@@ -93,16 +95,6 @@ npm install -g cx-skills
|
|
|
93
95
|
| `/test` | Claude | 테스트 실행 |
|
|
94
96
|
| `/explain` | Codex | 코드 설명 |
|
|
95
97
|
|
|
96
|
-
[전체 커맨드 목록 →](skills/sc/SKILL.md)
|
|
97
|
-
|
|
98
|
-
### 팀 오케스트레이션
|
|
99
|
-
|
|
100
|
-
| 스킬 | 설명 |
|
|
101
|
-
|------|------|
|
|
102
|
-
| **bteam** | Codex 워커 기반 멀티 에이전트 팀 |
|
|
103
|
-
| **hteam** | 하이브리드 팀 (Codex + Gemini + Claude) |
|
|
104
|
-
| **kteam** | Windows 안정화 팀 변형 |
|
|
105
|
-
|
|
106
98
|
### 유틸리티
|
|
107
99
|
|
|
108
100
|
| 스킬 | 설명 |
|
|
@@ -221,12 +213,23 @@ gemini -p "echo hello"
|
|
|
221
213
|
|
|
222
214
|
### CLI 없이 사용
|
|
223
215
|
|
|
224
|
-
Codex/Gemini CLI가 없어도 cx-skills는 동작합니다.
|
|
216
|
+
Codex/Gemini CLI가 없어도 cx-skills는 동작합니다. `cli-route.sh` (v1.4+)가 자동으로 **fallback** 처리합니다:
|
|
225
217
|
|
|
226
218
|
```
|
|
227
|
-
Codex 없음
|
|
228
|
-
Gemini 없음 →
|
|
229
|
-
둘 다 없음
|
|
219
|
+
Codex 없음 → Codex 에이전트를 Gemini로 리매핑 (있으면) 또는 Claude 네이티브
|
|
220
|
+
Gemini 없음 → Gemini 에이전트를 Codex로 리매핑 (있으면) 또는 Claude 네이티브
|
|
221
|
+
둘 다 없음 → 모든 작업이 Claude 네이티브로 실행 (비용 증가)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
환경변수 또는 전용 스킬로 모드를 강제할 수도 있습니다:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# 환경변수
|
|
228
|
+
CX_CLI_MODE=codex bash ~/.claude/scripts/cli-route.sh executor "작업" implement
|
|
229
|
+
|
|
230
|
+
# 전용 스킬
|
|
231
|
+
/cx-codex "작업" # 모든 외부 CLI 작업을 Codex로 강제
|
|
232
|
+
/cx-gemini "작업" # 모든 외부 CLI 작업을 Gemini로 강제
|
|
230
233
|
```
|
|
231
234
|
|
|
232
235
|
---
|
|
@@ -263,6 +266,8 @@ head -6 ~/.claude/scripts/cli-route.sh | grep VERSION
|
|
|
263
266
|
| v1.1 | stderr 분리, 출력 필터링, 타임아웃, MCP 프로필 지원 |
|
|
264
267
|
| v1.2 | effort 동적 라우팅, bg/fg 모드, Opus 직접 수행, Gemini 모델 분기 |
|
|
265
268
|
| v1.3 | context-file 지원, DAG 실행 연계 |
|
|
269
|
+
| v1.4 | CX_CLI_MODE 지원 (codex-only/gemini-only), CLI 미설치 자동 fallback |
|
|
270
|
+
| v1.5 | MCP 인벤토리 캐싱 — 실제 서버 가용성 기반 동적 힌트 생성 |
|
|
266
271
|
|
|
267
272
|
### 자동 업데이트
|
|
268
273
|
|
|
@@ -298,15 +303,16 @@ cx-skills/
|
|
|
298
303
|
├── hooks/
|
|
299
304
|
│ └── hooks.json # 라이프사이클 훅 (자동 설정)
|
|
300
305
|
├── scripts/
|
|
301
|
-
│ ├── cli-route.sh # CLI 라우팅 래퍼 (v1.
|
|
306
|
+
│ ├── cli-route.sh # CLI 라우팅 래퍼 (v1.5)
|
|
307
|
+
│ ├── mcp-check.mjs # MCP 인벤토리 캐시 (백그라운드)
|
|
302
308
|
│ └── setup.mjs # 세션 시작 시 자동 설치
|
|
303
|
-
├── skills/
|
|
309
|
+
├── skills/
|
|
304
310
|
│ ├── cx-auto/ # 통합 병렬 오케스트레이터
|
|
305
|
-
│ ├── cx-
|
|
306
|
-
│ ├──
|
|
307
|
-
│ ├──
|
|
308
|
-
│ ├──
|
|
309
|
-
│ └──
|
|
311
|
+
│ ├── cx-codex/ # Codex-only 오케스트레이터
|
|
312
|
+
│ ├── cx-gemini/ # Gemini-only 오케스트레이터
|
|
313
|
+
│ ├── auto-verify/ # 실행 후 검증
|
|
314
|
+
│ ├── manage-skills/ # 스킬 관리
|
|
315
|
+
│ └── verify-implementation/ # 통합 검증
|
|
310
316
|
├── hud/
|
|
311
317
|
│ └── hud-qos-status.mjs # 상태바 HUD
|
|
312
318
|
├── package.json
|
package/README.md
CHANGED
|
@@ -41,8 +41,11 @@ npm install -g cx-skills
|
|
|
41
41
|
# Manual mode — specify agent directly
|
|
42
42
|
/cx-auto 3:codex "review src/api, src/auth, src/payment"
|
|
43
43
|
|
|
44
|
-
#
|
|
45
|
-
/cx-
|
|
44
|
+
# Codex-only (no Gemini needed)
|
|
45
|
+
/cx-codex "refactor + review"
|
|
46
|
+
|
|
47
|
+
# Gemini-only (no Codex needed)
|
|
48
|
+
/cx-gemini "implement + document"
|
|
46
49
|
```
|
|
47
50
|
|
|
48
51
|
That's it. `cli-route.sh` is auto-installed on session start.
|
|
@@ -66,10 +69,8 @@ That's it. `cli-route.sh` is auto-installed on session start.
|
|
|
66
69
|
|------|---------|----------|
|
|
67
70
|
| **cx-auto** (auto) | N (AI decides) | Complex multi-part tasks — AI classifies + decomposes |
|
|
68
71
|
| **cx-auto** (manual) | N:agent | Direct control — you choose agent and worker count |
|
|
69
|
-
| **cx-
|
|
70
|
-
| **
|
|
71
|
-
| **hteam** | Codex + Gemini + Claude | Mixed tasks (code + UI + testing) |
|
|
72
|
-
| **kteam** | N (Windows stable) | Windows environments with Gemini stability fixes |
|
|
72
|
+
| **cx-codex** | N (Codex-only) | Environments without Gemini CLI |
|
|
73
|
+
| **cx-gemini** | N (Gemini-only) | Environments without Codex CLI |
|
|
73
74
|
|
|
74
75
|
---
|
|
75
76
|
|
|
@@ -80,9 +81,10 @@ That's it. `cli-route.sh` is auto-installed on session start.
|
|
|
80
81
|
| Skill | Description | Example |
|
|
81
82
|
|-------|-------------|---------|
|
|
82
83
|
| **cx-auto** | Unified parallel orchestrator | `/cx-auto "analyze + implement + test"` |
|
|
83
|
-
| **cx-
|
|
84
|
+
| **cx-codex** | Codex-only orchestrator (no Gemini) | `/cx-codex "refactor + review"` |
|
|
85
|
+
| **cx-gemini** | Gemini-only orchestrator (no Codex) | `/cx-gemini "implement + document"` |
|
|
84
86
|
|
|
85
|
-
### CLI Commands (via
|
|
87
|
+
### CLI Commands (via cx-auto shortcuts)
|
|
86
88
|
|
|
87
89
|
| Command | Routes To | Use Case |
|
|
88
90
|
|---------|-----------|----------|
|
|
@@ -95,16 +97,6 @@ That's it. `cli-route.sh` is auto-installed on session start.
|
|
|
95
97
|
| `/test` | Claude | Test execution |
|
|
96
98
|
| `/explain` | Codex | Code explanation |
|
|
97
99
|
|
|
98
|
-
[Full command list →](skills/sc/SKILL.md)
|
|
99
|
-
|
|
100
|
-
### Team Orchestration
|
|
101
|
-
|
|
102
|
-
| Skill | Description |
|
|
103
|
-
|-------|-------------|
|
|
104
|
-
| **bteam** | Multi-agent team with Codex workers |
|
|
105
|
-
| **hteam** | Hybrid team (Codex + Gemini + Claude) |
|
|
106
|
-
| **kteam** | Windows-stabilized team variant |
|
|
107
|
-
|
|
108
100
|
### Utilities
|
|
109
101
|
|
|
110
102
|
| Skill | Description |
|
|
@@ -223,14 +215,25 @@ gemini -p "echo hello"
|
|
|
223
215
|
|
|
224
216
|
### Without External CLIs
|
|
225
217
|
|
|
226
|
-
cx-skills works without Codex/Gemini. Missing CLIs automatically **fallback
|
|
218
|
+
cx-skills works without Codex/Gemini. Missing CLIs automatically **fallback** via `cli-route.sh` (v1.4+):
|
|
227
219
|
|
|
228
220
|
```
|
|
229
|
-
No Codex
|
|
230
|
-
No Gemini →
|
|
221
|
+
No Codex → Codex agents reroute to Gemini (if available) or Claude native
|
|
222
|
+
No Gemini → Gemini agents reroute to Codex (if available) or Claude native
|
|
231
223
|
Neither → All tasks run via Claude native (higher cost)
|
|
232
224
|
```
|
|
233
225
|
|
|
226
|
+
You can also force a specific mode via environment variable or dedicated skills:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
# Environment variable
|
|
230
|
+
CX_CLI_MODE=codex bash ~/.claude/scripts/cli-route.sh executor "task" implement
|
|
231
|
+
|
|
232
|
+
# Dedicated skills
|
|
233
|
+
/cx-codex "task" # Forces all external CLI work to Codex
|
|
234
|
+
/cx-gemini "task" # Forces all external CLI work to Gemini
|
|
235
|
+
```
|
|
236
|
+
|
|
234
237
|
---
|
|
235
238
|
|
|
236
239
|
## OMC Compatibility
|
|
@@ -276,6 +279,8 @@ Follows [Semantic Versioning](https://semver.org/):
|
|
|
276
279
|
| v1.1 | stderr separation, output filtering, timeout, MCP profile support |
|
|
277
280
|
| v1.2 | Dynamic effort routing, bg/fg modes, Opus oversight, Gemini model branching |
|
|
278
281
|
| v1.3 | Context-file support, DAG execution integration |
|
|
282
|
+
| v1.4 | CX_CLI_MODE support (codex-only/gemini-only), auto-fallback for missing CLIs |
|
|
283
|
+
| v1.5 | MCP inventory caching — dynamic hints based on actual server availability |
|
|
279
284
|
|
|
280
285
|
### Auto-Update
|
|
281
286
|
|
|
@@ -298,15 +303,16 @@ cx-skills/
|
|
|
298
303
|
├── hooks/
|
|
299
304
|
│ └── hooks.json # Lifecycle hooks (auto-setup)
|
|
300
305
|
├── scripts/
|
|
301
|
-
│ ├── cli-route.sh # CLI routing wrapper (v1.
|
|
306
|
+
│ ├── cli-route.sh # CLI routing wrapper (v1.5)
|
|
307
|
+
│ ├── mcp-check.mjs # MCP inventory cache (background)
|
|
302
308
|
│ └── setup.mjs # Auto-install on session start
|
|
303
|
-
├── skills/
|
|
309
|
+
├── skills/
|
|
304
310
|
│ ├── cx-auto/ # Unified parallel orchestrator
|
|
305
|
-
│ ├── cx-
|
|
306
|
-
│ ├──
|
|
307
|
-
│ ├──
|
|
308
|
-
│ ├──
|
|
309
|
-
│ └──
|
|
311
|
+
│ ├── cx-codex/ # Codex-only orchestrator
|
|
312
|
+
│ ├── cx-gemini/ # Gemini-only orchestrator
|
|
313
|
+
│ ├── auto-verify/ # Post-execution verification
|
|
314
|
+
│ ├── manage-skills/ # Skill management
|
|
315
|
+
│ └── verify-implementation/ # Integration verification
|
|
310
316
|
├── hud/
|
|
311
317
|
│ └── hud-qos-status.mjs # Status bar HUD
|
|
312
318
|
├── package.json
|
package/bin/cx-skills.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
// cx-skills CLI — setup, doctor, version
|
|
3
3
|
import { copyFileSync, existsSync, readFileSync, mkdirSync, chmodSync, readdirSync } from "fs";
|
|
4
4
|
import { join, dirname } from "path";
|
|
@@ -34,6 +34,32 @@ function which(cmd) {
|
|
|
34
34
|
} catch { return null; }
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function whichInShell(cmd, shell) {
|
|
38
|
+
const cmds = {
|
|
39
|
+
bash: `bash -c 'command -v ${cmd} 2>/dev/null'`,
|
|
40
|
+
cmd: `cmd /c where ${cmd} 2>nul`,
|
|
41
|
+
pwsh: `pwsh -NoProfile -c "(Get-Command ${cmd} -EA SilentlyContinue).Source"`,
|
|
42
|
+
};
|
|
43
|
+
const command = cmds[shell];
|
|
44
|
+
if (!command) return null;
|
|
45
|
+
try {
|
|
46
|
+
const result = execSync(command, {
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
timeout: 8000,
|
|
49
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
50
|
+
}).trim();
|
|
51
|
+
return result.split(/\r?\n/)[0] || null;
|
|
52
|
+
} catch { return null; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function checkShellAvailable(shell) {
|
|
56
|
+
const cmds = { bash: "bash --version", cmd: "cmd /c echo ok", pwsh: "pwsh -NoProfile -c echo ok" };
|
|
57
|
+
try {
|
|
58
|
+
execSync(cmds[shell], { encoding: "utf8", timeout: 5000, stdio: ["pipe", "pipe", "ignore"] });
|
|
59
|
+
return true;
|
|
60
|
+
} catch { return false; }
|
|
61
|
+
}
|
|
62
|
+
|
|
37
63
|
function getVersion(filePath) {
|
|
38
64
|
try {
|
|
39
65
|
const content = readFileSync(filePath, "utf8");
|
|
@@ -61,17 +87,57 @@ function syncFile(src, dst, label) {
|
|
|
61
87
|
return true;
|
|
62
88
|
}
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
const srcContent = readFileSync(src, "utf8");
|
|
91
|
+
const dstContent = readFileSync(dst, "utf8");
|
|
92
|
+
if (srcContent !== dstContent) {
|
|
65
93
|
copyFileSync(src, dst);
|
|
66
94
|
try { chmodSync(dst, 0o755); } catch {}
|
|
67
|
-
|
|
95
|
+
const verInfo = (srcVer && dstVer && srcVer !== dstVer)
|
|
96
|
+
? `(v${dstVer} → v${srcVer})`
|
|
97
|
+
: srcVer ? `(v${srcVer}, 내용 변경)` : "(내용 변경)";
|
|
98
|
+
ok(`${label}: 업데이트됨 ${verInfo}`);
|
|
68
99
|
return true;
|
|
69
100
|
}
|
|
70
101
|
|
|
71
|
-
ok(`${label}: 최신 상태 ${
|
|
102
|
+
ok(`${label}: 최신 상태 ${srcVer ? `(v${srcVer})` : ""}`);
|
|
72
103
|
return false;
|
|
73
104
|
}
|
|
74
105
|
|
|
106
|
+
// ── 크로스 셸 진단 ──
|
|
107
|
+
|
|
108
|
+
function checkCliCrossShell(cmd, installHint) {
|
|
109
|
+
const shells = process.platform === "win32" ? ["bash", "cmd", "pwsh"] : ["bash"];
|
|
110
|
+
let anyFound = false;
|
|
111
|
+
let bashMissing = false;
|
|
112
|
+
|
|
113
|
+
for (const shell of shells) {
|
|
114
|
+
if (!checkShellAvailable(shell)) {
|
|
115
|
+
info(`${shell}: ${DIM}셸 없음 (건너뜀)${RESET}`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
const p = whichInShell(cmd, shell);
|
|
119
|
+
if (p) {
|
|
120
|
+
ok(`${shell}: ${p}`);
|
|
121
|
+
anyFound = true;
|
|
122
|
+
} else {
|
|
123
|
+
fail(`${shell}: 미발견`);
|
|
124
|
+
if (shell === "bash") bashMissing = true;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!anyFound) {
|
|
129
|
+
info(`미설치 (선택사항) — ${installHint}`);
|
|
130
|
+
info("없으면 Claude 네이티브 에이전트로 fallback");
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
if (bashMissing) {
|
|
134
|
+
warn("bash에서 미발견 — cli-route.sh 실행 불가");
|
|
135
|
+
info('→ ~/.bashrc에 추가: export PATH="$PATH:$APPDATA/npm"');
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
return 0;
|
|
139
|
+
}
|
|
140
|
+
|
|
75
141
|
// ── 명령어 ──
|
|
76
142
|
|
|
77
143
|
function cmdSetup() {
|
|
@@ -150,37 +216,26 @@ function cmdDoctor() {
|
|
|
150
216
|
warn("미설치 (선택사항) — cx-skills setup으로 설치 가능");
|
|
151
217
|
}
|
|
152
218
|
|
|
153
|
-
// 3. Codex CLI
|
|
219
|
+
// 3. Codex CLI (크로스 셸 체크)
|
|
154
220
|
console.log(`${CYAN}[Codex CLI]${RESET}`);
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
ok(`설치됨 — ${codexPath}`);
|
|
158
|
-
// API 키 확인
|
|
221
|
+
issues += checkCliCrossShell("codex", "npm install -g @openai/codex");
|
|
222
|
+
if (which("codex")) {
|
|
159
223
|
if (process.env.OPENAI_API_KEY) {
|
|
160
224
|
ok("OPENAI_API_KEY 설정됨");
|
|
161
225
|
} else {
|
|
162
226
|
warn("OPENAI_API_KEY 미설정 (Codex Pro 구독이면 불필요)");
|
|
163
227
|
}
|
|
164
|
-
} else {
|
|
165
|
-
warn("미설치 (선택사항) — npm install -g @openai/codex");
|
|
166
|
-
info("없으면 Claude 네이티브 에이전트로 fallback");
|
|
167
|
-
issues++;
|
|
168
228
|
}
|
|
169
229
|
|
|
170
|
-
// 4. Gemini CLI
|
|
230
|
+
// 4. Gemini CLI (크로스 셸 체크)
|
|
171
231
|
console.log(`${CYAN}[Gemini CLI]${RESET}`);
|
|
172
|
-
|
|
173
|
-
if (
|
|
174
|
-
ok(`설치됨 — ${geminiPath}`);
|
|
232
|
+
issues += checkCliCrossShell("gemini", "npm install -g @google/gemini-cli");
|
|
233
|
+
if (which("gemini")) {
|
|
175
234
|
if (process.env.GEMINI_API_KEY) {
|
|
176
235
|
ok("GEMINI_API_KEY 설정됨");
|
|
177
236
|
} else {
|
|
178
237
|
warn("GEMINI_API_KEY 미설정 (gemini auth login 필요할 수 있음)");
|
|
179
238
|
}
|
|
180
|
-
} else {
|
|
181
|
-
warn("미설치 (선택사항) — npm install -g @google/gemini-cli");
|
|
182
|
-
info("없으면 Claude 네이티브 에이전트로 fallback");
|
|
183
|
-
issues++;
|
|
184
239
|
}
|
|
185
240
|
|
|
186
241
|
// 5. Claude Code
|
|
@@ -193,7 +248,33 @@ function cmdDoctor() {
|
|
|
193
248
|
issues++;
|
|
194
249
|
}
|
|
195
250
|
|
|
196
|
-
// 6.
|
|
251
|
+
// 6. 스킬 설치 상태
|
|
252
|
+
console.log(`${CYAN}[Skills]${RESET}`);
|
|
253
|
+
const skillsSrc = join(PKG_ROOT, "skills");
|
|
254
|
+
const skillsDst = join(CLAUDE_DIR, "skills");
|
|
255
|
+
if (existsSync(skillsSrc)) {
|
|
256
|
+
let installed = 0;
|
|
257
|
+
let total = 0;
|
|
258
|
+
const missing = [];
|
|
259
|
+
for (const name of readdirSync(skillsSrc)) {
|
|
260
|
+
if (!existsSync(join(skillsSrc, name, "SKILL.md"))) continue;
|
|
261
|
+
total++;
|
|
262
|
+
if (existsSync(join(skillsDst, name, "SKILL.md"))) {
|
|
263
|
+
installed++;
|
|
264
|
+
} else {
|
|
265
|
+
missing.push(name);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
if (installed === total) {
|
|
269
|
+
ok(`${installed}/${total}개 설치됨`);
|
|
270
|
+
} else {
|
|
271
|
+
warn(`${installed}/${total}개 설치됨 — 미설치: ${missing.join(", ")}`);
|
|
272
|
+
info("cx-skills setup으로 동기화 가능");
|
|
273
|
+
issues++;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 7. 플러그인 등록
|
|
197
278
|
console.log(`${CYAN}[Plugin]${RESET}`);
|
|
198
279
|
const pluginsFile = join(CLAUDE_DIR, "plugins", "installed_plugins.json");
|
|
199
280
|
if (existsSync(pluginsFile)) {
|
|
@@ -208,6 +289,29 @@ function cmdDoctor() {
|
|
|
208
289
|
info("플러그인 시스템 감지 안 됨 — npm 단독 사용");
|
|
209
290
|
}
|
|
210
291
|
|
|
292
|
+
// 8. MCP 인벤토리
|
|
293
|
+
console.log(`${CYAN}[MCP Inventory]${RESET}`);
|
|
294
|
+
const mcpCache = join(CLAUDE_DIR, "cache", "mcp-inventory.json");
|
|
295
|
+
if (existsSync(mcpCache)) {
|
|
296
|
+
try {
|
|
297
|
+
const inv = JSON.parse(readFileSync(mcpCache, "utf8"));
|
|
298
|
+
ok(`캐시 존재 (${inv.timestamp})`);
|
|
299
|
+
if (inv.codex?.servers?.length) {
|
|
300
|
+
const names = inv.codex.servers.map(s => s.name).join(", ");
|
|
301
|
+
info(`Codex: ${inv.codex.servers.length}개 서버 (${names})`);
|
|
302
|
+
}
|
|
303
|
+
if (inv.gemini?.servers?.length) {
|
|
304
|
+
const names = inv.gemini.servers.map(s => s.name).join(", ");
|
|
305
|
+
info(`Gemini: ${inv.gemini.servers.length}개 서버 (${names})`);
|
|
306
|
+
}
|
|
307
|
+
} catch {
|
|
308
|
+
warn("캐시 파일 파싱 실패");
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
warn("캐시 없음 — 다음 세션 시작 시 자동 생성");
|
|
312
|
+
info(`수동: node ${join(PKG_ROOT, "scripts", "mcp-check.mjs")}`);
|
|
313
|
+
}
|
|
314
|
+
|
|
211
315
|
// 결과
|
|
212
316
|
console.log("");
|
|
213
317
|
if (issues === 0) {
|
|
@@ -217,10 +321,185 @@ function cmdDoctor() {
|
|
|
217
321
|
}
|
|
218
322
|
}
|
|
219
323
|
|
|
324
|
+
function cmdUpdate() {
|
|
325
|
+
console.log(`\n${BOLD}cx-skills update${RESET}\n`);
|
|
326
|
+
|
|
327
|
+
// 1. 설치 방식 감지
|
|
328
|
+
const pluginsFile = join(CLAUDE_DIR, "plugins", "installed_plugins.json");
|
|
329
|
+
let installMode = "unknown";
|
|
330
|
+
let pluginPath = null;
|
|
331
|
+
|
|
332
|
+
// 플러그인 모드 감지
|
|
333
|
+
if (existsSync(pluginsFile)) {
|
|
334
|
+
try {
|
|
335
|
+
const plugins = JSON.parse(readFileSync(pluginsFile, "utf8"));
|
|
336
|
+
for (const [key, entries] of Object.entries(plugins.plugins || {})) {
|
|
337
|
+
if (key.startsWith("cx-skills")) {
|
|
338
|
+
pluginPath = entries[0]?.installPath;
|
|
339
|
+
installMode = "plugin";
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch {}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// PKG_ROOT가 플러그인 캐시 내에 있으면 플러그인 모드
|
|
347
|
+
if (installMode === "unknown" && PKG_ROOT.includes(join(".claude", "plugins"))) {
|
|
348
|
+
installMode = "plugin";
|
|
349
|
+
pluginPath = PKG_ROOT;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// npm global 감지
|
|
353
|
+
if (installMode === "unknown") {
|
|
354
|
+
try {
|
|
355
|
+
const npmList = execSync("npm list -g cx-skills --depth=0", {
|
|
356
|
+
encoding: "utf8",
|
|
357
|
+
timeout: 10000,
|
|
358
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
359
|
+
});
|
|
360
|
+
if (npmList.includes("cx-skills")) installMode = "npm-global";
|
|
361
|
+
} catch {}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// npm local 감지
|
|
365
|
+
if (installMode === "unknown") {
|
|
366
|
+
const localPkg = join(process.cwd(), "node_modules", "cx-skills");
|
|
367
|
+
if (existsSync(localPkg)) installMode = "npm-local";
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// git 저장소 직접 사용
|
|
371
|
+
if (installMode === "unknown" && existsSync(join(PKG_ROOT, ".git"))) {
|
|
372
|
+
installMode = "git-local";
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
info(`검색: ${installMode === "plugin" ? "플러그인" : installMode === "npm-global" ? "npm global" : installMode === "npm-local" ? "npm local" : installMode === "git-local" ? "git 로컬 저장소" : "알 수 없음"} 설치 감지`);
|
|
376
|
+
|
|
377
|
+
// 2. 설치 방식에 따라 업데이트
|
|
378
|
+
const oldVer = PKG.version;
|
|
379
|
+
let updated = false;
|
|
380
|
+
|
|
381
|
+
try {
|
|
382
|
+
switch (installMode) {
|
|
383
|
+
case "plugin": {
|
|
384
|
+
const gitDir = pluginPath || PKG_ROOT;
|
|
385
|
+
const result = execSync("git pull", {
|
|
386
|
+
encoding: "utf8",
|
|
387
|
+
timeout: 30000,
|
|
388
|
+
cwd: gitDir,
|
|
389
|
+
}).trim();
|
|
390
|
+
ok(`git pull — ${result}`);
|
|
391
|
+
updated = true;
|
|
392
|
+
break;
|
|
393
|
+
}
|
|
394
|
+
case "npm-global": {
|
|
395
|
+
const result = execSync("npm update -g cx-skills", {
|
|
396
|
+
encoding: "utf8",
|
|
397
|
+
timeout: 60000,
|
|
398
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
399
|
+
}).trim().split(/\r?\n/)[0];
|
|
400
|
+
ok(`npm update -g — ${result || "완료"}`);
|
|
401
|
+
updated = true;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case "npm-local": {
|
|
405
|
+
const result = execSync("npm update cx-skills", {
|
|
406
|
+
encoding: "utf8",
|
|
407
|
+
timeout: 60000,
|
|
408
|
+
cwd: process.cwd(),
|
|
409
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
410
|
+
}).trim().split(/\r?\n/)[0];
|
|
411
|
+
ok(`npm update — ${result || "완료"}`);
|
|
412
|
+
updated = true;
|
|
413
|
+
break;
|
|
414
|
+
}
|
|
415
|
+
case "git-local": {
|
|
416
|
+
const result = execSync("git pull", {
|
|
417
|
+
encoding: "utf8",
|
|
418
|
+
timeout: 30000,
|
|
419
|
+
cwd: PKG_ROOT,
|
|
420
|
+
}).trim();
|
|
421
|
+
ok(`git pull — ${result}`);
|
|
422
|
+
updated = true;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
default:
|
|
426
|
+
fail("설치 방식을 감지할 수 없음");
|
|
427
|
+
info("수동 업데이트: cd <cx-skills-dir> && git pull");
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
} catch (e) {
|
|
431
|
+
fail(`업데이트 실패: ${e.message}`);
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// 3. setup 재실행 (cli-route.sh, HUD, 스킬 동기화)
|
|
436
|
+
if (updated) {
|
|
437
|
+
console.log("");
|
|
438
|
+
// 업데이트 후 새 버전 읽기
|
|
439
|
+
let newVer = oldVer;
|
|
440
|
+
try {
|
|
441
|
+
const newPkg = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
442
|
+
newVer = newPkg.version;
|
|
443
|
+
} catch {}
|
|
444
|
+
|
|
445
|
+
if (newVer !== oldVer) {
|
|
446
|
+
ok(`버전: v${oldVer} → v${newVer}`);
|
|
447
|
+
} else {
|
|
448
|
+
ok(`버전: v${oldVer} (이미 최신)`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// setup 재실행
|
|
452
|
+
console.log("");
|
|
453
|
+
info("setup 재실행 중...");
|
|
454
|
+
cmdSetup();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
console.log(`${GREEN}${BOLD}업데이트 완료${RESET}\n`);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function cmdList() {
|
|
461
|
+
console.log(`\n${BOLD}cx-skills list${RESET}\n`);
|
|
462
|
+
|
|
463
|
+
// 플러그인 내 스킬
|
|
464
|
+
const pluginSkills = join(PKG_ROOT, "skills");
|
|
465
|
+
const installedSkills = join(CLAUDE_DIR, "skills");
|
|
466
|
+
|
|
467
|
+
console.log(`${CYAN}[패키지 스킬]${RESET}`);
|
|
468
|
+
if (existsSync(pluginSkills)) {
|
|
469
|
+
for (const name of readdirSync(pluginSkills).sort()) {
|
|
470
|
+
const src = join(pluginSkills, name, "SKILL.md");
|
|
471
|
+
if (!existsSync(src)) continue;
|
|
472
|
+
const dst = join(installedSkills, name, "SKILL.md");
|
|
473
|
+
const installed = existsSync(dst);
|
|
474
|
+
const status = installed ? `${GREEN}✓${RESET}` : `${RED}✗${RESET}`;
|
|
475
|
+
console.log(` ${status} ${BOLD}${name}${RESET}${installed ? "" : ` ${DIM}(미설치)${RESET}`}`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// 설치된 스킬 중 패키지에 없는 것
|
|
480
|
+
console.log(`\n${CYAN}[사용자 스킬]${RESET}`);
|
|
481
|
+
const pkgNames = new Set(existsSync(pluginSkills) ? readdirSync(pluginSkills) : []);
|
|
482
|
+
let userCount = 0;
|
|
483
|
+
if (existsSync(installedSkills)) {
|
|
484
|
+
for (const name of readdirSync(installedSkills).sort()) {
|
|
485
|
+
if (pkgNames.has(name)) continue;
|
|
486
|
+
const skill = join(installedSkills, name, "SKILL.md");
|
|
487
|
+
if (!existsSync(skill)) continue;
|
|
488
|
+
console.log(` ${DIM}●${RESET} ${name}`);
|
|
489
|
+
userCount++;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
if (userCount === 0) info("없음");
|
|
493
|
+
|
|
494
|
+
console.log(`\n${DIM}설치 위치: ${installedSkills}${RESET}\n`);
|
|
495
|
+
}
|
|
496
|
+
|
|
220
497
|
function cmdVersion() {
|
|
221
498
|
console.log(`cx-skills v${PKG.version}`);
|
|
222
499
|
const routeVer = getVersion(join(CLAUDE_DIR, "scripts", "cli-route.sh"));
|
|
223
500
|
if (routeVer) console.log(`cli-route.sh v${routeVer}`);
|
|
501
|
+
const hudVer = getVersion(join(CLAUDE_DIR, "hud", "hud-qos-status.mjs"));
|
|
502
|
+
if (hudVer) console.log(`hud-qos-status.mjs v${hudVer}`);
|
|
224
503
|
}
|
|
225
504
|
|
|
226
505
|
function cmdHelp() {
|
|
@@ -228,8 +507,10 @@ function cmdHelp() {
|
|
|
228
507
|
${BOLD}cx-skills${RESET} — CLI-first multi-model orchestrator for Claude Code
|
|
229
508
|
|
|
230
509
|
${CYAN}Usage:${RESET}
|
|
231
|
-
cx-skills setup cli-route.sh
|
|
232
|
-
cx-skills doctor
|
|
510
|
+
cx-skills setup cli-route.sh, HUD, 스킬을 ~/.claude/에 설치/업데이트
|
|
511
|
+
cx-skills doctor 크로스 셸 CLI 진단 + MCP 인벤토리 확인
|
|
512
|
+
cx-skills update 최신 버전으로 업데이트
|
|
513
|
+
cx-skills list 설치된 스킬 목록 표시
|
|
233
514
|
cx-skills version 버전 표시
|
|
234
515
|
cx-skills help 이 도움말
|
|
235
516
|
|
|
@@ -248,6 +529,8 @@ const cmd = process.argv[2] || "help";
|
|
|
248
529
|
switch (cmd) {
|
|
249
530
|
case "setup": cmdSetup(); break;
|
|
250
531
|
case "doctor": cmdDoctor(); break;
|
|
532
|
+
case "update": cmdUpdate(); break;
|
|
533
|
+
case "list": case "ls": cmdList(); break;
|
|
251
534
|
case "version": case "--version": case "-v": cmdVersion(); break;
|
|
252
535
|
case "help": case "--help": case "-h": cmdHelp(); break;
|
|
253
536
|
default:
|