@team-semicolon/semo-cli 3.5.0 → 3.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/dist/index.js +168 -270
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -235,48 +235,9 @@ async function showVersionComparison(cwd) {
|
|
|
235
235
|
level: 0,
|
|
236
236
|
});
|
|
237
237
|
}
|
|
238
|
-
//
|
|
239
|
-
// 그룹별로 묶어서 계층 구조로 출력
|
|
238
|
+
// Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
|
|
240
239
|
if (hasSemoSystem) {
|
|
241
|
-
for (const
|
|
242
|
-
const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
|
|
243
|
-
const hasGroupVersion = fs.existsSync(groupVersionPath);
|
|
244
|
-
// 해당 그룹의 하위 패키지 찾기
|
|
245
|
-
const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
|
|
246
|
-
const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
|
|
247
|
-
// 그룹 버전이 있거나 하위 패키지가 설치된 경우에만 표시
|
|
248
|
-
if (hasGroupVersion || installedGroupExtensions.length > 0) {
|
|
249
|
-
// 그룹 패키지 버전 추가
|
|
250
|
-
if (hasGroupVersion) {
|
|
251
|
-
const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
|
|
252
|
-
const remoteGroup = await getRemotePackageVersion(group);
|
|
253
|
-
versionInfos.push({
|
|
254
|
-
name: group,
|
|
255
|
-
local: localGroup,
|
|
256
|
-
remote: remoteGroup,
|
|
257
|
-
needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
|
|
258
|
-
level: 1,
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
// 하위 Extension 패키지들 추가
|
|
262
|
-
for (const key of installedGroupExtensions) {
|
|
263
|
-
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
264
|
-
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
265
|
-
const remoteExt = await getRemotePackageVersion(key);
|
|
266
|
-
versionInfos.push({
|
|
267
|
-
name: key,
|
|
268
|
-
local: localExt,
|
|
269
|
-
remote: remoteExt,
|
|
270
|
-
needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
|
|
271
|
-
level: 2,
|
|
272
|
-
group: group,
|
|
273
|
-
});
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// 그룹에 속하지 않는 Extension (meta 등)
|
|
278
|
-
const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
|
|
279
|
-
for (const key of nonGroupExtensions) {
|
|
240
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
280
241
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
281
242
|
if (fs.existsSync(extVersionPath)) {
|
|
282
243
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -650,92 +611,39 @@ function copyRecursive(src, dest) {
|
|
|
650
611
|
}
|
|
651
612
|
}
|
|
652
613
|
const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
|
|
653
|
-
//
|
|
614
|
+
// Extension 패키지 정의 (통합 구조)
|
|
654
615
|
const EXTENSION_PACKAGES = {
|
|
655
|
-
|
|
656
|
-
"
|
|
657
|
-
"
|
|
658
|
-
"biz/management": { name: "Management", desc: "일정/인력/스프린트 관리", layer: "biz", detect: [] },
|
|
659
|
-
"biz/poc": { name: "PoC", desc: "빠른 PoC, 패스트트랙", layer: "biz", detect: [] },
|
|
660
|
-
// Engineering Layer
|
|
661
|
-
"eng/nextjs": { name: "Next.js", desc: "Next.js 프론트엔드 개발", layer: "eng", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
|
|
662
|
-
"eng/spring": { name: "Spring", desc: "Spring Boot 백엔드 개발", layer: "eng", detect: ["pom.xml", "build.gradle"] },
|
|
663
|
-
"eng/ms": { name: "Microservice", desc: "마이크로서비스 아키텍처", layer: "eng", detect: [] },
|
|
664
|
-
"eng/infra": { name: "Infra", desc: "인프라/배포 관리", layer: "eng", detect: ["docker-compose.yml", "Dockerfile"] },
|
|
665
|
-
// Operations Layer
|
|
666
|
-
"ops/qa": { name: "QA", desc: "테스트/품질 관리", layer: "ops", detect: [] },
|
|
667
|
-
"ops/monitor": { name: "Monitor", desc: "서비스 현황 모니터링", layer: "ops", detect: [] },
|
|
668
|
-
"ops/improve": { name: "Improve", desc: "개선 제안", layer: "ops", detect: [] },
|
|
669
|
-
// Meta
|
|
670
|
-
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리", layer: "meta", detect: ["semo-core", "semo-skills"] },
|
|
671
|
-
// System (semo-system 하위 패키지)
|
|
672
|
-
"semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
|
|
673
|
-
"semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)", layer: "system", detect: [] },
|
|
616
|
+
meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
|
|
617
|
+
"semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
|
|
618
|
+
"semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
|
|
674
619
|
};
|
|
675
620
|
// 단축명 → 전체 패키지 경로 매핑
|
|
676
621
|
const SHORTNAME_MAPPING = {
|
|
677
|
-
// 하위 패키지명 단축 (discovery → biz/discovery)
|
|
678
|
-
discovery: "biz/discovery",
|
|
679
|
-
design: "biz/design",
|
|
680
|
-
management: "biz/management",
|
|
681
|
-
poc: "biz/poc",
|
|
682
|
-
nextjs: "eng/nextjs",
|
|
683
|
-
spring: "eng/spring",
|
|
684
|
-
ms: "eng/ms",
|
|
685
|
-
infra: "eng/infra",
|
|
686
|
-
qa: "ops/qa",
|
|
687
|
-
monitor: "ops/monitor",
|
|
688
|
-
improve: "ops/improve",
|
|
689
|
-
// 추가 별칭
|
|
690
|
-
next: "eng/nextjs",
|
|
691
|
-
backend: "eng/spring",
|
|
692
|
-
mvp: "biz/poc",
|
|
693
|
-
// System 패키지 단축명
|
|
694
622
|
hooks: "semo-hooks",
|
|
695
623
|
remote: "semo-remote",
|
|
696
624
|
};
|
|
697
|
-
//
|
|
698
|
-
const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
|
|
699
|
-
// 그룹명 → 해당 그룹의 모든 패키지 반환
|
|
700
|
-
function getPackagesByGroup(group) {
|
|
701
|
-
return Object.entries(EXTENSION_PACKAGES)
|
|
702
|
-
.filter(([, pkg]) => pkg.layer === group)
|
|
703
|
-
.map(([key]) => key);
|
|
704
|
-
}
|
|
705
|
-
// 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
|
|
625
|
+
// 패키지 입력을 해석
|
|
706
626
|
function resolvePackageInput(input) {
|
|
707
627
|
// 쉼표로 구분된 여러 패키지 처리
|
|
708
628
|
const parts = input.split(",").map(p => p.trim()).filter(p => p);
|
|
709
629
|
const resolvedPackages = [];
|
|
710
|
-
let isGroup = false;
|
|
711
|
-
let groupName;
|
|
712
630
|
for (const part of parts) {
|
|
713
|
-
// 1.
|
|
714
|
-
if (PACKAGE_GROUPS.includes(part)) {
|
|
715
|
-
const groupPackages = getPackagesByGroup(part);
|
|
716
|
-
resolvedPackages.push(...groupPackages);
|
|
717
|
-
isGroup = true;
|
|
718
|
-
groupName = part;
|
|
719
|
-
continue;
|
|
720
|
-
}
|
|
721
|
-
// 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
|
|
631
|
+
// 1. 단축명 매핑 확인 (hooks → semo-hooks 등)
|
|
722
632
|
if (part in SHORTNAME_MAPPING) {
|
|
723
633
|
resolvedPackages.push(SHORTNAME_MAPPING[part]);
|
|
724
634
|
continue;
|
|
725
635
|
}
|
|
726
|
-
//
|
|
636
|
+
// 2. 직접 패키지명 확인
|
|
727
637
|
if (part in EXTENSION_PACKAGES) {
|
|
728
638
|
resolvedPackages.push(part);
|
|
729
639
|
continue;
|
|
730
640
|
}
|
|
731
|
-
//
|
|
641
|
+
// 3. 유효하지 않은 패키지명
|
|
732
642
|
// (빈 배열 대신 null을 추가하여 나중에 에러 처리)
|
|
733
643
|
}
|
|
734
644
|
// 중복 제거
|
|
735
645
|
return {
|
|
736
646
|
packages: [...new Set(resolvedPackages)],
|
|
737
|
-
isGroup,
|
|
738
|
-
groupName
|
|
739
647
|
};
|
|
740
648
|
}
|
|
741
649
|
const program = new commander_1.Command();
|
|
@@ -796,47 +704,10 @@ async function showVersionInfo() {
|
|
|
796
704
|
level: 0,
|
|
797
705
|
});
|
|
798
706
|
}
|
|
799
|
-
// 4.
|
|
707
|
+
// 4. Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
|
|
800
708
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
801
709
|
if (fs.existsSync(semoSystemDir)) {
|
|
802
|
-
for (const
|
|
803
|
-
const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
|
|
804
|
-
const hasGroupVersion = fs.existsSync(groupVersionPath);
|
|
805
|
-
// 해당 그룹의 하위 패키지 찾기
|
|
806
|
-
const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
|
|
807
|
-
const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
|
|
808
|
-
if (hasGroupVersion || installedGroupExtensions.length > 0) {
|
|
809
|
-
// 그룹 패키지 버전 추가
|
|
810
|
-
if (hasGroupVersion) {
|
|
811
|
-
const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
|
|
812
|
-
const remoteGroup = await getRemotePackageVersion(group);
|
|
813
|
-
versionInfos.push({
|
|
814
|
-
name: group,
|
|
815
|
-
local: localGroup,
|
|
816
|
-
remote: remoteGroup,
|
|
817
|
-
needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
|
|
818
|
-
level: 1,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
// 하위 Extension 패키지들 추가
|
|
822
|
-
for (const key of installedGroupExtensions) {
|
|
823
|
-
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
824
|
-
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
825
|
-
const remoteExt = await getRemotePackageVersion(key);
|
|
826
|
-
versionInfos.push({
|
|
827
|
-
name: key,
|
|
828
|
-
local: localExt,
|
|
829
|
-
remote: remoteExt,
|
|
830
|
-
needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
|
|
831
|
-
level: 2,
|
|
832
|
-
group: group,
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
// 그룹에 속하지 않는 Extension (meta 등)
|
|
838
|
-
const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
|
|
839
|
-
for (const key of nonGroupExtensions) {
|
|
710
|
+
for (const key of Object.keys(EXTENSION_PACKAGES)) {
|
|
840
711
|
const extVersionPath = path.join(semoSystemDir, key, "VERSION");
|
|
841
712
|
if (fs.existsSync(extVersionPath)) {
|
|
842
713
|
const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
|
|
@@ -995,18 +866,6 @@ async function confirmOverwrite(itemName, itemPath) {
|
|
|
995
866
|
]);
|
|
996
867
|
return shouldOverwrite;
|
|
997
868
|
}
|
|
998
|
-
function detectProjectType(cwd) {
|
|
999
|
-
const detected = [];
|
|
1000
|
-
for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
|
|
1001
|
-
for (const file of pkg.detect) {
|
|
1002
|
-
if (fs.existsSync(path.join(cwd, file))) {
|
|
1003
|
-
detected.push(key);
|
|
1004
|
-
break;
|
|
1005
|
-
}
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
return detected;
|
|
1009
|
-
}
|
|
1010
869
|
// === 설치된 Extension 패키지 스캔 ===
|
|
1011
870
|
function getInstalledExtensions(cwd) {
|
|
1012
871
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
@@ -1136,56 +995,17 @@ program
|
|
|
1136
995
|
spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
|
|
1137
996
|
process.exit(1);
|
|
1138
997
|
}
|
|
1139
|
-
// 2.
|
|
1140
|
-
const detected = detectProjectType(cwd);
|
|
998
|
+
// 2. Extension 패키지 처리 (--with 옵션으로만 지정 가능)
|
|
1141
999
|
let extensionsToInstall = [];
|
|
1142
1000
|
if (options.with) {
|
|
1143
1001
|
extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
});
|
|
1150
|
-
const { installDetected } = await inquirer_1.default.prompt([
|
|
1151
|
-
{
|
|
1152
|
-
type: "confirm",
|
|
1153
|
-
name: "installDetected",
|
|
1154
|
-
message: "감지된 패키지를 함께 설치할까요?",
|
|
1155
|
-
default: true,
|
|
1156
|
-
},
|
|
1157
|
-
]);
|
|
1158
|
-
if (installDetected) {
|
|
1159
|
-
extensionsToInstall = detected;
|
|
1002
|
+
if (extensionsToInstall.length > 0) {
|
|
1003
|
+
console.log(chalk_1.default.cyan("\n📦 추가 Extension 설치:"));
|
|
1004
|
+
extensionsToInstall.forEach(pkg => {
|
|
1005
|
+
console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
|
|
1006
|
+
});
|
|
1160
1007
|
}
|
|
1161
1008
|
}
|
|
1162
|
-
else {
|
|
1163
|
-
// 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
|
|
1164
|
-
console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
|
|
1165
|
-
console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
|
|
1166
|
-
// 그룹별로 패키지 구성
|
|
1167
|
-
const packageChoices = [
|
|
1168
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
|
|
1169
|
-
{ name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
|
|
1170
|
-
{ name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
|
|
1171
|
-
{ name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
|
|
1172
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
|
|
1173
|
-
{ name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
|
|
1174
|
-
{ name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
|
|
1175
|
-
{ name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
|
|
1176
|
-
new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
|
|
1177
|
-
{ name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
|
|
1178
|
-
];
|
|
1179
|
-
const { selectedPackages } = await inquirer_1.default.prompt([
|
|
1180
|
-
{
|
|
1181
|
-
type: "checkbox",
|
|
1182
|
-
name: "selectedPackages",
|
|
1183
|
-
message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
|
|
1184
|
-
choices: packageChoices,
|
|
1185
|
-
},
|
|
1186
|
-
]);
|
|
1187
|
-
extensionsToInstall = selectedPackages;
|
|
1188
|
-
}
|
|
1189
1009
|
// 3. .claude 디렉토리 생성
|
|
1190
1010
|
const claudeDir = path.join(cwd, ".claude");
|
|
1191
1011
|
if (!fs.existsSync(claudeDir)) {
|
|
@@ -1238,12 +1058,22 @@ program
|
|
|
1238
1058
|
console.log(chalk_1.default.gray(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
|
|
1239
1059
|
});
|
|
1240
1060
|
}
|
|
1061
|
+
// GitHub MCP 토큰 설정 안내
|
|
1062
|
+
if (!isGitHubTokenConfigured()) {
|
|
1063
|
+
console.log(chalk_1.default.yellow("\n⚠️ GitHub MCP 설정 필요"));
|
|
1064
|
+
console.log(chalk_1.default.gray(" GitHub 이슈/PR 생성 기능을 사용하려면 토큰을 설정하세요:"));
|
|
1065
|
+
console.log(chalk_1.default.white("\n export GITHUB_PERSONAL_ACCESS_TOKEN=\"$(gh auth token)\""));
|
|
1066
|
+
console.log(chalk_1.default.gray("\n 영구 설정: ~/.zshrc 또는 ~/.bashrc에 위 명령어 추가"));
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
console.log(chalk_1.default.green("\n✓ GitHub 토큰 감지됨 (MCP 사용 가능)"));
|
|
1070
|
+
}
|
|
1241
1071
|
console.log(chalk_1.default.cyan("\n다음 단계:"));
|
|
1242
1072
|
console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
|
|
1243
1073
|
console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
|
|
1244
1074
|
console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
|
|
1245
|
-
if (extensionsToInstall.length === 0
|
|
1246
|
-
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add
|
|
1075
|
+
if (extensionsToInstall.length === 0) {
|
|
1076
|
+
console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add meta)"));
|
|
1247
1077
|
}
|
|
1248
1078
|
console.log();
|
|
1249
1079
|
});
|
|
@@ -1854,8 +1684,29 @@ const BASE_MCP_SERVERS = [
|
|
|
1854
1684
|
name: "github",
|
|
1855
1685
|
command: "npx",
|
|
1856
1686
|
args: ["-y", "@modelcontextprotocol/server-github"],
|
|
1687
|
+
env: {
|
|
1688
|
+
GITHUB_PERSONAL_ACCESS_TOKEN: "${GITHUB_PERSONAL_ACCESS_TOKEN}",
|
|
1689
|
+
},
|
|
1857
1690
|
},
|
|
1858
1691
|
];
|
|
1692
|
+
// === GitHub 토큰 자동 감지 ===
|
|
1693
|
+
function getGitHubTokenFromCLI() {
|
|
1694
|
+
try {
|
|
1695
|
+
const token = (0, child_process_1.execSync)("gh auth token", { stdio: "pipe", encoding: "utf-8" }).trim();
|
|
1696
|
+
return token || null;
|
|
1697
|
+
}
|
|
1698
|
+
catch {
|
|
1699
|
+
return null;
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
function isGitHubTokenConfigured() {
|
|
1703
|
+
// 환경변수 확인
|
|
1704
|
+
if (process.env.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
// gh CLI 토큰 확인
|
|
1708
|
+
return getGitHubTokenFromCLI() !== null;
|
|
1709
|
+
}
|
|
1859
1710
|
// === Claude MCP 서버 존재 여부 확인 ===
|
|
1860
1711
|
function isMCPServerRegistered(serverName) {
|
|
1861
1712
|
try {
|
|
@@ -2493,27 +2344,9 @@ async function setupClaudeMd(cwd, extensions, force) {
|
|
|
2493
2344
|
const extensionsList = extensions.length > 0
|
|
2494
2345
|
? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
|
|
2495
2346
|
: "";
|
|
2496
|
-
//
|
|
2347
|
+
// 패키지별 CLAUDE.md 병합 섹션 생성
|
|
2497
2348
|
let packageClaudeMdSections = "";
|
|
2498
|
-
//
|
|
2499
|
-
const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
|
|
2500
|
-
// 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
|
|
2501
|
-
for (const group of installedGroups) {
|
|
2502
|
-
const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
|
|
2503
|
-
if (fs.existsSync(groupClaudeMdPath)) {
|
|
2504
|
-
const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
|
|
2505
|
-
// 중복 제거 후 고유 콘텐츠만 추출
|
|
2506
|
-
const uniqueContent = extractUniqueContent(groupContent, group);
|
|
2507
|
-
// 헤더 레벨 조정 (# → ##, ## → ###)
|
|
2508
|
-
const adjustedContent = uniqueContent
|
|
2509
|
-
.replace(/^# /gm, "## ")
|
|
2510
|
-
.replace(/^## /gm, "### ")
|
|
2511
|
-
.replace(/^### /gm, "#### ");
|
|
2512
|
-
packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
|
|
2513
|
-
console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
|
|
2514
|
-
}
|
|
2515
|
-
}
|
|
2516
|
-
// 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2349
|
+
// 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
|
|
2517
2350
|
for (const pkg of extensions) {
|
|
2518
2351
|
const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
|
|
2519
2352
|
if (fs.existsSync(pkgClaudeMdPath)) {
|
|
@@ -2680,7 +2513,7 @@ ${packageClaudeMdSections}
|
|
|
2680
2513
|
// === add 명령어 ===
|
|
2681
2514
|
program
|
|
2682
2515
|
.command("add <packages>")
|
|
2683
|
-
.description("Extension 패키지를 추가로 설치합니다 (
|
|
2516
|
+
.description("Extension 패키지를 추가로 설치합니다 (meta, semo-hooks, semo-remote)")
|
|
2684
2517
|
.option("-f, --force", "기존 설정 덮어쓰기")
|
|
2685
2518
|
.action(async (packagesInput, options) => {
|
|
2686
2519
|
const cwd = process.cwd();
|
|
@@ -2689,25 +2522,16 @@ program
|
|
|
2689
2522
|
console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
|
|
2690
2523
|
process.exit(1);
|
|
2691
2524
|
}
|
|
2692
|
-
// 패키지 입력 해석
|
|
2693
|
-
const { packages
|
|
2525
|
+
// 패키지 입력 해석
|
|
2526
|
+
const { packages } = resolvePackageInput(packagesInput);
|
|
2694
2527
|
if (packages.length === 0) {
|
|
2695
2528
|
console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
|
|
2696
|
-
console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
|
|
2697
2529
|
console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
|
|
2698
2530
|
console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
|
|
2699
2531
|
process.exit(1);
|
|
2700
2532
|
}
|
|
2701
|
-
//
|
|
2702
|
-
if (
|
|
2703
|
-
console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
|
|
2704
|
-
console.log(chalk_1.default.gray(" 포함된 패키지:"));
|
|
2705
|
-
for (const pkg of packages) {
|
|
2706
|
-
console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
|
|
2707
|
-
}
|
|
2708
|
-
console.log();
|
|
2709
|
-
}
|
|
2710
|
-
else if (packages.length === 1) {
|
|
2533
|
+
// 패키지 설치 안내
|
|
2534
|
+
if (packages.length === 1) {
|
|
2711
2535
|
// 단일 패키지
|
|
2712
2536
|
const pkg = packages[0];
|
|
2713
2537
|
console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
|
|
@@ -2773,50 +2597,28 @@ program
|
|
|
2773
2597
|
.action(() => {
|
|
2774
2598
|
const cwd = process.cwd();
|
|
2775
2599
|
const semoSystemDir = path.join(cwd, "semo-system");
|
|
2776
|
-
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지
|
|
2777
|
-
// Standard
|
|
2600
|
+
console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
|
|
2601
|
+
// Standard (필수)
|
|
2778
2602
|
console.log(chalk_1.default.white.bold("Standard (필수)"));
|
|
2779
2603
|
const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
|
|
2780
2604
|
const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
|
|
2781
2605
|
console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
|
|
2782
2606
|
console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
|
|
2783
2607
|
console.log();
|
|
2784
|
-
// Extensions
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
const
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
|
|
2797
|
-
for (const [key, pkg] of layerPackages) {
|
|
2798
|
-
const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
|
|
2799
|
-
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2800
|
-
const displayKey = key.includes("/") ? key.split("/")[1] : key;
|
|
2801
|
-
console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
|
|
2802
|
-
console.log(chalk_1.default.gray(` semo add ${key}`));
|
|
2803
|
-
}
|
|
2804
|
-
console.log();
|
|
2608
|
+
// Extensions
|
|
2609
|
+
console.log(chalk_1.default.white.bold("Extensions (선택)"));
|
|
2610
|
+
const extensionList = [
|
|
2611
|
+
{ key: "meta", name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
|
|
2612
|
+
{ key: "semo-hooks", name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
|
|
2613
|
+
{ key: "semo-remote", name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
|
|
2614
|
+
];
|
|
2615
|
+
for (const ext of extensionList) {
|
|
2616
|
+
const isInstalled = fs.existsSync(path.join(semoSystemDir, ext.key));
|
|
2617
|
+
const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
|
|
2618
|
+
console.log(` ${status} ${chalk_1.default.cyan(ext.key)} - ${ext.desc}`);
|
|
2619
|
+
console.log(chalk_1.default.gray(` semo add ${ext.key}`));
|
|
2805
2620
|
}
|
|
2806
|
-
// 그룹 설치 안내
|
|
2807
|
-
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2808
|
-
console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
|
|
2809
|
-
console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
|
|
2810
|
-
console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
|
|
2811
|
-
console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
|
|
2812
|
-
console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
|
|
2813
2621
|
console.log();
|
|
2814
|
-
// 단축명 안내
|
|
2815
|
-
console.log(chalk_1.default.gray("─".repeat(50)));
|
|
2816
|
-
console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
|
|
2817
|
-
console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
|
|
2818
|
-
console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
|
|
2819
|
-
console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
|
|
2820
2622
|
});
|
|
2821
2623
|
// === status 명령어 ===
|
|
2822
2624
|
program
|
|
@@ -2876,6 +2678,102 @@ program
|
|
|
2876
2678
|
}
|
|
2877
2679
|
console.log();
|
|
2878
2680
|
});
|
|
2681
|
+
// === doctor 명령어 ===
|
|
2682
|
+
program
|
|
2683
|
+
.command("doctor")
|
|
2684
|
+
.description("SEMO 및 MCP 설정 문제를 진단합니다")
|
|
2685
|
+
.action(() => {
|
|
2686
|
+
console.log(chalk_1.default.cyan.bold("\n🩺 SEMO 진단\n"));
|
|
2687
|
+
const cwd = process.cwd();
|
|
2688
|
+
let hasIssues = false;
|
|
2689
|
+
// 1. gh CLI 확인
|
|
2690
|
+
console.log(chalk_1.default.white.bold("GitHub CLI:"));
|
|
2691
|
+
try {
|
|
2692
|
+
const ghVersion = (0, child_process_1.execSync)("gh --version", { stdio: "pipe", encoding: "utf-8" }).split("\n")[0];
|
|
2693
|
+
console.log(chalk_1.default.green(` ✓ 설치됨 (${ghVersion.replace("gh version ", "")})`));
|
|
2694
|
+
// gh 인증 상태
|
|
2695
|
+
try {
|
|
2696
|
+
(0, child_process_1.execSync)("gh auth status", { stdio: "pipe" });
|
|
2697
|
+
console.log(chalk_1.default.green(" ✓ 인증됨"));
|
|
2698
|
+
}
|
|
2699
|
+
catch {
|
|
2700
|
+
console.log(chalk_1.default.red(" ✗ 인증 필요: gh auth login"));
|
|
2701
|
+
hasIssues = true;
|
|
2702
|
+
}
|
|
2703
|
+
}
|
|
2704
|
+
catch {
|
|
2705
|
+
console.log(chalk_1.default.red(" ✗ 미설치: brew install gh"));
|
|
2706
|
+
hasIssues = true;
|
|
2707
|
+
}
|
|
2708
|
+
// 2. GitHub 토큰 확인
|
|
2709
|
+
console.log(chalk_1.default.white.bold("\nGitHub MCP 토큰:"));
|
|
2710
|
+
const ghToken = getGitHubTokenFromCLI();
|
|
2711
|
+
const envToken = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
|
2712
|
+
if (envToken) {
|
|
2713
|
+
console.log(chalk_1.default.green(" ✓ 환경변수 설정됨 (GITHUB_PERSONAL_ACCESS_TOKEN)"));
|
|
2714
|
+
}
|
|
2715
|
+
else if (ghToken) {
|
|
2716
|
+
console.log(chalk_1.default.yellow(" ⚠ gh CLI 토큰 있음 (환경변수 미설정)"));
|
|
2717
|
+
console.log(chalk_1.default.gray(" GitHub MCP 사용하려면:"));
|
|
2718
|
+
console.log(chalk_1.default.white(' export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
|
|
2719
|
+
hasIssues = true;
|
|
2720
|
+
}
|
|
2721
|
+
else {
|
|
2722
|
+
console.log(chalk_1.default.red(" ✗ 토큰 없음"));
|
|
2723
|
+
console.log(chalk_1.default.gray(" 1. gh auth login 실행"));
|
|
2724
|
+
console.log(chalk_1.default.white(' 2. export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
|
|
2725
|
+
hasIssues = true;
|
|
2726
|
+
}
|
|
2727
|
+
// 3. MCP 서버 등록 상태
|
|
2728
|
+
console.log(chalk_1.default.white.bold("\nMCP 서버:"));
|
|
2729
|
+
const requiredServers = ["semo-integrations", "github", "context7"];
|
|
2730
|
+
for (const server of requiredServers) {
|
|
2731
|
+
if (isMCPServerRegistered(server)) {
|
|
2732
|
+
console.log(chalk_1.default.green(` ✓ ${server}`));
|
|
2733
|
+
}
|
|
2734
|
+
else {
|
|
2735
|
+
console.log(chalk_1.default.yellow(` ⚠ ${server} 미등록`));
|
|
2736
|
+
hasIssues = true;
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
// 4. settings.json 확인
|
|
2740
|
+
console.log(chalk_1.default.white.bold("\n설정 파일:"));
|
|
2741
|
+
const settingsPath = path.join(cwd, ".claude", "settings.json");
|
|
2742
|
+
if (fs.existsSync(settingsPath)) {
|
|
2743
|
+
console.log(chalk_1.default.green(" ✓ .claude/settings.json"));
|
|
2744
|
+
try {
|
|
2745
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
2746
|
+
if (settings.mcpServers?.github?.env?.GITHUB_PERSONAL_ACCESS_TOKEN) {
|
|
2747
|
+
console.log(chalk_1.default.green(" ✓ GitHub MCP 환경변수 설정됨"));
|
|
2748
|
+
}
|
|
2749
|
+
else {
|
|
2750
|
+
console.log(chalk_1.default.yellow(" ⚠ GitHub MCP 환경변수 누락"));
|
|
2751
|
+
hasIssues = true;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
catch {
|
|
2755
|
+
console.log(chalk_1.default.yellow(" ⚠ settings.json 파싱 실패"));
|
|
2756
|
+
hasIssues = true;
|
|
2757
|
+
}
|
|
2758
|
+
}
|
|
2759
|
+
else {
|
|
2760
|
+
console.log(chalk_1.default.red(" ✗ .claude/settings.json 없음"));
|
|
2761
|
+
hasIssues = true;
|
|
2762
|
+
}
|
|
2763
|
+
// 결과
|
|
2764
|
+
console.log();
|
|
2765
|
+
if (hasIssues) {
|
|
2766
|
+
console.log(chalk_1.default.yellow.bold("⚠️ 일부 문제가 발견되었습니다."));
|
|
2767
|
+
console.log(chalk_1.default.gray("\nGitHub MCP 빠른 설정:"));
|
|
2768
|
+
console.log(chalk_1.default.white(' export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"'));
|
|
2769
|
+
console.log(chalk_1.default.gray("\n영구 설정 (셸 재시작 후 적용):"));
|
|
2770
|
+
console.log(chalk_1.default.white(' echo \'export GITHUB_PERSONAL_ACCESS_TOKEN="$(gh auth token)"\' >> ~/.zshrc'));
|
|
2771
|
+
}
|
|
2772
|
+
else {
|
|
2773
|
+
console.log(chalk_1.default.green.bold("✅ 모든 설정이 정상입니다."));
|
|
2774
|
+
}
|
|
2775
|
+
console.log();
|
|
2776
|
+
});
|
|
2879
2777
|
// === update 명령어 ===
|
|
2880
2778
|
program
|
|
2881
2779
|
.command("update")
|