@team-semicolon/semo-cli 3.4.0 → 3.6.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.
Files changed (2) hide show
  1. package/dist/index.js +78 -272
  2. 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
- // 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
239
- // 그룹별로 묶어서 계층 구조로 출력
238
+ // Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
240
239
  if (hasSemoSystem) {
241
- for (const group of PACKAGE_GROUPS) {
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();
@@ -481,6 +442,23 @@ function detectLegacyEnvironment(cwd) {
481
442
  legacyPaths.push(dir);
482
443
  }
483
444
  }
445
+ // semo-system/ 내부의 레거시 Extension 구조 확인
446
+ // 구버전: semo-system/biz/, semo-system/eng/, semo-system/ops/ (그룹 디렉토리)
447
+ // 신버전: semo-system/biz/design/, semo-system/eng/nextjs/ 등 (개별 패키지)
448
+ const semoSystemDir = path.join(cwd, "semo-system");
449
+ if (fs.existsSync(semoSystemDir)) {
450
+ const legacyExtGroups = ["biz", "eng", "ops"];
451
+ for (const group of legacyExtGroups) {
452
+ const groupDir = path.join(semoSystemDir, group);
453
+ if (fs.existsSync(groupDir) && fs.statSync(groupDir).isDirectory()) {
454
+ // VERSION 파일이 그룹 디렉토리에 직접 있으면 레거시 구조
455
+ const groupVersionFile = path.join(groupDir, "VERSION");
456
+ if (fs.existsSync(groupVersionFile)) {
457
+ legacyPaths.push(`semo-system/${group} (레거시 그룹 구조)`);
458
+ }
459
+ }
460
+ }
461
+ }
484
462
  // .claude/ 내부의 레거시 구조 확인
485
463
  const claudeDir = path.join(cwd, ".claude");
486
464
  if (fs.existsSync(claudeDir)) {
@@ -563,10 +541,28 @@ async function migrateLegacyEnvironment(cwd) {
563
541
  }
564
542
  }
565
543
  }
566
- // 3. 기존 semo-system 있으면 삭제 (새로 설치)
544
+ // 3. semo-system/ 내부의 레거시 Extension 그룹 삭제
567
545
  const semoSystemDir = path.join(cwd, "semo-system");
568
546
  if (fs.existsSync(semoSystemDir)) {
569
- removeRecursive(semoSystemDir);
547
+ const legacyExtGroups = ["biz", "eng", "ops"];
548
+ for (const group of legacyExtGroups) {
549
+ const groupDir = path.join(semoSystemDir, group);
550
+ const groupVersionFile = path.join(groupDir, "VERSION");
551
+ // VERSION 파일이 그룹 디렉토리에 직접 있으면 레거시 구조이므로 삭제
552
+ if (fs.existsSync(groupVersionFile)) {
553
+ removeRecursive(groupDir);
554
+ console.log(chalk_1.default.gray(` ✓ semo-system/${group}/ 삭제됨 (레거시 그룹 구조)`));
555
+ }
556
+ }
557
+ }
558
+ // 4. 기존 semo-system이 완전히 레거시인 경우에만 삭제
559
+ // (Standard 패키지가 없는 경우)
560
+ if (fs.existsSync(semoSystemDir)) {
561
+ const hasStandard = fs.existsSync(path.join(semoSystemDir, "semo-core"));
562
+ if (!hasStandard) {
563
+ removeRecursive(semoSystemDir);
564
+ console.log(chalk_1.default.gray(` ✓ semo-system/ 삭제됨 (완전 재설치)`));
565
+ }
570
566
  }
571
567
  spinner.succeed("레거시 환경 정리 완료");
572
568
  console.log(chalk_1.default.green(" → 새 환경으로 설치를 진행합니다.\n"));
@@ -615,92 +611,39 @@ function copyRecursive(src, dest) {
615
611
  }
616
612
  }
617
613
  const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
