@team-semicolon/semo-cli 3.0.3 → 3.0.5
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/dist/index.js +290 -25
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -153,6 +153,50 @@ const LEGACY_MAPPING = {
|
|
|
153
153
|
design: "biz/design",
|
|
154
154
|
mvp: "biz/poc",
|
|
155
155
|
};
|
|
156
|
+
// 그룹 이름 목록 (biz, eng, ops)
|
|
157
|
+
const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta"];
|
|
158
|
+
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
159
|
+
function getPackagesByGroup(group) {
|
|
160
|
+
return Object.entries(EXTENSION_PACKAGES)
|
|
161
|
+
.filter(([, pkg]) => pkg.layer === group)
|
|
162
|
+
.map(([key]) => key);
|
|
163
|
+
}
|
|
164
|
+
// 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
165
|
+
function resolvePackageInput(input) {
|
|
166
|
+
// 쉼표로 구분된 여러 패키지 처리
|
|
167
|
+
const parts = input.split(",").map(p => p.trim()).filter(p => p);
|
|
168
|
+
const resolvedPackages = [];
|
|
169
|
+
let isGroup = false;
|
|
170
|
+
let groupName;
|
|
171
|
+
for (const part of parts) {
|
|
172
|
+
// 1. 그룹명인지 확인 (biz, eng, ops, meta)
|
|
173
|
+
if (PACKAGE_GROUPS.includes(part)) {
|
|
174
|
+
const groupPackages = getPackagesByGroup(part);
|
|
175
|
+
resolvedPackages.push(...groupPackages);
|
|
176
|
+
isGroup = true;
|
|
177
|
+
groupName = part;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
// 2. 레거시 매핑 확인
|
|
181
|
+
if (part in LEGACY_MAPPING) {
|
|
182
|
+
resolvedPackages.push(LEGACY_MAPPING[part]);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
// 3. 직접 패키지명 확인
|
|
186
|
+
if (part in EXTENSION_PACKAGES) {
|
|
187
|
+
resolvedPackages.push(part);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
// 4. 유효하지 않은 패키지명
|
|
191
|
+
// (빈 배열 대신 null을 추가하여 나중에 에러 처리)
|
|
192
|
+
}
|
|
193
|
+
// 중복 제거
|
|
194
|
+
return {
|
|
195
|
+
packages: [...new Set(resolvedPackages)],
|
|
196
|
+
isGroup,
|
|
197
|
+
groupName
|
|
198
|
+
};
|
|
199
|
+
}
|
|
156
200
|
const program = new commander_1.Command();
|
|
157
201
|
program
|
|
158
202
|
.name("semo")
|
|
@@ -403,6 +447,148 @@ async function downloadExtensions(cwd, packages, force) {
|
|
|
403
447
|
console.error(chalk_1.default.red(` ${error}`));
|
|
404
448
|
}
|
|
405
449
|
}
|
|
450
|
+
// === Orchestrator 병합 파일 생성 ===
|
|
451
|
+
function createMergedOrchestrator(claudeAgentsDir, orchestratorSources) {
|
|
452
|
+
const orchestratorDir = path.join(claudeAgentsDir, "orchestrator");
|
|
453
|
+
fs.mkdirSync(orchestratorDir, { recursive: true });
|
|
454
|
+
// _packages 디렉토리 생성 (원본 참조용)
|
|
455
|
+
const packagesDir = path.join(orchestratorDir, "_packages");
|
|
456
|
+
fs.mkdirSync(packagesDir, { recursive: true });
|
|
457
|
+
// 각 패키지의 orchestrator 내용 수집
|
|
458
|
+
const routingTables = [];
|
|
459
|
+
const availableAgents = [];
|
|
460
|
+
const availableSkills = [];
|
|
461
|
+
const crossPackageRouting = [];
|
|
462
|
+
for (const source of orchestratorSources) {
|
|
463
|
+
const orchestratorMdPath = path.join(source.path, "orchestrator.md");
|
|
464
|
+
if (!fs.existsSync(orchestratorMdPath))
|
|
465
|
+
continue;
|
|
466
|
+
const content = fs.readFileSync(orchestratorMdPath, "utf-8");
|
|
467
|
+
const pkgShortName = source.pkg.replace(/\//g, "-");
|
|
468
|
+
// 원본 파일 복사 (참조용)
|
|
469
|
+
fs.writeFileSync(path.join(packagesDir, `${pkgShortName}.md`), content);
|
|
470
|
+
// Quick Routing Table 추출
|
|
471
|
+
const routingMatch = content.match(/## 🔴 Quick Routing Table[\s\S]*?\n\n([\s\S]*?)(?=\n## |$)/);
|
|
472
|
+
if (routingMatch) {
|
|
473
|
+
routingTables.push(`### ${EXTENSION_PACKAGES[source.pkg]?.name || source.pkg}\n\n${routingMatch[1].trim()}`);
|
|
474
|
+
}
|
|
475
|
+
// Available Agents 추출
|
|
476
|
+
const agentsMatch = content.match(/## Available Agents[\s\S]*?\n\n([\s\S]*?)(?=\n## |$)/);
|
|
477
|
+
if (agentsMatch) {
|
|
478
|
+
availableAgents.push(`### ${EXTENSION_PACKAGES[source.pkg]?.name || source.pkg}\n\n${agentsMatch[1].trim()}`);
|
|
479
|
+
}
|
|
480
|
+
// Available Skills 추출
|
|
481
|
+
const skillsMatch = content.match(/## Available Skills[\s\S]*?\n\n([\s\S]*?)(?=\n## |$)/);
|
|
482
|
+
if (skillsMatch) {
|
|
483
|
+
availableSkills.push(`### ${EXTENSION_PACKAGES[source.pkg]?.name || source.pkg}\n\n${skillsMatch[1].trim()}`);
|
|
484
|
+
}
|
|
485
|
+
// Cross-Package Routing 추출
|
|
486
|
+
const crossMatch = content.match(/## 🔄 Cross-Package Routing[\s\S]*?\n\n([\s\S]*?)(?=\n## |$)/);
|
|
487
|
+
if (crossMatch) {
|
|
488
|
+
crossPackageRouting.push(crossMatch[1].trim());
|
|
489
|
+
}
|
|
490
|
+
// references 폴더가 있으면 복사
|
|
491
|
+
const refsDir = path.join(source.path, "references");
|
|
492
|
+
if (fs.existsSync(refsDir)) {
|
|
493
|
+
const mergedRefsDir = path.join(orchestratorDir, "references");
|
|
494
|
+
fs.mkdirSync(mergedRefsDir, { recursive: true });
|
|
495
|
+
const refs = fs.readdirSync(refsDir);
|
|
496
|
+
for (const ref of refs) {
|
|
497
|
+
const srcRef = path.join(refsDir, ref);
|
|
498
|
+
const destRef = path.join(mergedRefsDir, `${pkgShortName}-${ref}`);
|
|
499
|
+
if (fs.statSync(srcRef).isFile()) {
|
|
500
|
+
fs.copyFileSync(srcRef, destRef);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
// 병합된 orchestrator.md 생성
|
|
506
|
+
const mergedContent = `---
|
|
507
|
+
name: orchestrator
|
|
508
|
+
description: |
|
|
509
|
+
SEMO Merged Orchestrator - Routes all user requests to appropriate agents/skills.
|
|
510
|
+
This orchestrator combines routing tables from ${orchestratorSources.length} packages.
|
|
511
|
+
PROACTIVELY delegate on ALL requests. Never process directly.
|
|
512
|
+
tools:
|
|
513
|
+
- read_file
|
|
514
|
+
- list_dir
|
|
515
|
+
- run_command
|
|
516
|
+
- glob
|
|
517
|
+
- grep
|
|
518
|
+
- task
|
|
519
|
+
- skill
|
|
520
|
+
model: inherit
|
|
521
|
+
---
|
|
522
|
+
|
|
523
|
+
# SEMO Merged Orchestrator
|
|
524
|
+
|
|
525
|
+
> 이 파일은 **자동 생성**되었습니다. 직접 수정하지 마세요.
|
|
526
|
+
> 원본 파일: \`_packages/\` 디렉토리 참조
|
|
527
|
+
|
|
528
|
+
모든 사용자 요청을 분석하고 적절한 Agent 또는 Skill로 라우팅하는 **Primary Router**입니다.
|
|
529
|
+
|
|
530
|
+
## 🔴 설치된 패키지
|
|
531
|
+
|
|
532
|
+
${orchestratorSources.map(s => `- **${EXTENSION_PACKAGES[s.pkg]?.name || s.pkg}**: \`semo-system/${s.pkg}\``).join("\n")}
|
|
533
|
+
|
|
534
|
+
## �� Quick Routing Table (Merged)
|
|
535
|
+
|
|
536
|
+
> 키워드 매칭 시 **첫 번째 매칭된 패키지**로 라우팅됩니다.
|
|
537
|
+
|
|
538
|
+
${routingTables.join("\n\n---\n\n")}
|
|
539
|
+
|
|
540
|
+
## SEMO 메시지 포맷
|
|
541
|
+
|
|
542
|
+
### Agent 위임
|
|
543
|
+
|
|
544
|
+
\`\`\`markdown
|
|
545
|
+
[SEMO] Orchestrator: 의도 분석 완료 → {intent_category}
|
|
546
|
+
|
|
547
|
+
[SEMO] Agent 위임: {agent_name} (사유: {reason})
|
|
548
|
+
\`\`\`
|
|
549
|
+
|
|
550
|
+
### Skill 호출
|
|
551
|
+
|
|
552
|
+
\`\`\`markdown
|
|
553
|
+
[SEMO] Orchestrator: 의도 분석 완료 → {intent_category}
|
|
554
|
+
|
|
555
|
+
[SEMO] Skill 호출: {skill_name}
|
|
556
|
+
\`\`\`
|
|
557
|
+
|
|
558
|
+
### 라우팅 실패
|
|
559
|
+
|
|
560
|
+
\`\`\`markdown
|
|
561
|
+
[SEMO] Orchestrator: 라우팅 실패 → 적절한 Agent/Skill 없음
|
|
562
|
+
|
|
563
|
+
⚠️ 직접 처리 필요
|
|
564
|
+
\`\`\`
|
|
565
|
+
|
|
566
|
+
## Critical Rules
|
|
567
|
+
|
|
568
|
+
1. **Routing-Only**: 직접 작업 수행 금지
|
|
569
|
+
2. **SEMO 메시지 필수**: 모든 위임에 SEMO 메시지 포함
|
|
570
|
+
3. **Package Priority**: 라우팅 충돌 시 설치 순서대로 우선순위 적용
|
|
571
|
+
4. **Cross-Package**: 다른 패키지 전문 영역 요청 시 인계 권유
|
|
572
|
+
|
|
573
|
+
${crossPackageRouting.length > 0 ? `## 🔄 Cross-Package Routing
|
|
574
|
+
|
|
575
|
+
${crossPackageRouting[0]}` : ""}
|
|
576
|
+
|
|
577
|
+
${availableAgents.length > 0 ? `## Available Agents (All Packages)
|
|
578
|
+
|
|
579
|
+
${availableAgents.join("\n\n")}` : ""}
|
|
580
|
+
|
|
581
|
+
${availableSkills.length > 0 ? `## Available Skills (All Packages)
|
|
582
|
+
|
|
583
|
+
${availableSkills.join("\n\n")}` : ""}
|
|
584
|
+
|
|
585
|
+
## References
|
|
586
|
+
|
|
587
|
+
- 원본 Orchestrator: \`_packages/\` 디렉토리
|
|
588
|
+
- 병합된 References: \`references/\` 디렉토리
|
|
589
|
+
`;
|
|
590
|
+
fs.writeFileSync(path.join(orchestratorDir, "orchestrator.md"), mergedContent);
|
|
591
|
+
}
|
|
406
592
|
// === Extensions 심볼릭 링크 설정 (agents/skills 병합) ===
|
|
407
593
|
async function setupExtensionSymlinks(cwd, packages) {
|
|
408
594
|
console.log(chalk_1.default.cyan("\n🔗 Extensions 연결"));
|
|
@@ -413,12 +599,12 @@ async function setupExtensionSymlinks(cwd, packages) {
|
|
|
413
599
|
const claudeSkillsDir = path.join(claudeDir, "skills");
|
|
414
600
|
fs.mkdirSync(claudeAgentsDir, { recursive: true });
|
|
415
601
|
fs.mkdirSync(claudeSkillsDir, { recursive: true });
|
|
602
|
+
// Orchestrator 소스 수집 (병합용)
|
|
603
|
+
const orchestratorSources = [];
|
|
416
604
|
for (const pkg of packages) {
|
|
417
605
|
const pkgPath = path.join(semoSystemDir, pkg);
|
|
418
606
|
if (!fs.existsSync(pkgPath))
|
|
419
607
|
continue;
|
|
420
|
-
// Note: .claude/semo-{pkg} 링크는 생성하지 않음 (불필요)
|
|
421
|
-
// Extension의 agents/skills만 개별 링크하여 병합
|
|
422
608
|
// 1. Extension의 agents를 .claude/agents/에 개별 링크
|
|
423
609
|
const extAgentsDir = path.join(pkgPath, "agents");
|
|
424
610
|
if (fs.existsSync(extAgentsDir)) {
|
|
@@ -426,6 +612,11 @@ async function setupExtensionSymlinks(cwd, packages) {
|
|
|
426
612
|
for (const agent of agents) {
|
|
427
613
|
const agentLink = path.join(claudeAgentsDir, agent);
|
|
428
614
|
const agentTarget = path.join(extAgentsDir, agent);
|
|
615
|
+
// Orchestrator는 특별 처리 (병합 필요)
|
|
616
|
+
if (agent === "orchestrator") {
|
|
617
|
+
orchestratorSources.push({ pkg, path: agentTarget });
|
|
618
|
+
continue; // 심볼릭 링크 생성 안 함
|
|
619
|
+
}
|
|
429
620
|
if (!fs.existsSync(agentLink)) {
|
|
430
621
|
createSymlinkOrJunction(agentTarget, agentLink);
|
|
431
622
|
console.log(chalk_1.default.green(` ✓ .claude/agents/${agent} → semo-system/${pkg}/agents/${agent}`));
|
|
@@ -446,6 +637,27 @@ async function setupExtensionSymlinks(cwd, packages) {
|
|
|
446
637
|
}
|
|
447
638
|
}
|
|
448
639
|
}
|
|
640
|
+
// 3. Orchestrator 병합 처리
|
|
641
|
+
if (orchestratorSources.length > 0) {
|
|
642
|
+
// 기존 orchestrator 링크/디렉토리 제거
|
|
643
|
+
const orchestratorPath = path.join(claudeAgentsDir, "orchestrator");
|
|
644
|
+
if (fs.existsSync(orchestratorPath)) {
|
|
645
|
+
removeRecursive(orchestratorPath);
|
|
646
|
+
}
|
|
647
|
+
if (orchestratorSources.length === 1) {
|
|
648
|
+
// 단일 패키지: 심볼릭 링크
|
|
649
|
+
createSymlinkOrJunction(orchestratorSources[0].path, orchestratorPath);
|
|
650
|
+
console.log(chalk_1.default.green(` ✓ .claude/agents/orchestrator → semo-system/${orchestratorSources[0].pkg}/agents/orchestrator`));
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
// 다중 패키지: 병합 파일 생성
|
|
654
|
+
createMergedOrchestrator(claudeAgentsDir, orchestratorSources);
|
|
655
|
+
console.log(chalk_1.default.green(` ✓ .claude/agents/orchestrator (${orchestratorSources.length}개 패키지 병합)`));
|
|
656
|
+
for (const source of orchestratorSources) {
|
|
657
|
+
console.log(chalk_1.default.gray(` - semo-system/${source.pkg}/agents/orchestrator`));
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
449
661
|
}
|
|
450
662
|
const BASE_MCP_SERVERS = [
|
|
451
663
|
{
|
|
@@ -997,44 +1209,90 @@ ${packageClaudeMdSections}
|
|
|
997
1209
|
}
|
|
998
1210
|
// === add 명령어 ===
|
|
999
1211
|
program
|
|
1000
|
-
.command("add <
|
|
1001
|
-
.description("Extension 패키지를 추가로 설치합니다")
|
|
1212
|
+
.command("add <packages>")
|
|
1213
|
+
.description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops / 개별: biz/discovery, eng/nextjs)")
|
|
1002
1214
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
1003
|
-
.action(async (
|
|
1215
|
+
.action(async (packagesInput, options) => {
|
|
1004
1216
|
const cwd = process.cwd();
|
|
1005
1217
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
1006
1218
|
if (!fs.existsSync(semoSystemDir)) {
|
|
1007
1219
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
1008
1220
|
process.exit(1);
|
|
1009
1221
|
}
|
|
1010
|
-
//
|
|
1011
|
-
|
|
1012
|
-
if (
|
|
1013
|
-
|
|
1014
|
-
console.log(chalk_1.default.
|
|
1015
|
-
}
|
|
1016
|
-
if (!(resolvedPackage in EXTENSION_PACKAGES)) {
|
|
1017
|
-
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packageName}`));
|
|
1222
|
+
// 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
1223
|
+
const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
|
|
1224
|
+
if (packages.length === 0) {
|
|
1225
|
+
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
1226
|
+
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
1018
1227
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
1019
1228
|
console.log(chalk_1.default.gray(`레거시 별칭: ${Object.keys(LEGACY_MAPPING).join(", ")}\n`));
|
|
1020
1229
|
process.exit(1);
|
|
1021
1230
|
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
console.log(chalk_1.default.
|
|
1026
|
-
|
|
1231
|
+
// 그룹 설치인 경우 안내
|
|
1232
|
+
if (isGroup) {
|
|
1233
|
+
console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
|
|
1234
|
+
console.log(chalk_1.default.gray(" 포함된 패키지:"));
|
|
1235
|
+
for (const pkg of packages) {
|
|
1236
|
+
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
1237
|
+
}
|
|
1238
|
+
console.log();
|
|
1239
|
+
}
|
|
1240
|
+
else if (packages.length === 1) {
|
|
1241
|
+
// 단일 패키지
|
|
1242
|
+
const pkg = packages[0];
|
|
1243
|
+
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
1244
|
+
console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[pkg].desc}\n`));
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
// 여러 패키지 (쉼표 구분)
|
|
1248
|
+
console.log(chalk_1.default.cyan.bold(`\n📦 ${packages.length}개 패키지 설치\n`));
|
|
1249
|
+
for (const pkg of packages) {
|
|
1250
|
+
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
1251
|
+
}
|
|
1252
|
+
console.log();
|
|
1253
|
+
}
|
|
1254
|
+
// 이미 설치된 패키지 확인
|
|
1255
|
+
const alreadyInstalled = [];
|
|
1256
|
+
const toInstall = [];
|
|
1257
|
+
for (const pkg of packages) {
|
|
1258
|
+
const pkgPath = path.join(semoSystemDir, pkg);
|
|
1259
|
+
if (fs.existsSync(pkgPath) && !options.force) {
|
|
1260
|
+
alreadyInstalled.push(pkg);
|
|
1261
|
+
}
|
|
1262
|
+
else {
|
|
1263
|
+
toInstall.push(pkg);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
if (alreadyInstalled.length > 0) {
|
|
1267
|
+
console.log(chalk_1.default.yellow("⚠ 이미 설치된 패키지 (건너뜀):"));
|
|
1268
|
+
for (const pkg of alreadyInstalled) {
|
|
1269
|
+
console.log(chalk_1.default.yellow(` - ${pkg}`));
|
|
1270
|
+
}
|
|
1271
|
+
console.log(chalk_1.default.gray(" 강제 재설치: semo add " + packagesInput + " --force\n"));
|
|
1272
|
+
}
|
|
1273
|
+
if (toInstall.length === 0) {
|
|
1274
|
+
console.log(chalk_1.default.yellow("\n모든 패키지가 이미 설치되어 있습니다.\n"));
|
|
1027
1275
|
return;
|
|
1028
1276
|
}
|
|
1029
|
-
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[packageName].name} 패키지 설치\n`));
|
|
1030
|
-
console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[packageName].desc}\n`));
|
|
1031
1277
|
// 1. 다운로드
|
|
1032
|
-
await downloadExtensions(cwd,
|
|
1278
|
+
await downloadExtensions(cwd, toInstall, options.force);
|
|
1033
1279
|
// 2. settings.json 병합
|
|
1034
|
-
await mergeExtensionSettings(cwd,
|
|
1035
|
-
// 3. 심볼릭 링크 설정
|
|
1036
|
-
|
|
1037
|
-
|
|
1280
|
+
await mergeExtensionSettings(cwd, toInstall);
|
|
1281
|
+
// 3. 심볼릭 링크 설정 (모든 설치된 패키지 포함하여 orchestrator 병합)
|
|
1282
|
+
const allInstalledPackages = [...new Set([...alreadyInstalled, ...toInstall])];
|
|
1283
|
+
await setupExtensionSymlinks(cwd, allInstalledPackages);
|
|
1284
|
+
// 4. CLAUDE.md 재생성 (모든 설치된 패키지 반영)
|
|
1285
|
+
await setupClaudeMd(cwd, allInstalledPackages, options.force);
|
|
1286
|
+
if (toInstall.length === 1) {
|
|
1287
|
+
console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[toInstall[0]].name} 패키지 설치 완료!\n`));
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
console.log(chalk_1.default.green.bold(`\n✅ ${toInstall.length}개 패키지 설치 완료!`));
|
|
1291
|
+
for (const pkg of toInstall) {
|
|
1292
|
+
console.log(chalk_1.default.green(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
1293
|
+
}
|
|
1294
|
+
console.log();
|
|
1295
|
+
}
|
|
1038
1296
|
});
|
|
1039
1297
|
// === list 명령어 ===
|
|
1040
1298
|
program
|
|
@@ -1072,6 +1330,13 @@ program
|
|
|
1072
1330
|
}
|
|
1073
1331
|
console.log();
|
|
1074
1332
|
}
|
|
1333
|
+
// 그룹 설치 안내
|
|
1334
|
+
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
1335
|
+
console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
|
|
1336
|
+
console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
|
|
1337
|
+
console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
|
|
1338
|
+
console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
|
|
1339
|
+
console.log();
|
|
1075
1340
|
// 레거시 호환성 안내
|
|
1076
1341
|
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
1077
1342
|
console.log(chalk_1.default.gray("레거시 명령어도 지원됩니다:"));
|