@team-semicolon/semo-cli 3.10.0 → 3.13.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 CHANGED
@@ -59,6 +59,7 @@ const child_process_1 = require("child_process");
59
59
  const fs = __importStar(require("fs"));
60
60
  const path = __importStar(require("path"));
61
61
  const os = __importStar(require("os"));
62
+ const supabase_1 = require("./supabase");
62
63
  const PACKAGE_NAME = "@team-semicolon/semo-cli";
63
64
  // package.json에서 버전 동적 로드
64
65
  function getCliVersion() {
@@ -673,8 +674,39 @@ function copyRecursive(src, dest) {
673
674
  }
674
675
  }
675
676
  const SEMO_REPO = "https://github.com/semicolon-devteam/semo.git";
676
- // 확장 패키지 정의 (v3.0 구조)
677
- const EXTENSION_PACKAGES = {
677
+ // ============================================================
678
+ // DB 기반 패키지 관리 (v3.10+)
679
+ // ============================================================
680
+ // 캐시된 패키지 데이터 (DB에서 조회 후 캐시)
681
+ let cachedExtensionPackages = null;
682
+ let cachedShortnameMappings = null;
683
+ let cachedPackageDefinitions = null;
684
+ // 패키지 데이터 초기화 (DB에서 조회)
685
+ async function initPackageData() {
686
+ if (cachedExtensionPackages && cachedShortnameMappings)
687
+ return;
688
+ try {
689
+ cachedExtensionPackages = await (0, supabase_1.buildExtensionPackagesFromDb)();
690
+ cachedShortnameMappings = await (0, supabase_1.buildShortnameMappingFromDb)();
691
+ cachedPackageDefinitions = await (0, supabase_1.getExtensionPackages)();
692
+ }
693
+ catch {
694
+ // 폴백: 하드코딩된 데이터 사용
695
+ cachedExtensionPackages = EXTENSION_PACKAGES_FALLBACK;
696
+ cachedShortnameMappings = SHORTNAME_MAPPING_FALLBACK;
697
+ cachedPackageDefinitions = null;
698
+ }
699
+ }
700
+ // EXTENSION_PACKAGES 동기 접근용 (초기화 후 사용)
701
+ function getExtensionPackagesSync() {
702
+ return cachedExtensionPackages || EXTENSION_PACKAGES_FALLBACK;
703
+ }
704
+ // SHORTNAME_MAPPING 동기 접근용
705
+ function getShortnameMappingSync() {
706
+ return cachedShortnameMappings || SHORTNAME_MAPPING_FALLBACK;
707
+ }
708
+ // 폴백용 하드코딩 데이터 (DB 연결 실패 시 사용)
709
+ const EXTENSION_PACKAGES_FALLBACK = {
678
710
  // Business Layer
679
711
  "biz/discovery": { name: "Discovery", desc: "아이템 발굴, 시장 조사, Epic/Task", layer: "biz", detect: [] },
680
712
  "biz/design": { name: "Design", desc: "컨셉 설계, 목업, UX", layer: "biz", detect: [] },
@@ -695,8 +727,8 @@ const EXTENSION_PACKAGES = {
695
727
  "semo-hooks": { name: "Hooks", desc: "Claude Code Hooks 기반 로깅 시스템", layer: "system", detect: [] },
696
728
  "semo-remote": { name: "Remote", desc: "Claude Code 원격 제어 (모바일 PWA)", layer: "system", detect: [] },
697
729
  };
698
- // 단축명 → 전체 패키지 경로 매핑
699
- const SHORTNAME_MAPPING = {
730
+ // 단축명 → 전체 패키지 경로 매핑 (폴백)
731
+ const SHORTNAME_MAPPING_FALLBACK = {
700
732
  // 하위 패키지명 단축 (discovery → biz/discovery)
701
733
  discovery: "biz/discovery",
702
734
  design: "biz/design",
@@ -717,11 +749,20 @@ const SHORTNAME_MAPPING = {
717
749
  hooks: "semo-hooks",
718
750
  remote: "semo-remote",
719
751
  };
752
+ // 호환성을 위한 상수 별칭 (기존 코드에서 사용)
753
+ const EXTENSION_PACKAGES = EXTENSION_PACKAGES_FALLBACK;
754
+ const SHORTNAME_MAPPING = SHORTNAME_MAPPING_FALLBACK;
720
755
  // 그룹 이름 목록 (biz, eng, ops, meta, system)
721
756
  const PACKAGE_GROUPS = ["biz", "eng", "ops", "meta", "system"];
722
- // 그룹명 → 해당 그룹의 모든 패키지 반환
723
- function getPackagesByGroup(group) {
724
- return Object.entries(EXTENSION_PACKAGES)
757
+ // 그룹명 → 해당 그룹의 모든 패키지 반환 (DB 기반)
758
+ async function getPackagesByGroupAsync(group) {
759
+ const packages = await (0, supabase_1.getPackagesByGroup)(group);
760
+ return packages.map(p => p.name);
761
+ }
762
+ // 그룹명 → 해당 그룹의 모든 패키지 반환 (동기, 폴백)
763
+ function getPackagesByGroupSync(group) {
764
+ const extPkgs = getExtensionPackagesSync();
765
+ return Object.entries(extPkgs)
725
766
  .filter(([, pkg]) => pkg.layer === group)
726
767
  .map(([key]) => key);
727
768
  }
@@ -732,22 +773,25 @@ function resolvePackageInput(input) {
732
773
  const resolvedPackages = [];
733
774
  let isGroup = false;
734
775
  let groupName;
776
+ // DB에서 로드된 데이터 또는 폴백 사용
777
+ const extPkgs = getExtensionPackagesSync();
778
+ const shortnames = getShortnameMappingSync();
735
779
  for (const part of parts) {
736
780
  // 1. 그룹명인지 확인 (biz, eng, ops, meta)
737
781
  if (PACKAGE_GROUPS.includes(part)) {
738
- const groupPackages = getPackagesByGroup(part);
782
+ const groupPackages = getPackagesByGroupSync(part);
739
783
  resolvedPackages.push(...groupPackages);
740
784
  isGroup = true;
741
785
  groupName = part;
742
786
  continue;
743
787
  }
744
788
  // 2. 단축명 매핑 확인 (discovery → biz/discovery 등)
745
- if (part in SHORTNAME_MAPPING) {
746
- resolvedPackages.push(SHORTNAME_MAPPING[part]);
789
+ if (part in shortnames) {
790
+ resolvedPackages.push(shortnames[part]);
747
791
  continue;
748
792
  }
749
793
  // 3. 직접 패키지명 확인
750
- if (part in EXTENSION_PACKAGES) {
794
+ if (part in extPkgs) {
751
795
  resolvedPackages.push(part);
752
796
  continue;
753
797
  }
@@ -1132,8 +1176,10 @@ program
1132
1176
  .action(async (options) => {
1133
1177
  console.log(chalk_1.default.cyan.bold("\n🚀 SEMO 설치 시작\n"));
1134
1178
  console.log(chalk_1.default.gray("Gemini 하이브리드 전략: White Box + Black Box\n"));
1179
+ // 0. 패키지 데이터 초기화 (DB에서 조회)
1180
+ await initPackageData();
1135
1181
  const cwd = process.cwd();
1136
- // 0. 버전 비교
1182
+ // 0.1. 버전 비교
1137
1183
  await showVersionComparison(cwd);
1138
1184
  // 0.5. 레거시 환경 감지 및 마이그레이션
1139
1185
  const legacyCheck = detectLegacyEnvironment(cwd);
@@ -1161,18 +1207,23 @@ program
1161
1207
  }
1162
1208
  // 2. Extension 패키지 처리 (--with 옵션만 지원, 인터랙션 없음)
1163
1209
  let extensionsToInstall = [];
1210
+ const extPkgs = getExtensionPackagesSync();
1211
+ const shortnames = getShortnameMappingSync();
1164
1212
  if (options.with) {
1165
1213
  // --with 옵션으로 명시적 패키지 지정 시에만 Extension 설치
1166
- extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in EXTENSION_PACKAGES || p in SHORTNAME_MAPPING);
1214
+ extensionsToInstall = options.with.split(",").map((p) => p.trim()).filter((p) => p in extPkgs || p in shortnames);
1167
1215
  // 별칭 처리
1168
- extensionsToInstall = extensionsToInstall.map((p) => SHORTNAME_MAPPING[p] || p);
1216
+ extensionsToInstall = extensionsToInstall.map((p) => shortnames[p] || p);
1169
1217
  }
1170
1218
  // 프로젝트 유형 감지는 정보 제공용으로만 사용 (자동 설치 안 함)
1171
1219
  const detected = detectProjectType(cwd);
1172
1220
  if (detected.length > 0 && !options.with) {
1173
1221
  console.log(chalk_1.default.cyan("\n💡 감지된 프로젝트 유형:"));
1174
1222
  detected.forEach(pkg => {
1175
- console.log(chalk_1.default.gray(` - ${EXTENSION_PACKAGES[pkg].name}: ${EXTENSION_PACKAGES[pkg].desc}`));
1223
+ const pkgInfo = extPkgs[pkg];
1224
+ if (pkgInfo) {
1225
+ console.log(chalk_1.default.gray(` - ${pkgInfo.name}: ${pkgInfo.desc}`));
1226
+ }
1176
1227
  });
1177
1228
  console.log(chalk_1.default.gray(`\n 추가 패키지가 필요하면: semo add ${detected[0].split("/")[1] || detected[0]}`));
1178
1229
  }
@@ -1303,7 +1354,7 @@ async function createStandardSymlinks(cwd) {
1303
1354
  }
1304
1355
  console.log(chalk_1.default.green(` ✓ .claude/agents/ (${agents.length}개 agent 링크됨)`));
1305
1356
  }
1306
- // skills 디렉토리 생성 및 개별 링크 (Extension 병합 지원)
1357
+ // skills 디렉토리 생성 및 개별 링크 (DB 기반 - 활성 스킬만)
1307
1358
  const claudeSkillsDir = path.join(claudeDir, "skills");
1308
1359
  const coreSkillsDir = path.join(semoSystemDir, "semo-skills");
1309
1360
  if (fs.existsSync(coreSkillsDir)) {
@@ -1312,15 +1363,19 @@ async function createStandardSymlinks(cwd) {
1312
1363
  removeRecursive(claudeSkillsDir);
1313
1364
  }
1314
1365
  fs.mkdirSync(claudeSkillsDir, { recursive: true });
1315
- const skills = fs.readdirSync(coreSkillsDir).filter(f => fs.statSync(path.join(coreSkillsDir, f)).isDirectory());
1316
- for (const skill of skills) {
1317
- const skillLink = path.join(claudeSkillsDir, skill);
1318
- const skillTarget = path.join(coreSkillsDir, skill);
1319
- if (!fs.existsSync(skillLink)) {
1366
+ // DB에서 활성 스킬 목록 조회 (19개 핵심 스킬만)
1367
+ const activeSkillNames = await (0, supabase_1.getActiveSkillNames)();
1368
+ let linkedCount = 0;
1369
+ for (const skillName of activeSkillNames) {
1370
+ const skillLink = path.join(claudeSkillsDir, skillName);
1371
+ const skillTarget = path.join(coreSkillsDir, skillName);
1372
+ // 스킬 폴더가 존재하는 경우에만 링크
1373
+ if (fs.existsSync(skillTarget) && !fs.existsSync(skillLink)) {
1320
1374
  createSymlinkOrJunction(skillTarget, skillLink);
1375
+ linkedCount++;
1321
1376
  }
1322
1377
  }
1323
- console.log(chalk_1.default.green(` ✓ .claude/skills/ (${skills.length}개 skill 링크됨)`));
1378
+ console.log(chalk_1.default.green(` ✓ .claude/skills/ (${linkedCount}개 skill 링크됨 - DB 기반)`));
1324
1379
  }
1325
1380
  // commands 링크
1326
1381
  const commandsDir = path.join(claudeDir, "commands");
@@ -2744,6 +2799,8 @@ program
2744
2799
  .description("Extension 패키지를 추가로 설치합니다 (그룹: biz, eng, ops, system / 개별: biz/discovery, eng/nextjs, semo-hooks)")
2745
2800
  .option("-f, --force", "기존 설정 덮어쓰기")
2746
2801
  .action(async (packagesInput, options) => {
2802
+ // 패키지 데이터 초기화 (DB에서 조회)
2803
+ await initPackageData();
2747
2804
  const cwd = process.cwd();
2748
2805
  const semoSystemDir = path.join(cwd, "semo-system");
2749
2806
  if (!fs.existsSync(semoSystemDir)) {
@@ -2752,11 +2809,13 @@ program
2752
2809
  }
2753
2810
  // 패키지 입력 해석 (그룹, 레거시, 쉼표 구분 모두 처리)
2754
2811
  const { packages, isGroup, groupName } = resolvePackageInput(packagesInput);
2812
+ const extPkgs = getExtensionPackagesSync();
2813
+ const shortnames = getShortnameMappingSync();
2755
2814
  if (packages.length === 0) {
2756
2815
  console.log(chalk_1.default.red(`\n알 수 없는 패키지: ${packagesInput}`));
2757
2816
  console.log(chalk_1.default.gray(`사용 가능한 그룹: ${PACKAGE_GROUPS.join(", ")}`));
2758
- console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(EXTENSION_PACKAGES).join(", ")}`));
2759
- console.log(chalk_1.default.gray(`단축명: ${Object.keys(SHORTNAME_MAPPING).join(", ")}\n`));
2817
+ console.log(chalk_1.default.gray(`사용 가능한 패키지: ${Object.keys(extPkgs).join(", ")}`));
2818
+ console.log(chalk_1.default.gray(`단축명: ${Object.keys(shortnames).join(", ")}\n`));
2760
2819
  process.exit(1);
2761
2820
  }
2762
2821
  // 그룹 설치인 경우 안내
@@ -2764,21 +2823,24 @@ program
2764
2823
  console.log(chalk_1.default.cyan.bold(`\n📦 ${groupName?.toUpperCase()} 그룹 패키지 일괄 설치\n`));
2765
2824
  console.log(chalk_1.default.gray(" 포함된 패키지:"));
2766
2825
  for (const pkg of packages) {
2767
- console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
2826
+ const pkgInfo = extPkgs[pkg];
2827
+ console.log(chalk_1.default.gray(` - ${pkg} (${pkgInfo?.name || pkg})`));
2768
2828
  }
2769
2829
  console.log();
2770
2830
  }
2771
2831
  else if (packages.length === 1) {
2772
2832
  // 단일 패키지
2773
2833
  const pkg = packages[0];
2774
- console.log(chalk_1.default.cyan(`\n📦 ${EXTENSION_PACKAGES[pkg].name} 패키지 설치\n`));
2775
- console.log(chalk_1.default.gray(` ${EXTENSION_PACKAGES[pkg].desc}\n`));
2834
+ const pkgInfo = extPkgs[pkg];
2835
+ console.log(chalk_1.default.cyan(`\n📦 ${pkgInfo?.name || pkg} 패키지 설치\n`));
2836
+ console.log(chalk_1.default.gray(` ${pkgInfo?.desc || ""}\n`));
2776
2837
  }
2777
2838
  else {
2778
2839
  // 여러 패키지 (쉼표 구분)
2779
2840
  console.log(chalk_1.default.cyan.bold(`\n📦 ${packages.length}개 패키지 설치\n`));
2780
2841
  for (const pkg of packages) {
2781
- console.log(chalk_1.default.gray(` - ${pkg} (${EXTENSION_PACKAGES[pkg].name})`));
2842
+ const pkgInfo = extPkgs[pkg];
2843
+ console.log(chalk_1.default.gray(` - ${pkg} (${pkgInfo?.name || pkg})`));
2782
2844
  }
2783
2845
  console.log();
2784
2846
  }
@@ -2817,12 +2879,14 @@ program
2817
2879
  // 4. CLAUDE.md 재생성 (모든 설치된 패키지 반영)
2818
2880
  await setupClaudeMd(cwd, allInstalledPackages, options.force);
2819
2881
  if (toInstall.length === 1) {
2820
- console.log(chalk_1.default.green.bold(`\n✅ ${EXTENSION_PACKAGES[toInstall[0]].name} 패키지 설치 완료!\n`));
2882
+ const pkgInfo = extPkgs[toInstall[0]];
2883
+ console.log(chalk_1.default.green.bold(`\n✅ ${pkgInfo?.name || toInstall[0]} 패키지 설치 완료!\n`));
2821
2884
  }
2822
2885
  else {
2823
2886
  console.log(chalk_1.default.green.bold(`\n✅ ${toInstall.length}개 패키지 설치 완료!`));
2824
2887
  for (const pkg of toInstall) {
2825
- console.log(chalk_1.default.green(` ✓ ${EXTENSION_PACKAGES[pkg].name}`));
2888
+ const pkgInfo = extPkgs[pkg];
2889
+ console.log(chalk_1.default.green(` ✓ ${pkgInfo?.name || pkg}`));
2826
2890
  }
2827
2891
  console.log();
2828
2892
  }
@@ -2831,10 +2895,13 @@ program
2831
2895
  program
2832
2896
  .command("list")
2833
2897
  .description("사용 가능한 모든 패키지를 표시합니다")
2834
- .action(() => {
2898
+ .action(async () => {
2899
+ // 패키지 데이터 초기화 (DB에서 조회)
2900
+ await initPackageData();
2835
2901
  const cwd = process.cwd();
2836
2902
  const semoSystemDir = path.join(cwd, "semo-system");
2837
- console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.0)\n"));
2903
+ const extPkgs = getExtensionPackagesSync();
2904
+ console.log(chalk_1.default.cyan.bold("\n📦 SEMO 패키지 목록 (v3.10 - DB 기반)\n"));
2838
2905
  // Standard
2839
2906
  console.log(chalk_1.default.white.bold("Standard (필수)"));
2840
2907
  const coreInstalled = fs.existsSync(path.join(semoSystemDir, "semo-core"));
@@ -2851,7 +2918,7 @@ program
2851
2918
  system: { title: "System", emoji: "🔩" },
2852
2919
  };
2853
2920
  for (const [layerKey, layerInfo] of Object.entries(layers)) {
2854
- const layerPackages = Object.entries(EXTENSION_PACKAGES).filter(([, pkg]) => pkg.layer === layerKey);
2921
+ const layerPackages = Object.entries(extPkgs).filter(([, pkg]) => pkg.layer === layerKey);
2855
2922
  if (layerPackages.length === 0)
2856
2923
  continue;
2857
2924
  console.log(chalk_1.default.white.bold(`${layerInfo.emoji} ${layerInfo.title}`));
@@ -2948,6 +3015,8 @@ program
2948
3015
  .option("--migrate", "레거시 환경 강제 마이그레이션")
2949
3016
  .action(async (options) => {
2950
3017
  console.log(chalk_1.default.cyan.bold("\n🔄 SEMO 업데이트\n"));
3018
+ // 패키지 데이터 초기화 (DB에서 조회)
3019
+ await initPackageData();
2951
3020
  const cwd = process.cwd();
2952
3021
  const semoSystemDir = path.join(cwd, "semo-system");
2953
3022
  const claudeDir = path.join(cwd, ".claude");
@@ -3000,7 +3069,8 @@ program
3000
3069
  }
3001
3070
  // 설치된 Extensions 확인
3002
3071
  const installedExtensions = [];
3003
- for (const key of Object.keys(EXTENSION_PACKAGES)) {
3072
+ const extPkgs = getExtensionPackagesSync();
3073
+ for (const key of Object.keys(extPkgs)) {
3004
3074
  if (fs.existsSync(path.join(semoSystemDir, key))) {
3005
3075
  installedExtensions.push(key);
3006
3076
  }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * SEMO CLI - Supabase 클라이언트
3
+ *
4
+ * package_definitions 테이블에서 패키지 정보를 조회합니다.
5
+ * DB 연결 실패 시 하드코딩된 폴백 데이터를 사용합니다.
6
+ */
7
+ export interface PackageDefinition {
8
+ id: string;
9
+ name: string;
10
+ display_name: string;
11
+ description: string | null;
12
+ layer: "standard" | "biz" | "eng" | "ops" | "system" | "meta";
13
+ package_type: "standard" | "extension";
14
+ version: string;
15
+ repo_url: string;
16
+ source_path: string;
17
+ detect_files: string[];
18
+ depends_on: string[];
19
+ aliases: string[];
20
+ is_active: boolean;
21
+ is_required: boolean;
22
+ install_order: number;
23
+ }
24
+ /**
25
+ * 모든 패키지 목록 조회
26
+ */
27
+ export declare function getPackages(layer?: string): Promise<PackageDefinition[]>;
28
+ /**
29
+ * Standard 패키지 목록 조회
30
+ */
31
+ export declare function getStandardPackages(): Promise<PackageDefinition[]>;
32
+ /**
33
+ * Extension 패키지 목록 조회
34
+ */
35
+ export declare function getExtensionPackages(layer?: string): Promise<PackageDefinition[]>;
36
+ /**
37
+ * 패키지명 또는 별칭으로 패키지 찾기
38
+ */
39
+ export declare function resolvePackageName(input: string): Promise<string | null>;
40
+ /**
41
+ * 패키지 버전 조회
42
+ */
43
+ export declare function getPackageVersion(name: string): Promise<string | null>;
44
+ /**
45
+ * 그룹별 패키지 목록 조회
46
+ */
47
+ export declare function getPackagesByGroup(group: string): Promise<PackageDefinition[]>;
48
+ /**
49
+ * 프로젝트 타입 감지용 패키지 조회
50
+ */
51
+ export declare function getDetectablePackages(): Promise<PackageDefinition[]>;
52
+ /**
53
+ * 패키지 정보를 CLI 포맷으로 변환
54
+ */
55
+ export declare function toExtensionPackageFormat(pkg: PackageDefinition): {
56
+ name: string;
57
+ desc: string;
58
+ detect: string[];
59
+ layer: string;
60
+ };
61
+ /**
62
+ * 별칭 매핑 객체 생성 (SHORTNAME_MAPPING 대체)
63
+ */
64
+ export declare function buildShortnameMappingFromDb(): Promise<Record<string, string>>;
65
+ /**
66
+ * EXTENSION_PACKAGES 형태의 객체 생성 (호환성용)
67
+ */
68
+ export declare function buildExtensionPackagesFromDb(): Promise<Record<string, {
69
+ name: string;
70
+ desc: string;
71
+ detect: string[];
72
+ layer: string;
73
+ }>>;
74
+ export interface SkillDefinition {
75
+ id: string;
76
+ name: string;
77
+ display_name: string;
78
+ description: string | null;
79
+ category: "workflow" | "discovery" | "planning" | "solutioning" | "implementation" | "supporting";
80
+ source_path: string;
81
+ is_active: boolean;
82
+ is_required: boolean;
83
+ install_order: number;
84
+ version: string;
85
+ }
86
+ /**
87
+ * 활성 스킬 목록 조회 (설치할 스킬)
88
+ */
89
+ export declare function getActiveSkills(): Promise<SkillDefinition[]>;
90
+ /**
91
+ * 스킬 이름 목록만 조회
92
+ */
93
+ export declare function getActiveSkillNames(): Promise<string[]>;
94
+ /**
95
+ * 카테고리별 스킬 개수 조회
96
+ */
97
+ export declare function getSkillCountByCategory(): Promise<Record<string, number>>;
@@ -0,0 +1,602 @@
1
+ "use strict";
2
+ /**
3
+ * SEMO CLI - Supabase 클라이언트
4
+ *
5
+ * package_definitions 테이블에서 패키지 정보를 조회합니다.
6
+ * DB 연결 실패 시 하드코딩된 폴백 데이터를 사용합니다.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.getPackages = getPackages;
10
+ exports.getStandardPackages = getStandardPackages;
11
+ exports.getExtensionPackages = getExtensionPackages;
12
+ exports.resolvePackageName = resolvePackageName;
13
+ exports.getPackageVersion = getPackageVersion;
14
+ exports.getPackagesByGroup = getPackagesByGroup;
15
+ exports.getDetectablePackages = getDetectablePackages;
16
+ exports.toExtensionPackageFormat = toExtensionPackageFormat;
17
+ exports.buildShortnameMappingFromDb = buildShortnameMappingFromDb;
18
+ exports.buildExtensionPackagesFromDb = buildExtensionPackagesFromDb;
19
+ exports.getActiveSkills = getActiveSkills;
20
+ exports.getActiveSkillNames = getActiveSkillNames;
21
+ exports.getSkillCountByCategory = getSkillCountByCategory;
22
+ const supabase_js_1 = require("@supabase/supabase-js");
23
+ // Supabase 연결 정보 (공개 프로젝트용)
24
+ const SUPABASE_URL = process.env.SEMO_SUPABASE_URL || "https://vdrllieckyeumbhyclkc.supabase.co";
25
+ const SUPABASE_ANON_KEY = process.env.SEMO_SUPABASE_ANON_KEY ||
26
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InZkcmxsaWVja3lldW1iaHljbGtjIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njc5NzgxMTEsImV4cCI6MjA4MzU1NDExMX0.SrruG4y9geH1bCWE4uLzMHiUA38UcGMQwbAxxaaa718";
27
+ // Supabase 클라이언트 (싱글톤)
28
+ let supabaseClient = null;
29
+ function getSupabaseClient() {
30
+ if (!supabaseClient) {
31
+ supabaseClient = (0, supabase_js_1.createClient)(SUPABASE_URL, SUPABASE_ANON_KEY);
32
+ }
33
+ return supabaseClient;
34
+ }
35
+ // DB 연결 가능 여부 확인
36
+ let dbAvailable = null;
37
+ async function checkDbConnection() {
38
+ if (dbAvailable !== null)
39
+ return dbAvailable;
40
+ try {
41
+ const supabase = getSupabaseClient();
42
+ const { error } = await supabase
43
+ .from("package_definitions")
44
+ .select("id")
45
+ .limit(1);
46
+ dbAvailable = !error;
47
+ }
48
+ catch {
49
+ dbAvailable = false;
50
+ }
51
+ return dbAvailable;
52
+ }
53
+ /**
54
+ * 모든 패키지 목록 조회
55
+ */
56
+ async function getPackages(layer) {
57
+ const isConnected = await checkDbConnection();
58
+ if (!isConnected) {
59
+ console.warn("⚠️ DB 연결 실패, 폴백 데이터 사용");
60
+ return getFallbackPackages(layer);
61
+ }
62
+ const supabase = getSupabaseClient();
63
+ let query = supabase
64
+ .from("package_definitions")
65
+ .select("*")
66
+ .eq("is_active", true);
67
+ if (layer) {
68
+ query = query.eq("layer", layer);
69
+ }
70
+ const { data, error } = await query.order("install_order");
71
+ if (error) {
72
+ console.warn("⚠️ 패키지 조회 실패, 폴백 데이터 사용");
73
+ return getFallbackPackages(layer);
74
+ }
75
+ return data || [];
76
+ }
77
+ /**
78
+ * Standard 패키지 목록 조회
79
+ */
80
+ async function getStandardPackages() {
81
+ const isConnected = await checkDbConnection();
82
+ if (!isConnected) {
83
+ return getFallbackPackages().filter((p) => p.package_type === "standard");
84
+ }
85
+ const supabase = getSupabaseClient();
86
+ const { data, error } = await supabase
87
+ .from("package_definitions")
88
+ .select("*")
89
+ .eq("package_type", "standard")
90
+ .eq("is_active", true)
91
+ .order("install_order");
92
+ if (error) {
93
+ return getFallbackPackages().filter((p) => p.package_type === "standard");
94
+ }
95
+ return data || [];
96
+ }
97
+ /**
98
+ * Extension 패키지 목록 조회
99
+ */
100
+ async function getExtensionPackages(layer) {
101
+ const isConnected = await checkDbConnection();
102
+ if (!isConnected) {
103
+ const fallback = getFallbackPackages(layer);
104
+ return fallback.filter((p) => p.package_type === "extension");
105
+ }
106
+ const supabase = getSupabaseClient();
107
+ let query = supabase
108
+ .from("package_definitions")
109
+ .select("*")
110
+ .eq("package_type", "extension")
111
+ .eq("is_active", true);
112
+ if (layer) {
113
+ query = query.eq("layer", layer);
114
+ }
115
+ const { data, error } = await query.order("install_order");
116
+ if (error) {
117
+ const fallback = getFallbackPackages(layer);
118
+ return fallback.filter((p) => p.package_type === "extension");
119
+ }
120
+ return data || [];
121
+ }
122
+ /**
123
+ * 패키지명 또는 별칭으로 패키지 찾기
124
+ */
125
+ async function resolvePackageName(input) {
126
+ const isConnected = await checkDbConnection();
127
+ if (!isConnected) {
128
+ return resolveFallbackPackageName(input);
129
+ }
130
+ const supabase = getSupabaseClient();
131
+ // 1. 정확한 이름 매칭
132
+ const { data: exactMatch } = await supabase
133
+ .from("package_definitions")
134
+ .select("name")
135
+ .eq("name", input)
136
+ .eq("is_active", true)
137
+ .single();
138
+ if (exactMatch)
139
+ return exactMatch.name;
140
+ // 2. 별칭 매칭 (aliases 배열에 포함)
141
+ const { data: aliasMatch } = await supabase
142
+ .from("package_definitions")
143
+ .select("name")
144
+ .contains("aliases", [input])
145
+ .eq("is_active", true)
146
+ .single();
147
+ if (aliasMatch)
148
+ return aliasMatch.name;
149
+ return null;
150
+ }
151
+ /**
152
+ * 패키지 버전 조회
153
+ */
154
+ async function getPackageVersion(name) {
155
+ const isConnected = await checkDbConnection();
156
+ if (!isConnected) {
157
+ const pkg = getFallbackPackages().find((p) => p.name === name);
158
+ return pkg?.version || null;
159
+ }
160
+ const supabase = getSupabaseClient();
161
+ const { data } = await supabase
162
+ .from("package_definitions")
163
+ .select("version")
164
+ .eq("name", name)
165
+ .single();
166
+ return data?.version || null;
167
+ }
168
+ /**
169
+ * 그룹별 패키지 목록 조회
170
+ */
171
+ async function getPackagesByGroup(group) {
172
+ return getExtensionPackages(group);
173
+ }
174
+ /**
175
+ * 프로젝트 타입 감지용 패키지 조회
176
+ */
177
+ async function getDetectablePackages() {
178
+ const packages = await getExtensionPackages();
179
+ return packages.filter((p) => p.detect_files && p.detect_files.length > 0);
180
+ }
181
+ // ============================================================
182
+ // 폴백 데이터 (DB 연결 실패 시 사용)
183
+ // ============================================================
184
+ const FALLBACK_PACKAGES = [
185
+ // Standard
186
+ {
187
+ id: "std-core",
188
+ name: "semo-core",
189
+ display_name: "SEMO Core",
190
+ description: "원칙, 오케스트레이터, 워크플로우 커맨드",
191
+ layer: "standard",
192
+ package_type: "standard",
193
+ version: "3.12.0",
194
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
195
+ source_path: "semo-system/semo-core",
196
+ detect_files: [],
197
+ depends_on: [],
198
+ aliases: [],
199
+ is_active: true,
200
+ is_required: true,
201
+ install_order: 10,
202
+ },
203
+ {
204
+ id: "std-skills",
205
+ name: "semo-skills",
206
+ display_name: "SEMO Skills",
207
+ description: "18개 통합 스킬 (워크플로우 포함)",
208
+ layer: "standard",
209
+ package_type: "standard",
210
+ version: "3.12.0",
211
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
212
+ source_path: "semo-system/semo-skills",
213
+ detect_files: [],
214
+ depends_on: [],
215
+ aliases: [],
216
+ is_active: true,
217
+ is_required: true,
218
+ install_order: 20,
219
+ },
220
+ {
221
+ id: "std-scripts",
222
+ name: "semo-scripts",
223
+ display_name: "SEMO Scripts",
224
+ description: "자동화 스크립트",
225
+ layer: "standard",
226
+ package_type: "standard",
227
+ version: "3.12.0",
228
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
229
+ source_path: "semo-system/semo-scripts",
230
+ detect_files: [],
231
+ depends_on: [],
232
+ aliases: [],
233
+ is_active: true,
234
+ is_required: true,
235
+ install_order: 30,
236
+ },
237
+ // Business Layer
238
+ {
239
+ id: "biz-discovery",
240
+ name: "biz/discovery",
241
+ display_name: "Discovery",
242
+ description: "아이템 발굴, 시장 조사, Epic/Task",
243
+ layer: "biz",
244
+ package_type: "extension",
245
+ version: "1.0.0",
246
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
247
+ source_path: "semo-system/biz/discovery",
248
+ detect_files: [],
249
+ depends_on: [],
250
+ aliases: ["discovery"],
251
+ is_active: true,
252
+ is_required: false,
253
+ install_order: 100,
254
+ },
255
+ {
256
+ id: "biz-design",
257
+ name: "biz/design",
258
+ display_name: "Design",
259
+ description: "컨셉 설계, 목업, UX",
260
+ layer: "biz",
261
+ package_type: "extension",
262
+ version: "1.0.0",
263
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
264
+ source_path: "semo-system/biz/design",
265
+ detect_files: [],
266
+ depends_on: [],
267
+ aliases: ["design"],
268
+ is_active: true,
269
+ is_required: false,
270
+ install_order: 101,
271
+ },
272
+ {
273
+ id: "biz-management",
274
+ name: "biz/management",
275
+ display_name: "Management",
276
+ description: "일정/인력/스프린트 관리",
277
+ layer: "biz",
278
+ package_type: "extension",
279
+ version: "1.0.0",
280
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
281
+ source_path: "semo-system/biz/management",
282
+ detect_files: [],
283
+ depends_on: [],
284
+ aliases: ["management"],
285
+ is_active: true,
286
+ is_required: false,
287
+ install_order: 102,
288
+ },
289
+ {
290
+ id: "biz-poc",
291
+ name: "biz/poc",
292
+ display_name: "PoC",
293
+ description: "빠른 PoC, 패스트트랙",
294
+ layer: "biz",
295
+ package_type: "extension",
296
+ version: "1.0.0",
297
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
298
+ source_path: "semo-system/biz/poc",
299
+ detect_files: [],
300
+ depends_on: [],
301
+ aliases: ["poc", "mvp"],
302
+ is_active: true,
303
+ is_required: false,
304
+ install_order: 103,
305
+ },
306
+ // Engineering Layer
307
+ {
308
+ id: "eng-nextjs",
309
+ name: "eng/nextjs",
310
+ display_name: "Next.js",
311
+ description: "Next.js 프론트엔드 개발",
312
+ layer: "eng",
313
+ package_type: "extension",
314
+ version: "1.0.0",
315
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
316
+ source_path: "semo-system/eng/nextjs",
317
+ detect_files: ["next.config.js", "next.config.mjs", "next.config.ts"],
318
+ depends_on: [],
319
+ aliases: ["nextjs", "next"],
320
+ is_active: true,
321
+ is_required: false,
322
+ install_order: 110,
323
+ },
324
+ {
325
+ id: "eng-spring",
326
+ name: "eng/spring",
327
+ display_name: "Spring",
328
+ description: "Spring Boot 백엔드 개발",
329
+ layer: "eng",
330
+ package_type: "extension",
331
+ version: "1.0.0",
332
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
333
+ source_path: "semo-system/eng/spring",
334
+ detect_files: ["pom.xml", "build.gradle"],
335
+ depends_on: [],
336
+ aliases: ["spring", "backend"],
337
+ is_active: true,
338
+ is_required: false,
339
+ install_order: 111,
340
+ },
341
+ {
342
+ id: "eng-ms",
343
+ name: "eng/ms",
344
+ display_name: "Microservice",
345
+ description: "마이크로서비스 아키텍처",
346
+ layer: "eng",
347
+ package_type: "extension",
348
+ version: "1.0.0",
349
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
350
+ source_path: "semo-system/eng/ms",
351
+ detect_files: [],
352
+ depends_on: [],
353
+ aliases: ["ms"],
354
+ is_active: true,
355
+ is_required: false,
356
+ install_order: 112,
357
+ },
358
+ {
359
+ id: "eng-infra",
360
+ name: "eng/infra",
361
+ display_name: "Infra",
362
+ description: "인프라/배포 관리",
363
+ layer: "eng",
364
+ package_type: "extension",
365
+ version: "1.0.0",
366
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
367
+ source_path: "semo-system/eng/infra",
368
+ detect_files: ["docker-compose.yml", "Dockerfile"],
369
+ depends_on: [],
370
+ aliases: ["infra"],
371
+ is_active: true,
372
+ is_required: false,
373
+ install_order: 113,
374
+ },
375
+ // Operations Layer
376
+ {
377
+ id: "ops-qa",
378
+ name: "ops/qa",
379
+ display_name: "QA",
380
+ description: "테스트/품질 관리",
381
+ layer: "ops",
382
+ package_type: "extension",
383
+ version: "1.0.0",
384
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
385
+ source_path: "semo-system/ops/qa",
386
+ detect_files: [],
387
+ depends_on: [],
388
+ aliases: ["qa"],
389
+ is_active: true,
390
+ is_required: false,
391
+ install_order: 120,
392
+ },
393
+ {
394
+ id: "ops-monitor",
395
+ name: "ops/monitor",
396
+ display_name: "Monitor",
397
+ description: "서비스 현황 모니터링",
398
+ layer: "ops",
399
+ package_type: "extension",
400
+ version: "1.0.0",
401
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
402
+ source_path: "semo-system/ops/monitor",
403
+ detect_files: [],
404
+ depends_on: [],
405
+ aliases: ["monitor"],
406
+ is_active: true,
407
+ is_required: false,
408
+ install_order: 121,
409
+ },
410
+ {
411
+ id: "ops-improve",
412
+ name: "ops/improve",
413
+ display_name: "Improve",
414
+ description: "개선 제안",
415
+ layer: "ops",
416
+ package_type: "extension",
417
+ version: "1.0.0",
418
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
419
+ source_path: "semo-system/ops/improve",
420
+ detect_files: [],
421
+ depends_on: [],
422
+ aliases: ["improve"],
423
+ is_active: true,
424
+ is_required: false,
425
+ install_order: 122,
426
+ },
427
+ // Meta Layer
428
+ {
429
+ id: "meta",
430
+ name: "meta",
431
+ display_name: "Meta",
432
+ description: "SEMO 프레임워크 자체 개발/관리",
433
+ layer: "meta",
434
+ package_type: "extension",
435
+ version: "3.12.0",
436
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
437
+ source_path: "semo-system/meta",
438
+ detect_files: ["semo-core", "semo-skills"],
439
+ depends_on: [],
440
+ aliases: [],
441
+ is_active: true,
442
+ is_required: false,
443
+ install_order: 200,
444
+ },
445
+ // System Layer
446
+ {
447
+ id: "sys-hooks",
448
+ name: "semo-hooks",
449
+ display_name: "Hooks",
450
+ description: "Claude Code Hooks 기반 로깅 시스템",
451
+ layer: "system",
452
+ package_type: "extension",
453
+ version: "1.0.0",
454
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
455
+ source_path: "semo-system/semo-hooks",
456
+ detect_files: [],
457
+ depends_on: [],
458
+ aliases: ["hooks"],
459
+ is_active: true,
460
+ is_required: false,
461
+ install_order: 210,
462
+ },
463
+ {
464
+ id: "sys-remote",
465
+ name: "semo-remote",
466
+ display_name: "Remote",
467
+ description: "Claude Code 원격 제어 (모바일 PWA)",
468
+ layer: "system",
469
+ package_type: "extension",
470
+ version: "1.0.0",
471
+ repo_url: "https://github.com/semicolon-devteam/semo.git",
472
+ source_path: "semo-system/semo-remote",
473
+ detect_files: [],
474
+ depends_on: [],
475
+ aliases: ["remote"],
476
+ is_active: true,
477
+ is_required: false,
478
+ install_order: 211,
479
+ },
480
+ ];
481
+ function getFallbackPackages(layer) {
482
+ if (layer) {
483
+ return FALLBACK_PACKAGES.filter((p) => p.layer === layer);
484
+ }
485
+ return FALLBACK_PACKAGES;
486
+ }
487
+ function resolveFallbackPackageName(input) {
488
+ // 1. 정확한 이름 매칭
489
+ const exactMatch = FALLBACK_PACKAGES.find((p) => p.name === input);
490
+ if (exactMatch)
491
+ return exactMatch.name;
492
+ // 2. 별칭 매칭
493
+ const aliasMatch = FALLBACK_PACKAGES.find((p) => p.aliases.includes(input));
494
+ if (aliasMatch)
495
+ return aliasMatch.name;
496
+ return null;
497
+ }
498
+ // ============================================================
499
+ // 유틸리티
500
+ // ============================================================
501
+ /**
502
+ * 패키지 정보를 CLI 포맷으로 변환
503
+ */
504
+ function toExtensionPackageFormat(pkg) {
505
+ return {
506
+ name: pkg.display_name,
507
+ desc: pkg.description || "",
508
+ detect: pkg.detect_files,
509
+ layer: pkg.layer,
510
+ };
511
+ }
512
+ /**
513
+ * 별칭 매핑 객체 생성 (SHORTNAME_MAPPING 대체)
514
+ */
515
+ async function buildShortnameMappingFromDb() {
516
+ const packages = await getPackages();
517
+ const mapping = {};
518
+ for (const pkg of packages) {
519
+ for (const alias of pkg.aliases) {
520
+ mapping[alias] = pkg.name;
521
+ }
522
+ }
523
+ return mapping;
524
+ }
525
+ /**
526
+ * EXTENSION_PACKAGES 형태의 객체 생성 (호환성용)
527
+ */
528
+ async function buildExtensionPackagesFromDb() {
529
+ const packages = await getExtensionPackages();
530
+ const result = {};
531
+ for (const pkg of packages) {
532
+ result[pkg.name] = toExtensionPackageFormat(pkg);
533
+ }
534
+ return result;
535
+ }
536
+ // 폴백 스킬 목록 (DB 연결 실패 시 사용)
537
+ const FALLBACK_SKILLS = [
538
+ // Workflow Management (3개)
539
+ { id: "sk-1", name: "workflow-start", display_name: "워크플로우 시작", description: "워크플로우 인스턴스 생성 및 시작", category: "workflow", source_path: "semo-skills/workflow-start", is_active: true, is_required: true, install_order: 1, version: "1.0.0" },
540
+ { id: "sk-2", name: "workflow-progress", display_name: "워크플로우 진행", description: "워크플로우 진행 상황 조회", category: "workflow", source_path: "semo-skills/workflow-progress", is_active: true, is_required: true, install_order: 2, version: "1.0.0" },
541
+ { id: "sk-3", name: "workflow-resume", display_name: "워크플로우 재개", description: "중단된 워크플로우 재개", category: "workflow", source_path: "semo-skills/workflow-resume", is_active: true, is_required: true, install_order: 3, version: "1.0.0" },
542
+ // Discovery (1개)
543
+ { id: "sk-10", name: "ideate", display_name: "아이디에이션", description: "아이디어 발굴 및 분석", category: "discovery", source_path: "semo-skills/ideate", is_active: true, is_required: true, install_order: 10, version: "1.0.0" },
544
+ // Planning (3개)
545
+ { id: "sk-20", name: "create-epic", display_name: "Epic 생성", description: "Epic 이슈 생성", category: "planning", source_path: "semo-skills/create-epic", is_active: true, is_required: true, install_order: 20, version: "1.0.0" },
546
+ { id: "sk-21", name: "design-user-flow", display_name: "사용자 흐름 설계", description: "UX 사용자 흐름 다이어그램 설계", category: "planning", source_path: "semo-skills/design-user-flow", is_active: true, is_required: true, install_order: 21, version: "1.0.0" },
547
+ { id: "sk-22", name: "generate-mockup", display_name: "목업 생성", description: "UI 목업 생성", category: "planning", source_path: "semo-skills/generate-mockup", is_active: true, is_required: true, install_order: 22, version: "1.0.0" },
548
+ // Solutioning (4개)
549
+ { id: "sk-30", name: "scaffold-domain", display_name: "도메인 스캐폴딩", description: "DDD 4-layer 도메인 구조 생성", category: "solutioning", source_path: "semo-skills/scaffold-domain", is_active: true, is_required: true, install_order: 30, version: "1.0.0" },
550
+ { id: "sk-31", name: "validate-architecture", display_name: "아키텍처 검증", description: "DDD 4-layer 아키텍처 준수 검증", category: "solutioning", source_path: "semo-skills/validate-architecture", is_active: true, is_required: true, install_order: 31, version: "1.0.0" },
551
+ { id: "sk-32", name: "generate-spec", display_name: "명세 생성", description: "Speckit 워크플로우 통합 실행", category: "solutioning", source_path: "semo-skills/generate-spec", is_active: true, is_required: true, install_order: 32, version: "1.0.0" },
552
+ { id: "sk-33", name: "design-tests", display_name: "테스트 설계", description: "구현 전 테스트 케이스 설계 (TDD)", category: "solutioning", source_path: "semo-skills/design-tests", is_active: true, is_required: true, install_order: 33, version: "1.0.0" },
553
+ // Implementation (6개)
554
+ { id: "sk-40", name: "create-sprint", display_name: "스프린트 생성", description: "Sprint 목표 설정 및 시작", category: "implementation", source_path: "semo-skills/create-sprint", is_active: true, is_required: true, install_order: 40, version: "1.0.0" },
555
+ { id: "sk-41", name: "start-task", display_name: "태스크 시작", description: "작업 시작 (이슈 상태 변경, 브랜치 생성)", category: "implementation", source_path: "semo-skills/start-task", is_active: true, is_required: true, install_order: 41, version: "1.0.0" },
556
+ { id: "sk-42", name: "review-task", display_name: "태스크 리뷰", description: "태스크 이슈 기반 구현 완료 리뷰", category: "implementation", source_path: "semo-skills/review-task", is_active: true, is_required: true, install_order: 42, version: "1.0.0" },
557
+ { id: "sk-43", name: "write-code", display_name: "코드 작성", description: "코드 작성, 수정, 구현", category: "implementation", source_path: "semo-skills/write-code", is_active: true, is_required: true, install_order: 43, version: "1.0.0" },
558
+ { id: "sk-44", name: "run-code-review", display_name: "코드 리뷰", description: "프로젝트 통합 코드 리뷰", category: "implementation", source_path: "semo-skills/run-code-review", is_active: true, is_required: true, install_order: 44, version: "1.0.0" },
559
+ { id: "sk-45", name: "close-sprint", display_name: "스프린트 종료", description: "Sprint 종료 및 회고 정리", category: "implementation", source_path: "semo-skills/close-sprint", is_active: true, is_required: true, install_order: 45, version: "1.0.0" },
560
+ // Supporting (2개)
561
+ { id: "sk-50", name: "git-workflow", display_name: "Git 워크플로우", description: "Git 커밋/푸시/PR 자동화", category: "supporting", source_path: "semo-skills/git-workflow", is_active: true, is_required: true, install_order: 50, version: "1.0.0" },
562
+ { id: "sk-51", name: "notify-slack", display_name: "Slack 알림", description: "Slack 채널에 메시지 전송", category: "supporting", source_path: "semo-skills/notify-slack", is_active: true, is_required: true, install_order: 51, version: "1.0.0" },
563
+ ];
564
+ /**
565
+ * 활성 스킬 목록 조회 (설치할 스킬)
566
+ */
567
+ async function getActiveSkills() {
568
+ const isConnected = await checkDbConnection();
569
+ if (!isConnected) {
570
+ console.warn("⚠️ DB 연결 실패, 폴백 스킬 목록 사용 (19개)");
571
+ return FALLBACK_SKILLS.filter(s => s.is_active);
572
+ }
573
+ const supabase = getSupabaseClient();
574
+ const { data, error } = await supabase
575
+ .from("skill_definitions")
576
+ .select("*")
577
+ .eq("is_active", true)
578
+ .order("install_order");
579
+ if (error) {
580
+ console.warn("⚠️ 스킬 조회 실패, 폴백 데이터 사용");
581
+ return FALLBACK_SKILLS.filter(s => s.is_active);
582
+ }
583
+ return data || FALLBACK_SKILLS.filter(s => s.is_active);
584
+ }
585
+ /**
586
+ * 스킬 이름 목록만 조회
587
+ */
588
+ async function getActiveSkillNames() {
589
+ const skills = await getActiveSkills();
590
+ return skills.map(s => s.name);
591
+ }
592
+ /**
593
+ * 카테고리별 스킬 개수 조회
594
+ */
595
+ async function getSkillCountByCategory() {
596
+ const skills = await getActiveSkills();
597
+ const counts = {};
598
+ for (const skill of skills) {
599
+ counts[skill.category] = (counts[skill.category] || 0) + 1;
600
+ }
601
+ return counts;
602
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-semicolon/semo-cli",
3
- "version": "3.10.0",
3
+ "version": "3.13.0",
4
4
  "description": "SEMO CLI - AI Agent Orchestration Framework Installer",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "directory": "packages/cli"
28
28
  },
29
29
  "dependencies": {
30
+ "@supabase/supabase-js": "^2.49.1",
30
31
  "chalk": "^4.1.2",
31
32
  "commander": "^12.0.0",
32
33
  "ora": "^5.4.1",