618
- // 확장 패키지 정의 (v3.0 구조)
614
+ // Extension 패키지 정의 (통합 구조)
619
615
  const EXTENSION_PACKAGES = {
620
- // Business Layer
621
- "biz/discovery": { name: "Discovery", desc: "아이템 발굴, 시장 조사, Epic/Task", layer: "biz", detect: [] },
622
- "biz/design": { name: "Design", desc: "컨셉 설계, 목업, UX", layer: "biz", detect: [] },
623
- "biz/management": { name: "Management", desc: "일정/인력/스프린트 관리", layer: "biz", detect: [] },
624
- "biz/poc": { name: "PoC", desc: "빠른 PoC, 패스트트랙", layer: "biz", detect: [] },
625
- // Engineering Layer
626
- "eng/nextjs": { name: "Next.js", desc: "Next.js 프론트엔드 개발", layer: "eng", detect: ["next.config.js", "next.config.mjs", "next.config.ts"] },
627
- "eng/spring": { name: "Spring", desc: "Spring Boot 백엔드 개발", layer: "eng", detect: ["pom.xml", "build.gradle"] },
628
- "eng/ms": { name: "Microservice", desc: "마이크로서비스 아키텍처", layer: "eng", detect: [] },
629
- "eng/infra": { name: "Infra", desc: "인프라/배포 관리", layer: "eng", detect: ["docker-compose.yml", "Dockerfile"] },
630
- // Operations Layer
631
- "ops/qa": { name: "QA", desc: "테스트/품질 관리", layer: "ops", detect: [] },
632
- "ops/monitor": { name: "Monitor", desc: "서비스 현황 모니터링", layer: "ops", detect: [] },
633
- "ops/improve": { name: "Improve", desc: "개선 제안", layer: "ops", detect: [] },
634
- // Meta
635
- meta: { name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리", layer: "meta", detect: ["semo-core", "semo-skills"] },
636
- // System (semo-system 하위 패키지)
637
- "semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
638
- "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)" },
639
619
  };
640
620
  // 단축명 → 전체 패키지 경로 매핑
641
621
  const SHORTNAME_MAPPING = {
642
- // 하위 패키지명 단축 (discovery → biz/discovery)
643
- discovery: "biz/discovery",
644
- design: "biz/design",
645
- management: "biz/management",
646
- poc: "biz/poc",
647
- nextjs: "eng/nextjs",
648
- spring: "eng/spring",
649
- ms: "eng/ms",
650
- infra: "eng/infra",
651
- qa: "ops/qa",
652
- monitor: "ops/monitor",
653
- improve: "ops/improve",
654
- // 추가 별칭
655
- next: "eng/nextjs",
656
- backend: "eng/spring",
657
- mvp: "biz/poc",
658
- // System 패키지 단축명
659
622
  hooks: "semo-hooks",
660
623
  remote: "semo-remote",
661
624
  };
662
- // 그룹 이름 목록 (biz, eng, ops, meta, system)
663
- const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
664
- // 그룹명 → 해당 그룹의 모든 패키지 반환
665
- function getPackagesByGroup(group) {
666
- return Object.entries(EXTENSION_PACKAGES)
667
- .filter(([, pkg]) => pkg.layer === group)
668
- .map(([key]) => key);
669
- }
670
- // 패키지 입력을 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
625
+ // 패키지 입력을 해석
671
626
  function resolvePackageInput(input) {
672
627
  // 쉼표로 구분된 여러 패키지 처리
673
628
  const parts = input.split(",").map(p => p.trim()).filter(p => p);
674
629
  const resolvedPackages = [];
675
- let isGroup = false;
676
- let groupName;
677
630
  for (const part of parts) {
678
- // 1. 그룹명인지 확인 (biz, eng, ops, meta)
679
- if (PACKAGE_GROUPS.includes(part)) {
680
- const groupPackages = getPackagesByGroup(part);
681
- resolvedPackages.push(...groupPackages);
682
- isGroup = true;
683
- groupName = part;
684
- continue;
685
- }
686
- // 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
631
+ // 1. 단축명 매핑 확인 (hooks semo-hooks )
687
632
  if (part in SHORTNAME_MAPPING) {
688
633
  resolvedPackages.push(SHORTNAME_MAPPING[part]);
689
634
  continue;
690
635
  }
691
- // 3. 직접 패키지명 확인
636
+ // 2. 직접 패키지명 확인
692
637
  if (part in EXTENSION_PACKAGES) {
693
638
  resolvedPackages.push(part);
694
639
  continue;
695
640
  }
696
- // 4. 유효하지 않은 패키지명
641
+ // 3. 유효하지 않은 패키지명
697
642
  // (빈 배열 대신 null을 추가하여 나중에 에러 처리)
698
643
  }
699
644
  // 중복 제거
700
645
  return {
701
646
  packages: [...new Set(resolvedPackages)],
702
- isGroup,
703
- groupName
704
647
  };
705
648
  }
