@team-semicolon/semo-cli 2.0.1 → 2.0.2
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.md +118 -118
- package/dist/index.js +390 -186
- package/package.json +48 -48
package/README.md
CHANGED
|
@@ -1,118 +1,118 @@
|
|
|
1
|
-
# @team-semicolon/semo-cli
|
|
2
|
-
|
|
3
|
-
> SEMO CLI - AI Agent Orchestration Framework Installer
|
|
4
|
-
|
|
5
|
-
## 개요
|
|
6
|
-
|
|
7
|
-
Gemini의 하이브리드 전략에 따라 SEMO를 자동 설치하는 CLI 도구입니다.
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx @team-semicolon/semo-cli init
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
## 동작 방식
|
|
14
|
-
|
|
15
|
-
`semo init` 명령어는 다음을 자동으로 수행합니다:
|
|
16
|
-
|
|
17
|
-
### 1. White Box 설정 (Git Subtree)
|
|
18
|
-
|
|
19
|
-
에이전트가 **읽고 학습**해야 하는 지식 베이스:
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
semo-system/
|
|
23
|
-
├── semo-core/ # Layer 0: 원칙, 오케스트레이션
|
|
24
|
-
└── semo-skills/ # Layer 1: coder, tester, planner
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### 2. Black Box 설정 (MCP Server)
|
|
28
|
-
|
|
29
|
-
토큰이 격리된 **외부 연동 도구**:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
// .claude/settings.json
|
|
33
|
-
{
|
|
34
|
-
"mcpServers": {
|
|
35
|
-
"semo-integrations": {
|
|
36
|
-
"command": "npx",
|
|
37
|
-
"args": ["-y", "@team-semicolon/semo-mcp"]
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### 3. Context Mesh 초기화
|
|
44
|
-
|
|
45
|
-
세션 간 **컨텍스트 영속화**:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
.claude/memory/
|
|
49
|
-
├── context.md # 프로젝트 상태
|
|
50
|
-
├── decisions.md # 아키텍처 결정
|
|
51
|
-
└── rules/ # 프로젝트별 규칙
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
## 명령어
|
|
55
|
-
|
|
56
|
-
### init
|
|
57
|
-
|
|
58
|
-
현재 프로젝트에 SEMO를 설치합니다.
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
semo init # 기본 설치
|
|
62
|
-
semo init --force # 기존 설정 덮어쓰기
|
|
63
|
-
semo init --skip-mcp # MCP 설정 생략
|
|
64
|
-
semo init --skip-subtree # Git Subtree 생략
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
### status
|
|
68
|
-
|
|
69
|
-
SEMO 설치 상태를 확인합니다.
|
|
70
|
-
|
|
71
|
-
```bash
|
|
72
|
-
semo status
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### update
|
|
76
|
-
|
|
77
|
-
SEMO를 최신 버전으로 업데이트합니다.
|
|
78
|
-
|
|
79
|
-
```bash
|
|
80
|
-
semo update
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## 설치 후 구조
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
your-project/
|
|
87
|
-
├── .claude/
|
|
88
|
-
│ ├── CLAUDE.md # 프로젝트 설정
|
|
89
|
-
│ ├── settings.json # MCP 서버 설정
|
|
90
|
-
│ ├── memory/ # Context Mesh
|
|
91
|
-
│ ├── agents → semo-system/semo-core/agents
|
|
92
|
-
│ └── skills → semo-system/semo-skills
|
|
93
|
-
│
|
|
94
|
-
└── semo-system/ # White Box (읽기 전용)
|
|
95
|
-
├── semo-core/
|
|
96
|
-
└── semo-skills/
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## 환경변수
|
|
100
|
-
|
|
101
|
-
MCP 연동을 위해 다음 환경변수를 설정하세요:
|
|
102
|
-
|
|
103
|
-
| 변수 | 설명 |
|
|
104
|
-
|------|------|
|
|
105
|
-
| `GITHUB_TOKEN` | GitHub API 토큰 |
|
|
106
|
-
| `SLACK_BOT_TOKEN` | Slack Bot 토큰 |
|
|
107
|
-
| `SUPABASE_URL` | Supabase 프로젝트 URL |
|
|
108
|
-
| `SUPABASE_KEY` | Supabase 서비스 키 |
|
|
109
|
-
|
|
110
|
-
## 참조
|
|
111
|
-
|
|
112
|
-
- [SEMO 레포지토리](https://github.com/semicolon-devteam/semo)
|
|
113
|
-
- [SEMO MCP Server](../mcp-server/README.md)
|
|
114
|
-
- [Gemini 하이브리드 전략](../../docs/SEMO_ARCHITECTURE_REVIEW.md)
|
|
115
|
-
|
|
116
|
-
## 라이선스
|
|
117
|
-
|
|
118
|
-
MIT
|
|
1
|
+
# @team-semicolon/semo-cli
|
|
2
|
+
|
|
3
|
+
> SEMO CLI - AI Agent Orchestration Framework Installer
|
|
4
|
+
|
|
5
|
+
## 개요
|
|
6
|
+
|
|
7
|
+
Gemini의 하이브리드 전략에 따라 SEMO를 자동 설치하는 CLI 도구입니다.
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @team-semicolon/semo-cli init
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## 동작 방식
|
|
14
|
+
|
|
15
|
+
`semo init` 명령어는 다음을 자동으로 수행합니다:
|
|
16
|
+
|
|
17
|
+
### 1. White Box 설정 (Git Subtree)
|
|
18
|
+
|
|
19
|
+
에이전트가 **읽고 학습**해야 하는 지식 베이스:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
semo-system/
|
|
23
|
+
├── semo-core/ # Layer 0: 원칙, 오케스트레이션
|
|
24
|
+
└── semo-skills/ # Layer 1: coder, tester, planner
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 2. Black Box 설정 (MCP Server)
|
|
28
|
+
|
|
29
|
+
토큰이 격리된 **외부 연동 도구**:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
// .claude/settings.json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"semo-integrations": {
|
|
36
|
+
"command": "npx",
|
|
37
|
+
"args": ["-y", "@team-semicolon/semo-mcp"]
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. Context Mesh 초기화
|
|
44
|
+
|
|
45
|
+
세션 간 **컨텍스트 영속화**:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
.claude/memory/
|
|
49
|
+
├── context.md # 프로젝트 상태
|
|
50
|
+
├── decisions.md # 아키텍처 결정
|
|
51
|
+
└── rules/ # 프로젝트별 규칙
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 명령어
|
|
55
|
+
|
|
56
|
+
### init
|
|
57
|
+
|
|
58
|
+
현재 프로젝트에 SEMO를 설치합니다.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
semo init # 기본 설치
|
|
62
|
+
semo init --force # 기존 설정 덮어쓰기
|
|
63
|
+
semo init --skip-mcp # MCP 설정 생략
|
|
64
|
+
semo init --skip-subtree # Git Subtree 생략
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### status
|
|
68
|
+
|
|
69
|
+
SEMO 설치 상태를 확인합니다.
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
semo status
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### update
|
|
76
|
+
|
|
77
|
+
SEMO를 최신 버전으로 업데이트합니다.
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
semo update
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## 설치 후 구조
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
your-project/
|
|
87
|
+
├── .claude/
|
|
88
|
+
│ ├── CLAUDE.md # 프로젝트 설정
|
|
89
|
+
│ ├── settings.json # MCP 서버 설정
|
|
90
|
+
│ ├── memory/ # Context Mesh
|
|
91
|
+
│ ├── agents → semo-system/semo-core/agents
|
|
92
|
+
│ └── skills → semo-system/semo-skills
|
|
93
|
+
│
|
|
94
|
+
└── semo-system/ # White Box (읽기 전용)
|
|
95
|
+
├── semo-core/
|
|
96
|
+
└── semo-skills/
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## 환경변수
|
|
100
|
+
|
|
101
|
+
MCP 연동을 위해 다음 환경변수를 설정하세요:
|
|
102
|
+
|
|
103
|
+
| 변수 | 설명 |
|
|
104
|
+
|------|------|
|
|
105
|
+
| `GITHUB_TOKEN` | GitHub API 토큰 |
|
|
106
|
+
| `SLACK_BOT_TOKEN` | Slack Bot 토큰 |
|
|
107
|
+
| `SUPABASE_URL` | Supabase 프로젝트 URL |
|
|
108
|
+
| `SUPABASE_KEY` | Supabase 서비스 키 |
|
|
109
|
+
|
|
110
|
+
## 참조
|
|
111
|
+
|
|
112
|
+
- [SEMO 레포지토리](https://github.com/semicolon-devteam/semo)
|
|
113
|
+
- [SEMO MCP Server](../mcp-server/README.md)
|
|
114
|
+
- [Gemini 하이브리드 전략](../../docs/SEMO_ARCHITECTURE_REVIEW.md)
|
|
115
|
+
|
|
116
|
+
## 라이선스
|
|
117
|
+
|
|
118
|
+
MIT
|
package/dist/index.js
CHANGED
|
@@ -58,7 +58,69 @@ const inquirer_1 = __importDefault(require("inquirer"));
|
|
|
58
58
|
const child_process_1 = require("child_process");
|
|
59
59
|
const fs = __importStar(require("fs"));
|
|
60
60
|
const path = __importStar(require("path"));
|
|
61
|
-
const
|
|
61
|
+
const os = __importStar(require("os"));
|
|
62
|
+
const VERSION = "2.0.2";
|
|
63
|
+
// === Windows 지원 유틸리티 ===
|
|
64
|
+
const isWindows = os.platform() === "win32";
|
|
65
|
+
/**
|
|
66
|
+
* Windows에서 Junction 링크를 생성하거나, Unix에서 심볼릭 링크를 생성
|
|
67
|
+
* Junction은 관리자 권한 없이 디렉토리 링크를 생성할 수 있음
|
|
68
|
+
*/
|
|
69
|
+
function createSymlinkOrJunction(targetPath, linkPath) {
|
|
70
|
+
if (isWindows) {
|
|
71
|
+
// Windows: Junction 사용 (절대 경로 필요)
|
|
72
|
+
const absoluteTarget = path.resolve(targetPath);
|
|
73
|
+
try {
|
|
74
|
+
(0, child_process_1.execSync)(`cmd /c "mklink /J "${linkPath}" "${absoluteTarget}""`, { stdio: "pipe" });
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
// fallback: 디렉토리 복사
|
|
78
|
+
console.log(chalk_1.default.yellow(` ⚠ Junction 생성 실패, 복사로 대체: ${path.basename(linkPath)}`));
|
|
79
|
+
(0, child_process_1.execSync)(`xcopy /E /I /Q "${absoluteTarget}" "${linkPath}"`, { stdio: "pipe" });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Unix: 상대 경로 심볼릭 링크
|
|
84
|
+
const relativeTarget = path.relative(path.dirname(linkPath), targetPath);
|
|
85
|
+
fs.symlinkSync(relativeTarget, linkPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* 플랫폼에 맞는 rm -rf 실행
|
|
90
|
+
*/
|
|
91
|
+
function removeRecursive(targetPath) {
|
|
92
|
+
if (!fs.existsSync(targetPath))
|
|
93
|
+
return;
|
|
94
|
+
if (isWindows) {
|
|
95
|
+
try {
|
|
96
|
+
const stats = fs.lstatSync(targetPath);
|
|
97
|
+
if (stats.isSymbolicLink()) {
|
|
98
|
+
// Junction/Symlink는 rmdir로 제거 (내용물 보존)
|
|
99
|
+
(0, child_process_1.execSync)(`cmd /c "rmdir "${targetPath}""`, { stdio: "pipe" });
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
(0, child_process_1.execSync)(`cmd /c "rd /s /q "${targetPath}""`, { stdio: "pipe" });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
(0, child_process_1.execSync)(`rm -rf "${targetPath}"`, { stdio: "pipe" });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* 플랫폼에 맞는 cp -r 실행
|
|
115
|
+
*/
|
|
116
|
+
function copyRecursive(src, dest) {
|
|
117
|
+
if (isWindows) {
|
|
118
|
+
(0, child_process_1.execSync)(`xcopy /E /I /Q "${src}" "${dest}"`, { stdio: "pipe" });
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
(0, child_process_1.execSync)(`cp -r "${src}" "${dest}"`, { stdio: "pipe" });
|
|
122
|
+
}
|
|
123
|
+
}
|
|
62
124
|
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
63
125
|
// 확장 패키지 정의
|
|
64
126
|
const EXTENSION_PACKAGES = {
|
|
@@ -157,18 +219,22 @@ program
|
|
|
157
219
|
}
|
|
158
220
|
// 4. Standard 설치 (semo-core + semo-skills)
|
|
159
221
|
await setupStandard(cwd, options.force);
|
|
160
|
-
// 5. Extensions
|
|
222
|
+
// 5. Extensions 다운로드 (심볼릭 링크는 아직)
|
|
161
223
|
if (extensionsToInstall.length > 0) {
|
|
162
|
-
await
|
|
224
|
+
await downloadExtensions(cwd, extensionsToInstall, options.force);
|
|
163
225
|
}
|
|
164
|
-
// 6. MCP 설정
|
|
226
|
+
// 6. MCP 설정 (Extension 설정 병합 포함)
|
|
165
227
|
if (!options.skipMcp) {
|
|
166
|
-
await setupMCP(cwd, options.force);
|
|
228
|
+
await setupMCP(cwd, extensionsToInstall, options.force);
|
|
167
229
|
}
|
|
168
230
|
// 7. Context Mesh 초기화
|
|
169
231
|
await setupContextMesh(cwd);
|
|
170
232
|
// 8. CLAUDE.md 생성
|
|
171
233
|
await setupClaudeMd(cwd, extensionsToInstall, options.force);
|
|
234
|
+
// 9. Extensions 심볼릭 링크 (agents/skills 병합)
|
|
235
|
+
if (extensionsToInstall.length > 0) {
|
|
236
|
+
await setupExtensionSymlinks(cwd, extensionsToInstall);
|
|
237
|
+
}
|
|
172
238
|
// 완료 메시지
|
|
173
239
|
console.log(chalk_1.default.green.bold("\n✅ SEMO 설치 완료!\n"));
|
|
174
240
|
console.log(chalk_1.default.cyan("설치된 구성:"));
|
|
@@ -203,24 +269,24 @@ async function setupStandard(cwd, force) {
|
|
|
203
269
|
console.log(chalk_1.default.gray(" → semo-system/ 건너뜀"));
|
|
204
270
|
return;
|
|
205
271
|
}
|
|
206
|
-
(
|
|
272
|
+
removeRecursive(semoSystemDir);
|
|
207
273
|
console.log(chalk_1.default.green(" ✓ 기존 semo-system/ 삭제됨"));
|
|
208
274
|
}
|
|
209
275
|
const spinner = (0, ora_1.default)("semo-core, semo-skills 다운로드 중...").start();
|
|
210
276
|
try {
|
|
211
277
|
const tempDir = path.join(cwd, ".semo-temp");
|
|
212
|
-
(
|
|
278
|
+
removeRecursive(tempDir);
|
|
213
279
|
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
214
280
|
fs.mkdirSync(semoSystemDir, { recursive: true });
|
|
215
281
|
// semo-core 복사
|
|
216
282
|
if (fs.existsSync(path.join(tempDir, "semo-core"))) {
|
|
217
|
-
(
|
|
283
|
+
copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
|
|
218
284
|
}
|
|
219
285
|
// semo-skills 복사
|
|
220
286
|
if (fs.existsSync(path.join(tempDir, "semo-skills"))) {
|
|
221
|
-
(
|
|
287
|
+
copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
|
|
222
288
|
}
|
|
223
|
-
(
|
|
289
|
+
removeRecursive(tempDir);
|
|
224
290
|
spinner.succeed("Standard 설치 완료");
|
|
225
291
|
// 심볼릭 링크 생성
|
|
226
292
|
await createStandardSymlinks(cwd);
|
|
@@ -234,23 +300,43 @@ async function setupStandard(cwd, force) {
|
|
|
234
300
|
async function createStandardSymlinks(cwd) {
|
|
235
301
|
const claudeDir = path.join(cwd, ".claude");
|
|
236
302
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
237
|
-
// agents 링크
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
303
|
+
// agents 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
|
|
304
|
+
const claudeAgentsDir = path.join(claudeDir, "agents");
|
|
305
|
+
const coreAgentsDir = path.join(semoSystemDir, "semo-core", "agents");
|
|
306
|
+
if (fs.existsSync(coreAgentsDir)) {
|
|
307
|
+
// 기존 심볼릭 링크면 삭제 (디렉토리로 변경)
|
|
308
|
+
if (fs.existsSync(claudeAgentsDir) && fs.lstatSync(claudeAgentsDir).isSymbolicLink()) {
|
|
309
|
+
fs.unlinkSync(claudeAgentsDir);
|
|
244
310
|
}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
311
|
+
fs.mkdirSync(claudeAgentsDir, { recursive: true });
|
|
312
|
+
const agents = fs.readdirSync(coreAgentsDir).filter(f => fs.statSync(path.join(coreAgentsDir, f)).isDirectory());
|
|
313
|
+
for (const agent of agents) {
|
|
314
|
+
const agentLink = path.join(claudeAgentsDir, agent);
|
|
315
|
+
const agentTarget = path.join(coreAgentsDir, agent);
|
|
316
|
+
if (!fs.existsSync(agentLink)) {
|
|
317
|
+
createSymlinkOrJunction(agentTarget, agentLink);
|
|
318
|
+
}
|
|
253
319
|
}
|
|
320
|
+
console.log(chalk_1.default.green(` ✓ .claude/agents/ (${agents.length}개 agent 링크됨)`));
|
|
321
|
+
}
|
|
322
|
+
// skills 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
|
|
323
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
324
|
+
const coreSkillsDir = path.join(semoSystemDir, "semo-skills");
|
|
325
|
+
if (fs.existsSync(coreSkillsDir)) {
|
|
326
|
+
// 기존 심볼릭 링크면 삭제 (디렉토리로 변경)
|
|
327
|
+
if (fs.existsSync(claudeSkillsDir) && fs.lstatSync(claudeSkillsDir).isSymbolicLink()) {
|
|
328
|
+
removeRecursive(claudeSkillsDir);
|
|
329
|
+
}
|
|
330
|
+
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
331
|
+
const skills = fs.readdirSync(coreSkillsDir).filter(f => fs.statSync(path.join(coreSkillsDir, f)).isDirectory());
|
|
332
|
+
for (const skill of skills) {
|
|
333
|
+
const skillLink = path.join(claudeSkillsDir, skill);
|
|
334
|
+
const skillTarget = path.join(coreSkillsDir, skill);
|
|
335
|
+
if (!fs.existsSync(skillLink)) {
|
|
336
|
+
createSymlinkOrJunction(skillTarget, skillLink);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
console.log(chalk_1.default.green(` ✓ .claude/skills/ (${skills.length}개 skill 링크됨)`));
|
|
254
340
|
}
|
|
255
341
|
// commands 링크
|
|
256
342
|
const commandsDir = path.join(claudeDir, "commands");
|
|
@@ -259,14 +345,14 @@ async function createStandardSymlinks(cwd) {
|
|
|
259
345
|
if (!fs.existsSync(semoCommandsLink)) {
|
|
260
346
|
const commandsTarget = path.join(semoSystemDir, "semo-core", "commands", "SEMO");
|
|
261
347
|
if (fs.existsSync(commandsTarget)) {
|
|
262
|
-
|
|
348
|
+
createSymlinkOrJunction(commandsTarget, semoCommandsLink);
|
|
263
349
|
console.log(chalk_1.default.green(" ✓ .claude/commands/SEMO → semo-system/semo-core/commands/SEMO"));
|
|
264
350
|
}
|
|
265
351
|
}
|
|
266
352
|
}
|
|
267
|
-
// === Extensions
|
|
268
|
-
async function
|
|
269
|
-
console.log(chalk_1.default.cyan("\n📦 Extensions
|
|
353
|
+
// === Extensions 다운로드 (심볼릭 링크 제외) ===
|
|
354
|
+
async function downloadExtensions(cwd, packages, force) {
|
|
355
|
+
console.log(chalk_1.default.cyan("\n📦 Extensions 다운로드"));
|
|
270
356
|
packages.forEach(pkg => {
|
|
271
357
|
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}`));
|
|
272
358
|
});
|
|
@@ -287,36 +373,65 @@ async function setupExtensions(cwd, packages, force) {
|
|
|
287
373
|
console.log(chalk_1.default.yellow(` ⚠ ${pkg}/ 이미 존재 (건너뜀)`));
|
|
288
374
|
continue;
|
|
289
375
|
}
|
|
290
|
-
(
|
|
291
|
-
(
|
|
376
|
+
removeRecursive(destPath);
|
|
377
|
+
copyRecursive(srcPath, destPath);
|
|
292
378
|
}
|
|
293
379
|
}
|
|
294
|
-
(
|
|
295
|
-
spinner.succeed(`Extensions
|
|
296
|
-
// Extensions 심볼릭 링크 생성
|
|
297
|
-
await createExtensionSymlinks(cwd, packages);
|
|
380
|
+
removeRecursive(tempDir);
|
|
381
|
+
spinner.succeed(`Extensions 다운로드 완료 (${packages.length}개)`);
|
|
298
382
|
}
|
|
299
383
|
catch (error) {
|
|
300
|
-
spinner.fail("Extensions
|
|
384
|
+
spinner.fail("Extensions 다운로드 실패");
|
|
301
385
|
console.error(chalk_1.default.red(` ${error}`));
|
|
302
386
|
}
|
|
303
387
|
}
|
|
304
|
-
// === Extensions 심볼릭 링크 ===
|
|
305
|
-
async function
|
|
388
|
+
// === Extensions 심볼릭 링크 설정 (agents/skills 병합) ===
|
|
389
|
+
async function setupExtensionSymlinks(cwd, packages) {
|
|
390
|
+
console.log(chalk_1.default.cyan("\n🔗 Extensions 연결"));
|
|
306
391
|
const claudeDir = path.join(cwd, ".claude");
|
|
307
392
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
308
393
|
for (const pkg of packages) {
|
|
309
|
-
// sax-{pkg} 호환 링크 (기존 SAX 사용자용)
|
|
310
|
-
const saxPkgLink = path.join(claudeDir, `sax-${pkg}`);
|
|
311
394
|
const pkgPath = path.join(semoSystemDir, pkg);
|
|
312
|
-
if (fs.existsSync(pkgPath)
|
|
313
|
-
|
|
314
|
-
|
|
395
|
+
if (!fs.existsSync(pkgPath))
|
|
396
|
+
continue;
|
|
397
|
+
// 1. semo-{pkg} 링크
|
|
398
|
+
const semoPkgLink = path.join(claudeDir, `semo-${pkg}`);
|
|
399
|
+
if (!fs.existsSync(semoPkgLink)) {
|
|
400
|
+
createSymlinkOrJunction(pkgPath, semoPkgLink);
|
|
401
|
+
console.log(chalk_1.default.green(` ✓ .claude/semo-${pkg} → semo-system/${pkg}`));
|
|
402
|
+
}
|
|
403
|
+
// 2. Extension의 agents를 .claude/agents/에 개별 링크
|
|
404
|
+
const extAgentsDir = path.join(pkgPath, "agents");
|
|
405
|
+
const claudeAgentsDir = path.join(claudeDir, "agents");
|
|
406
|
+
if (fs.existsSync(extAgentsDir)) {
|
|
407
|
+
const agents = fs.readdirSync(extAgentsDir).filter(f => fs.statSync(path.join(extAgentsDir, f)).isDirectory());
|
|
408
|
+
for (const agent of agents) {
|
|
409
|
+
const agentLink = path.join(claudeAgentsDir, agent);
|
|
410
|
+
const agentTarget = path.join(extAgentsDir, agent);
|
|
411
|
+
if (!fs.existsSync(agentLink)) {
|
|
412
|
+
createSymlinkOrJunction(agentTarget, agentLink);
|
|
413
|
+
console.log(chalk_1.default.green(` ✓ .claude/agents/${agent} → semo-system/${pkg}/agents/${agent}`));
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// 3. Extension의 skills를 .claude/skills/에 개별 링크
|
|
418
|
+
const extSkillsDir = path.join(pkgPath, "skills");
|
|
419
|
+
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
420
|
+
if (fs.existsSync(extSkillsDir)) {
|
|
421
|
+
const skills = fs.readdirSync(extSkillsDir).filter(f => fs.statSync(path.join(extSkillsDir, f)).isDirectory());
|
|
422
|
+
for (const skill of skills) {
|
|
423
|
+
const skillLink = path.join(claudeSkillsDir, skill);
|
|
424
|
+
const skillTarget = path.join(extSkillsDir, skill);
|
|
425
|
+
if (!fs.existsSync(skillLink)) {
|
|
426
|
+
createSymlinkOrJunction(skillTarget, skillLink);
|
|
427
|
+
console.log(chalk_1.default.green(` ✓ .claude/skills/${skill} → semo-system/${pkg}/skills/${skill}`));
|
|
428
|
+
}
|
|
429
|
+
}
|
|
315
430
|
}
|
|
316
431
|
}
|
|
317
432
|
}
|
|
318
433
|
// === MCP 설정 ===
|
|
319
|
-
async function setupMCP(cwd, force) {
|
|
434
|
+
async function setupMCP(cwd, extensions, force) {
|
|
320
435
|
console.log(chalk_1.default.cyan("\n🔧 Black Box 설정 (MCP Server)"));
|
|
321
436
|
console.log(chalk_1.default.gray(" 토큰이 격리된 외부 연동 도구\n"));
|
|
322
437
|
const settingsPath = path.join(cwd, ".claude", "settings.json");
|
|
@@ -327,6 +442,7 @@ async function setupMCP(cwd, force) {
|
|
|
327
442
|
return;
|
|
328
443
|
}
|
|
329
444
|
}
|
|
445
|
+
// Base settings (Standard)
|
|
330
446
|
const settings = {
|
|
331
447
|
mcpServers: {
|
|
332
448
|
"semo-integrations": {
|
|
@@ -341,9 +457,91 @@ async function setupMCP(cwd, force) {
|
|
|
341
457
|
},
|
|
342
458
|
},
|
|
343
459
|
};
|
|
460
|
+
// Extension settings 병합
|
|
461
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
462
|
+
for (const pkg of extensions) {
|
|
463
|
+
const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
|
|
464
|
+
if (fs.existsSync(extSettingsPath)) {
|
|
465
|
+
try {
|
|
466
|
+
const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
|
|
467
|
+
// mcpServers 병합
|
|
468
|
+
if (extSettings.mcpServers) {
|
|
469
|
+
Object.assign(settings.mcpServers, extSettings.mcpServers);
|
|
470
|
+
console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 병합됨`));
|
|
471
|
+
}
|
|
472
|
+
// permissions 병합
|
|
473
|
+
if (extSettings.permissions) {
|
|
474
|
+
if (!settings.permissions) {
|
|
475
|
+
settings.permissions = { allow: [], deny: [] };
|
|
476
|
+
}
|
|
477
|
+
if (extSettings.permissions.allow) {
|
|
478
|
+
settings.permissions.allow = [
|
|
479
|
+
...(settings.permissions.allow || []),
|
|
480
|
+
...extSettings.permissions.allow,
|
|
481
|
+
];
|
|
482
|
+
}
|
|
483
|
+
if (extSettings.permissions.deny) {
|
|
484
|
+
settings.permissions.deny = [
|
|
485
|
+
...(settings.permissions.deny || []),
|
|
486
|
+
...extSettings.permissions.deny,
|
|
487
|
+
];
|
|
488
|
+
}
|
|
489
|
+
console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
344
497
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
345
498
|
console.log(chalk_1.default.green("✓ .claude/settings.json 생성됨 (MCP 설정)"));
|
|
346
499
|
}
|
|
500
|
+
// === Extension settings 병합 (add 명령어용) ===
|
|
501
|
+
async function mergeExtensionSettings(cwd, packages) {
|
|
502
|
+
const settingsPath = path.join(cwd, ".claude", "settings.json");
|
|
503
|
+
const semoSystemDir = path.join(cwd, "semo-system");
|
|
504
|
+
if (!fs.existsSync(settingsPath)) {
|
|
505
|
+
console.log(chalk_1.default.yellow(" ⚠ settings.json이 없습니다. 'semo init'을 먼저 실행하세요."));
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
509
|
+
for (const pkg of packages) {
|
|
510
|
+
const extSettingsPath = path.join(semoSystemDir, pkg, "settings.local.json");
|
|
511
|
+
if (fs.existsSync(extSettingsPath)) {
|
|
512
|
+
try {
|
|
513
|
+
const extSettings = JSON.parse(fs.readFileSync(extSettingsPath, "utf-8"));
|
|
514
|
+
// mcpServers 병합
|
|
515
|
+
if (extSettings.mcpServers) {
|
|
516
|
+
settings.mcpServers = settings.mcpServers || {};
|
|
517
|
+
Object.assign(settings.mcpServers, extSettings.mcpServers);
|
|
518
|
+
console.log(chalk_1.default.gray(` + ${pkg} MCP 설정 병합됨`));
|
|
519
|
+
}
|
|
520
|
+
// permissions 병합
|
|
521
|
+
if (extSettings.permissions) {
|
|
522
|
+
settings.permissions = settings.permissions || { allow: [], deny: [] };
|
|
523
|
+
if (extSettings.permissions.allow) {
|
|
524
|
+
settings.permissions.allow = [
|
|
525
|
+
...(settings.permissions.allow || []),
|
|
526
|
+
...extSettings.permissions.allow,
|
|
527
|
+
];
|
|
528
|
+
}
|
|
529
|
+
if (extSettings.permissions.deny) {
|
|
530
|
+
settings.permissions.deny = [
|
|
531
|
+
...(settings.permissions.deny || []),
|
|
532
|
+
...extSettings.permissions.deny,
|
|
533
|
+
];
|
|
534
|
+
}
|
|
535
|
+
console.log(chalk_1.default.gray(` + ${pkg} permissions 병합됨`));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
console.log(chalk_1.default.yellow(` ⚠ ${pkg} settings.local.json 파싱 실패`));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
544
|
+
}
|
|
347
545
|
// === Context Mesh 초기화 ===
|
|
348
546
|
async function setupContextMesh(cwd) {
|
|
349
547
|
console.log(chalk_1.default.cyan("\n🧠 Context Mesh 초기화"));
|
|
@@ -353,36 +551,36 @@ async function setupContextMesh(cwd) {
|
|
|
353
551
|
// context.md
|
|
354
552
|
const contextPath = path.join(memoryDir, "context.md");
|
|
355
553
|
if (!fs.existsSync(contextPath)) {
|
|
356
|
-
const contextContent = `# Project Context
|
|
357
|
-
|
|
358
|
-
> 세션 간 영속화되는 프로젝트 컨텍스트
|
|
359
|
-
> SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
|
|
360
|
-
|
|
361
|
-
---
|
|
362
|
-
|
|
363
|
-
## 프로젝트 정보
|
|
364
|
-
|
|
365
|
-
| 항목 | 값 |
|
|
366
|
-
|------|-----|
|
|
367
|
-
| **이름** | ${path.basename(cwd)} |
|
|
368
|
-
| **SEMO 버전** | ${VERSION} |
|
|
369
|
-
| **설치일** | ${new Date().toISOString().split("T")[0]} |
|
|
370
|
-
|
|
371
|
-
---
|
|
372
|
-
|
|
373
|
-
## 현재 작업 상태
|
|
374
|
-
|
|
375
|
-
_아직 작업 기록이 없습니다._
|
|
376
|
-
|
|
377
|
-
---
|
|
378
|
-
|
|
379
|
-
## 기술 스택
|
|
380
|
-
|
|
381
|
-
_프로젝트 분석 후 자동으로 채워집니다._
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
*마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
|
|
554
|
+
const contextContent = `# Project Context
|
|
555
|
+
|
|
556
|
+
> 세션 간 영속화되는 프로젝트 컨텍스트
|
|
557
|
+
> SEMO의 memory 스킬이 이 파일을 자동으로 업데이트합니다.
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 프로젝트 정보
|
|
562
|
+
|
|
563
|
+
| 항목 | 값 |
|
|
564
|
+
|------|-----|
|
|
565
|
+
| **이름** | ${path.basename(cwd)} |
|
|
566
|
+
| **SEMO 버전** | ${VERSION} |
|
|
567
|
+
| **설치일** | ${new Date().toISOString().split("T")[0]} |
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## 현재 작업 상태
|
|
572
|
+
|
|
573
|
+
_아직 작업 기록이 없습니다._
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## 기술 스택
|
|
578
|
+
|
|
579
|
+
_프로젝트 분석 후 자동으로 채워집니다._
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
*마지막 업데이트: ${new Date().toISOString().split("T")[0]}*
|
|
386
584
|
`;
|
|
387
585
|
fs.writeFileSync(contextPath, contextContent);
|
|
388
586
|
console.log(chalk_1.default.green("✓ .claude/memory/context.md 생성됨"));
|
|
@@ -390,36 +588,36 @@ _프로젝트 분석 후 자동으로 채워집니다._
|
|
|
390
588
|
// decisions.md
|
|
391
589
|
const decisionsPath = path.join(memoryDir, "decisions.md");
|
|
392
590
|
if (!fs.existsSync(decisionsPath)) {
|
|
393
|
-
const decisionsContent = `# Architecture Decisions
|
|
394
|
-
|
|
395
|
-
> 프로젝트 아키텍처 결정 기록 (ADR)
|
|
396
|
-
> 중요한 기술적 결정을 여기에 기록합니다.
|
|
397
|
-
|
|
398
|
-
---
|
|
399
|
-
|
|
400
|
-
## 결정 목록
|
|
401
|
-
|
|
402
|
-
_아직 기록된 결정이 없습니다._
|
|
403
|
-
|
|
404
|
-
---
|
|
405
|
-
|
|
406
|
-
## 템플릿
|
|
407
|
-
|
|
408
|
-
\`\`\`markdown
|
|
409
|
-
### ADR-XXX: 결정 제목
|
|
410
|
-
|
|
411
|
-
**날짜**: YYYY-MM-DD
|
|
412
|
-
**상태**: Proposed | Accepted | Deprecated
|
|
413
|
-
|
|
414
|
-
#### 배경
|
|
415
|
-
결정이 필요한 이유
|
|
416
|
-
|
|
417
|
-
#### 결정
|
|
418
|
-
선택한 방안
|
|
419
|
-
|
|
420
|
-
#### 근거
|
|
421
|
-
선택 이유
|
|
422
|
-
\`\`\`
|
|
591
|
+
const decisionsContent = `# Architecture Decisions
|
|
592
|
+
|
|
593
|
+
> 프로젝트 아키텍처 결정 기록 (ADR)
|
|
594
|
+
> 중요한 기술적 결정을 여기에 기록합니다.
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## 결정 목록
|
|
599
|
+
|
|
600
|
+
_아직 기록된 결정이 없습니다._
|
|
601
|
+
|
|
602
|
+
---
|
|
603
|
+
|
|
604
|
+
## 템플릿
|
|
605
|
+
|
|
606
|
+
\`\`\`markdown
|
|
607
|
+
### ADR-XXX: 결정 제목
|
|
608
|
+
|
|
609
|
+
**날짜**: YYYY-MM-DD
|
|
610
|
+
**상태**: Proposed | Accepted | Deprecated
|
|
611
|
+
|
|
612
|
+
#### 배경
|
|
613
|
+
결정이 필요한 이유
|
|
614
|
+
|
|
615
|
+
#### 결정
|
|
616
|
+
선택한 방안
|
|
617
|
+
|
|
618
|
+
#### 근거
|
|
619
|
+
선택 이유
|
|
620
|
+
\`\`\`
|
|
423
621
|
`;
|
|
424
622
|
fs.writeFileSync(decisionsPath, decisionsContent);
|
|
425
623
|
console.log(chalk_1.default.green("✓ .claude/memory/decisions.md 생성됨"));
|
|
@@ -429,21 +627,21 @@ _아직 기록된 결정이 없습니다._
|
|
|
429
627
|
fs.mkdirSync(rulesDir, { recursive: true });
|
|
430
628
|
const rulesPath = path.join(rulesDir, "project-specific.md");
|
|
431
629
|
if (!fs.existsSync(rulesPath)) {
|
|
432
|
-
const rulesContent = `# Project-Specific Rules
|
|
433
|
-
|
|
434
|
-
> 이 프로젝트에만 적용되는 규칙
|
|
435
|
-
|
|
436
|
-
---
|
|
437
|
-
|
|
438
|
-
## 코딩 규칙
|
|
439
|
-
|
|
440
|
-
_프로젝트별 코딩 규칙을 여기에 추가하세요._
|
|
441
|
-
|
|
442
|
-
---
|
|
443
|
-
|
|
444
|
-
## 예외 사항
|
|
445
|
-
|
|
446
|
-
_SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
|
|
630
|
+
const rulesContent = `# Project-Specific Rules
|
|
631
|
+
|
|
632
|
+
> 이 프로젝트에만 적용되는 규칙
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
636
|
+
## 코딩 규칙
|
|
637
|
+
|
|
638
|
+
_프로젝트별 코딩 규칙을 여기에 추가하세요._
|
|
639
|
+
|
|
640
|
+
---
|
|
641
|
+
|
|
642
|
+
## 예외 사항
|
|
643
|
+
|
|
644
|
+
_SEMO 기본 규칙의 예외 사항을 여기에 추가하세요._
|
|
447
645
|
`;
|
|
448
646
|
fs.writeFileSync(rulesPath, rulesContent);
|
|
449
647
|
console.log(chalk_1.default.green("✓ .claude/memory/rules/project-specific.md 생성됨"));
|
|
@@ -463,65 +661,65 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
463
661
|
const extensionsList = extensions.length > 0
|
|
464
662
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
465
663
|
: "";
|
|
466
|
-
const claudeMdContent = `# SEMO Project Configuration
|
|
467
|
-
|
|
468
|
-
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
469
|
-
|
|
470
|
-
## 설치된 구성
|
|
471
|
-
|
|
472
|
-
### Standard (필수)
|
|
473
|
-
- **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
|
|
474
|
-
- **semo-skills**: 13개 통합 스킬
|
|
475
|
-
- 행동: coder, tester, planner, deployer, writer
|
|
476
|
-
- 운영: memory, notify-slack, feedback, version-updater,
|
|
477
|
-
|
|
478
|
-
${extensions.length > 0 ? `### Extensions (선택)
|
|
479
|
-
${extensions.map(pkg => `- **${pkg}**: ${EXTENSION_PACKAGES[pkg].desc}`).join("\n")}` : ""}
|
|
480
|
-
|
|
481
|
-
## 구조
|
|
482
|
-
|
|
483
|
-
\`\`\`
|
|
484
|
-
.claude/
|
|
485
|
-
├── settings.json # MCP 서버 설정 (Black Box)
|
|
486
|
-
├── memory/ # Context Mesh (장기 기억)
|
|
487
|
-
│ ├── context.md # 프로젝트 상태
|
|
488
|
-
│ ├── decisions.md # 아키텍처 결정
|
|
489
|
-
│ └── rules/ # 프로젝트별 규칙
|
|
490
|
-
├── agents → semo-system/semo-core/agents
|
|
491
|
-
├── skills → semo-system/semo-skills
|
|
492
|
-
└── commands/SEMO → semo-system/semo-core/commands/SEMO
|
|
493
|
-
|
|
494
|
-
semo-system/ # White Box (읽기 전용)
|
|
495
|
-
├── semo-core/ # Layer 0: 원칙, 오케스트레이션
|
|
496
|
-
├── semo-skills/ # Layer 1: 통합 스킬
|
|
497
|
-
${extensionsList}
|
|
498
|
-
\`\`\`
|
|
499
|
-
|
|
500
|
-
## 사용 가능한 커맨드
|
|
501
|
-
|
|
502
|
-
| 커맨드 | 설명 |
|
|
503
|
-
|--------|------|
|
|
504
|
-
| \`/SEMO:help\` | 도움말 |
|
|
505
|
-
| \`/SEMO:slack\` | Slack 메시지 전송 |
|
|
506
|
-
| \`/SEMO:feedback\` | 피드백 제출 |
|
|
507
|
-
| \`/SEMO:health\` | 환경 검증 |
|
|
508
|
-
| \`/SEMO:update\` | SEMO 업데이트 |
|
|
509
|
-
|
|
510
|
-
## Context Mesh 사용
|
|
511
|
-
|
|
512
|
-
SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
|
|
513
|
-
|
|
514
|
-
- **context.md**: 프로젝트 상태, 진행 중인 작업
|
|
515
|
-
- **decisions.md**: 아키텍처 결정 기록 (ADR)
|
|
516
|
-
- **rules/**: 프로젝트별 커스텀 규칙
|
|
517
|
-
|
|
518
|
-
memory 스킬이 자동으로 이 파일들을 관리합니다.
|
|
519
|
-
|
|
520
|
-
## References
|
|
521
|
-
|
|
522
|
-
- [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
|
|
523
|
-
- [SEMO Skills](semo-system/semo-skills/)
|
|
524
|
-
${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
|
|
664
|
+
const claudeMdContent = `# SEMO Project Configuration
|
|
665
|
+
|
|
666
|
+
> SEMO (Semicolon Orchestrate) - AI Agent Orchestration Framework v${VERSION}
|
|
667
|
+
|
|
668
|
+
## 설치된 구성
|
|
669
|
+
|
|
670
|
+
### Standard (필수)
|
|
671
|
+
- **semo-core**: 원칙, 오케스트레이터, 공통 커맨드
|
|
672
|
+
- **semo-skills**: 13개 통합 스킬
|
|
673
|
+
- 행동: coder, tester, planner, deployer, writer
|
|
674
|
+
- 운영: memory, notify-slack, feedback, version-updater, semo-help, semo-architecture-checker, circuit-breaker, list-bugs
|
|
675
|
+
|
|
676
|
+
${extensions.length > 0 ? `### Extensions (선택)
|
|
677
|
+
${extensions.map(pkg => `- **${pkg}**: ${EXTENSION_PACKAGES[pkg].desc}`).join("\n")}` : ""}
|
|
678
|
+
|
|
679
|
+
## 구조
|
|
680
|
+
|
|
681
|
+
\`\`\`
|
|
682
|
+
.claude/
|
|
683
|
+
├── settings.json # MCP 서버 설정 (Black Box)
|
|
684
|
+
├── memory/ # Context Mesh (장기 기억)
|
|
685
|
+
│ ├── context.md # 프로젝트 상태
|
|
686
|
+
│ ├── decisions.md # 아키텍처 결정
|
|
687
|
+
│ └── rules/ # 프로젝트별 규칙
|
|
688
|
+
├── agents → semo-system/semo-core/agents
|
|
689
|
+
├── skills → semo-system/semo-skills
|
|
690
|
+
└── commands/SEMO → semo-system/semo-core/commands/SEMO
|
|
691
|
+
|
|
692
|
+
semo-system/ # White Box (읽기 전용)
|
|
693
|
+
├── semo-core/ # Layer 0: 원칙, 오케스트레이션
|
|
694
|
+
├── semo-skills/ # Layer 1: 통합 스킬
|
|
695
|
+
${extensionsList}
|
|
696
|
+
\`\`\`
|
|
697
|
+
|
|
698
|
+
## 사용 가능한 커맨드
|
|
699
|
+
|
|
700
|
+
| 커맨드 | 설명 |
|
|
701
|
+
|--------|------|
|
|
702
|
+
| \`/SEMO:help\` | 도움말 |
|
|
703
|
+
| \`/SEMO:slack\` | Slack 메시지 전송 |
|
|
704
|
+
| \`/SEMO:feedback\` | 피드백 제출 |
|
|
705
|
+
| \`/SEMO:health\` | 환경 검증 |
|
|
706
|
+
| \`/SEMO:update\` | SEMO 업데이트 |
|
|
707
|
+
|
|
708
|
+
## Context Mesh 사용
|
|
709
|
+
|
|
710
|
+
SEMO는 \`.claude/memory/\`를 통해 세션 간 컨텍스트를 유지합니다:
|
|
711
|
+
|
|
712
|
+
- **context.md**: 프로젝트 상태, 진행 중인 작업
|
|
713
|
+
- **decisions.md**: 아키텍처 결정 기록 (ADR)
|
|
714
|
+
- **rules/**: 프로젝트별 커스텀 규칙
|
|
715
|
+
|
|
716
|
+
memory 스킬이 자동으로 이 파일들을 관리합니다.
|
|
717
|
+
|
|
718
|
+
## References
|
|
719
|
+
|
|
720
|
+
- [SEMO Principles](semo-system/semo-core/principles/PRINCIPLES.md)
|
|
721
|
+
- [SEMO Skills](semo-system/semo-skills/)
|
|
722
|
+
${extensions.length > 0 ? extensions.map(pkg => `- [${EXTENSION_PACKAGES[pkg].name} Package](semo-system/${pkg}/)`).join("\n") : ""}
|
|
525
723
|
`;
|
|
526
724
|
fs.writeFileSync(claudeMdPath, claudeMdContent);
|
|
527
725
|
console.log(chalk_1.default.green("✓ .claude/CLAUDE.md 생성됨"));
|
|
@@ -551,7 +749,12 @@ program
|
|
|
551
749
|
}
|
|
552
750
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[packageName].name} 패키지 설치\n`));
|
|
553
751
|
console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[packageName].desc}\n`));
|
|
554
|
-
|
|
752
|
+
// 1. 다운로드
|
|
753
|
+
await downloadExtensions(cwd, [packageName], options.force);
|
|
754
|
+
// 2. settings.json 병합
|
|
755
|
+
await mergeExtensionSettings(cwd, [packageName]);
|
|
756
|
+
// 3. 심볼릭 링크 설정
|
|
757
|
+
await setupExtensionSymlinks(cwd, [packageName]);
|
|
555
758
|
console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[packageName].name} 패키지 설치 완료!\n`));
|
|
556
759
|
});
|
|
557
760
|
// === list 명령어 ===
|
|
@@ -666,22 +869,23 @@ program
|
|
|
666
869
|
const spinner = (0, ora_1.default)("\n최신 버전 다운로드 중...").start();
|
|
667
870
|
try {
|
|
668
871
|
const tempDir = path.join(cwd, ".semo-temp");
|
|
669
|
-
(
|
|
872
|
+
removeRecursive(tempDir);
|
|
670
873
|
(0, child_process_1.execSync)(`git clone --depth 1 ${SEMO_REPO} "${tempDir}"`, { stdio: "pipe" });
|
|
671
874
|
// Standard 업데이트
|
|
672
|
-
(
|
|
673
|
-
(
|
|
674
|
-
(
|
|
875
|
+
removeRecursive(path.join(semoSystemDir, "semo-core"));
|
|
876
|
+
removeRecursive(path.join(semoSystemDir, "semo-skills"));
|
|
877
|
+
copyRecursive(path.join(tempDir, "semo-core"), path.join(semoSystemDir, "semo-core"));
|
|
878
|
+
copyRecursive(path.join(tempDir, "semo-skills"), path.join(semoSystemDir, "semo-skills"));
|
|
675
879
|
// Extensions 업데이트
|
|
676
880
|
for (const pkg of installedExtensions) {
|
|
677
881
|
const srcPath = path.join(tempDir, "packages", pkg);
|
|
678
882
|
const destPath = path.join(semoSystemDir, pkg);
|
|
679
883
|
if (fs.existsSync(srcPath)) {
|
|
680
|
-
(
|
|
681
|
-
(
|
|
884
|
+
removeRecursive(destPath);
|
|
885
|
+
copyRecursive(srcPath, destPath);
|
|
682
886
|
}
|
|
683
887
|
}
|
|
684
|
-
(
|
|
888
|
+
removeRecursive(tempDir);
|
|
685
889
|
spinner.succeed("SEMO 업데이트 완료");
|
|
686
890
|
}
|
|
687
891
|
catch (error) {
|
package/package.json
CHANGED
|
@@ -1,48 +1,48 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@team-semicolon/semo-cli",
|
|
3
|
-
"version": "2.0.
|
|
4
|
-
"description": "SEMO CLI - AI Agent Orchestration Framework Installer",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"bin": {
|
|
7
|
-
"semo": "./dist/index.js",
|
|
8
|
-
"semo-cli": "./dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"scripts": {
|
|
11
|
-
"build": "tsc",
|
|
12
|
-
"start": "node dist/index.js",
|
|
13
|
-
"dev": "ts-node src/index.ts"
|
|
14
|
-
},
|
|
15
|
-
"keywords": [
|
|
16
|
-
"semo",
|
|
17
|
-
"cli",
|
|
18
|
-
"ai-agent",
|
|
19
|
-
"claude",
|
|
20
|
-
"semicolon"
|
|
21
|
-
],
|
|
22
|
-
"author": "Semicolon DevTeam",
|
|
23
|
-
"license": "MIT",
|
|
24
|
-
"repository": {
|
|
25
|
-
"type": "git",
|
|
26
|
-
"url": "https://github.com/semicolon-devteam/semo.git",
|
|
27
|
-
"directory": "packages/cli"
|
|
28
|
-
},
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"chalk": "^5.3.0",
|
|
31
|
-
"commander": "^12.0.0",
|
|
32
|
-
"ora": "^8.0.0",
|
|
33
|
-
"inquirer": "^9.2.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/node": "^20.0.0",
|
|
37
|
-
"@types/inquirer": "^9.0.0",
|
|
38
|
-
"typescript": "^5.0.0",
|
|
39
|
-
"ts-node": "^10.0.0"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18.0.0"
|
|
43
|
-
},
|
|
44
|
-
"files": [
|
|
45
|
-
"dist",
|
|
46
|
-
"README.md"
|
|
47
|
-
]
|
|
48
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@team-semicolon/semo-cli",
|
|
3
|
+
"version": "2.0.2",
|
|
4
|
+
"description": "SEMO CLI - AI Agent Orchestration Framework Installer",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"semo": "./dist/index.js",
|
|
8
|
+
"semo-cli": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "ts-node src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"semo",
|
|
17
|
+
"cli",
|
|
18
|
+
"ai-agent",
|
|
19
|
+
"claude",
|
|
20
|
+
"semicolon"
|
|
21
|
+
],
|
|
22
|
+
"author": "Semicolon DevTeam",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"repository": {
|
|
25
|
+
"type": "git",
|
|
26
|
+
"url": "https://github.com/semicolon-devteam/semo.git",
|
|
27
|
+
"directory": "packages/cli"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"chalk": "^5.3.0",
|
|
31
|
+
"commander": "^12.0.0",
|
|
32
|
+
"ora": "^8.0.0",
|
|
33
|
+
"inquirer": "^9.2.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/node": "^20.0.0",
|
|
37
|
+
"@types/inquirer": "^9.0.0",
|
|
38
|
+
"typescript": "^5.0.0",
|
|
39
|
+
"ts-node": "^10.0.0"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md"
|
|
47
|
+
]
|
|
48
|
+
}
|