706
649
  const program = new commander_1.Command();
@@ -761,47 +704,10 @@ async function showVersionInfo() {
761
704
  level: 0,
762
705
  });
763
706
  }
764
- // 4. 그룹 패키지 (eng, biz, ops) 및 하위 Extension - semo-system 내부
707
+ // 4. Extension 패키지들 (meta, semo-hooks, semo-remote 등) - semo-system 내부
765
708
  const semoSystemDir = path.join(cwd, "semo-system");
766
709
  if (fs.existsSync(semoSystemDir)) {
767
- for (const group of PACKAGE_GROUPS) {
768
- const groupVersionPath = path.join(semoSystemDir, group, "VERSION");
769
- const hasGroupVersion = fs.existsSync(groupVersionPath);
770
- // 해당 그룹의 하위 패키지 찾기
771
- const groupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => key.startsWith(`${group}/`));
772
- const installedGroupExtensions = groupExtensions.filter(key => fs.existsSync(path.join(semoSystemDir, key, "VERSION")));
773
- if (hasGroupVersion || installedGroupExtensions.length > 0) {
774
- // 그룹 패키지 버전 추가
775
- if (hasGroupVersion) {
776
- const localGroup = fs.readFileSync(groupVersionPath, "utf-8").trim();
777
- const remoteGroup = await getRemotePackageVersion(group);
778
- versionInfos.push({
779
- name: group,
780
- local: localGroup,
781
- remote: remoteGroup,
782
- needsUpdate: remoteGroup ? isVersionLower(localGroup, remoteGroup) : false,
783
- level: 1,
784
- });
785
- }
786
- // 하위 Extension 패키지들 추가
787
- for (const key of installedGroupExtensions) {
788
- const extVersionPath = path.join(semoSystemDir, key, "VERSION");
789
- const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
790
- const remoteExt = await getRemotePackageVersion(key);
791
- versionInfos.push({
792
- name: key,
793
- local: localExt,
794
- remote: remoteExt,
795
- needsUpdate: remoteExt ? isVersionLower(localExt, remoteExt) : false,
796
- level: 2,
797
- group: group,
798
- });
799
- }
800
- }
801
- }
802
- // 그룹에 속하지 않는 Extension (meta 등)
803
- const nonGroupExtensions = Object.keys(EXTENSION_PACKAGES).filter(key => !PACKAGE_GROUPS.some(g => key.startsWith(`${g}/`)));
804
- for (const key of nonGroupExtensions) {
710
+ for (const key of Object.keys(EXTENSION_PACKAGES)) {
805
711
  const extVersionPath = path.join(semoSystemDir, key, "VERSION");
806
712
  if (fs.existsSync(extVersionPath)) {
807
713
  const localExt = fs.readFileSync(extVersionPath, "utf-8").trim();
@@ -960,18 +866,6 @@ async function confirmOverwrite(itemName, itemPath) {
960
866
  ]);
961
867
  return shouldOverwrite;
962
868
  }
963
- function detectProjectType(cwd) {
964
- const detected = [];
965
- for (const [key, pkg] of Object.entries(EXTENSION_PACKAGES)) {
966
- for (const file of pkg.detect) {
967
- if (fs.existsSync(path.join(cwd, file))) {
968
- detected.push(key);
969
- break;
970
- }
971
- }
972
- }
973
- return detected;
974
- }
975
869
  // === 설치된 Extension 패키지 스캔 ===
976
870
  function getInstalledExtensions(cwd) {
977
871
  const semoSystemDir = path.join(cwd, "semo-system");
@@ -1101,56 +995,17 @@ program
1101
995
  spinner.fail("Git 레포지토리가 아닙니다. 'git init'을 먼저 실행하세요.");
1102
996
  process.exit(1);
1103
997
  }
1104
- // 2. 프로젝트 유형 감지
1105
- const detected = detectProjectType(cwd);
998
+ // 2. Extension 패키지 처리 (--with 옵션으로만 지정 가능)
1106
999
  let extensionsToInstall = [];
1107
1000
  if (options.with) {
1108
1001
  extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES);
1109
- }
1110
- else if (detected.length > 0) {
1111
- console.log(chalk_1.default.cyan("\n📦 감지된 프로젝트 유형:"));
1112
- detected.forEach(pkg => {
1113
- console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
1114
- });
1115
- const { installDetected } = await inquirer_1.default.prompt([
1116
- {
1117
- type: "confirm",
1118
- name: "installDetected",
1119
- message: "감지된 패키지를 함께 설치할까요?",
1120
- default: true,
1121
- },
1122
- ]);
1123
- if (installDetected) {
1124
- 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
+ });
1125
1007
  }
1126
1008
  }
1127
- else {
1128
- // 프로젝트 유형이 감지되지 않은 경우 패키지 선택 프롬프트
1129
- console.log(chalk_1.default.cyan("\n📦 추가 패키지 선택"));
1130
- console.log(chalk_1.default.gray(" 기본 설치 (semo-core + semo-skills) 외에 추가할 패키지를 선택하세요.\n"));
1131
- // 그룹별로 패키지 구성
1132
- const packageChoices = [
1133
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Engineering ──")),
1134
- { name: `eng/nextjs - ${EXTENSION_PACKAGES["eng/nextjs"].desc}`, value: "eng/nextjs" },
1135
- { name: `eng/spring - ${EXTENSION_PACKAGES["eng/spring"].desc}`, value: "eng/spring" },
1136
- { name: `eng/infra - ${EXTENSION_PACKAGES["eng/infra"].desc}`, value: "eng/infra" },
1137
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Business ──")),
1138
- { name: `biz/discovery - ${EXTENSION_PACKAGES["biz/discovery"].desc}`, value: "biz/discovery" },
1139
- { name: `biz/management - ${EXTENSION_PACKAGES["biz/management"].desc}`, value: "biz/management" },
1140
- { name: `biz/design - ${EXTENSION_PACKAGES["biz/design"].desc}`, value: "biz/design" },
1141
- new inquirer_1.default.Separator(chalk_1.default.yellow("── Operations ──")),
1142
- { name: `ops/qa - ${EXTENSION_PACKAGES["ops/qa"].desc}`, value: "ops/qa" },
1143
- ];
1144
- const { selectedPackages } = await inquirer_1.default.prompt([
1145
- {
1146
- type: "checkbox",
1147
- name: "selectedPackages",
1148
- message: "설치할 패키지 선택 (Space로 선택, Enter로 완료):",
1149
- choices: packageChoices,
1150
- },
1151
- ]);
1152
- extensionsToInstall = selectedPackages;
1153
- }
1154
1009
  // 3. .claude 디렉토리 생성
1155
1010
  const claudeDir = path.join(cwd, ".claude");
1156
1011
  if (!fs.existsSync(claudeDir)) {
@@ -1207,8 +1062,8 @@ program
1207
1062
  console.log(chalk_1.default.gray(" 1. Claude Code에서 프로젝트 열기"));
1208
1063
  console.log(chalk_1.default.gray(" 2. 자연어로 요청하기 (예: \"댓글 기능 구현해줘\")"));
1209
1064
  console.log(chalk_1.default.gray(" 3. /SEMO:help로 도움말 확인"));
1210
- if (extensionsToInstall.length === 0 && detected.length === 0) {
1211
- console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add next)"));
1065
+ if (extensionsToInstall.length === 0) {
1066
+ console.log(chalk_1.default.gray("\n💡 추가 패키지: semo add <package> (예: semo add meta)"));
1212
1067
  }
1213
1068
  console.log();
1214
1069
  });
@@ -2458,27 +2313,9 @@ async function setupClaudeMd(cwd, extensions, force) {
2458
2313
  const extensionsList = extensions.length > 0
2459
2314
  ? extensions.map(pkg => `├── ${pkg}/ # ${EXTENSION_PACKAGES[pkg].name}`).join("\n")
2460
2315
  : "";
2461
- // 그룹 및 패키지별 CLAUDE.md 병합 섹션 생성
2316
+ // 패키지별 CLAUDE.md 병합 섹션 생성
2462
2317
  let packageClaudeMdSections = "";
2463
- // 1. 설치된 패키지에서 그룹 추출 (중복 제거)
2464
- const installedGroups = [...new Set(extensions.map(pkg => pkg.split("/")[0]).filter(g => PACKAGE_GROUPS.includes(g)))];
2465
- // 2. 그룹 레벨 CLAUDE.md 먼저 병합 (biz, eng, ops) - 중복 제거 적용
2466
- for (const group of installedGroups) {
2467
- const groupClaudeMdPath = path.join(semoSystemDir, group, "CLAUDE.md");
2468
- if (fs.existsSync(groupClaudeMdPath)) {
2469
- const groupContent = fs.readFileSync(groupClaudeMdPath, "utf-8");
2470
- // 중복 제거 후 고유 콘텐츠만 추출
2471
- const uniqueContent = extractUniqueContent(groupContent, group);
2472
- // 헤더 레벨 조정 (# → ##, ## → ###)
2473
- const adjustedContent = uniqueContent
2474
- .replace(/^# /gm, "## ")
2475
- .replace(/^## /gm, "### ")
2476
- .replace(/^### /gm, "#### ");
2477
- packageClaudeMdSections += `\n\n---\n\n${adjustedContent}`;
2478
- console.log(chalk_1.default.green(` + ${group}/ 그룹 CLAUDE.md 병합됨 (고유 섹션만)`));
2479
- }
2480
- }
2481
- // 3. 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
2318
+ // 개별 패키지 CLAUDE.md 병합 - 중복 제거 적용
2482
2319
  for (const pkg of extensions) {
2483
2320
  const pkgClaudeMdPath = path.join(semoSystemDir, pkg, "CLAUDE.md");
2484
2321
  if (fs.existsSync(pkgClaudeMdPath)) {
@@ -2645,7 +2482,7 @@ ${packageClaudeMdSections}
2645
2482
  // === add 명령어 ===
2646
2483
  program
2647
2484
  .command("add <packages>")
2648
- .description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
2485
+ .description("Extension 패키지를 추가로 설치합니다 (meta, semo-hooks, semo-remote)")
2649
2486
  .option("-f, --force", "기존 설정 덮어쓰기")
2650
2487
  .action(async (packagesInput, options) => {
2651
2488
  const cwd = process.cwd();
@@ -2654,25 +2491,16 @@ program
2654
2491
  console.log(chalk_1.default.red("\nSEMO가 설치되어 있지 않습니다. 'semo init'을 먼저 실행하세요.\n"));
2655
2492
  process.exit(1);
2656
2493
  }
2657
- // 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
2658
- const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
2494
+ // 패키지 입력 해석
2495
+ const { packages } = resolvePackageInput(packagesInput);
2659
2496
  if (packages.length === 0) {
2660
2497
  console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
2661
- console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
2662
2498
  console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
2663
2499
  console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
2664
2500
  process.exit(1);
2665
2501
  }
2666
- // 그룹 설치인 경우 안내
2667
- if (isGroup) {
2668
- console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
2669
- console.log(chalk_1.default.gray(" 포함된 패키지:"));
2670
- for (const pkg of packages) {
2671
- console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
2672
- }
2673
- console.log();
2674
- }
2675
- else if (packages.length === 1) {
2502
+ // 패키지 설치 안내
2503
+ if (packages.length === 1) {
2676
2504
  // 단일 패키지
2677
2505
  const pkg = packages[0];
2678
2506
  console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
@@ -2738,50 +2566,28 @@ program
2738
2566
  .action(() => {
2739
2567
  const cwd = process.cwd();
2740
2568
  const semoSystemDir = path.join(cwd, "semo-system");
2741
- console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
2742
- // Standard
2569
+ console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록\n"));
2570
+ // Standard (필수)
2743
2571
  console.log(chalk_1.default.white.bold("Standard (필수)"));
2744
2572
  const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
2745
2573
  const skillsInstalled = fs.existsSync(path.join(semoSystemDir, "semo-skills"));
2746
2574
  console.log(` ${coreInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-core - 원칙, 오케스트레이터`);
2747
2575
  console.log(` ${skillsInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○")} semo-skills - 통합 스킬`);
2748
2576
  console.log();
2749
- // Extensions - 레이어별 그룹화
2750
- const layers = {
2751
- biz: { title: "Business Layer", emoji: "💼" },
2752
- eng: { title: "Engineering Layer", emoji: "⚙️" },
2753
- ops: { title: "Operations Layer", emoji: "📊" },
2754
- meta: { title: "Meta", emoji: "🔧" },
2755
- system: { title: "System", emoji: "🔩" },
2756
- };
2757
- for (const [layerKey, layerInfo] of Object.entries(layers)) {
2758
- const layerPackages = Object.entries(EXTENSION_PACKAGES).filter(([, pkg]) => pkg.layer === layerKey);
2759
- if (layerPackages.length === 0)
2760
- continue;
2761
- console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
2762
- for (const [key, pkg] of layerPackages) {
2763
- const isInstalled = fs.existsSync(path.join(semoSystemDir, key));
2764
- const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
2765
- const displayKey = key.includes("/") ? key.split("/")[1] : key;
2766
- console.log(` ${status} ${chalk_1.default.cyan(displayKey)} - ${pkg.desc}`);
2767
- console.log(chalk_1.default.gray(` semo add ${key}`));
2768
- }
2769
- console.log();
2577
+ // Extensions
2578
+ console.log(chalk_1.default.white.bold("Extensions (선택)"));
2579
+ const extensionList = [
2580
+ { key: "meta", name: "Meta", desc: "SEMO 프레임워크 자체 개발/관리" },
2581
+ { key: "semo-hooks", name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템" },
2582
+ { key: "semo-remote", name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)" },
2583
+ ];
2584
+ for (const ext of extensionList) {
2585
+ const isInstalled = fs.existsSync(path.join(semoSystemDir, ext.key));
2586
+ const status = isInstalled ? chalk_1.default.green("✓") : chalk_1.default.gray("○");
2587
+ console.log(` ${status} ${chalk_1.default.cyan(ext.key)} - ${ext.desc}`);
2588
+ console.log(chalk_1.default.gray(` semo add ${ext.key}`));
2770
2589
  }
2771
- // 그룹 설치 안내
2772
- console.log(chalk_1.default.gray("─".repeat(50)));
2773
- console.log(chalk_1.default.white.bold("📦 그룹 일괄 설치"));
2774
- console.log(chalk_1.default.gray(" semo add biz → Business 전체 (discovery, design, management, poc)"));
2775
- console.log(chalk_1.default.gray(" semo add eng → Engineering 전체 (nextjs, spring, ms, infra)"));
2776
- console.log(chalk_1.default.gray(" semo add ops → Operations 전체 (qa, monitor, improve)"));
2777
- console.log(chalk_1.default.gray(" semo add system → System 전체 (hooks, remote)"));
2778
2590
  console.log();
2779
- // 단축명 안내
2780
- console.log(chalk_1.default.gray("─".repeat(50)));
2781
- console.log(chalk_1.default.white.bold("⚡ 단축명 지원"));
2782
- console.log(chalk_1.default.gray(" semo add discovery → biz/discovery"));
2783
- console.log(chalk_1.default.gray(" semo add qa → ops/qa"));
2784
- console.log(chalk_1.default.gray(" semo add nextjs → eng/nextjs\n"));
2785
2591
  });
2786
2592
  // === status 명령어 ===
2787
2593
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.4.0",
3
+ "version": "3.6.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {